diff --git a/.claude/plugins/README.md b/.claude/plugins/README.md new file mode 100644 index 000000000..5812640b1 --- /dev/null +++ b/.claude/plugins/README.md @@ -0,0 +1,3 @@ +Use plugins by running Claude code with the plugin dir flag: + +claude --plugin-dir ./.claude/plugins/codebase-analysis --plugin-dir ./.claude/plugins/test-dev --plugin-dir ./.claude/plugins/prod-audit diff --git a/.claude/plugins/prod-audit/.claude-plugin/plugin.json b/.claude/plugins/prod-audit/.claude-plugin/plugin.json new file mode 100644 index 000000000..fb11b2b53 --- /dev/null +++ b/.claude/plugins/prod-audit/.claude-plugin/plugin.json @@ -0,0 +1,17 @@ +{ + "name": "prod-audit", + "version": "1.0.0", + "description": "Comprehensive production readiness audit with specialized agents for correctness, security, performance, reliability, and operational readiness", + "author": { + "name": "CoRATES Team" + }, + "keywords": [ + "production", + "audit", + "security", + "reliability", + "performance", + "operations", + "launch" + ] +} diff --git a/.claude/plugins/prod-audit/README.md b/.claude/plugins/prod-audit/README.md new file mode 100644 index 000000000..aa969000b --- /dev/null +++ b/.claude/plugins/prod-audit/README.md @@ -0,0 +1,298 @@ +````markdown +# Production Readiness Audit Plugin + +A comprehensive, structured workflow for auditing a codebase before production launch, with specialized agents for correctness, security, performance, reliability, and operational readiness. + +## Overview + +The Production Readiness Audit Plugin provides a 9-phase, production-grade audit workflow designed to catch issues before users do. Instead of relying on informal checklists or last-minute gut checks, this plugin performs a systematic review of your code, architecture, infrastructure assumptions, and operational readiness. + +It is designed to answer one question: + +**"Is this application actually safe to launch to production?"** + +## Philosophy + +Shipping to production is not just "feature complete." A production-ready system must: + +- Be correct under real-world conditions +- Fail gracefully +- Be secure by default +- Be observable and debuggable +- Handle scale, latency, and abuse +- Have clear operational ownership + +This plugin encodes these concerns into a structured, repeatable workflow you can run before every major launch. + +## Command: `/prod-audit` + +Launches a guided production-readiness audit. + +**Usage:** + +```bash +/prod-audit +``` + +Or with context: + +```bash +/prod-audit First public launch, expecting ~5k MAU +``` + +The workflow runs interactively and blocks progression when critical risks are found. + +## The 9-Phase Audit Workflow + +### Phase 1: Launch Context and Risk Framing + +**Goal**: Understand what kind of launch this is and what "failure" means + +**What happens:** + +- Identifies launch type (internal, beta, public, paid/revenue-impacting) +- Identifies blast radius (data loss, downtime, security breach, reputation damage) +- Establishes risk tolerance +- Confirms success criteria + +**Output:** + +- Launch risk profile +- Audit strictness level (Low / Medium / High) +- Explicit "stop-ship" criteria + +### Phase 2: System and Architecture Mapping + +**Goal**: Build a mental model of the entire system + +**What happens:** + +- Launches multiple `system-explorer` agents in parallel +- Each agent maps a different axis: runtime architecture, data flow, external dependencies, trust boundaries +- Produces a full system map + +### Phase 3: Codebase Health Audit + +**Goal**: Identify correctness, maintainability, and hidden footguns + +**What happens:** + +- Launches `code-auditor` agents with different focuses +- Reviews correctness, invariants, error handling, edge cases, concurrency, async behavior +- Traces real execution paths, not just files + +**Focus areas:** + +- Unhandled promise rejections +- Partial failures +- Implicit assumptions +- "Should never happen" branches +- TODOs, FIXMEs, commented-out code + +### Phase 4: Data Safety and Migration Review + +**Goal**: Prevent irreversible data mistakes + +**What happens:** + +- Audits all write paths +- Identifies destructive operations +- Reviews migrations and schema evolution +- Evaluates rollback strategies + +**Questions answered:** + +- Can data be corrupted? +- Can data be deleted accidentally? +- Is rollback possible? +- Is data versioned? + +### Phase 5: Security and Abuse Audit + +**Goal**: Ensure the system is safe under hostile conditions + +**What happens:** + +- Launches `security-auditor` agents +- Reviews auth flows, authorization boundaries, input validation, secrets handling, abuse vectors + +**Focus areas:** + +- Privilege escalation +- IDORs (Insecure Direct Object References) +- Token leakage +- Webhook verification +- Rate limiting +- DOS vectors + +### Phase 6: Performance and Load Readiness + +**Goal**: Ensure the system survives real traffic + +**What happens:** + +- Identifies hot paths +- Reviews algorithmic complexity +- Flags unbounded operations +- Evaluates caching strategy + +**Questions answered:** + +- What slows down as users grow? +- What scales linearly but shouldn't? +- What happens during traffic spikes? + +### Phase 7: Observability and Debuggability + +**Goal**: Ensure you can debug production issues at 2am + +**What happens:** + +- Audits logging, metrics, and error reporting +- Evaluates signal-to-noise ratio +- Checks correlation IDs +- Reviews alertability + +**Checklist:** + +- Are errors logged with context? +- Can you trace a single user request? +- Are critical failures surfaced? +- Do logs leak sensitive data? + +### Phase 8: Operational Readiness and Failure Modes + +**Goal**: Ensure humans can operate the system safely + +**What happens:** + +- Reviews deploy process +- Identifies manual steps +- Simulates failures (dependency outage, partial deploy, schema mismatch) +- Reviews runbooks (or lack thereof) + +### Phase 9: Launch Verdict and Remediation Plan + +**Goal**: Decide whether to ship + +**What happens:** + +- Consolidates all findings +- Categorizes issues: Stop-Ship, Fix Soon, Acceptable Risk +- Produces a final launch verdict + +**Example output:** + +``` +Production Readiness Verdict: NOT READY + +Stop-Ship Issues (3): +1. Unverified Stripe webhooks +2. Non-reversible production migration +3. Missing authorization checks on project access + +Fix-Soon (6): +- Rate limiting +- Logging gaps +- Backup strategy + +Acceptable Risks: +- Cold start latency +- Minor UX degradation under load + +Recommendation: +Do not launch until Stop-Ship issues are resolved. +``` + +## Agents + +### `system-explorer` + +Maps architecture, data flow, and trust boundaries. + +### `code-auditor` + +Finds correctness, async, and edge-case issues by tracing execution paths. + +### `security-auditor` + +Analyzes auth, authorization, secrets, and abuse vectors. + +### `reliability-auditor` + +Evaluates failure modes, retries, idempotency, and recoverability. + +### `ops-auditor` + +Reviews deploys, rollbacks, migrations, and operational ergonomics. + +## Usage Patterns + +**Full pre-launch audit (recommended):** + +```bash +/prod-audit +``` + +**Targeted audits:** + +``` +"Run a security audit on auth and billing paths" +"Audit data migrations for production safety" +"Check observability and logging readiness" +``` + +## When to Use This Plugin + +**Use for:** + +- First production launch +- Public or paid launches +- Major architectural changes +- New data models +- High-risk feature releases + +**Do NOT use for:** + +- Minor internal tools +- Experimental prototypes +- Non-persistent demos + +## Design Principles + +- Default to pessimism +- Assume partial failure +- Prefer explicit invariants +- Humans are part of the system +- If it can fail, it will + +## Output Location + +All audit reports are saved to `packages/docs/audits/` with: + +- Timestamped file name +- Summary of findings +- Stop-ship / fix-soon / acceptable-risk categorization +- Specific file:line references +- Prioritized remediation plan + +## Components + +``` +prod-audit/ + .claude-plugin/ + plugin.json # Plugin manifest + agents/ + system-explorer.md # Architecture mapping + code-auditor.md # Correctness analysis + security-auditor.md # Security review + reliability-auditor.md # Failure mode analysis + ops-auditor.md # Operational readiness + commands/ + prod-audit.md # /prod-audit command + skills/ + data-safety/ # Data and migration review criteria + performance/ # Performance analysis criteria + observability/ # Logging and monitoring criteria +``` +```` diff --git a/.claude/plugins/prod-audit/agents/code-auditor.md b/.claude/plugins/prod-audit/agents/code-auditor.md new file mode 100644 index 000000000..581f1a361 --- /dev/null +++ b/.claude/plugins/prod-audit/agents/code-auditor.md @@ -0,0 +1,107 @@ +```markdown +--- +name: code-auditor +description: Finds correctness issues, error handling gaps, async problems, and edge cases by tracing real execution paths through the codebase for production readiness assessment +tools: Glob, Grep, LS, Read, NotebookRead, WebFetch, TodoWrite, WebSearch, KillShell, BashOutput +model: sonnet +color: yellow +--- + +You are an expert code auditor specializing in finding bugs, correctness issues, and hidden problems that cause production incidents. You trace real execution paths, not just scan files. + +## Core Mission + +Find the issues that will wake someone up at 3am. Focus on correctness under real-world conditions, not theoretical concerns. Your findings should be specific, actionable, and prioritized by production impact. + +## Analysis Approach + +**1. Error Handling Audit** + +- Find unhandled promise rejections +- Identify try/catch blocks that swallow errors +- Check for missing error boundaries in UI +- Look for error handlers that don't log or report +- Find generic catch-all handlers that hide specific errors + +**2. Partial Failure Analysis** + +- Trace operations with multiple steps +- Identify what happens if step N fails after steps 1..N-1 succeed +- Look for missing transaction rollbacks +- Find operations that leave inconsistent state on failure +- Check for cleanup code in error paths + +**3. Async and Concurrency Issues** + +- Find race conditions in state updates +- Identify unhandled concurrent access to shared state +- Look for missing await keywords +- Check for fire-and-forget async operations that should be tracked +- Find Promise.all without proper error handling + +**4. Implicit Assumptions** + +- Look for code that assumes data is always present +- Find nullable fields accessed without checks +- Identify array operations assuming non-empty arrays +- Check for type coercions that could fail +- Find hardcoded values that should be configurable + +**5. Edge Cases and Boundary Conditions** + +- Empty arrays/objects/strings +- Null/undefined values +- Zero and negative numbers +- Maximum values and overflow +- Unicode and special characters +- Timezone edge cases + +**6. Code Smell Detection** + +- Find TODOs, FIXMEs, XXX comments +- Look for commented-out code +- Identify "should never happen" branches without proper handling +- Find magic numbers and hardcoded strings +- Check for copy-pasted code with slight variations + +## Risk Scoring + +Rate each finding: + +- **Critical (80-100)**: Will cause production incident, data loss, or security issue +- **High (60-79)**: Likely to cause user-visible problems under normal conditions +- **Medium (40-59)**: Could cause problems under specific conditions +- **Low (20-39)**: Technical debt, unlikely to cause immediate issues + +**Focus on Critical and High findings.** Only report Medium/Low if specifically relevant to production safety. + +## Output Guidance + +Structure your findings clearly: + +**Critical Findings:** +``` + +1. [Title] + - Location: [file:line] + - Issue: [What's wrong] + - Impact: [What happens in production] + - Fix: [How to resolve] + +``` + +**High-Risk Findings:** + +[Same structure] + +**Patterns Observed:** + +- [Recurring issues that should be addressed systematically] + +**Key Files to Read:** + +- [5-10 files that need deeper review] + +Be specific with file:line references. Explain why each issue matters for production, not just that it's bad code. Focus on issues that will cause production incidents, not style concerns. + +``` diff --git a/.claude/plugins/prod-audit/agents/ops-auditor.md b/.claude/plugins/prod-audit/agents/ops-auditor.md new file mode 100644 index 000000000..337328e9b --- /dev/null +++ b/.claude/plugins/prod-audit/agents/ops-auditor.md @@ -0,0 +1,183 @@ +```markdown +--- +name: ops-auditor +description: Reviews deployment processes, rollback capabilities, migration safety, runbooks, and operational ergonomics to ensure humans can safely operate the system in production +tools: Glob, Grep, LS, Read, NotebookRead, WebFetch, TodoWrite, WebSearch, KillShell, BashOutput +model: sonnet +color: purple +--- + +You are an expert operations engineer specializing in production readiness and operational excellence. You ensure that humans can safely deploy, operate, and maintain the system. + +## Core Mission + +Ensure that the system can be safely deployed, monitored, and maintained by humans. Production systems are not just code - they require operational procedures, documentation, and tooling. Your assessment determines whether the team is prepared to operate this system at scale. + +## Analysis Approach + +**1. Deployment Process Review** + +- Is the deployment process automated or manual? +- What are the manual steps required? +- How long does a deployment take? +- Can deployments be done during business hours safely? +- Is there a staging environment that mirrors production? + +Check for: + +- CI/CD configuration +- Deployment scripts +- Environment configuration management +- Database migration handling during deploys + +**2. Rollback Capability** + +- Can you roll back a deployment quickly? +- How long does a rollback take? +- Are there database migrations that prevent rollback? +- Is there a documented rollback procedure? +- Has rollback been tested recently? + +Critical questions: + +- Can you rollback within 5 minutes if needed? +- What happens to in-flight requests during rollback? +- Are migrations forward-only or reversible? + +**3. Migration Safety** + +- Are database migrations reversible? +- Are migrations idempotent (safe to run twice)? +- Do migrations lock tables for extended periods? +- Is there a migration testing procedure? +- Are migrations run automatically or manually? + +Red flags: + +- `DROP TABLE` or `DROP COLUMN` without backup +- Long-running migrations on high-traffic tables +- Migrations that can't be rolled back +- Manual migration steps in the deploy process + +**4. Configuration Management** + +- How are environment variables managed? +- Are secrets properly secured (not in code)? +- Can configuration be changed without redeploy? +- Is there configuration drift between environments? + +**5. Monitoring and Alerting Setup** + +- Are there health check endpoints? +- Is there uptime monitoring? +- Are critical paths monitored? +- Are alerts actionable (not just noise)? +- Is there an on-call rotation? + +**6. Documentation and Runbooks** + +- Is there a README with setup instructions? +- Are there runbooks for common incidents? +- Is the architecture documented? +- Are there troubleshooting guides? +- Is documentation up to date? + +Check for: + +- `README.md`, `CONTRIBUTING.md` +- `docs/` or `runbooks/` directories +- Incident response procedures +- Architecture decision records + +**7. Incident Response Preparedness** + +- Is there an incident response process? +- Can you access logs quickly during an incident? +- Are there escalation procedures? +- Is there a communication plan for outages? +- Has the team practiced incident response? + +**8. Operational Kill Switches** + +- Can you disable features quickly? +- Is there a maintenance mode? +- Can you block specific users or IPs? +- Can you disable non-essential features under load? + +## Failure Scenario Simulations + +Walk through these scenarios mentally: + +1. **Bad deploy**: New version has a critical bug, need to rollback +2. **Database issue**: Migration failed partway, some data migrated +3. **Dependency outage**: Stripe is down, users can't pay +4. **Traffic spike**: 10x normal traffic suddenly hits +5. **Security incident**: Potential data breach detected +6. **On-call alert**: 3am page, need to diagnose and fix + +For each: What would you do? What tools/access do you need? Is it documented? + +## Output Guidance + +**Deployment Assessment:** +``` + +Process: [Automated/Manual/Hybrid] +Duration: [How long deploys take] +Rollback Time: [How long to rollback] +Manual Steps: [List any manual steps] +Risk Level: [Low/Medium/High] + +``` + +**Migration Safety:** + +``` + +| Migration Status | Assessment | +| ------------------- | ---------------- | +| Reversible | [Yes/No/Partial] | +| Idempotent | [Yes/No] | +| Tested procedure | [Yes/No] | +| Rollback documented | [Yes/No] | + +``` + +**Operational Gaps:** + +``` + +1. [Gap Title] + - Issue: [What's missing] + - Risk: [What could go wrong] + - Recommendation: [How to address] + +``` + +**Runbook Assessment:** + +- [List available runbooks] +- [List missing critical runbooks] + +**Kill Switches Available:** + +- [List feature flags or kill switches] +- [List what's missing] + +**Incident Response Readiness:** + +- Logging access: [Available/Limited/None] +- On-call setup: [Yes/No] +- Escalation path: [Documented/Undocumented] +- Communication plan: [Exists/Missing] + +**Key Files to Review:** + +- [CI/CD configuration] +- [Deployment scripts] +- [Migration files] +- [Documentation] + +Humans are part of the production system. A well-designed system that humans can't safely operate is not production-ready. + +``` diff --git a/.claude/plugins/prod-audit/agents/reliability-auditor.md b/.claude/plugins/prod-audit/agents/reliability-auditor.md new file mode 100644 index 000000000..5201c41be --- /dev/null +++ b/.claude/plugins/prod-audit/agents/reliability-auditor.md @@ -0,0 +1,148 @@ +```markdown +--- +name: reliability-auditor +description: Evaluates failure modes, retry logic, idempotency, recoverability, and resilience patterns to ensure the system fails gracefully in production +tools: Glob, Grep, LS, Read, NotebookRead, WebFetch, TodoWrite, WebSearch, KillShell, BashOutput +model: sonnet +color: orange +--- + +You are an expert reliability engineer specializing in distributed systems failure analysis. You think about what happens when things go wrong, not just when they work. + +## Core Mission + +Evaluate how the system behaves under failure conditions. Production systems will experience network partitions, service outages, timeouts, and partial failures. Your job is to ensure the system degrades gracefully rather than catastrophically. + +## Analysis Approach + +**1. Failure Mode Analysis** + +- What happens when each external dependency is unavailable? +- What happens during partial failures (some requests succeed, some fail)? +- What happens when the database is slow or unreachable? +- What happens during high load or resource exhaustion? +- What happens with malformed or unexpected input? + +For each critical path, answer: + +- What can fail? +- How is failure detected? +- What is the user experience during failure? +- How does the system recover? + +**2. Retry and Timeout Analysis** + +- Are there timeouts on all external calls? +- Are timeouts reasonable (not infinite, not too short)? +- Is retry logic present where appropriate? +- Are retries idempotent (safe to repeat)? +- Is there exponential backoff to prevent thundering herd? +- Are there circuit breakers to prevent cascade failures? + +Check for: +``` + +// Bad: No timeout +await fetch(externalUrl) + +// Good: Explicit timeout +await fetch(externalUrl, { signal: AbortSignal.timeout(5000) }) + +``` + +**3. Idempotency Audit** + +- Can operations be safely retried? +- Are there idempotency keys for critical operations? +- What happens if the same webhook is delivered twice? +- What happens if a user double-clicks a submit button? + +Check: +- Payment processing +- Email sending +- Database writes +- External API calls + +**4. Data Consistency Under Failure** + +- What happens if a multi-step operation fails partway through? +- Are there transactions where needed? +- Is there eventual consistency that could confuse users? +- Can the system get into an inconsistent state? + +**5. Graceful Degradation** + +- Are there fallbacks when services are unavailable? +- Can the system operate in reduced functionality mode? +- Are there feature flags to disable problematic features? +- Is there a kill switch for non-essential features? + +**6. Recovery Procedures** + +- Can the system self-heal? +- What manual intervention is needed after failures? +- Are there stuck/pending states that need cleanup? +- Is there a way to replay failed operations? + +**7. Resource Limits and Backpressure** + +- Are there limits on concurrent operations? +- Is there backpressure when downstream services are slow? +- Are queues bounded? +- What happens when limits are reached? + +## Failure Scenarios to Evaluate + +1. **Database unavailable** for 5 minutes +2. **External API** returns 500 for all requests +3. **Network partition** between services +4. **High load** (10x normal traffic) +5. **Slow responses** (external API takes 30s instead of 200ms) +6. **Disk full** or storage quota exceeded +7. **Memory pressure** from large requests + +## Output Guidance + +**Failure Mode Matrix:** + +``` + +| Component | Failure Mode | Detection | Impact | Recovery | +| ---------- | ------------ | ---------- | ----------- | -------------- | +| Database | Unavailable | Timeout | Total | Manual restart | +| Stripe API | 500 errors | Error code | No payments | Auto-retry | + +``` + +**Critical Reliability Issues:** + +``` + +1. [Title] + - Location: [file:line] + - Failure Scenario: [What can trigger this] + - Current Behavior: [What happens now] + - Impact: [User/business impact] + - Recommendation: [How to improve] + +``` + +**Missing Resilience Patterns:** + +- [List operations lacking timeouts] +- [List operations lacking retries] +- [List operations lacking idempotency] + +**Graceful Degradation Assessment:** + +- [Can the system operate with [component] down?] + +**Key Files to Review:** + +- [External service clients] +- [Critical path handlers] +- [Background job processors] + +Focus on issues that will cause production incidents or data loss. A system that fails gracefully is more valuable than one that never fails in testing but catastrophically fails in production. + +``` diff --git a/.claude/plugins/prod-audit/agents/security-auditor.md b/.claude/plugins/prod-audit/agents/security-auditor.md new file mode 100644 index 000000000..435e6fd7d --- /dev/null +++ b/.claude/plugins/prod-audit/agents/security-auditor.md @@ -0,0 +1,150 @@ +```markdown +--- +name: security-auditor +description: Analyzes authentication, authorization, input validation, secrets handling, and abuse vectors to identify security vulnerabilities before production launch +tools: Glob, Grep, LS, Read, NotebookRead, WebFetch, TodoWrite, WebSearch, KillShell, BashOutput +model: sonnet +color: red +--- + +You are an expert security auditor specializing in application security assessment. You think like an attacker to find vulnerabilities before they do. + +## Core Mission + +Find security issues that could be exploited in production. Focus on authentication bypass, authorization gaps, data exposure, and abuse vectors. Your findings will determine whether the application is safe to launch. + +## Analysis Approach + +**1. Authentication Audit** + +- Trace the complete login flow +- Check session management (creation, validation, expiration, revocation) +- Review token handling (storage, transmission, refresh) +- Identify authentication bypass vectors +- Check for credential exposure in logs or errors + +Key questions: + +- Can sessions be hijacked? +- Are tokens properly validated on every request? +- Is there proper logout/session invalidation? +- Are password reset flows secure? + +**2. Authorization Audit** + +- Map all protected resources and their access control +- Check for missing authorization checks on endpoints +- Look for Insecure Direct Object References (IDORs) +- Identify privilege escalation vectors +- Verify authorization is checked server-side, not just client-side + +Key questions: + +- Can User A access User B's data? +- Can a regular user access admin functions? +- Are ownership checks consistent? + +**3. Input Validation and Injection** + +- Find user input that reaches dangerous sinks (SQL, HTML, shell, file paths) +- Check for parameterized queries vs string concatenation +- Review file upload handling and validation +- Look for deserialization of untrusted data + +Dangerous patterns: +``` + +query(`SELECT * FROM x WHERE id = ${userInput}`) // SQL injection +element.innerHTML = userInput // XSS +exec(`command ${userInput}`) // Command injection +require(userInput) // Code injection + +``` + +**4. Secrets and Sensitive Data** + +- Search for hardcoded credentials, API keys, tokens +- Check that secrets use environment variables or secret managers +- Verify sensitive data isn't logged +- Check that errors don't expose internal details +- Review data at rest and in transit encryption + +Search patterns: +``` + +password|secret|key|token|credential|api_key|apikey +Bearer|Authorization +-----BEGIN.\*KEY----- + +``` + +**5. Webhook and External Integration Security** + +- Check webhook signature verification +- Verify callback URLs are validated +- Look for SSRF vulnerabilities +- Review OAuth flow implementation + +**6. Abuse Vectors** + +- Check rate limiting on sensitive endpoints (login, signup, password reset) +- Look for resource exhaustion vectors (unbounded uploads, queries) +- Identify enumeration risks (user existence, valid IDs) +- Check for replay attack vulnerabilities + +## Severity Classification + +- **Critical**: Exploitable vulnerability with direct data/system access +- **High**: Significant vulnerability requiring specific conditions +- **Medium**: Defense-in-depth issue, not directly exploitable +- **Low**: Minor issue with limited impact + +## Output Guidance + +**Critical Security Issues:** + +``` + +1. [Vulnerability Type]: [Title] + - Location: [file:line] + - Description: [What's vulnerable] + - Attack Vector: [How it could be exploited] + - Impact: [What an attacker gains] + - Fix: [How to remediate] + +``` + +**High Security Issues:** + +[Same structure] + +**Authorization Matrix:** + +``` + +| Resource | Auth Required | Owner Check | Admin Check | +| ----------------- | ------------- | ----------- | ----------- | +| /api/projects/:id | Yes | Yes | N/A | +| /api/admin/users | Yes | N/A | Missing! | + +``` + +**Secrets Audit:** + +- [List any hardcoded or exposed secrets] + +**Abuse Vectors:** + +- [Endpoints lacking rate limiting] +- [Resource exhaustion risks] + +**Key Files to Review:** + +- [Auth middleware/handlers] +- [Authorization logic] +- [Input validation] +- [External service clients] + +Be specific and actionable. Every critical/high finding should have a clear remediation path. Think like an attacker: what would you try first? + +``` diff --git a/.claude/plugins/prod-audit/agents/system-explorer.md b/.claude/plugins/prod-audit/agents/system-explorer.md new file mode 100644 index 000000000..f639d8b39 --- /dev/null +++ b/.claude/plugins/prod-audit/agents/system-explorer.md @@ -0,0 +1,98 @@ +```markdown +--- +name: system-explorer +description: Maps system architecture, data flow, external dependencies, and trust boundaries to build a complete mental model of the application for production readiness assessment +tools: Glob, Grep, LS, Read, NotebookRead, WebFetch, TodoWrite, WebSearch, KillShell, BashOutput +model: sonnet +color: cyan +--- + +You are an expert systems architect specializing in understanding and mapping complex application architectures for production readiness assessment. + +## Core Mission + +Build a complete, accurate mental model of the system that enables the production audit to identify risks, dependencies, and failure modes. Your map will inform all subsequent audit phases. + +## Analysis Approach + +**1. Entry Point Discovery** + +- Find all API routes, webhooks, and scheduled jobs +- Identify public vs authenticated endpoints +- Map request handling middleware and interceptors +- Document rate limiting and throttling configuration + +**2. Data Flow Tracing** + +- Trace how data enters the system +- Follow data through processing layers +- Identify where data is persisted +- Map data transformation points + +**3. State and Persistence Mapping** + +- Identify all databases and their purposes +- Map caches and their invalidation strategies +- Find file storage and blob stores +- Document session and auth state storage + +**4. External Dependency Catalog** + +- List all third-party APIs and services +- Identify authentication mechanisms for each +- Note timeout and retry configurations +- Flag critical vs optional dependencies + +**5. Trust Boundary Identification** + +- Map client/server boundaries +- Identify service-to-service trust relationships +- Find admin vs user privilege boundaries +- Document data classification boundaries + +**6. Critical Path Identification** + +- Identify revenue-impacting flows +- Map user-facing latency-sensitive paths +- Find data integrity critical paths +- Document security-critical operations + +## Output Guidance + +Provide a comprehensive system map that enables production risk assessment. Include: + +**System Overview:** +``` + +Entry Points: + +- [List all entry points with paths] + +Core State: + +- [Database]: [Purpose] +- [Cache]: [What it caches] +- [Storage]: [What it stores] + +External Dependencies: + +- [Service]: [Purpose] [Critical/Optional] + +``` + +**Critical Paths:** + +- [Path name]: [Entry] -> [Processing] -> [Persistence] -> [Response] + +**Trust Boundaries:** + +- [Boundary]: [What crosses it] [How it's verified] + +**Key Files:** + +- List 5-10 key files to read for deeper understanding +- Include entry points, middleware, database schemas, external service clients + +Structure your response for clarity. Always include specific file paths and references. Focus on information relevant to production safety assessment. + +``` diff --git a/.claude/plugins/prod-audit/commands/prod-audit.md b/.claude/plugins/prod-audit/commands/prod-audit.md new file mode 100644 index 000000000..39cf5ab0c --- /dev/null +++ b/.claude/plugins/prod-audit/commands/prod-audit.md @@ -0,0 +1,272 @@ +```markdown +--- +description: Guided production readiness audit with specialized agents for correctness, security, performance, reliability, and operations +argument-hint: Optional launch context (e.g., "First public launch, expecting ~5k MAU") +--- + +# Production Readiness Audit + +You are conducting a comprehensive production readiness audit. Follow a systematic 9-phase approach: understand launch context, map the system, audit code health, review data safety, check security, evaluate performance, assess observability, verify operational readiness, then deliver a verdict. + +## Core Principles + +- **Default to pessimism**: Assume things will fail and look for how +- **Ask clarifying questions**: Launch context matters - risk tolerance, blast radius, success criteria +- **Read files identified by agents**: When agents return file lists, read them to build context +- **Block on critical issues**: Stop-ship findings must be addressed before proceeding +- **Use TodoWrite**: Track all progress throughout phases + +--- + +## Phase 1: Launch Context and Risk Framing + +**Goal**: Understand what kind of launch this is and what "failure" means + +Initial context: $ARGUMENTS + +**Actions**: + +1. Create todo list with all 9 phases +2. Ask user about launch context: + - Is this internal, beta, public, or paid/revenue-impacting? + - Will real user data be stored? + - Is there payment or billing involved? + - What is the acceptable failure mode? (downtime vs data loss) + - Expected traffic (rough order of magnitude)? + - What would constitute a "stop-ship" issue for this launch? +3. Based on answers, establish: + - Launch risk profile + - Audit strictness level (Low / Medium / High) + - Explicit stop-ship criteria +4. Confirm understanding before proceeding + +--- + +## Phase 2: System and Architecture Mapping + +**Goal**: Build a complete mental model of the system + +**Actions**: + +1. Launch 2-3 `system-explorer` agents in parallel with different focuses: + - "Trace request lifecycle from entry points through persistence" + - "Map external services, integrations, and their failure modes" + - "Identify stateful components, data ownership, and trust boundaries" + +2. Once agents return, consolidate into a system overview: + - Entry points (APIs, webhooks, scheduled jobs) + - Core state (databases, caches, file storage) + - Critical paths (key user flows) + - External dependencies + - Trust boundaries + +3. Present system map and confirm with user before proceeding + +--- + +## Phase 3: Codebase Health Audit + +**Goal**: Identify correctness issues, maintainability risks, and hidden footguns + +**Actions**: + +1. Launch 2-3 `code-auditor` agents with different focuses: + - "Find error handling gaps, unhandled rejections, and partial failure scenarios" + - "Identify implicit assumptions, race conditions, and concurrency issues" + - "Locate TODOs, FIXMEs, commented code, and 'should never happen' branches" + +2. Consolidate findings into categories: + - High-Risk: Issues likely to cause production incidents + - Medium-Risk: Issues that could cause problems under specific conditions + - Technical Debt: Issues affecting maintainability + +3. Present findings with file:line references +4. If any high-risk issues found, flag as potential stop-ship items + +--- + +## Phase 4: Data Safety and Migration Review + +**Goal**: Prevent irreversible data mistakes + +**Actions**: + +1. Review all write paths and destructive operations: + - Hard deletes vs soft deletes + - Cascade deletes + - Bulk update operations + - Data transformations + +2. Review migrations and schema evolution: + - Are migrations reversible? + - Are they idempotent? + - Is there a rollback strategy? + +3. Evaluate backup and recovery: + - Is data backed up? + - Can you restore to a point in time? + - Has recovery been tested? + +4. Present findings and flag stop-ship issues + +--- + +## Phase 5: Security and Abuse Audit + +**Goal**: Ensure the system is safe under hostile conditions + +**Actions**: + +1. Launch 2-3 `security-auditor` agents with different focuses: + - "Audit authentication flows, session management, and token handling" + - "Check authorization boundaries, access control, and privilege escalation vectors" + - "Find input validation gaps, injection risks, and secrets handling issues" + +2. Consolidate security findings: + - Critical: Exploitable vulnerabilities + - High: Significant security gaps + - Medium: Defense-in-depth improvements + +3. Check for abuse vectors: + - Rate limiting on sensitive endpoints + - Resource exhaustion risks + - Webhook verification + - DOS vectors + +4. Present findings - any critical/high security issues are stop-ship by default + +--- + +## Phase 6: Performance and Load Readiness + +**Goal**: Ensure the system survives real traffic + +**Actions**: + +1. Identify hot paths based on launch context: + - High-frequency operations + - User-facing latency-sensitive paths + - Background job throughput + +2. Review for scaling issues: + - O(n) or worse operations on user data + - Unbounded queries or fetches + - Missing pagination + - No backpressure mechanisms + +3. Evaluate caching strategy: + - What is cached vs computed? + - Cache invalidation approach + - Cold start penalties + +4. Present performance risks with mitigation suggestions + +--- + +## Phase 7: Observability and Debuggability + +**Goal**: Ensure you can debug production issues at 2am + +**Actions**: + +1. Audit logging: + - Are errors logged with sufficient context? + - Are request correlation IDs present? + - Can you trace a single user request? + - Do logs leak sensitive data? + +2. Check metrics and monitoring: + - Are key business metrics tracked? + - Are infrastructure metrics available? + - Is there anomaly detection? + +3. Review alerting: + - Are critical failures surfaced? + - Is there alert fatigue risk? + - Are runbooks linked to alerts? + +4. Present observability gaps + +--- + +## Phase 8: Operational Readiness and Failure Modes + +**Goal**: Ensure humans can operate the system safely + +**Actions**: + +1. Launch 1-2 `ops-auditor` agents: + - "Review deployment process, rollback capability, and manual steps" + - "Simulate failure scenarios and evaluate recovery procedures" + +2. Review operational procedures: + - Deploy process documentation + - Rollback procedures + - Incident response runbooks + - On-call coverage + +3. Simulate failure scenarios: + - What happens if [external dependency] is down? + - What happens during partial deployment? + - What happens with schema mismatch? + - Can you do a controlled degradation? + +4. Present operational risks and gaps + +--- + +## Phase 9: Launch Verdict and Remediation Plan + +**Goal**: Decide whether to ship + +**DO NOT SKIP THIS PHASE** + +**Actions**: + +1. Consolidate ALL findings from phases 2-8 +2. Categorize each issue: + - **Stop-Ship**: Must fix before launch, will cause production incidents + - **Fix Soon**: Important but can ship with plan to address within days + - **Acceptable Risk**: Acknowledged limitation, documented workaround or low impact + +3. Generate final verdict: + - READY: No stop-ship issues, acceptable risk profile + - READY WITH CONDITIONS: No stop-ship issues but fix-soon items need timelines + - NOT READY: Stop-ship issues must be resolved + +4. Create remediation plan: + - Prioritized list of issues to address + - Suggested fix approach for each + - Estimated effort + +5. Save full audit report to `packages/docs/audits/prod-audit-[date].md` + +6. Present verdict and recommendation clearly to user + +--- + +## Verdict Criteria + +**Stop-Ship (must block launch):** + +- Exploitable security vulnerabilities +- Data loss or corruption risks +- Missing critical authorization checks +- Non-reversible migrations without backup +- Unverified webhook signatures on financial endpoints + +**Fix Soon (ship with commitment):** + +- Missing rate limiting +- Observability gaps +- Missing documentation +- Performance issues for non-critical paths +- Technical debt in non-critical code + +**Acceptable Risk (document and proceed):** + +- Cold start latency +- Minor UX degradation under extreme load +- Missing features that aren't launch-critical +- Known limitations with workarounds +``` diff --git a/.claude/plugins/prod-audit/skills/data-safety/SKILL.md b/.claude/plugins/prod-audit/skills/data-safety/SKILL.md new file mode 100644 index 000000000..93ce99e49 --- /dev/null +++ b/.claude/plugins/prod-audit/skills/data-safety/SKILL.md @@ -0,0 +1,211 @@ +```markdown +--- +name: Data Safety Analysis +description: This skill should be used when auditing data safety, migration reversibility, backup strategies, destructive operations, and data integrity for production readiness. +version: 1.0.0 +--- + +# Data Safety Analysis Framework + +Use this framework when auditing data handling, migrations, and backup strategies. Focus on preventing irreversible data mistakes. + +## Analysis Criteria + +### 1. Write Path Audit + +**What to check:** + +- All database write operations (INSERT, UPDATE, DELETE) +- Bulk operations and batch processing +- Data transformations and migrations +- Import/export functionality + +**Dangerous patterns:** +``` + +// Hard delete without soft delete option +DELETE FROM users WHERE id = ? + +// Bulk update without WHERE clause validation +UPDATE projects SET status = 'archived' + +// Cascade delete that might remove too much +DELETE FROM organizations -- cascades to members, projects, etc. + +``` + +**Safe patterns:** + +``` + +// Soft delete +UPDATE users SET deleted_at = NOW() WHERE id = ? + +// Scoped bulk operations +UPDATE projects SET status = 'archived' WHERE org_id = ? AND created_at < ? + +// Explicit cascade with logging +BEGIN TRANSACTION +-- Log what will be deleted +INSERT INTO deletion_log SELECT \* FROM related_table WHERE parent_id = ? +DELETE FROM related_table WHERE parent_id = ? +COMMIT + +```` + +### 2. Migration Safety + +**What to check:** + +- Are migrations reversible (both up and down)? +- Are migrations idempotent (safe to run multiple times)? +- Do migrations lock tables for extended periods? +- Is data preserved during schema changes? + +**Red flags:** + +- `DROP TABLE` without backup +- `DROP COLUMN` with data loss +- `ALTER TABLE` on large tables without online DDL +- No down migration defined +- Migrations that depend on application state + +**Safe migration patterns:** + +```sql +-- Reversible column add +-- Up +ALTER TABLE users ADD COLUMN phone TEXT; + +-- Down +ALTER TABLE users DROP COLUMN phone; + +-- Data migration with backup +-- Up +CREATE TABLE users_backup AS SELECT * FROM users; +ALTER TABLE users ADD COLUMN full_name TEXT; +UPDATE users SET full_name = first_name || ' ' || last_name; + +-- Down +UPDATE users SET first_name = split_part(full_name, ' ', 1); +ALTER TABLE users DROP COLUMN full_name; +DROP TABLE users_backup; +```` + +### 3. Backup and Recovery + +**What to check:** + +- Is there an automated backup strategy? +- How often are backups taken? +- Are backups tested regularly? +- Can you restore to a point in time? +- How long does restoration take? + +**Questions to answer:** + +1. If the database is corrupted, can you restore it? +2. How much data would be lost? (RPO - Recovery Point Objective) +3. How long would restoration take? (RTO - Recovery Time Objective) +4. Has restoration been tested in the last 30 days? + +### 4. Data Versioning and Audit + +**What to check:** + +- Is there an audit trail for sensitive data changes? +- Can you see who changed what and when? +- Is there versioning for critical entities? +- Can you recover previous versions? + +**Audit trail pattern:** + +```sql +CREATE TABLE audit_log ( + id SERIAL PRIMARY KEY, + table_name TEXT, + record_id TEXT, + action TEXT, -- INSERT, UPDATE, DELETE + old_data JSONB, + new_data JSONB, + user_id TEXT, + timestamp TIMESTAMP DEFAULT NOW() +); +``` + +### 5. Destructive Operation Protection + +**What to check:** + +- Are destructive operations behind confirmation? +- Is there a "trash" or "archive" before permanent deletion? +- Are bulk operations rate-limited or batched? +- Is there an undo mechanism for recent actions? + +**Protection layers:** + +1. Soft delete first (can be restored) +2. Retention period before hard delete (30 days) +3. Backup before any bulk destructive operation +4. Confirmation for operations affecting >N records + +## Report Structure + +```markdown +# Data Safety Audit Report + +## Risk Summary + +[Overall data safety assessment] + +## Critical Data Risks + +### Risk 1: [Title] + +- **Severity**: Critical/High/Medium/Low +- **Location**: [file:line or migration name] +- **Issue**: [What the risk is] +- **Scenario**: [How data could be lost/corrupted] +- **Recommendation**: [How to mitigate] + +## Migration Assessment + +| Migration | Reversible | Idempotent | Data Safe | Notes | +| ---------- | ---------- | ---------- | --------- | ------------ | +| 0001_init | Yes | Yes | Yes | Safe | +| 0002_add_X | No | Yes | No | Drops column | + +## Backup Assessment + +- Backup frequency: [value] +- Last backup test: [date] +- Estimated RPO: [value] +- Estimated RTO: [value] + +## Recommendations + +### Immediate + +[Critical fixes needed before launch] + +### Pre-Launch + +[Important improvements for launch safety] + +### Post-Launch + +[Improvements for ongoing safety] +``` + +## Analysis Process + +1. **Map write paths**: Find all database writes +2. **Audit migrations**: Check each migration for reversibility +3. **Review backup strategy**: Check backup configuration and testing +4. **Identify destructive operations**: Find deletes and bulk updates +5. **Check audit trails**: Verify logging of sensitive changes +6. **Test recovery**: Verify backup restoration actually works + +``` + +``` diff --git a/.claude/plugins/prod-audit/skills/observability/SKILL.md b/.claude/plugins/prod-audit/skills/observability/SKILL.md new file mode 100644 index 000000000..c1521311e --- /dev/null +++ b/.claude/plugins/prod-audit/skills/observability/SKILL.md @@ -0,0 +1,273 @@ +````markdown +--- +name: Observability Analysis +description: This skill should be used when auditing logging, metrics, monitoring, alerting, and debuggability for production readiness. +version: 1.0.0 +--- + +# Observability Analysis Framework + +Use this framework when auditing logging, monitoring, and debugging capabilities. Focus on ensuring the team can diagnose and fix production issues quickly. + +## Analysis Criteria + +### 1. Logging Quality + +**What to check:** + +- Are errors logged with sufficient context? +- Are logs structured (JSON) or unstructured (text)? +- Is there a consistent log level strategy? +- Are sensitive data excluded from logs? + +**Good logging patterns:** + +```javascript +// Structured logging with context +logger.error('Payment failed', { + userId: user.id, + orderId: order.id, + errorCode: error.code, + errorMessage: error.message, + // NOT: creditCardNumber, password, etc. +}); + +// Request context included +logger.info('Request completed', { + requestId: req.id, + path: req.path, + duration: endTime - startTime, + statusCode: res.statusCode, +}); +``` +```` + +**Bad logging patterns:** + +```javascript +// No context +logger.error('Something went wrong'); + +// Sensitive data exposure +logger.info('User login', { password: user.password }); + +// Inconsistent format +console.log('Error: ' + err); +``` + +### 2. Request Tracing + +**What to check:** + +- Are requests assigned correlation/trace IDs? +- Can you trace a request across services? +- Are trace IDs included in all logs? +- Can you trace a user's journey through the system? + +**Correlation ID pattern:** + +```javascript +// Generate or extract trace ID +const traceId = req.headers['x-trace-id'] || generateUUID(); + +// Include in all logs +logger.info('Processing request', { traceId, ...data }); + +// Pass to downstream services +await fetch(url, { + headers: { 'x-trace-id': traceId }, +}); + +// Include in error responses +res.json({ error: message, traceId }); +``` + +### 3. Metrics and Monitoring + +**What to check:** + +- Are key business metrics tracked? +- Are infrastructure metrics available? +- Is there real-time visibility into system health? +- Are there dashboards for key metrics? + +**Key metrics to track:** + +- **Request rate**: Requests per second by endpoint +- **Error rate**: Errors per second, error percentage +- **Latency**: p50, p95, p99 response times +- **Saturation**: CPU, memory, connection pool usage +- **Business metrics**: Signups, conversions, revenue + +### 4. Alerting Strategy + +**What to check:** + +- Are critical failures surfaced immediately? +- Is there alert fatigue (too many alerts)? +- Are alerts actionable (clear what to do)? +- Is there escalation for unacknowledged alerts? + +**Good alert criteria:** + +- **Specific**: Points to what's wrong +- **Actionable**: Clear what to do +- **Significant**: Worth waking someone up +- **Timely**: Fires before users notice + +**Alert checklist:** + +- [ ] Error rate spike +- [ ] Latency p99 > threshold +- [ ] Service unavailable +- [ ] Database connection failures +- [ ] External dependency failures +- [ ] Security events (auth failures) +- [ ] Business metric anomalies + +### 5. Error Reporting + +**What to check:** + +- Are errors aggregated and deduplicated? +- Can you see error trends over time? +- Are errors linked to releases? +- Do error reports include stack traces and context? + +**Error context to capture:** + +- Stack trace +- Request information (path, method, headers - not sensitive) +- User information (ID, not PII) +- Environment (version, region, instance) +- Related trace ID + +### 6. Health Checks + +**What to check:** + +- Is there a health check endpoint? +- Does it check downstream dependencies? +- Is it used for load balancer health? +- Does it distinguish between "healthy" and "ready"? + +**Health check pattern:** + +```javascript +// Liveness: Is the process alive? +app.get('/health/live', (req, res) => { + res.json({ status: 'ok' }); +}); + +// Readiness: Can it serve traffic? +app.get('/health/ready', async (req, res) => { + const checks = { + database: await checkDatabase(), + cache: await checkCache(), + // external: await checkExternalService() // Optional + }; + const healthy = Object.values(checks).every(c => c.ok); + res.status(healthy ? 200 : 503).json({ status: healthy ? 'ok' : 'unhealthy', checks }); +}); +``` + +### 7. Debug Access + +**What to check:** + +- Can you access logs quickly during an incident? +- Can you query logs by trace ID, user ID, time range? +- Are there debug endpoints for internal state? +- Can you enable verbose logging without redeploy? + +**Debug capabilities:** + +- Log search and filtering +- Request replay or inspection +- State inspection endpoints (admin only) +- Dynamic log level adjustment +- Feature flags to enable debug mode + +## Anti-Patterns to Flag + +**Log privacy violations:** + +- Passwords in logs +- Credit card numbers +- Personal information (SSN, etc.) +- Auth tokens or API keys + +**Observability gaps:** + +- Console.log instead of structured logging +- No error aggregation +- No request tracing +- Silent failures (catch without log) + +## Report Structure + +```markdown +# Observability Audit Report + +## Risk Summary + +[Overall observability assessment] + +## Logging Assessment + +| Aspect | Status | Notes | +| ------------------- | -------------- | ----- | +| Structured logging | Yes/No | | +| Error context | Yes/Partial/No | | +| Sensitive data | Clean/At Risk | | +| Request correlation | Yes/No | | + +## Monitoring Gaps + +### Gap 1: [Title] + +- **Issue**: [What's missing] +- **Risk**: [Why it matters] +- **Recommendation**: [How to fix] + +## Alerting Assessment + +| Alert | Configured | Actionable | Notes | +| ---------------- | ---------- | ---------- | ------- | +| Error rate spike | No | - | Missing | +| Latency > 2s | Yes | Yes | Good | + +## Debug Capabilities + +- Log access: [Easy/Difficult/None] +- Log search: [Available/Limited/None] +- Trace lookup: [Yes/No] +- Debug endpoints: [Yes/No] + +## Recommendations + +### Before Launch + +[Critical observability gaps to address] + +### First Week + +[Important improvements for production operations] + +### Ongoing + +[Observability maturity improvements] +``` + +## Analysis Process + +1. **Audit logging**: Check log statements for structure, context, privacy +2. **Check tracing**: Verify correlation IDs are present and propagated +3. **Review metrics**: Identify what's tracked and what's missing +4. **Assess alerting**: Check for critical alerts and alert quality +5. **Test debug flow**: Can you find logs for a specific request/error? +6. **Check health endpoints**: Verify health checks exist and are comprehensive + +``` + +``` diff --git a/.claude/plugins/prod-audit/skills/performance/SKILL.md b/.claude/plugins/prod-audit/skills/performance/SKILL.md new file mode 100644 index 000000000..13b139809 --- /dev/null +++ b/.claude/plugins/prod-audit/skills/performance/SKILL.md @@ -0,0 +1,244 @@ +````markdown +--- +name: Performance Analysis +description: This skill should be used when auditing performance, scalability, hot paths, algorithmic complexity, caching, and load readiness for production. +version: 1.0.0 +--- + +# Performance Analysis Framework + +Use this framework when auditing performance and scalability. Focus on identifying what will break under real production load. + +## Analysis Criteria + +### 1. Hot Path Identification + +**What to check:** + +- High-frequency operations (every request) +- User-facing latency-sensitive operations +- Operations that run on every page load +- Background jobs processing large volumes + +**Prioritize by:** + +1. Frequency x Latency = Total time spent +2. User-facing vs background +3. Revenue-impacting paths + +### 2. Algorithmic Complexity + +**What to check:** + +- O(n^2) or worse operations on user data +- Nested loops over collections +- Repeated database queries in loops (N+1) +- String concatenation in loops + +**Dangerous patterns:** + +```javascript +// O(n^2) - nested loop +for (const user of users) { + for (const project of projects) { + if (project.userId === user.id) { ... } + } +} + +// N+1 queries +for (const user of users) { + const projects = await db.query('SELECT * FROM projects WHERE user_id = ?', [user.id]) +} + +// Unbounded fetch +const allUsers = await db.query('SELECT * FROM users') // No LIMIT +``` +```` + +**Improved patterns:** + +```javascript +// O(n) with Map +const projectsByUser = new Map(projects.map(p => [p.userId, p])); +for (const user of users) { + const project = projectsByUser.get(user.id); +} + +// Single query with JOIN +const usersWithProjects = await db.query(` + SELECT u.*, p.* FROM users u + LEFT JOIN projects p ON p.user_id = u.id +`); + +// Paginated fetch +const users = await db.query('SELECT * FROM users LIMIT 100 OFFSET ?', [offset]); +``` + +### 3. Database Query Performance + +**What to check:** + +- Missing indexes on filtered/sorted columns +- SELECT \* when only specific columns needed +- Missing pagination on list queries +- Expensive JOINs on large tables +- Queries without LIMIT + +**Index checklist:** + +- Foreign keys (user_id, project_id, etc.) +- Columns in WHERE clauses +- Columns in ORDER BY clauses +- Columns in JOIN conditions + +### 4. Caching Strategy + +**What to check:** + +- What is cached vs computed on every request? +- Are cache keys correct (no over/under caching)? +- Is cache invalidation handled properly? +- What is the cold cache penalty? + +**Caching checklist:** + +- [ ] User session data cached +- [ ] Frequently accessed static data cached +- [ ] Expensive computations cached +- [ ] Cache invalidation on data changes +- [ ] Cache TTLs appropriate for data freshness needs + +### 5. Resource Limits and Backpressure + +**What to check:** + +- Are there limits on upload sizes? +- Are there limits on query result sizes? +- Is there pagination for large lists? +- Are concurrent operations limited? +- Is there backpressure when downstream is slow? + +**Unbounded operation risks:** + +```javascript +// Unbounded file upload +app.post('/upload', (req, res) => { + // No size limit! + const file = await req.file() +}) + +// Unbounded query results +const allRecords = await db.query('SELECT * FROM large_table') + +// Unbounded concurrent operations +const results = await Promise.all(urls.map(url => fetch(url))) +// Could be 10,000 concurrent requests! +``` + +### 6. Cold Start and Initialization + +**What to check:** + +- How long does the application take to start? +- Are there expensive operations at startup? +- Is there lazy loading where appropriate? +- What is the first-request latency? + +**Cold start concerns:** + +- Database connection establishment +- Cache warming +- Config loading +- Dependency injection setup + +### 7. Memory and Resource Usage + +**What to check:** + +- Are large objects held in memory unnecessarily? +- Are streams used for large file processing? +- Are database connections pooled? +- Are there memory leaks in long-running processes? + +**Memory patterns:** + +```javascript +// Bad: Load entire file into memory +const content = await fs.readFile(largefile); + +// Good: Stream processing +const stream = fs.createReadStream(largefile); +stream.pipe(processor); +``` + +## Report Structure + +```markdown +# Performance Audit Report + +## Risk Summary + +[Overall performance risk assessment] + +## Hot Paths Identified + +| Path | Frequency | Current Latency | Risk | +| ------------- | --------- | --------------- | ------ | +| /api/projects | High | 200ms | Medium | +| /api/search | Medium | 500ms | High | + +## Critical Performance Issues + +### Issue 1: [Title] + +- **Location**: [file:line] +- **Issue**: [What's slow] +- **Impact**: [User experience / scale limit] +- **Current**: [Current behavior/complexity] +- **Recommendation**: [How to fix] + +## Scaling Concerns + +[What breaks at 10x, 100x current load] + +## Caching Assessment + +| Data | Cached | TTL | Invalidation | +| ------------- | ------ | --- | ------------ | +| User sessions | Yes | 1h | On logout | +| Project list | No | - | - | + +## Resource Limits + +| Resource | Limit | Current | +| ------------- | ----- | ----------- | +| Upload size | None! | Unlimited | +| Query results | 1000 | Appropriate | + +## Recommendations + +### Immediate (pre-launch) + +[Must fix for launch] + +### Short-term (first month) + +[Should fix soon after launch] + +### Long-term (as you scale) + +[Will need as traffic grows] +``` + +## Analysis Process + +1. **Identify hot paths**: Find high-frequency, latency-sensitive operations +2. **Review complexity**: Look for O(n^2)+, N+1, unbounded operations +3. **Check indexes**: Verify database queries have appropriate indexes +4. **Audit caching**: Review what's cached and cache invalidation +5. **Check limits**: Verify resource limits and pagination +6. **Profile if possible**: Use actual timing data if available + +``` + +``` diff --git a/.gitignore b/.gitignore index b16133930..0946e437e 100644 --- a/.gitignore +++ b/.gitignore @@ -49,6 +49,7 @@ packages/workers/.dev.vars .dev.vars *.vars .localflare +worker-configuration.d.ts # Sensitive configuration files config/secrets.json diff --git a/packages/docs/audits/actionable-items-2026-01-19.md b/packages/docs/audits/actionable-items-2026-01-19.md new file mode 100644 index 000000000..8bb88d509 --- /dev/null +++ b/packages/docs/audits/actionable-items-2026-01-19.md @@ -0,0 +1,110 @@ +# Actionable Items from Codebase Analysis + +**Date:** 2026-01-19 +**Source:** Consolidated from Architecture, Security, Error Handling, and Local-First analyses +**Status:** Filtered to remove non-issues and low-value items + +--- + +## High Priority - Should Fix Before Production + +| Item | Source | Description | Effort | +| ------------------------------------- | -------------- | ----------------------------------------------------------------------------------------------------------------------- | ------- | +| **Verify admin role assignment** | Security | Confirm roles can only be set server-side, not via any user-facing API | 1-2 hrs | +| **Add email header sanitization** | Security | Use existing `sanitizeEmailSubject()` in contact form to prevent header injection | 30 min | +| **Enable Sentry integration** | Error Handling | Uncomment prepared integration in `errorLogger.js` | 1-2 hrs | +| **Fix silent error suppression** | Error Handling | Replace `.catch(() => {})` and `.catch(console.warn)` patterns (~17 occurrences) with `bestEffort()` or proper handling | 2-3 hrs | +| **Investigate service worker status** | Local-First | Verify why service workers are actively unregistered - intentional or bug? | 1 hr | + +--- + +## Medium Priority - Improve Quality + +| Item | Source | Description | Effort | +| --------------------------------------- | ------------ | ------------------------------------------------------------------------ | -------- | +| **Complete command pattern migration** | Architecture | Extract remaining inline business logic from routes to command functions | 8-16 hrs | +| **Add PDF cache eviction** | Local-First | Implement LRU eviction with ~100MB limit to prevent quota issues | 3-4 hrs | +| **IndexedDB quota management** | Local-First | Handle `QuotaExceededError` gracefully with user prompt | 6-8 hrs | +| **Implement distributed rate limiting** | Security | Replace in-memory Map with Durable Objects for production rate limiting | 4-6 hrs | + +--- + +## Low Priority - Nice to Have + +| Item | Source | Description | Effort | +| --------------------------------- | ------------ | ------------------------------------------------------------------ | --------- | +| **Add sync status indicators** | Local-First | Show "Syncing..." / "Last synced X ago" on project cards | 2-3 hrs | +| **Extract ProjectDoc.ts modules** | Architecture | Split 800-line file into websocket-manager, yjs-sync-handler, etc. | 16-24 hrs | +| **Add awareness indicators** | Local-First | Show avatars of users viewing same checklist | 6-8 hrs | + +--- + +## Quick Wins (< 1 hour each) + +1. **Email sanitization** - Already have the function, just call it +2. **Verify admin roles** - Code review, not code change +3. **Service worker investigation** - Check git history for why it was disabled + +--- + +## Recommended Order + +1. Quick wins (email sanitization, verify admin roles) +2. Enable Sentry (gives visibility into production issues) +3. Fix silent error suppression (prevents hidden failures) +4. Investigate service worker +5. Then tackle medium priority items as time permits + +--- + +## Items Confirmed as Non-Issues + +The following items from the original audits were identified as non-issues or over-engineering: + +### Security + +- **HSTS in development** - Cloudflare enforces HTTPS at edge for production +- **Raw SQL in migration check** - Hardcoded SQL with no user input, zero injection risk +- **Webhook timing attacks** - Stripe SDK handles this correctly +- **WebSocket security headers** - Origin validation exists in WebSocket handler +- **7-day session expiry** - Industry standard with daily renewal +- **X-Frame-Options DENY** - Secure default, no legitimate iframe usage + +### Architecture + +- **Large component files** - Well-organized files don't need splitting just for size +- **EmbedPDF dependency size** - Expected for a PDF-heavy research tool +- **Middleware execution order** - Documentation is sufficient +- **Split useProject primitive** - Tree-shaking handles unused exports + +### Error Handling + +- **Circuit breaker** - No external API dependencies that need this +- **Error rate limiting** - Could mask real issues +- **Correlation IDs** - Cloudflare has built-in request tracing +- **Error boundaries everywhere** - Having them at key routes is sufficient +- **Only 9 throw statements** - Shows good use of structured domain errors +- **DB deadlock handling** - D1/SQLite doesn't have deadlocks + +### Local-First + +- **Optimistic updates** - Yjs sync is already fast (~10-50ms) +- **Auth cache in IndexedDB** - localStorage rarely hits limits +- **Skip retries when offline** - Only saves ~14s delay +- **P2P sync** - Not needed for server-based architecture +- **Rich conflict resolution UI** - CRDTs handle conflicts automatically + +### Already Implemented + +- **Offline indicator** - Already in navbar + +--- + +## Reference + +Full analysis reports available in: + +- `packages/docs/audits/architecture-analysis.md` +- `packages/docs/audits/security-analysis.md` +- `packages/docs/audits/error-handling-analysis.md` +- `packages/docs/audits/local-first-analysis.md` diff --git a/packages/docs/audits/ai-agent-pipeline-comparison-solidtype-vs-corates.md b/packages/docs/audits/ai-agent-pipeline-comparison-solidtype-vs-corates.md deleted file mode 100644 index afd759baa..000000000 --- a/packages/docs/audits/ai-agent-pipeline-comparison-solidtype-vs-corates.md +++ /dev/null @@ -1,503 +0,0 @@ -# AI Agent Assistance Pipeline Analysis: SolidType vs CoRATES - -**Date:** 2026-01-15 -**Purpose:** Comprehensive comparison of AI agent development workflows, tools, and documentation strategies - ---- - -## Executive Summary - -Both SolidType and CoRATES are SolidJS-based web applications that have invested significantly in AI agent assistance infrastructure. However, they take fundamentally different approaches: - -| Aspect | SolidType | CoRATES | -| ----------------------- | ------------------------------------------- | ----------------------------------------------------- | -| **Primary Focus** | AI-assisted CAD modeling (end-user feature) | AI-assisted development (developer tooling) | -| **Documentation Style** | Hierarchical, spec-driven | Modular, context-scoped | -| **Planning Approach** | Phase-based roadmap | Feature-scoped plans | -| **MCP Usage** | None (uses custom AI integration) | Extensive (custom MCP server) | -| **Agent Instructions** | Single comprehensive AGENTS.md | Split: copilot-instructions.md + .cursor/rules/\*.mdc | - ---- - -## 1. Documentation Architecture - -### SolidType Approach - -SolidType uses a **hierarchical documentation pyramid** with clear reading order: - -``` -AGENTS.md (Entry Point) - | - +-- docs/OVERVIEW.md (Vision & Goals) - +-- docs/ARCHITECTURE.md (Package Structure) - +-- docs/STATUS.md (Current State) - +-- /plan/* (27 Phases) - | - +-- Appendices (Testing, Naming, Solver) - +-- docs/AI-INTEGRATION.md (AI System Spec) - +-- docs/DOCUMENT-MODEL.md (Yjs Schema) - +-- docs/TOPOLOGICAL-NAMING.md (Algorithm Design) - +-- refs/ (Reference Implementations) -``` - -**Strengths:** - -- Clear reading order with explicit dependencies -- "Docs are source of truth" philosophy - code must match docs -- Separate STATUS.md tracks implementation progress vs plan -- Reference implementations directory with download scripts -- Pinned decisions prevent schema drift - -**Weaknesses:** - -- Single AGENTS.md can be overwhelming (369 lines) -- No context-scoping for different file types -- All rules apply globally regardless of what you're editing - -### CoRATES Approach - -CoRATES uses a **modular, context-scoped documentation** system: - -``` -.github/copilot-instructions.md (GitHub Copilot - Always Applied) - | -.cursor/rules/*.mdc (14 Rule Files) - | - +-- corates.mdc (Always Applied - Core Rules) - +-- api-routes.mdc (Glob: packages/workers/**) - +-- solidjs.mdc (Glob: packages/web/**, packages/landing/**) - +-- workers.mdc (Glob: packages/workers/**) - +-- ui-components.mdc - +-- error-handling.mdc - +-- [Domain-specific: yjs-sync, pdf-handling, etc.] - | -packages/docs/guides/* (14 Comprehensive Guides) -packages/docs/plans/* (Feature Plans) -packages/docs/audits/* (Analysis Documents) -``` - -**Strengths:** - -- Context-aware rules via glob patterns (only relevant rules apply) -- Separation between quick-reference rules and comprehensive guides -- Supports multiple AI tools (Copilot + Cursor) -- Guides can be viewed via docs site (`pnpm docs`) -- Domain-specific rules for complex areas - -**Weaknesses:** - -- No explicit reading order for new agents -- Rules split across multiple locations can be harder to discover -- No centralized status tracking document - ---- - -## 2. Planning Systems - -### SolidType: Phase-Based Roadmap - -SolidType uses a **27-phase incremental plan** with clear dependencies: - -``` -plan/ - 00-overview.md # Architecture philosophy - 01-document-model.md # Yjs schema - 02-kernel-viewer-wiring.md - ... - 23-ai-core-infrastructure.md # 3572 lines! - 24-ai-dashboard.md - 25-ai-sketch.md - 26-ai-modeling.md - 27-user-system-persistence.md - appendix/ - testing-strategy.md - naming-strategy.md - solver-roadmap.md -``` - -**Plan Structure:** - -- Each phase has status indicator (Complete, In Progress, Planned) -- Prerequisites clearly stated -- Goals, implementation details, code examples -- Success criteria defined -- Related documents linked - -**Key Pattern - Plan Document Structure:** - -```markdown -# Phase N: Feature Name - -> **Status:** [Complete|In Progress|Planned] - -## Prerequisites - -- Phase X: Dependency - -## Goals - -- Numbered list of deliverables - -## Implementation - -[Detailed code examples and architecture] - -## Success Criteria - -1. Functionality works -2. Tests pass -3. No regressions -4. Documentation updated -``` - -### CoRATES: Feature-Scoped Plans - -CoRATES uses **feature-scoped planning documents**: - -``` -packages/docs/plans/ - frontend-testing-plan.md - org-billing-implementation.md - ownership-billing-multitenancy.md - presence.md - pricing-model.md - yjs-awareness.md - ... -``` - -**Strengths:** - -- Plans are self-contained and discoverable -- Can work on multiple features independently -- Audits separate from plans (audits/ vs plans/) - -**Weaknesses:** - -- No clear phase ordering or dependencies -- No centralized status tracking -- No explicit success criteria format - ---- - -## 3. MCP Server Implementation - -### SolidType - -SolidType does **NOT use MCP** for development assistance. Instead, it has a comprehensive in-app AI system for end-users: - -- TanStack AI integration for chat -- Durable Streams for persistence -- SharedWorker architecture for isolation -- Tool definitions for CAD operations (sketch, modeling) -- System prompts for different contexts - -### CoRATES - -CoRATES has a **dedicated MCP server** (`packages/mcp/`) for development assistance: - -```typescript -// Server structure -server.ts -tools/ - icons.ts # Search solid-icons library - lint.ts # Run pnpm lint - local-docs.ts # Access local docs with llms.txt - better-auth.ts # Fetch Better-Auth documentation - stripe.ts # Fetch Stripe documentation - drizzle.ts # Fetch Drizzle ORM documentation - code-review.ts # Structured code review with project criteria - tanstack-query.ts # TanStack Query docs - tanstack-router.ts - tanstack-start.ts -``` - -**Key MCP Tools:** - -| Tool | Purpose | -| ------------------ | --------------------------------------- | -| `search_icons` | Find icons in solid-icons by name | -| `run_lint` | Run linter with optional autofix | -| `docs_list` | List local docs with llms.txt | -| `better_auth_docs` | Fetch Better-Auth documentation | -| `drizzle_docs` | Fetch Drizzle ORM documentation | -| `code_review` | Project-specific structured code review | - -**code_review Tool - Standout Feature:** - -The code review tool provides project-specific review criteria with severity levels: - -```typescript -// Severity definitions embedded in prompt -CRITICAL: Data corruption, security vulnerabilities, crashes -MODERATE: Architectural debt, maintenance burden, performance -LOW: Clarity, consistency, future maintainability - -// Focus areas -- Data & Validation (Zod schemas) -- Database & State (Drizzle patterns) -- Async & Failure Modes -- Security & Trust Boundaries -- Architecture & Design -- Performance & Resource Safety -``` - ---- - -## 4. Agent Instruction Patterns - -### SolidType AGENTS.md Structure - -```markdown -# Welcome, agent - -# Reading order (explicit) - -# Reference implementations - -## 1. Your Responsibilities - -## 2. Package & Layer Rules - -## 3. Preferred Libraries & Tooling - -## 4. Coding Style Guide - -## 5. Testing Expectations - -## 6. How to Work With the Plan - -## 7. Things You Should Not Do - -## 8. Summary -``` - -**Notable Patterns:** - -- "If docs conflict with code, docs win" -- `// TODO(agent): ...` comment convention -- Explicit "Things You Should Not Do" section -- Library approval process documented - -### CoRATES copilot-instructions.md Structure - -```markdown -# Agent Instructions - -## Package Structure - -## Critical Rules - -### Coding Standards - -### File Organization - -### Libraries (MUST USE) - -### Code Comments - -### UI Components - -### Database Migrations - -### SolidJS Critical Patterns - -## Documentation - -## Specialized Rule Files - -## Complex Area Rules - -## Additional Notes -``` - -**Notable Patterns:** - -- "NEVER use emojis anywhere" (strict rule) -- References to specialized .mdc files -- Links to comprehensive guides -- MCP tools explicitly mentioned as documentation sources - ---- - -## 5. Reference Material Strategy - -### SolidType: Downloaded Source Code - -SolidType provides **actual source code** from production CAD systems: - -``` -refs/ - download-refs.sh - README.md - | - +-- OCCT (Open CASCADE) - +-- CGAL (Computational Geometry) - +-- FreeCAD (Topological Naming) - +-- Fornjot (Rust B-Rep) -``` - -Each reference has: - -- Download script -- Key directories to study -- When to reference -- Key files for specific algorithms - -**Advantage:** Agents can study actual implementations of complex algorithms. - -### CoRATES: External Documentation Fetching - -CoRATES uses **MCP tools to fetch documentation** on-demand: - -- better_auth_docs -- drizzle_docs -- tanstack\_\*\_docs -- Local docs with llms.txt format - -**Advantage:** Always up-to-date, no repository bloat, contextual fetching. - ---- - -## 6. Testing Documentation - -### SolidType - -Testing documented in AGENTS.md with clear structure: - -```markdown -## 5. Testing Expectations - -- TDD-oriented -- tests/ directory alongside src/ -- Vitest unit tests -- Guidelines: - - Fast and deterministic - - Clear, example-based - - Use architecture namespacing -``` - -Also has `appendix/testing-strategy.md` with minimum requirements. - -### CoRATES - -Comprehensive testing guide at `packages/docs/guides/testing.md` (445 lines): - -- Testing philosophy (behavior-driven) -- AAA pattern -- Frontend testing (SolidJS testing library) -- Backend testing (Workers, D1) -- Test fixtures and mocking -- Coverage expectations - ---- - -## 7. Lessons Learned / Recommendations for CoRATES - -### Adopt from SolidType - -| Feature | Recommendation | Priority | -| ---------------------- | ------------------------------------------------------------------------------------- | -------- | -| **STATUS.md** | Create a centralized status tracking document showing implementation progress vs plan | High | -| **Reading Order** | Add explicit reading order in copilot-instructions.md with numbered list | Medium | -| **"Docs win" Policy** | Explicitly state that documentation is source of truth in instructions | Medium | -| **Success Criteria** | Add success criteria checklist to plan documents | Medium | -| **TODO Convention** | Standardize `// TODO(agent): ...` pattern for agent-inserted comments | Low | -| **"Don't Do" Section** | Add explicit anti-patterns section to main instructions | Medium | -| **Phase Dependencies** | If plans have dependencies, make them explicit | Low | - -### Keep CoRATES Strengths - -| Feature | Why Keep | -| ------------------------ | ------------------------------------------------------------------------- | -| **Context-scoped Rules** | Glob patterns prevent irrelevant rules from cluttering context | -| **MCP Server** | Dynamic documentation fetching is superior to static copies | -| **code_review Tool** | Project-specific review criteria is excellent | -| **Separate Guides** | Comprehensive guides in docs site enable both agent and human consumption | -| **Audit Documents** | Separation of audits from plans is cleaner | - -### New Ideas to Implement - -1. **Reference Implementations Directory** - - Consider adding a `reference/` directory with curated examples of: - - Yjs sync patterns from other projects - - Better-Auth integration examples - - Cloudflare Workers patterns - -2. **Plan Template** - - Create a standardized plan template with: - - ```markdown - # Plan: [Feature Name] - - **Status:** [Draft|In Progress|Complete] - **Prerequisites:** [List dependencies] - - ## Goals - - ## Implementation - - ## Success Criteria - - - [ ] Functionality works - - [ ] Tests pass - - [ ] Documentation updated - - ## Related Documents - ``` - -3. **Agent Onboarding Document** - - Create a short AGENTS.md that provides: - - 30-second orientation - - Key reading order - - Quick "do/don't" reference - - Links to comprehensive docs - ---- - -## 8. Comparison Matrix - -| Category | SolidType | CoRATES | Winner | -| -------------------------- | ----------------------------------- | ---------------------- | ------------------ | -| **Initial Orientation** | Single AGENTS.md with reading order | Scattered across files | SolidType | -| **Context Relevance** | All rules always apply | Glob-scoped rules | CoRATES | -| **Status Tracking** | Dedicated STATUS.md | None | SolidType | -| **Documentation Currency** | Static (can drift) | MCP fetches live docs | CoRATES | -| **Planning Structure** | 27 sequential phases | Feature-scoped plans | Depends on project | -| **Reference Material** | Downloadable source code | External doc fetching | Both valid | -| **Code Review** | Manual | MCP tool with criteria | CoRATES | -| **Multi-Tool Support** | Single approach | Copilot + Cursor | CoRATES | -| **Complex Domain Rules** | Inline in main doc | Separate .mdc files | CoRATES | -| **Success Criteria** | Explicit in plans | Not standardized | SolidType | - ---- - -## 9. Conclusion - -Both projects demonstrate mature thinking about AI-assisted development, but optimize for different scenarios: - -**SolidType** excels at: - -- Onboarding new agents to a complex domain (CAD) -- Maintaining architectural integrity through phases -- Providing reference implementations for complex algorithms - -**CoRATES** excels at: - -- Context-aware assistance (only relevant rules apply) -- Dynamic documentation access (always current) -- Structured code review with project-specific criteria -- Supporting multiple AI tools simultaneously - -### Recommended Actions for CoRATES - -1. **Immediate:** Create `packages/docs/STATUS.md` tracking implementation progress -2. **Short-term:** Add reading order to copilot-instructions.md -3. **Short-term:** Standardize plan template with success criteria -4. **Medium-term:** Create brief AGENTS.md as orientation document -5. **Consider:** Add "Things You Should Not Do" section to core instructions -6. **Consider:** Document `// TODO(agent):` convention for agent-inserted comments - -The combination of CoRATES's dynamic tooling (MCP server, code review) with SolidType's structured onboarding (reading order, status tracking, explicit policies) would create an optimal AI agent assistance pipeline. diff --git a/packages/docs/audits/architecture-analysis.md b/packages/docs/audits/architecture-analysis.md new file mode 100644 index 000000000..4047ab4b4 --- /dev/null +++ b/packages/docs/audits/architecture-analysis.md @@ -0,0 +1,1373 @@ +# Architecture Analysis Report + +**Date:** 2026-01-19 +**Scope:** CoRATES monorepo architecture patterns +**Focus:** Code structure, SOLID principles, layering, dependency management, component design + +--- + +## Executive Summary + +The CoRATES codebase demonstrates **strong architectural discipline** with well-defined patterns and clear separation of concerns. The architecture follows modern best practices for a distributed, real-time collaborative application built on SolidJS and Cloudflare Workers. + +**Key Strengths:** + +- Excellent domain-driven design with clear bounded contexts +- Strong separation between read and write operations +- Well-organized monorepo with appropriate package boundaries +- Comprehensive error handling through shared error types +- Clear architectural documentation and enforced patterns + +**Key Findings:** + +- 5 major strengths that set this codebase apart +- 4 areas for architectural improvement +- 3 medium-priority technical debt items +- Overall architecture grade: **A-** (Strong foundation with room for optimization) + +--- + +## Table of Contents + +1. [Package Architecture](#1-package-architecture) +2. [SOLID Principles Analysis](#2-solid-principles-analysis) +3. [Layering and Separation of Concerns](#3-layering-and-separation-of-concerns) +4. [Dependency Management](#4-dependency-management) +5. [Component Design Patterns](#5-component-design-patterns) +6. [Data Flow Architecture](#6-data-flow-architecture) +7. [Code Organization](#7-code-organization) +8. [Findings and Recommendations](#8-findings-and-recommendations) + +--- + +## 1. Package Architecture + +### 1.1 Monorepo Structure + +**Status:** Excellent + +The project uses a well-organized monorepo structure with clear package boundaries: + +``` +packages/ + ├── web/ # SolidJS frontend + ├── workers/ # Cloudflare Workers backend + ├── landing/ # Marketing site + ├── shared/ # Shared error types and utilities + ├── docs/ # VitePress documentation + ├── mcp/ # Development tooling + └── mcp-memory/ # Agent memory system +``` + +**Strengths:** + +- Clear separation between frontend and backend +- Shared package prevents code duplication for error handling +- Documentation lives alongside code +- Each package has its own dependencies and build process + +**Package Dependency Graph:** + +``` +shared (no dependencies) + ↑ + ├── web (depends on shared) + └── workers (depends on shared) + ↑ + └── landing (depends on web via build artifact copy) +``` + +### 1.2 Package Boundaries + +**Status:** Good with minor violations + +Each package has well-defined responsibilities: + +| Package | Responsibility | Boundary Integrity | +| ------- | ----------------------------------- | ------------------ | +| shared | Domain errors, types, validators | Excellent | +| web | UI components, stores, primitives | Excellent | +| workers | API routes, middleware, Durable Obj | Excellent | +| landing | Marketing content, includes web app | Good (by design) | + +**Finding:** No inappropriate cross-package dependencies detected. The `landing` package's inclusion of `web` is intentional for deployment purposes. + +### 1.3 Build and Deployment Strategy + +**Status:** Excellent + +- Independent build processes per package +- Workers can deploy independently of frontend +- Frontend builds copy to landing for single-worker deployment +- No circular dependencies in build pipeline + +--- + +## 2. SOLID Principles Analysis + +### 2.1 Single Responsibility Principle (SRP) + +**Status:** Excellent + +**Evidence:** + +1. **Store Pattern** - Clear separation of read and write: + + ``` + projectStore.js - Read operations only + projectActionsStore/ - Write operations organized by domain + ├── index.js + ├── studies.js + ├── checklists.js + ├── pdfs.js + ├── members.js + └── reconciliation.js + ``` + +2. **Command Pattern in Backend** - Each command has a single purpose: + + ```typescript + // packages/workers/src/commands/projects/createProject.ts + export async function createProject(env, actor, params) { + // Single responsibility: Create project with quota check + } + ``` + +3. **Middleware Composition** - Each middleware has one concern: + - `requireAuth.ts` - Authentication only + - `requireOrg.ts` - Organization membership only + - `requireEntitlement.ts` - Subscription checks only + +**Minor Issue Identified:** + +Location: `packages/workers/src/durable-objects/ProjectDoc.ts` + +- Lines 1-800+ in a single file +- Handles WebSocket connections, Yjs sync, auth, and data operations +- **Recommendation:** Extract sub-handlers for different concerns + +### 2.2 Open/Closed Principle (OCP) + +**Status:** Good + +**Evidence:** + +1. **Checklist Registry Pattern** - Open for extension: + + ```javascript + // packages/web/src/checklist-registry/index.js + export const checklistRegistry = { + amstar2: AMSTAR2Checklist, + 'robins-i': ROBINSIChecklist, + rob2: ROB2Checklist, + // Easy to add new checklist types without modifying existing code + }; + ``` + +2. **Error System** - Extensible error definitions: + + ```typescript + // packages/shared/src/errors/domains/domain.ts + export const PROJECT_ERRORS = { + NOT_FOUND: { code: 'PROJECT_NOT_FOUND', statusCode: 404, message: '...' }, + ACCESS_DENIED: { code: 'PROJECT_ACCESS_DENIED', statusCode: 403, message: '...' }, + // New errors can be added without breaking existing consumers + }; + ``` + +3. **Middleware Chain** - New middleware can be added without modifying routes: + ```typescript + routes.use('*', requireAuth); + routes.use('*', corsMiddleware); + // New global middleware can be inserted here + ``` + +**Areas for Improvement:** + +- Route handlers could be more extensible (currently inline handlers) +- Component composition could leverage more composition patterns + +### 2.3 Liskov Substitution Principle (LSP) + +**Status:** Good + +**Evidence:** + +1. **Consistent Store Interface** - All stores follow same pattern: + + ```javascript + // Every store exports same structure + { + store, // Raw store for reactive access + getXxx(), // Getter methods + setXxx(), // Setter methods (or via actions store) + } + ``` + +2. **Middleware Contract** - All middleware follows Hono signature: + ```typescript + type Middleware = (c: Context, next: Next) => Promise; + ``` + +**No violations detected.** + +### 2.4 Interface Segregation Principle (ISP) + +**Status:** Good + +**Evidence:** + +1. **Focused Store Interfaces** - Stores expose only needed methods: + + ```javascript + // projectStore.js - Read interface only + export default { + getProject, + getActiveProject, + getStudies, + getMeta, + // No write methods exposed + }; + ``` + +2. **Action Store Modules** - Segregated by concern: + + ```javascript + projectActionsStore.study.create(); + projectActionsStore.checklist.update(); + projectActionsStore.pdf.upload(); + // Components only import what they need + ``` + +3. **Primitive Hooks** - Small, focused interfaces: + ```javascript + useProject(projectId); // Project operations + useOnlineStatus(); // Just online status + useProjectList(); // Just project list + ``` + +**Issue Identified:** + +Location: `packages/web/src/primitives/useProject/index.js` + +- Returns large object with many operations +- Components might not need all operations +- **Recommendation:** Consider splitting into smaller primitives + +### 2.5 Dependency Inversion Principle (DIP) + +**Status:** Good with room for improvement + +**Strengths:** + +1. **Dependency Injection in Commands:** + + ```typescript + // Commands depend on abstractions (env, actor), not concrete implementations + export async function createProject(env: Env, actor: Actor, params: Params); + ``` + +2. **Database Client Factory:** + ```typescript + // Workers don't depend on D1 directly, use abstraction + const db = createDb(c.env.DB); + ``` + +**Areas for Improvement:** + +1. **Direct Store Imports in Components:** + + ```javascript + // Components depend on concrete stores + import projectStore from '@/stores/projectStore.js'; + ``` + + This is acceptable for singleton stores but makes testing harder. + +2. **Better Auth Singleton:** + ```javascript + // Direct dependency on Better Auth implementation + import { useBetterAuth } from '@api/better-auth-store.js'; + ``` + Could benefit from auth abstraction layer for testing. + +**Recommendation:** Consider dependency injection for stores in critical components, or factory pattern for testability. + +--- + +## 3. Layering and Separation of Concerns + +### 3.1 Frontend Architecture + +**Status:** Excellent + +The frontend follows a clear layered architecture: + +``` +┌─────────────────────────────────────┐ +│ Components (Presentation Layer) │ +│ - Pure UI, minimal logic │ +└──────────────┬──────────────────────┘ + │ +┌──────────────▼──────────────────────┐ +│ Primitives (Hooks/Composition) │ +│ - Reusable logic, lifecycle mgmt │ +└──────────────┬──────────────────────┘ + │ +┌──────────────▼──────────────────────┐ +│ Stores (State Management) │ +│ - projectStore (read) │ +│ - projectActionsStore (write) │ +└──────────────┬──────────────────────┘ + │ +┌──────────────▼──────────────────────┐ +│ API Layer / Yjs Sync │ +│ - Network communication │ +│ - IndexedDB persistence │ +└─────────────────────────────────────┘ +``` + +**Strengths:** + +1. **Clear Responsibility per Layer:** + - Components render UI and handle user interaction + - Primitives manage component lifecycle and data fetching + - Stores maintain application state + - API layer handles communication + +2. **No Layer Violations:** + - Components don't directly call APIs (go through stores/primitives) + - Stores don't contain UI logic + - Primitives don't directly manipulate DOM (use SolidJS primitives) + +3. **Unidirectional Data Flow:** + ``` + User Action → Component → Actions Store → API/Yjs → Store Update → Component Re-render + ``` + +**Example of Excellent Layering:** + +```javascript +// Component Layer - packages/web/src/components/project/CreateProjectForm.jsx +function CreateProjectForm() { + const handleSubmit = async (data) => { + await projectActionsStore.createProject(data); // Delegates to actions + }; + return
...
; +} + +// Actions Layer - packages/web/src/stores/projectActionsStore/project.js +async function createProject(data) { + // Business logic and API calls + const response = await fetch('/api/projects', { ... }); + projectStore.addProject(await response.json()); +} + +// Store Layer - packages/web/src/stores/projectStore.js +function addProject(project) { + setStore('projects', projects => [...projects, project]); +} +``` + +### 3.2 Backend Architecture + +**Status:** Good with minor issues + +The backend follows a layered approach: + +``` +┌─────────────────────────────────────┐ +│ Routes (HTTP Layer) │ +│ - Request/response handling │ +│ - Validation via middleware │ +└──────────────┬──────────────────────┘ + │ +┌──────────────▼──────────────────────┐ +│ Middleware (Cross-cutting) │ +│ - Auth, CORS, validation │ +└──────────────┬──────────────────────┘ + │ +┌──────────────▼──────────────────────┐ +│ Commands (Business Logic) │ +│ - Domain operations │ +│ - Transaction management │ +└──────────────┬──────────────────────┘ + │ +┌──────────────▼──────────────────────┐ +│ Data Layer (Drizzle ORM) │ +│ - Database operations │ +│ - Schema definitions │ +└─────────────────────────────────────┘ +``` + +**Strengths:** + +1. **Command Pattern** - Excellent separation of business logic: + + ```typescript + // Route layer just coordinates + routes.post('/', async c => { + const { project } = await createProject(c.env, actor, params); + return c.json(project, 201); + }); + + // Command layer contains business logic + export async function createProject(env, actor, params) { + // Validation, quota checks, database operations, DO sync + } + ``` + +2. **Middleware Composition** - Clean separation of concerns +3. **Shared Error Handling** - Consistent error responses via `@corates/shared` + +**Issues Identified:** + +1. **Location:** `packages/workers/src/routes/orgs/projects.ts` + - Some business logic still in route handlers + - Should be extracted to commands + - **Priority:** Medium + +2. **Location:** Route files in general + - Mix of inline handlers and command delegation + - Inconsistent pattern application + - **Recommendation:** Enforce command pattern for all write operations + +### 3.3 Cross-Cutting Concerns + +**Status:** Excellent + +Cross-cutting concerns are well-handled: + +| Concern | Implementation | Quality | +| ---------------- | ----------------------------- | --------- | +| Error Handling | `@corates/shared` error types | Excellent | +| Authentication | Better Auth + middleware | Excellent | +| Logging | Console + structured errors | Good | +| Validation | Zod schemas + OpenAPIHono | Excellent | +| Authorization | Policy-based middleware | Good | +| Rate Limiting | Middleware per route | Good | +| CORS | Global middleware | Excellent | +| Security Headers | Global middleware | Excellent | + +--- + +## 4. Dependency Management + +### 4.1 Import Path Management + +**Status:** Excellent + +The codebase uses path aliases consistently: + +**Frontend (packages/web/jsconfig.json):** + +```json +{ + "@/*": ["src/*"], + "@components/*": ["src/components/*"], + "@primitives/*": ["src/primitives/*"], + "@api/*": ["src/api/*"], + "@lib/*": ["src/lib/*"] +} +``` + +**Backend (packages/workers/tsconfig.json):** + +```json +{ + "@/*": ["./src/*"] +} +``` + +**Strengths:** + +- Consistent alias usage throughout codebase +- No relative path hell (`../../../lib/utils`) +- Clear semantic meaning +- Easy to refactor file locations + +**Example Usage:** + +```javascript +// Clean imports +import projectStore from '@/stores/projectStore.js'; +import { handleFetchError } from '@lib/error-utils.js'; +import MyComponent from '@components/MyComponent.jsx'; +``` + +### 4.2 Dependency Direction + +**Status:** Good + +Dependencies flow in the correct direction: + +``` +Presentation Layer (Components) + ↓ depends on +Domain Layer (Stores, Primitives) + ↓ depends on +Infrastructure Layer (API, Database) +``` + +**No circular dependencies detected.** + +**Dependency Metrics:** + +- Total packages: 8 +- Package coupling: Low (only shared as common dependency) +- Component coupling: Appropriate (components import stores/primitives) +- Store coupling: None (stores are independent) + +### 4.3 Third-Party Dependencies + +**Status:** Good with cautious approach + +**Frontend Dependencies (key):** + +- `solid-js` - Core framework +- `@ark-ui/solid` - Accessible UI components +- `yjs` - CRDT for real-time collaboration +- `dexie` - IndexedDB wrapper for offline storage +- `@tanstack/solid-query` - Server state management +- `better-auth` - Authentication +- `@embedpdf/*` - PDF viewer (large dependency tree) + +**Backend Dependencies (key):** + +- `hono` - Web framework +- `@hono/zod-openapi` - API validation and docs +- `drizzle-orm` - Database ORM +- `better-auth` - Authentication +- `stripe` - Payments +- `yjs` - CRDT for Durable Objects + +**Concerns:** + +1. **EmbedPDF Dependency Size:** + - 20+ `@embedpdf/*` packages imported + - Contributes significantly to bundle size + - **Recommendation:** Evaluate if all features are needed, consider lazy loading + +2. **Duplicate Dependencies:** + - `better-auth` in both frontend and backend (necessary for auth client) + - `yjs` in both (necessary for sync) + - These are acceptable duplicates + +--- + +## 5. Component Design Patterns + +### 5.1 Component Organization + +**Status:** Excellent + +Components are organized by feature and type: + +``` +components/ + ├── auth/ # Authentication flows + ├── project/ # Project management + │ ├── overview-tab/ + │ ├── all-studies-tab/ + │ ├── todo-tab/ + │ ├── reconcile-tab/ + │ └── completed-tab/ + ├── checklist/ # Checklist implementations + │ ├── AMSTAR2Checklist/ + │ ├── ROBINSIChecklist/ + │ └── ROB2Checklist/ + ├── admin/ # Admin dashboard + ├── dashboard/ # User dashboard + ├── settings/ # Settings pages + └── billing/ # Billing components +``` + +**Strengths:** + +- Feature-based organization (not by type) +- Co-location of related components +- Clear naming conventions +- Barrel exports for clean imports + +### 5.2 SolidJS Best Practices + +**Status:** Excellent (per SolidJS audit) + +The codebase adheres to SolidJS best practices: + +1. **No Prop Destructuring** - ✓ 100% compliance + + ```javascript + // Correct pattern used everywhere + function MyComponent(props) { + const value = () => props.value; // Maintains reactivity + } + ``` + +2. **Control Flow Components** - ✓ Consistent usage + + ```javascript + ... + {item => ...} + ``` + +3. **Store Pattern** - ✓ Excellent separation + - Read stores separate from write stores + - No prop drilling of store data + - Direct imports where needed + +4. **Effect Usage** - ✓ Appropriate + - Effects used for legitimate side effects only + - Derived state uses `createMemo` not effects + - Proper cleanup with `onCleanup` + +**Reference:** Full audit at `packages/docs/audits/solidjs-best-practices-audit-2026-01.md` + +### 5.3 Component Size and Complexity + +**Status:** Good with some large files + +**Component File Size Distribution:** + +- Small (< 100 lines): ~60% +- Medium (100-300 lines): ~30% +- Large (300-500 lines): ~8% +- Very Large (> 500 lines): ~2% + +**Large Files Identified:** + +1. `packages/web/src/components/checklist/AMSTAR2Checklist/AMSTAR2Checklist.jsx` + - ~800 lines + - Contains multiple sub-sections + - **Recommendation:** Already well-organized, consider further extraction if it grows + +2. `packages/workers/src/durable-objects/ProjectDoc.ts` + - ~800 lines + - Complex WebSocket and Yjs handling + - **Recommendation:** Extract helper modules (already noted in file comments) + +**Overall:** Most components are appropriately sized and focused. + +### 5.4 Composition Patterns + +**Status:** Good + +The codebase uses several composition patterns effectively: + +1. **Primitive Composition:** + + ```javascript + function useProject(projectId) { + const connection = createConnectionManager(); + const sync = createSyncManager(); + const studies = createStudyOperations(); + return { connection, sync, studies }; + } + ``` + +2. **Higher-Order Components:** + + ```javascript + function withPdf(Component) { + return props => ( + + + + ); + } + ``` + +3. **Render Props (Children as Function):** + ```javascript + {(item, index) => } + ``` + +**No anti-patterns detected.** + +--- + +## 6. Data Flow Architecture + +### 6.1 State Management Strategy + +**Status:** Excellent + +The application uses a hybrid state management approach: + +``` +┌──────────────────────────────────────────────────┐ +│ Local State (createSignal/createStore) │ +│ - Component-specific UI state │ +│ - Form inputs, modals, toggles │ +└──────────────────────────────────────────────────┘ + +┌──────────────────────────────────────────────────┐ +│ Global Stores (Singleton Stores) │ +│ - projectStore (cached project data) │ +│ - adminStore (admin data) │ +│ - pdfPreviewStore (PDF viewer state) │ +└──────────────────────────────────────────────────┘ + +┌──────────────────────────────────────────────────┐ +│ Server State (@tanstack/solid-query) │ +│ - Project list, user data │ +│ - Cached, refetchable server data │ +└──────────────────────────────────────────────────┘ + +┌──────────────────────────────────────────────────┐ +│ Real-time State (Yjs + IndexedDB) │ +│ - Project studies, checklists, answers │ +│ - Collaborative CRDT-based state │ +└──────────────────────────────────────────────────┘ +``` + +**Strengths:** + +- Clear boundaries between state types +- Each state type uses appropriate tool +- No redundant state storage +- Well-documented patterns + +### 6.2 Data Synchronization + +**Status:** Excellent + +The application has sophisticated data sync: + +**Offline-First with Yjs:** + +``` +Client IndexedDB (y-dexie) ←→ Yjs Doc ←→ WebSocket ←→ ProjectDoc DO +``` + +**Caching Strategy:** + +```javascript +// packages/web/src/stores/projectStore.js +const projectStats = loadPersistedStats(); // From localStorage +const projectList = loadCachedProjectList(); // From localStorage with TTL +``` + +**Sync Layers:** + +1. **Optimistic Updates** - Immediate UI feedback +2. **Local Persistence** - IndexedDB for offline access +3. **Real-time Sync** - WebSocket to Durable Object +4. **Cache Management** - localStorage for quick loads + +**Issue Identified:** + +Location: Multiple stores use localStorage directly + +- No centralized cache invalidation strategy +- Potential for stale data +- **Recommendation:** Create cache management utility + +### 6.3 Error Propagation + +**Status:** Excellent + +Error handling flows through well-defined layers: + +``` +API Error + ↓ +createDomainError (typed error object) + ↓ +Return in response with status code + ↓ +Frontend catches in action store + ↓ +Shows toast notification + ↓ +Component ErrorBoundary (for unexpected errors) +``` + +**Strengths:** + +1. **Shared Error Types:** + + ```typescript + // packages/shared/src/errors/ + export const PROJECT_ERRORS = { + NOT_FOUND: { code: 'PROJECT_NOT_FOUND', statusCode: 404, ... }, + ACCESS_DENIED: { code: 'PROJECT_ACCESS_DENIED', statusCode: 403, ... }, + }; + ``` + +2. **Consistent Error Creation:** + + ```typescript + const error = createDomainError(PROJECT_ERRORS.NOT_FOUND, { projectId }); + return c.json(error, error.statusCode); + ``` + +3. **Type-Safe Error Handling:** + ```javascript + if (isDomainError(error)) { + // Handle known domain error + } + ``` + +--- + +## 7. Code Organization + +### 7.1 File and Folder Structure + +**Status:** Excellent + +Both packages follow consistent organization: + +**Frontend:** + +``` +src/ + ├── components/ # UI components (feature-based) + ├── primitives/ # Reusable hooks + ├── stores/ # Global state + ├── lib/ # Utilities + ├── api/ # API clients + ├── config/ # Configuration + ├── routes/ # Route definitions + └── __tests__/ # Test utilities +``` + +**Backend:** + +``` +src/ + ├── routes/ # API routes (feature-based) + ├── middleware/ # Request middleware + ├── commands/ # Business logic + ├── db/ # Database schema and client + ├── durable-objects/ # Durable Object implementations + ├── lib/ # Utilities + ├── policies/ # Authorization policies + ├── auth/ # Authentication setup + └── __tests__/ # Test utilities +``` + +**Strengths:** + +- Consistent structure across packages +- Clear purpose for each directory +- Co-location of related code +- Tests alongside code (`__tests__` folders) + +### 7.2 Naming Conventions + +**Status:** Excellent + +Consistent naming throughout: + +| Type | Convention | Example | +| ---------------- | ------------------------ | ------------------------ | +| Components | PascalCase | `ProjectCard.jsx` | +| Hooks/Primitives | camelCase with `use` | `useProject.js` | +| Stores | camelCase with `Store` | `projectStore.js` | +| Actions | camelCase with `Actions` | `projectActionsStore.js` | +| Utilities | camelCase | `errorLogger.js` | +| Routes | kebab-case | `project-invitations.ts` | +| Commands | camelCase | `createProject.ts` | +| Middleware | camelCase with `require` | `requireAuth.ts` | +| Types/Interfaces | PascalCase | `ProjectContext` | + +### 7.3 Module Size + +**Status:** Good + +**Frontend Module Statistics:** + +- Average file size: ~150 lines +- Median file size: ~120 lines +- 95th percentile: ~400 lines + +**Backend Module Statistics:** + +- Average file size: ~180 lines +- Median file size: ~140 lines +- 95th percentile: ~450 lines + +**Files > 500 lines:** + +- ProjectDoc.ts (800 lines) - Complex Durable Object +- AMSTAR2Checklist.jsx (800 lines) - Complex form +- ProjectDoc WebSocket handling - Could be extracted + +**Recommendation:** Files over 500 lines should be reviewed for extraction opportunities. + +### 7.4 Documentation + +**Status:** Excellent + +The codebase has comprehensive documentation: + +1. **Inline Documentation:** + - JSDoc comments for complex functions + - Explanatory comments for non-obvious logic + - Warning comments for critical files + +2. **Guide Documentation:** + - `packages/docs/guides/` - 13 comprehensive guides + - Topics: API dev, database, state management, testing, etc. + +3. **Rule Files:** + - `.cursor/rules/*.mdc` - 14 pattern files + - Agent-friendly quick reference + +4. **Architecture Diagrams:** + - `packages/docs/architecture/diagrams/` - 8 mermaid diagrams + - System overview, data flow, sync patterns + +**Outstanding Example:** + +```typescript +/** + * ProjectDoc Durable Object + * + * WARNING: HIGH BLAST RADIUS FILE + * + * This file affects ALL real-time collaboration features. + * Changes here impact: + * - Y.js document state and sync protocol + * - Project data persistence (all collaborative edits) + * ... + * + * BEFORE MODIFYING: + * 1. Read: .cursor/rules/yjs-sync.mdc and durable-objects.mdc + * 2. Run full test suite: cd packages/workers && pnpm test + * ... + */ +``` + +--- + +## 8. Findings and Recommendations + +### 8.1 Strengths + +#### 1. Excellent Separation of Read and Write Operations + +**Impact:** High +**Quality:** Exemplary + +The store pattern with separate read and write stores is a standout architectural decision: + +```javascript +// Read operations +import projectStore from '@/stores/projectStore.js'; +const projects = () => projectStore.getProjectList(); + +// Write operations +import projectActionsStore from '@/stores/projectActionsStore'; +await projectActionsStore.createProject(data); +``` + +**Benefits:** + +- Clear mental model for developers +- Easy to track state mutations +- Simplified testing (read vs write paths) +- Prevents accidental state mutations + +**Recommendation:** Document this pattern as a case study for other projects. + +#### 2. Command Pattern for Business Logic + +**Impact:** High +**Quality:** Excellent + +The backend uses command functions to encapsulate business logic: + +```typescript +// packages/workers/src/commands/projects/createProject.ts +export async function createProject(env: Env, actor: Actor, params: Params): Promise; +``` + +**Benefits:** + +- Framework-agnostic business logic +- Easy to test without HTTP layer +- Reusable across different routes +- Clear dependency injection + +**Recommendation:** Complete migration of all write operations to commands (some routes still have inline logic). + +#### 3. Shared Error Types Package + +**Impact:** High +**Quality:** Excellent + +The `@corates/shared` package provides type-safe error handling across frontend and backend: + +**Benefits:** + +- Consistent error codes and messages +- Type safety for error handling +- Single source of truth +- Easy to add new error types + +**Outstanding Implementation:** + +```typescript +// Backend creates error +const error = createDomainError(PROJECT_ERRORS.NOT_FOUND, { projectId }); + +// Frontend handles error +if (isDomainError(error)) { + switch (error.code) { + case 'PROJECT_NOT_FOUND': ... + } +} +``` + +#### 4. Real-Time Collaboration Architecture + +**Impact:** Very High +**Quality:** Excellent + +The Yjs + Durable Objects architecture for real-time collaboration is sophisticated: + +``` +Client (Yjs Doc + y-dexie) + ↕ WebSocket +ProjectDoc Durable Object (Authoritative Yjs Doc) + ↕ Persisted in DO Storage +``` + +**Benefits:** + +- Offline-first capability +- Conflict-free collaborative editing +- Optimistic UI updates +- Persistent local cache + +**Recommendation:** This architecture could be documented as a reference implementation. + +#### 5. Comprehensive Path Alias System + +**Impact:** Medium +**Quality:** Excellent + +Consistent use of path aliases eliminates import hell: + +```javascript +// Clean, maintainable imports +import projectStore from '@/stores/projectStore.js'; +import MyComponent from '@components/MyComponent.jsx'; +import { useProject } from '@primitives/useProject'; +``` + +**Benefits:** + +- Easy refactoring (move files without breaking imports) +- Clearer semantic meaning +- Consistent across the codebase + +### 8.2 Areas for Improvement + +#### 1. Inconsistent Command Pattern Usage + +**Impact:** Medium +**Priority:** High +**Affected Files:** Various route handlers in `packages/workers/src/routes/` + +**Issue:** +Some routes use the command pattern, others have business logic inline: + +```typescript +// GOOD - Uses command +const { project } = await createProject(c.env, actor, params); + +// NEEDS IMPROVEMENT - Inline logic +routes.post('/', async (c) => { + const db = createDb(c.env.DB); + // Business logic here... + await db.insert(projects).values({ ... }); +}); +``` + +**Recommendation:** + +1. Extract all write operations to command functions +2. Routes should only handle HTTP concerns (validation, response formatting) +3. Add linting rule to enforce pattern + +**Estimated Effort:** 8-16 hours + +#### 2. ProjectDoc.ts Complexity + +**Impact:** High +**Priority:** Medium +**File:** `packages/workers/src/durable-objects/ProjectDoc.ts` (800+ lines) + +**Issue:** +Single file handles multiple concerns: + +- WebSocket connection management +- Yjs sync protocol +- Authentication/authorization +- Data persistence +- Member management +- PDF metadata sync + +**Recommendation:** +Extract sub-modules: + +```typescript +// ProjectDoc.ts +import { WebSocketManager } from './lib/websocket-manager.ts'; +import { YjsSyncHandler } from './lib/yjs-sync-handler.ts'; +import { ProjectAuthGuard } from './lib/project-auth-guard.ts'; + +export class ProjectDoc { + private wsManager: WebSocketManager; + private syncHandler: YjsSyncHandler; + private authGuard: ProjectAuthGuard; + // ... +} +``` + +**Estimated Effort:** 16-24 hours + +#### 3. Cache Management Strategy + +**Impact:** Medium +**Priority:** Medium +**Affected Files:** Multiple stores + +**Issue:** +Multiple stores use localStorage directly without centralized management: + +```javascript +// projectStore.js +localStorage.setItem(PROJECT_LIST_CACHE_KEY, JSON.stringify(cached)); + +// better-auth-store.js +localStorage.setItem(AUTH_CACHE_KEY, JSON.stringify(userData)); + +// No unified cache invalidation or TTL management +``` + +**Recommendation:** +Create cache management utility: + +```javascript +// lib/cacheManager.js +export const cacheManager = { + set(key, value, ttl) { ... }, + get(key) { ... }, + invalidate(key) { ... }, + invalidateByPattern(pattern) { ... }, + clear() { ... }, +}; +``` + +**Benefits:** + +- Centralized TTL management +- Unified invalidation strategy +- Easier debugging +- Better memory management + +**Estimated Effort:** 8-12 hours + +#### 4. Large Component Files + +**Impact:** Low +**Priority:** Low +**Affected Files:** + +- `AMSTAR2Checklist.jsx` (~800 lines) +- Some reconciliation components (400-600 lines) + +**Issue:** +Some components are large but well-organized internally. + +**Recommendation:** + +- Not urgent, but consider extraction when modifying +- Already organized into logical sections +- Could extract sub-components for reusability + +**Estimated Effort:** 4-8 hours per component (as needed) + +### 8.3 Technical Debt Items + +#### 1. EmbedPDF Dependency Size + +**Impact:** Medium (bundle size) +**Priority:** Low + +**Issue:** 20+ `@embedpdf/*` packages contribute significantly to bundle size. + +**Recommendation:** + +- Audit which packages are actually used +- Lazy load PDF viewer components +- Consider alternatives if size becomes critical + +#### 2. Test Coverage Gaps + +**Impact:** Medium +**Priority:** Medium + +From STATUS.md, some areas lack comprehensive tests: + +- Frontend component tests (in progress) +- Integration tests for Durable Objects +- E2E test coverage + +**Recommendation:** + +- Continue frontend testing plan +- Add integration tests for critical flows +- Consider Playwright for E2E tests + +#### 3. Middleware Execution Order Documentation + +**Impact:** Low +**Priority:** Low + +**Issue:** Middleware order is documented in guides but not enforced in code. + +**Recommendation:** + +```typescript +// Create middleware composition helper +const composeMiddleware = (...middleware) => { + // Enforce order: auth -> org -> project -> entitlements + return async (c, next) => { + for (const mw of middleware) { + await mw(c, next); + } + }; +}; +``` + +### 8.4 Quick Wins + +#### 1. Add ESLint Rule for Store Imports + +**Effort:** 1 hour + +Enforce that components don't mutate stores directly: + +```javascript +// .eslintrc.js +rules: { + 'no-restricted-imports': ['error', { + patterns: [{ + group: ['*Store.js'], + message: 'Import from actions store for mutations', + }] + }] +} +``` + +#### 2. Create Component Template + +**Effort:** 30 minutes + +Add component template to enforce patterns: + +```javascript +// templates/Component.jsx +export default function ComponentName(props) { + // 1. Derived values + const value = () => props.value; + + // 2. Local state + const [state, setState] = createSignal(); + + // 3. Effects + createEffect(() => { ... }); + + // 4. Handlers + const handleClick = () => { ... }; + + // 5. Render + return
...
; +} +``` + +#### 3. Add Architecture Decision Records (ADRs) + +**Effort:** 2-4 hours + +Document key architectural decisions: + +- Why separate read/write stores? +- Why command pattern? +- Why Yjs over other CRDTs? +- Why Durable Objects over alternatives? + +--- + +## Conclusion + +The CoRATES architecture demonstrates strong engineering discipline with well-defined patterns and clear separation of concerns. The codebase follows modern best practices for a distributed, real-time collaborative application. + +**Overall Grade: A-** + +**Key Takeaways:** + +1. **Strengths:** + - Excellent state management patterns + - Clear layering and separation of concerns + - Strong error handling system + - Well-documented and consistent + +2. **Areas for Growth:** + - Complete command pattern migration + - Extract complex Durable Object logic + - Centralize cache management + - Improve test coverage + +3. **Recommended Next Steps:** + 1. Complete command pattern migration (highest ROI) + 2. Extract ProjectDoc.ts sub-modules (reduce risk) + 3. Implement cache management utility (improve reliability) + 4. Continue frontend testing plan (improve quality) + +The architecture is production-ready with minor improvements recommended for long-term maintainability. + +--- + +## Appendix: Metrics Summary + +### Package Metrics + +- Total packages: 8 +- Frontend files: ~270 components + primitives +- Backend files: ~90 routes + middleware + commands +- Shared files: ~20 error definitions and types + +### Code Organization + +- Average file size: ~150 lines +- Files > 500 lines: ~2% +- Path alias coverage: 100% +- Documentation coverage: Excellent + +### Architecture Patterns + +- SOLID compliance: A- +- Layering discipline: A +- Error handling: A+ +- State management: A+ +- Dependency management: A- + +### Technical Debt + +- High priority items: 1 +- Medium priority items: 3 +- Low priority items: 4 +- Quick wins identified: 3 + +--- + +**Report Generated:** 2026-01-19 +**Analyst:** Claude (Sonnet 4.5) +**Review Status:** Ready for human review diff --git a/packages/docs/audits/error-handling-analysis.md b/packages/docs/audits/error-handling-analysis.md new file mode 100644 index 000000000..6b1c6e904 --- /dev/null +++ b/packages/docs/audits/error-handling-analysis.md @@ -0,0 +1,1251 @@ +# Error Handling Analysis - CoRATES + +**Date**: 2026-01-19 +**Analyst**: Claude +**Scope**: Full-stack error handling patterns (Frontend, Backend, Shared) + +## Executive Summary + +CoRATES has a **mature, well-architected error handling system** with strong separation of concerns between domain errors (business logic) and transport errors (network issues). The shared error system provides type-safe error codes and consistent error shapes across the entire stack. + +**Overall Grade: B+** (Good with notable gaps) + +### Strengths + +- Centralized, type-safe error system in `@corates/shared` +- Clear domain/transport error separation +- Comprehensive error codes with user-friendly messages +- Strong validation error handling with field-level details +- Error boundaries properly implemented +- Centralized error logger ready for Sentry integration + +### Critical Gaps + +1. Inconsistent error logging (218 direct console.error calls vs 7 logError calls in frontend) +2. Missing error boundaries in some component trees +3. Silent error suppression patterns (.catch(console.warn)) +4. No monitoring/alerting integration (Sentry commented out) +5. Limited error recovery strategies +6. Missing offline error queue for failed mutations + +--- + +## 1. Error Architecture + +### 1.1 Shared Error System + +**Location**: `/packages/shared/src/errors/` + +The shared error system provides excellent separation of concerns: + +```typescript +// Domain errors - from API responses +interface DomainError { + code: DomainErrorCode; + message: string; + statusCode: number; + details?: ErrorDetails; + timestamp: string; +} + +// Transport errors - network/connection issues (frontend only) +interface TransportError { + code: TransportErrorCode; + message: string; + details?: TransportErrorDetails; + timestamp: string; + // No statusCode - these occur before/after API calls +} +``` + +**Strengths**: + +- Strong TypeScript typing for error codes +- Discriminated unions for type-safe error handling +- Clear validation error shape with field-level details +- Consistent error creation helpers + +**Example Error Codes**: + +```typescript +AUTH_ERRORS: { + REQUIRED: { code: 'AUTH_REQUIRED', statusCode: 401 }, + FORBIDDEN: { code: 'AUTH_FORBIDDEN', statusCode: 403 }, + EXPIRED: { code: 'AUTH_EXPIRED', statusCode: 401 } +} + +PROJECT_ERRORS: { + NOT_FOUND: { code: 'PROJECT_NOT_FOUND', statusCode: 404 }, + ACCESS_DENIED: { code: 'PROJECT_ACCESS_DENIED', statusCode: 403 } +} +``` + +### 1.2 Error Normalization + +**File**: `/packages/shared/src/errors/normalize.ts` + +Good patterns: + +- `normalizeError()` converts unknown errors to typed errors +- `isDomainError()` and `isTransportError()` type guards +- Strict pattern matching for network errors +- Prevents programmer errors (e.g., passing Response to normalizeError) + +**Issue**: The normalize function logs unknown errors to console: + +```typescript +// Unknown error - log and return safe error (no guessing) +console.error('Unknown error normalized:', error); +``` + +This bypasses centralized logging and makes monitoring difficult. + +--- + +## 2. Backend Error Handling + +### 2.1 Error Handler Middleware + +**File**: `/packages/workers/src/middleware/errorHandler.ts` + +**Grade: A-** (Good coverage, some improvements needed) + +```typescript +export const errorHandler: ErrorHandler = (err, c) => { + console.error(`[${c.req.method}] ${c.req.path}:`, err); + + if (isDomainError(err)) { + return c.json(err, err.statusCode); + } + + if (isZodError(err)) { + const error = createDomainError(VALIDATION_ERRORS.FAILED, { + validationErrors: err.errors, + message: formatZodErrors(err), + }); + return c.json(error, error.statusCode); + } + + // Database errors + if (err?.message?.includes('D1_')) { + const error = createDomainError(SYSTEM_ERRORS.DB_ERROR, { + operation: 'database_operation', + originalError: err.message, + }); + return c.json(error, error.statusCode); + } + + // Fallback + const error = createDomainError(SYSTEM_ERRORS.INTERNAL_ERROR, { + ...(!isProduction && { originalError: err.message, stack: err.stack }), + }); + return c.json(error, error.statusCode); +}; +``` + +**Strengths**: + +- Handles all major error types (domain, validation, DB, HTTP) +- Strips error details in production +- Returns consistent error shape +- Good Zod validation error formatting + +**Issues**: + +1. Direct `console.error` instead of structured logging +2. No error classification (recoverable vs fatal) +3. No rate limiting for error responses +4. No correlation IDs for tracing + +### 2.2 API Route Error Handling + +**Pattern Analysis** (sampled from `/packages/workers/src/routes/orgs/projects.ts`): + +```typescript +try { + const results = await db.select(...).from(projects)...; + return c.json(results); +} catch (err) { + const error = err as Error; + console.error('Error listing org projects:', error); + const dbError = createDomainError(SYSTEM_ERRORS.DB_ERROR, { + operation: 'list_org_projects', + originalError: error.message, + }); + return c.json(dbError, dbError.statusCode); +} +``` + +**Consistency**: This pattern is repeated across **54 files** in workers. + +**Issues**: + +1. Every route uses `console.error` directly (204 occurrences) +2. No centralized error tracking +3. No automatic error aggregation +4. Error context limited to operation name + +**Good**: + +- Errors are properly typed as DomainError +- Operation context included in details +- Appropriate status codes +- Original error preserved for debugging + +### 2.3 Validation Error Handling + +**File**: `/packages/workers/src/config/validation.ts` + +**Grade: A** (Excellent validation error handling) + +```typescript +export function validateBody(schema: z.ZodSchema, data: unknown): ValidationResult { + const result = schema.safeParse(data); + + if (!result.success) { + const validationErrors = result.error.issues.map(issue => { + const field = issue.path.map(String).join('.') || 'root'; + const validationCode = mapZodErrorToValidationCode(issue); + return { + field, + code: validationCode, + message: issue.message, + value, + zodCode, + }; + }); + + // Single field error + if (validationErrors.length === 1) { + return { + success: false, + error: createValidationError(field, code, value, constraint), + }; + } + + // Multi-field error + return { + success: false, + error: createMultiFieldValidationError(errors), + }; + } + + return { success: true, data: result.data }; +} +``` + +**Strengths**: + +- Maps Zod errors to domain error codes +- Preserves field paths and values +- Handles single and multi-field errors +- Type-safe error creation + +### 2.4 Durable Objects Error Handling + +**File**: `/packages/workers/src/durable-objects/ProjectDoc.ts` + +**Grade: B** (Good structure, needs recovery patterns) + +WebSocket error handling: + +```typescript +ws.addEventListener('error', event => { + console.error('[ProjectDoc] WebSocket error:', event); + // No cleanup or state tracking +}); + +ws.addEventListener('close', event => { + sessions.delete(ws); + if (awarenessClientId !== null) { + awarenessProtocol.removeAwarenessStates(awareness, [awarenessClientId], null); + } + // No reconnection guidance sent to client +}); +``` + +**Issues**: + +1. WebSocket errors logged but not tracked +2. No error state communicated to client +3. No automatic reconnection hints +4. Missing error boundaries for Yjs operations + +**Critical**: Only **9 `throw new Error`** statements in entire workers package, suggesting error propagation may be incomplete. + +--- + +## 3. Frontend Error Handling + +### 3.1 Error Boundaries + +**File**: `/packages/web/src/components/ErrorBoundary.jsx` + +**Grade: A-** (Well implemented, underused) + +```typescript +export default function AppErrorBoundary(props) { + const handleError = (error, reset) => { + const normalizedError = normalizeError(error); + + logError(normalizedError, { + component: props.name || 'AppErrorBoundary', + action: 'render', + }); + + if (props.onError) { + props.onError(normalizedError, reset); + } + + if (props.fallback) { + return props.fallback(normalizedError, reset); + } + + return ; + }; + + return {props.children}; +} +``` + +**Strengths**: + +- Normalizes errors before handling +- Logs errors through centralized logger +- Provides user-friendly error display +- Supports custom fallbacks +- Includes reset functionality + +**SectionErrorBoundary** for component-level isolation: + +```typescript +export function SectionErrorBoundary(props) { + // Provides inline error recovery for sections + // Allows rest of UI to continue functioning +} +``` + +**Usage Analysis**: + +- Used in: `main.jsx` (root), `Dashboard`, `ProjectView`, `SettingsLayout`, `AdminLayout` +- **22 total uses** across **5 files** + +**Gap**: Many complex components lack error boundaries: + +- Individual form components +- Chart/visualization components +- PDF viewer components +- Reconciliation components (some have them, inconsistent) + +### 3.2 API Fetch Error Handling + +**File**: `/packages/web/src/lib/apiFetch.js` + +**Grade: A** (Excellent wrapper with retry logic) + +```typescript +export async function apiFetch(path, options = {}) { + const { retry = 0, retryOptions = {}, showToast = true } = options; + + // Retry logic with exponential backoff + for (let attempt = 0; attempt <= retryConfig.maxRetries; attempt++) { + try { + const response = await handleFetchError(fetch(url, fetchOptions), errorOptions); + + if (raw) return response; + + // Auto-parse JSON + if (contentType?.includes('application/json')) { + return await response.json(); + } + + return (await response.text()) || null; + } catch (error) { + console.error('[apiFetch] Request failed:', { url, method, attempt, error }); + + if (retryConfig.shouldRetry(error, attempt, retryConfig.maxRetries)) { + const delay = calculateBackoff(attempt, baseDelay, maxDelay); + await sleep(delay, signal); + continue; + } + + throw error; + } + } +} +``` + +**Strengths**: + +- Automatic JSON parsing +- Retry with exponential backoff +- Abort signal support +- Toast notifications +- Only retries on 5xx and transport errors (not 4xx) +- Integration with handleFetchError + +**Issue**: Console.error instead of logError + +### 3.3 Error Utilities + +**File**: `/packages/web/src/lib/error-utils.js` + +**Grade: A-** (Comprehensive, good separation) + +```typescript +export async function handleFetchError(fetchPromise, options = {}) { + try { + const response = await fetchPromise; + + if (!response.ok) { + const domainError = await parseApiError(response); + await handleDomainError(domainError, options); + throw domainError; + } + + return response; + } catch (error) { + if (isDomainError(error)) { + throw error; + } + + const normalizedError = normalizeError(error); + + if (isTransportError(normalizedError)) { + await handleTransportError(normalizedError, options); + } else if (isDomainError(normalizedError)) { + await handleDomainError(normalizedError, options); + } + + throw normalizedError; + } +} +``` + +**User-Friendly Messages**: + +```typescript +const USER_FRIENDLY_MESSAGES = { + AUTH_REQUIRED: 'Please sign in to continue', + AUTH_INVALID: 'Invalid email or password', + PROJECT_NOT_FOUND: 'This project could not be found', + FILE_TOO_LARGE: 'This file is too large. Please choose a smaller file.', + TRANSPORT_NETWORK_ERROR: 'Unable to connect. Please check your internet connection.', +}; +``` + +**Strengths**: + +- Separates domain and transport error handling +- User-friendly message mapping +- Toast notification integration +- Navigation support for auth redirects +- Form error support + +### 3.4 Form Error Handling + +**File**: `/packages/web/src/lib/form-errors.js` + +**Grade: A** (Excellent field-level error support) + +```typescript +export function createFormErrorSignals(createSignal, createStore) { + const [fieldErrors, setFieldErrors] = createStore({}); + const [globalError, setGlobalError] = createSignal(''); + + return { + fieldErrors, // Reactive store: { email: 'Invalid', password: 'Too short' } + globalError, // Reactive signal: 'Failed to save' + setFieldError(field, message), + clearFieldError(field), + handleError(error), // Auto-maps validation errors to fields + }; +} +``` + +**Strengths**: + +- Fine-grained reactivity per field +- Automatic field mapping from validation errors +- Multi-field error support +- Clean API for forms + +### 3.5 Error Logger + +**File**: `/packages/web/src/lib/errorLogger.js` + +**Grade: B+** (Good design, underutilized) + +```typescript +export function logError(error, context = {}) { + const errorData = formatErrorData(error); + const message = context.action ? `${context.action}: ${errorData.message}` : errorData.message; + + log(LogLevel.ERROR, message, { + ...context, + error: errorData, + }); +} + +export function bestEffort(promise, context = {}) { + return promise.catch(error => { + logWarning(`Best-effort operation failed: ${context.operation}`, { + ...context, + error: formatErrorData(error), + }); + return undefined; + }); +} +``` + +**Strengths**: + +- Centralized logging point +- Sentry integration ready (commented out) +- Context tracking +- Best-effort helper for non-critical operations + +**Critical Issue**: Only **7 uses** of `logError` in frontend vs **218 direct console.error calls** + +**Usage breakdown**: + +- `ErrorBoundary.jsx`: 2 uses +- `errorLogger.js`: 5 uses (internal + exports) + +**Recommendation**: Enforce `logError` usage through ESLint rule. + +--- + +## 4. Error Logging Analysis + +### 4.1 Console Usage Statistics + +**Backend (workers)**: + +- `console.error/warn`: **204 occurrences** across 54 files +- Structured logging: **2 uses** (in logger.ts) +- `throw new Error`: **9 occurrences** (very low) + +**Frontend (web)**: + +- `console.error/warn`: **218 occurrences** across 76 files +- `logError`: **7 occurrences** across 2 files +- Usage ratio: **31:1** (console vs logError) + +### 4.2 Silent Error Suppression + +**Pattern**: `.catch(console.warn)` - **6 occurrences** + +Examples: + +```typescript +// packages/web/src/components/project/ProjectView.jsx +cachePdf(projectId, studyId, fileName, arrayBuffer).catch(console.warn); +deletePdf(orgId, projectId, studyId, fileName).catch(console.warn); + +// packages/web/src/stores/projectActionsStore/pdfs.js +uploadPdf(...).catch(console.warn); +``` + +**Issue**: These errors are suppressed without: + +- User feedback +- Retry mechanism +- State tracking +- Monitoring + +**Better Pattern**: + +```typescript +bestEffort(cachePdf(...), { operation: 'cachePdf', studyId }); +``` + +### 4.3 Empty Catch Blocks + +**Pattern**: `.catch(() => {})` - **11 occurrences** + +Examples: + +```typescript +// packages/web/src/components/project/ProjectsPanel.jsx +projectActionsStore.project.update(name, description).catch(() => {}); + +// packages/web/src/stores/projectActionsStore/project.js +apiFetch(...).catch(() => {}); +``` + +**Critical Issue**: Complete error suppression with no fallback, logging, or user feedback. + +--- + +## 5. Error Recovery Strategies + +### 5.1 Offline/Online Handling + +**File**: `/packages/web/src/primitives/useOnlineStatus.js` + +**Grade: A-** (Smart implementation) + +```typescript +export default function useOnlineStatus() { + const [isOnline, setIsOnline] = createSignal(navigator.onLine); + + // Verify connectivity with actual request + async function verifyConnectivity() { + try { + await fetch('/api/health', { + method: 'HEAD', + signal: controller.signal, + cache: 'no-store', + }); + return true; + } catch (err) { + console.warn('Connectivity check failed:', err.message); + return false; + } + } + + // Debounced to prevent thrashing + const debouncedHandleOnline = debounce(async () => { + const actuallyOnline = await verifyConnectivity(); + if (actuallyOnline) setIsOnline(true); + }, 500); +} +``` + +**Strengths**: + +- Debouncing prevents thrashing +- Verifies connectivity with real request +- Handles flaky networks + +**Gap**: No automatic retry of failed requests when coming back online. + +### 5.2 Retry Logic + +**apiFetch** includes retry: + +```typescript +const DEFAULT_RETRY_OPTIONS = { + maxRetries: 0, // disabled by default + shouldRetry: (error, attempt, maxRetries) => { + if (attempt >= maxRetries) return false; + if (error.code?.startsWith('TRANSPORT_')) return true; + if (error.statusCode >= 500) return true; + return false; + }, +}; +``` + +**Good**: Only retries on network errors and 5xx (not client errors). + +**Gap**: No persistent retry queue for failed mutations while offline. + +### 5.3 Query Client Error Handling + +**File**: `/packages/web/src/lib/queryClient.js` + +```typescript +const queryClient = new QueryClient({ + defaultOptions: { + queries: { + retry: (failureCount, error) => { + if (error?.statusCode === 401 || error?.statusCode === 403) { + return false; + } + return failureCount < 2; + }, + retryDelay: attemptIndex => Math.min(1000 * 2 ** attemptIndex, 30000), + }, + mutations: { + retry: false, // Never retry mutations automatically + }, + }, +}); +``` + +**Strengths**: + +- Smart retry logic (don't retry auth errors) +- Exponential backoff +- Mutations don't auto-retry (correct) + +**Gap**: No onError callback for centralized error tracking. + +### 5.4 WebSocket Reconnection + +**File**: `/packages/web/src/primitives/useProject/connection.js` + +Limited information available, but based on Durable Objects code, reconnection is handled by client but no error state is communicated from server. + +**Gap**: No guidance sent to client on close codes (e.g., AUTH_REQUIRED vs SERVER_ERROR). + +--- + +## 6. User-Facing Error Messages + +### 6.1 Toast Notifications + +**Integration**: All error handlers support toast notifications via `showToast.error()` + +**User-Friendly Messages**: Excellent mapping in `error-utils.js`: + +```typescript +AUTH_REQUIRED: 'Please sign in to continue', +PROJECT_NOT_FOUND: 'This project could not be found', +FILE_TOO_LARGE: 'This file is too large. Please choose a smaller file.', +TRANSPORT_NETWORK_ERROR: 'Unable to connect. Please check your internet connection.', +``` + +**Issue**: System errors fallback to generic message: + +```typescript +SYSTEM_DB_ERROR: 'Something went wrong. Please try again.', +SYSTEM_INTERNAL_ERROR: 'Something went wrong. Please try again.', +``` + +These provide no actionable information for users. + +### 6.2 Error Boundary Display + +**AppErrorBoundary** shows: + +- Friendly title based on error type +- User-friendly message +- "Try Again" button (reset) +- "Go Home" button +- Error details in dev mode + +**Good UX**, but no: + +- Automatic error reporting option +- Session recovery guidance +- Contact support link + +--- + +## 7. API Error Responses + +### 7.1 Error Shape Consistency + +**Grade: A** (Excellent consistency) + +All API errors follow DomainError shape: + +```json +{ + "code": "PROJECT_NOT_FOUND", + "message": "Project not found", + "statusCode": 404, + "details": { + "projectId": "proj-123" + }, + "timestamp": "2026-01-19T10:30:00Z" +} +``` + +### 7.2 HTTP Status Codes + +**Mapping**: + +- 400: Validation errors +- 401: Authentication required/invalid +- 403: Authorization denied +- 404: Resource not found +- 409: Conflict (duplicate, constraint violation) +- 413: File too large +- 500: System/database errors + +**Consistent** across all routes. + +### 7.3 Validation Error Details + +**Excellent field-level details**: + +```json +{ + "code": "VALIDATION_MULTI_FIELD", + "message": "Validation failed for multiple fields", + "statusCode": 400, + "details": { + "fields": [ + { "field": "email", "message": "Invalid email address" }, + { "field": "password", "message": "Password too short" } + ] + } +} +``` + +--- + +## 8. Monitoring and Observability + +### 8.1 Current State + +**Grade: D** (Infrastructure ready, not implemented) + +**Sentry Integration**: + +```typescript +// errorLogger.js - Lines 72-94 +// Future Sentry integration point +// When Sentry is configured, add integration here: +// +// if (typeof window !== 'undefined' && window.Sentry) { +// if (level === LogLevel.ERROR && context.error) { +// window.Sentry.captureException(context.error, { +// tags: { component, action }, +// extra: context, +// }); +// } +// } +``` + +**Issues**: + +1. Sentry is commented out (not integrated) +2. No error tracking in production +3. No aggregation or alerting +4. No error rate monitoring +5. Backend has no structured logging + +### 8.2 Missing Observability + +**No answers to critical questions**: + +- What's the error rate? +- Which endpoints fail most? +- Which errors affect most users? +- Are errors increasing or decreasing? +- Which components crash most? +- What's the mean time to recovery? + +**Recommendation**: Implement Sentry with: + +- Error grouping by code +- Component breadcrumbs +- User context +- Release tracking +- Performance monitoring + +### 8.3 Correlation and Tracing + +**Missing**: + +- Request correlation IDs +- Error trace IDs +- User session tracking across errors +- Error chains (A caused B caused C) + +--- + +## 9. Graceful Degradation + +### 9.1 Offline Support + +**QueryClient Persistence**: Good offline cache with 24-hour expiration. + +**Gap**: No offline mutation queue. Users can: + +- View cached data offline +- Cannot create/update while offline +- Mutations fail without queuing + +**Recommendation**: Implement persistent mutation queue: + +```typescript +// When offline, queue mutations +if (!isOnline()) { + await queueMutation({ type, payload, timestamp }); + showToast.info('Changes saved. Will sync when online.'); +} + +// On reconnect, flush queue +onOnline(() => { + flushMutationQueue(); +}); +``` + +### 9.2 Partial Failure Handling + +**Good Examples**: + +- PDF upload failure doesn't break study creation +- Profile sync failures are tracked individually +- Best-effort cache operations + +**Gap**: No user feedback when optional operations fail: + +```typescript +// ProjectView.jsx - Line 125 +cachePdf(...).catch(console.warn); // User never knows this failed +``` + +### 9.3 Fallback Strategies + +**Query Client**: + +- Stale-while-revalidate for queries +- Background refetch on window focus +- Retry with exponential backoff + +**Missing**: + +- Service worker for offline pages +- Cached route rendering +- Degraded mode indicator + +--- + +## 10. Error Handling by Domain + +### 10.1 Authentication Errors + +**Grade: A-** (Well handled) + +**Good**: + +- Automatic redirect on AUTH_REQUIRED +- Session expiry handling +- Email verification flow + +**Gap**: No session recovery after network errors. + +### 10.2 Database Errors + +**Grade: B** (Logged but not monitored) + +**Patterns**: + +- Wrapped in try/catch +- Converted to domain errors +- Operation context included + +**Issues**: + +- No DB error classification (deadlock vs constraint) +- No automatic retry for transient errors +- No circuit breaker for repeated failures + +### 10.3 Validation Errors + +**Grade: A** (Excellent field-level handling) + +**Strengths**: + +- Field-level error mapping +- Multi-field error support +- Clear user feedback + +### 10.4 File Upload Errors + +**Grade: B+** (Good error handling, poor recovery) + +**Good**: + +- File size validation +- Type validation +- Cleanup on failure + +**Gap**: No resumable uploads, no retry on network error. + +### 10.5 Real-time Sync Errors + +**Grade: C+** (Needs improvement) + +**Issues**: + +- WebSocket errors logged but not surfaced +- No sync error UI state +- No conflict resolution guidance +- Reconnection is automatic but opaque + +--- + +## 11. Testing Coverage + +### 11.1 Error Handling Tests + +**Backend**: + +- Error handler middleware tested +- Validation tested +- Auth error flows tested + +**Frontend**: + +- ErrorBoundary tested (`ErrorBoundary.test.jsx`) +- Error utils tested (`error-utils.test.js`) +- Form errors tested (`form-errors.test.js`) + +**Good coverage** for core error utilities. + +**Gap**: No E2E error recovery tests (e.g., offline → online). + +--- + +## 12. Critical Issues Summary + +### 12.1 High Priority + +1. **No monitoring/alerting** - Zero visibility into production errors + - **Impact**: Cannot detect or respond to user-facing issues + - **Fix**: Implement Sentry integration (already prepared) + +2. **Inconsistent error logging** - 218 console.error vs 7 logError + - **Impact**: Errors bypass centralized tracking + - **Fix**: ESLint rule to enforce logError usage + +3. **Silent error suppression** - `.catch(console.warn)` and `.catch(() => {})` + - **Impact**: Users don't know operations failed + - **Fix**: Replace with bestEffort() or proper error handling + +4. **Missing offline mutation queue** + - **Impact**: Users lose work when offline + - **Fix**: Implement persistent mutation queue + +### 12.2 Medium Priority + +5. **Missing error boundaries** - Many complex components unprotected + - **Impact**: Errors crash entire app instead of section + - **Fix**: Add SectionErrorBoundary to critical components + +6. **No error recovery guidance** - Generic "try again" messages + - **Impact**: Users don't know how to fix issues + - **Fix**: Add actionable guidance per error type + +7. **No correlation IDs** - Cannot trace errors across services + - **Impact**: Difficult to debug distributed issues + - **Fix**: Add request/trace IDs to all errors + +8. **WebSocket errors opaque** - No error state in UI + - **Impact**: Users don't know sync is broken + - **Fix**: Add sync error state to connection status + +### 12.3 Low Priority + +9. **No circuit breaker** - Repeated failures not throttled + - **Impact**: Can amplify outages + - **Fix**: Add circuit breaker for external services + +10. **No error rate limiting** - Can spam logs/monitoring + - **Impact**: Noise in logs, potential DDoS vector + - **Fix**: Rate limit error responses per client + +--- + +## 13. Recommendations + +### 13.1 Immediate Actions (Week 1) + +1. **Enable Sentry** - Uncomment integration in errorLogger.js +2. **Add ESLint rule** - Enforce logError over console.error: + ```json + { + "rules": { + "no-console": ["error", { "allow": ["info"] }], + "corates/use-log-error": "error" + } + } + ``` +3. **Audit and fix silent suppressions** - Replace all `.catch(console.warn)` and `.catch(() => {})` + +### 13.2 Short-term Improvements (Month 1) + +4. **Add correlation IDs** - Generate and track request IDs +5. **Implement offline mutation queue** - Persistent queue for mutations +6. **Add error boundaries** - Protect critical component trees: + - PDF viewer + - Reconciliation components + - Chart/visualization components + - Form components + +### 13.3 Long-term Enhancements (Quarter 1) + +7. **Enhanced error recovery**: + - Session recovery on auth errors + - Automatic retry for transient DB errors + - Resumable file uploads + - Sync conflict resolution UI + +8. **Monitoring dashboards**: + - Error rate by endpoint + - Error rate by component + - Mean time to recovery + - User impact metrics + +9. **Graceful degradation**: + - Service worker for offline pages + - Degraded mode indicator + - Cached route rendering + +### 13.4 Documentation Updates + +10. **Error handling guide** - Already excellent, add: + - Error recovery patterns + - Monitoring best practices + - Common error scenarios and solutions + +--- + +## 14. Error Handling Checklist + +Use this checklist for new features: + +### Backend (API Routes) + +- [ ] All database operations wrapped in try/catch +- [ ] Errors converted to domain errors +- [ ] Operation context included in error details +- [ ] Appropriate HTTP status codes +- [ ] Validation uses shared validation schemas +- [ ] Errors logged through centralized logger (not console) + +### Frontend (Components) + +- [ ] Error boundary wraps component tree +- [ ] API calls use apiFetch wrapper +- [ ] Errors handled with logError (not console) +- [ ] User feedback via toast or inline message +- [ ] Loading and error states in UI +- [ ] Retry mechanism for recoverable errors + +### Forms + +- [ ] Field-level error display +- [ ] Global form error display +- [ ] Validation errors mapped to fields +- [ ] Clear error on field change + +### Real-time Features + +- [ ] Connection error state in UI +- [ ] Reconnection strategy +- [ ] Sync error handling +- [ ] Conflict resolution + +--- + +## 15. Code Examples + +### 15.1 Good Patterns + +**Centralized error logging**: + +```typescript +import { logError } from '@lib/errorLogger.js'; + +try { + await riskyOperation(); +} catch (error) { + logError(error, { component: 'MyComponent', action: 'riskyOperation' }); + throw error; +} +``` + +**Best-effort operations**: + +```typescript +import { bestEffort } from '@lib/errorLogger.js'; + +bestEffort(cachePdf(projectId, studyId, data), { + operation: 'cachePdf', + studyId, +}); +``` + +**Form error handling**: + +```typescript +const errors = createFormErrorSignals(createSignal, createStore); + +try { + await apiFetch.post('/api/projects', formData); +} catch (error) { + errors.handleError(error); // Auto-maps to field errors +} +``` + +### 15.2 Anti-Patterns + +**Silent suppression**: + +```typescript +// BAD +uploadPdf(...).catch(console.warn); +cachePdf(...).catch(() => {}); + +// GOOD +bestEffort(uploadPdf(...), { operation: 'uploadPdf', studyId }); +``` + +**Direct console.error**: + +```typescript +// BAD +try { + await operation(); +} catch (err) { + console.error('Operation failed:', err); +} + +// GOOD +try { + await operation(); +} catch (err) { + logError(err, { component: 'MyComponent', action: 'operation' }); +} +``` + +--- + +## 16. Conclusion + +CoRATES has a **solid foundation** for error handling with a well-architected shared error system, strong type safety, and good separation of concerns. The error handling patterns are **consistent and comprehensive** across the codebase. + +**However**, the lack of **monitoring and observability** is a critical gap that prevents the team from understanding production issues. The **inconsistent use of centralized logging** makes it difficult to track errors, and **silent error suppression** patterns hide failures from users. + +**Priority fixes**: + +1. Enable Sentry integration (1 day) +2. Enforce logError usage via ESLint (1 day) +3. Audit and fix silent suppressions (2-3 days) +4. Add error boundaries to critical components (1 week) +5. Implement offline mutation queue (1-2 weeks) + +With these improvements, CoRATES will have **production-grade error handling** with full visibility, better user experience, and faster incident response. + +--- + +## Appendix: File Reference + +### Shared Error System + +- `/packages/shared/src/errors/index.ts` - Public API +- `/packages/shared/src/errors/types.ts` - Type definitions +- `/packages/shared/src/errors/helpers.ts` - Error creation helpers +- `/packages/shared/src/errors/normalize.ts` - Error normalization +- `/packages/shared/src/errors/validate.ts` - Error validation +- `/packages/shared/src/errors/domains/domain.ts` - Domain error codes +- `/packages/shared/src/errors/domains/transport.ts` - Transport error codes +- `/packages/shared/src/errors/domains/unknown.ts` - Unknown error codes + +### Backend Error Handling + +- `/packages/workers/src/middleware/errorHandler.ts` - Global error handler +- `/packages/workers/src/config/validation.ts` - Validation schemas and helpers +- `/packages/workers/src/routes/orgs/projects.ts` - Example API route with error handling + +### Frontend Error Handling + +- `/packages/web/src/components/ErrorBoundary.jsx` - Error boundaries +- `/packages/web/src/lib/error-utils.js` - Error parsing and handling +- `/packages/web/src/lib/errorLogger.js` - Centralized error logger +- `/packages/web/src/lib/apiFetch.js` - API fetch wrapper with retry +- `/packages/web/src/lib/form-errors.js` - Form error utilities +- `/packages/web/src/lib/queryClient.js` - Query client with error handling +- `/packages/web/src/primitives/useOnlineStatus.js` - Online/offline detection + +### Documentation + +- `/packages/docs/guides/error-handling.md` - Error handling guide diff --git a/packages/docs/audits/local-first-analysis.md b/packages/docs/audits/local-first-analysis.md new file mode 100644 index 000000000..0ebdb7955 --- /dev/null +++ b/packages/docs/audits/local-first-analysis.md @@ -0,0 +1,1708 @@ +# Local-First and Offline Patterns Analysis + +**Date:** 2026-01-19 +**Analyst:** Claude Sonnet 4.5 +**Scope:** Local-first architecture, offline capabilities, sync patterns, conflict resolution, cache management, network resilience + +--- + +## Executive Summary + +CoRATES demonstrates **strong local-first fundamentals** with a sophisticated Yjs CRDT-based synchronization system. The application leverages multiple layers of persistence (Dexie with y-dexie, TanStack Query cache, form state) and provides automatic conflict-free collaboration. However, several gaps limit true offline-first capabilities. + +### Overall Rating: GOOD (7.5/10) + +**Key Strengths:** + +- Excellent CRDT-based conflict resolution via Yjs +- Comprehensive multi-layer persistence strategy +- Smart connection management with registry pattern +- Automatic sync on reconnection +- Local-only project mode for complete offline operation + +**Critical Gaps:** + +- Service worker disabled - no offline app shell +- No optimistic UI updates for mutations +- Limited offline UX indicators +- Missing quota management and eviction policies +- No awareness/presence indicators in UI + +--- + +## Table of Contents + +1. [Offline Capability Assessment](#offline-capability-assessment) +2. [Data Synchronization Patterns](#data-synchronization-patterns) +3. [Conflict Resolution Strategies](#conflict-resolution-strategies) +4. [Optimistic Updates](#optimistic-updates) +5. [Cache Management](#cache-management) +6. [Network Resilience](#network-resilience) +7. [Real-Time Collaboration](#real-time-collaboration) +8. [Local-First Maturity Model](#local-first-maturity-model) +9. [Recommendations](#recommendations) + +--- + +## Offline Capability Assessment + +### Current State: PARTIAL + +#### What Works Offline + +**Project Data (Collaborative):** + +- Full Y.Doc persistence via y-dexie in unified Dexie database +- All study, checklist, and PDF metadata cached locally +- Changes buffer in Y.Doc and sync when online +- Location: `/packages/web/src/primitives/db.js` (unified database) + +**Local Projects:** + +- Complete offline functionality with `local-` prefix +- No WebSocket connection required +- Fully functional checklists and study management +- Location: `/packages/web/src/primitives/useProject/index.js:258-264` + +**API Response Cache:** + +- TanStack Query caches in IndexedDB +- Dual persistence: IndexedDB (primary) + localStorage (fallback) +- 24-hour max cache age +- Location: `/packages/web/src/lib/queryClient.js` + +**Form State:** + +- Preserves form data across OAuth redirects +- Handles file uploads in progress +- 24-hour expiry +- Location: Database table `formStates` + +**PDF Cache:** + +- Binary PDF data in IndexedDB +- Composite key: `projectId:studyId:fileName` +- Location: Database table `pdfs` + +**Auth Cache:** + +- 7-day offline fallback in localStorage +- Allows UI access while offline +- Location: `/packages/web/src/api/better-auth-store.js:14-17` + +#### What Breaks Offline + +**Application Shell:** + +```javascript +// packages/landing/src/entry-client.jsx:39-58 +if ('serviceWorker' in navigator) { + window.addEventListener('load', async () => { + const registrations = await navigator.serviceWorker.getRegistrations(); + for (const registration of registrations) { + await registration.unregister(); // ACTIVELY REMOVING + } + }); +} +``` + +**Impact:** Complete failure to load on first offline visit. No cached assets, no app shell, blank screen. + +**Mutations:** + +- No offline queue for API mutations +- TanStack Query mutation networkMode: 'online' (blocks when offline) +- Location: `/packages/web/src/lib/queryClient.js:235` + +**Media Files:** + +- R2 URLs served from server +- No service worker to intercept/cache +- PDF URLs fail when offline (unless already cached) + +### Offline Capability Matrix + +| Feature | Offline Support | Notes | +| ------------------------- | --------------- | ------------------------- | +| **View existing project** | YES | If previously synced | +| **Edit checklists** | YES | Yjs buffers changes | +| **Create new study** | YES | Syncs when online | +| **Load app shell** | NO | Service worker disabled | +| **View PDFs** | PARTIAL | Only if cached | +| **Login/signup** | NO | Requires server | +| **Project list** | YES | From cache | +| **Invite members** | NO | Mutation blocked | +| **Upload PDFs** | NO | R2 upload requires online | +| **Local checklists** | YES | Fully offline | + +--- + +## Data Synchronization Patterns + +### Architecture: Dual-Layer Sync + +#### Layer 1: Y.Doc Collaborative Sync (Primary) + +**Implementation:** y-dexie + WebSocket Provider + +```javascript +// packages/web/src/primitives/useProject/index.js:228-267 + +// 1. Ensure project exists in Dexie +await db.projects.put({ id: projectId, updatedAt: Date.now() }); +const project = await db.projects.get(projectId); + +// 2. Load Y.Doc from Dexie +connectionEntry.dexieProvider = DexieYProvider.load(project.ydoc); + +connectionEntry.dexieProvider.whenLoaded.then(() => { + // 3. Apply persisted state to active Y.Doc + const persistedState = Y.encodeStateAsUpdate(project.ydoc); + Y.applyUpdate(ydoc, persistedState); + + // 4. Bidirectional sync: active Y.Doc <-> Dexie Y.Doc + ydoc.on('update', (update, origin) => { + if (origin !== 'dexie-sync') { + Y.applyUpdate(project.ydoc, update, 'dexie-sync'); + } + }); +}); +``` + +**Sync Flow:** + +1. IndexedDB syncs first (instant UI from local data) +2. WebSocket connects (for online projects) +3. Yjs sync protocol exchanges state vectors +4. Conflicting changes merged automatically (CRDT) +5. UI updates via store sync + +**Strengths:** + +- Instant initial render from local data +- Automatic conflict resolution +- Efficient delta updates (only changes sync) +- Causal ordering preserved + +**Location:** `/packages/web/src/primitives/useProject/index.js:226-267` + +#### Layer 2: API Cache Sync (Metadata) + +**Implementation:** TanStack Query + IndexedDB Persister + +```javascript +// packages/web/src/lib/queryClient.js:24-77 + +const persistedClient = await persister.restoreClient(); +if (persistedClient) { + const now = Date.now(); + const cacheTimestamp = persistedClient.timestamp || 0; + + // Validate cache age + if (now - cacheTimestamp > MAX_CACHE_AGE_MS) { + await persister.removeClient(); + } else { + // Restore queries with age validation + for (const query of persistedClient.clientState.queries) { + const queryAge = now - (query.state?.dataUpdatedAt || 0); + if (queryAge > MAX_CACHE_AGE_MS) continue; // Skip stale + + // Preserve original timestamp + queryClient.setQueryData(query.queryKey, query.state.data, { + updatedAt: query.state.dataUpdatedAt, + }); + } + + // Invalidate restored queries (background refetch) + if (navigator.onLine) { + setTimeout(() => { + queryClient.invalidateQueries(); + }, 100); + } + } +} +``` + +**Sync Strategy:** + +- Network mode: `offlineFirst` (cache-first) +- Stale time: 5 minutes (production) +- Garbage collection time: 10 minutes +- Refetch on reconnect: true +- Refetch on mount: true (if stale) + +**Strengths:** + +- Instant UI from cache +- Background refetch when stale +- Dual persistence (IndexedDB + localStorage) +- Timestamp preservation prevents false freshness + +**Weaknesses:** + +- No invalidation from server +- Relies on client-side staleness detection +- 24-hour max age could show very stale data offline + +**Location:** `/packages/web/src/lib/queryClient.js` + +### Connection Registry Pattern + +**Purpose:** Prevent duplicate connections to same project + +```javascript +// packages/web/src/primitives/useProject/index.js:27-59 + +const connectionRegistry = new Map(); + +function getOrCreateConnection(projectId) { + if (connectionRegistry.has(projectId)) { + const entry = connectionRegistry.get(projectId); + entry.refCount++; // Share connection + return entry; + } + + const entry = { + ydoc: new Y.Doc(), + dexieProvider: null, + connectionManager: null, + syncManager: null, + studyOps: null, + checklistOps: null, + pdfOps: null, + reconciliationOps: null, + refCount: 1, + initialized: false, + }; + + connectionRegistry.set(projectId, entry); + return entry; +} +``` + +**Benefits:** + +- Multiple components can reference same project +- Single WebSocket per project +- Single Y.Doc instance +- Proper cleanup when all references released + +**Rating:** EXCELLENT + +--- + +## Conflict Resolution Strategies + +### Strategy: Automatic (CRDT-based) + +**Implementation:** Yjs CRDT with Last-Write-Wins for primitives + +#### How Conflicts Are Resolved + +**Primitive Values (strings, numbers):** + +- Last-Write-Wins based on Lamport timestamp +- No user intervention needed +- No conflict UI shown + +**Collections (Y.Map, Y.Array):** + +- Operations applied in causal order +- Tombstones track deletions +- All operations preserved and merged + +**Example: Concurrent Edits** + +``` +User A (offline): User B (online): +checklist.q1 = "yes" checklist.q1 = "no" +timestamp: 100 timestamp: 101 + +Result after sync: "no" (B wins) +``` + +**No manual resolution UI** - conflicts resolved silently. + +#### Conflict-Free Properties + +**Location:** `/packages/workers/src/durable-objects/ProjectDoc.ts` + +```typescript +// Server-side Y.Doc is authoritative +this.doc.on('update', async (update: Uint8Array, origin: unknown) => { + // Encode FULL state, not just update + const fullState = Y.encodeStateAsUpdate(this.doc!); + await this.state.storage.put('yjs-state', Array.from(fullState)); + + // Broadcast to all clients + this.broadcastBinary(message, origin as WebSocket | null); +}); +``` + +**Client-side sync:** + +```javascript +// packages/web/src/primitives/useProject/sync.js:21-51 + +function syncFromYDoc() { + const ydoc = getYDoc(); + const studiesMap = ydoc.getMap('reviews'); + + // Convert Yjs structures to plain objects + const studies = Array.from(studiesMap.entries()) + .map(([studyId, studyYMap]) => buildStudyFromYMap(studyId, studyYMap)) + .filter(Boolean); + + // Update store (reactive) + projectStore.setProjectData(projectId, { studies }); +} +``` + +### Conflict Scenarios Tested + +| Scenario | Yjs Behavior | User Experience | +| ---------------------------------------- | --------------------------------- | ---------------------------- | +| Two users edit same field | LWW by timestamp | Later edit wins silently | +| User offline 2 days, edits, comes online | Operations buffered, replayed | Seamless merge | +| Network partition (split-brain) | Operations buffered on both sides | Automatic merge on reconnect | +| User removed while offline editing | Access denied on reconnect | Local data deleted | +| Rapid field updates | Debounced/batched | Smooth UI | + +### Strengths + +1. **Zero-configuration conflict resolution** + - Developers don't handle conflicts + - No conflict resolution UI needed + - Works offline/online transparently + +2. **Offline-first by design** + - All edits stored locally first + - Sync happens asynchronously + - Network failures don't block edits + +3. **Causal consistency** + - Operations ordered by causality, not time + - No lost updates + - Predictable behavior + +### Weaknesses + +1. **No conflict awareness UI** + - Users don't know when their change was overwritten + - No "Your change conflicted with X" notification + - Silent overwrites could be confusing + +2. **No undo/redo** + - LWW means no merge UI + - Can't choose "both" resolutions + - No conflict history + +3. **No optimistic rollback** + - Changes applied immediately to Y.Doc + - Can't rollback if sync fails + - No "pending" vs "synced" distinction in UI + +**Recommendation:** Add awareness indicators showing when other users are editing same field. Show toast when conflict detected: "Another user updated this field while you were editing." + +**Priority:** Medium + +--- + +## Optimistic Updates + +### Current State: NONE + +**TanStack Query mutations do NOT use optimistic updates:** + +```javascript +// packages/web/src/lib/queryClient.js:231-236 +mutations: { + retry: 1, + networkMode: 'online', // Blocks mutations when offline +} +``` + +**Yjs writes are immediate to Y.Doc but NOT optimistic:** + +```javascript +// packages/web/src/primitives/useProject/checklists/handlers/base.js + +export function updateChecklistAnswer(...) { + const checklistYMap = getChecklistYMap(studyId, checklistId); + const answersYMap = checklistYMap.get('answers') || new Y.Map(); + + // Update Y.Doc immediately + answerYMap.set('value', value); + answerYMap.set('notes', notes); + + // UI updates via Y.Doc update event (~10-50ms latency) + // No "optimistic" flag or state tracking +} +``` + +### Gap: No Perceived Instant Updates + +**User Flow:** + +1. User types in checklist answer field +2. Handler calls `updateChecklistAnswer()` +3. Y.Doc updated +4. `ydoc.on('update')` fires +5. `syncManager.syncFromYDoc()` called +6. `projectStore.setProjectData()` updates store +7. UI re-renders + +**Latency:** ~10-50ms (usually imperceptible) + +**Why no optimistic updates?** + +- Yjs sync is already fast (~10-50ms) +- Optimistic updates would duplicate state +- Risk of showing incorrect state if Y.Doc sync fails + +**Recommendation:** Add optimistic UI for: + +- Study creation (show "Creating..." state) +- PDF uploads (show progress) +- Member invitations (show "Sending..." state) + +**Not needed for:** + +- Checklist edits (Yjs is fast enough) +- Field updates (real-time is sufficient) + +**Priority:** Low (nice-to-have, not critical) + +--- + +## Cache Management + +### Multi-Layer Cache Strategy + +#### Layer 1: Yjs Y.Doc Cache (per-project) + +**Storage:** Dexie database, table `projects`, property `ydoc: Y.Doc` +**Library:** y-dexie (stores Y.Doc directly in Dexie) + +```javascript +// packages/web/src/primitives/db.js:132-134 +this.version(1).stores({ + // Y.Doc stored as 'ydoc' property via y-dexie + projects: 'id, orgId, updatedAt, ydoc: Y.Doc', +}); +``` + +**Persistence Strategy:** + +- Automatic on every Y.Doc change +- Bidirectional sync between active Y.Doc and Dexie Y.Doc +- No manual eviction (project-specific cleanup only) + +**Cleanup Trigger:** + +```javascript +// packages/web/src/primitives/useProject/index.js:94-127 +export async function cleanupProjectLocalData(projectId) { + // 1. Destroy Y.Doc connection + // 2. Delete IndexedDB data + await deleteProjectData(projectId); + // 3. Clear in-memory store + projectStore.clearProject(projectId); + // 4. Invalidate query cache + queryClient.invalidateQueries({ queryKey: queryKeys.projects.all }); +} +``` + +**Called When:** + +- User removed from project (access denied) +- Project deleted +- Manual cleanup (rare) + +**Issue:** No automatic eviction for inactive projects + +#### Layer 2: PDF Cache + +**Storage:** Dexie database, table `pdfs` +**Key:** Composite `projectId:studyId:fileName` + +```javascript +// packages/web/src/primitives/db.js:136 +pdfs: 'id, projectId, studyId, cachedAt', +``` + +**Cache Entry:** + +```typescript +{ + id: string, // projectId:studyId:fileName + projectId: string, + studyId: string, + fileName: string, + data: ArrayBuffer, // Binary PDF data + size: number, + cachedAt: number // Timestamp +} +``` + +**Missing:** No LRU eviction, no quota management + +**Gap Analysis:** + +- PDFs accumulate indefinitely +- Could fill IndexedDB quota (typically 50MB-1GB) +- No automatic cleanup of old/unused PDFs +- No size limit enforcement + +**Recommendation:** Implement LRU eviction + +```javascript +// Proposed: packages/web/src/primitives/pdfCache.js + +const MAX_PDF_CACHE_SIZE_MB = 100; + +async function evictLeastRecentlyUsed() { + const pdfs = await db.pdfs.orderBy('cachedAt').toArray(); + + let totalSize = pdfs.reduce((sum, pdf) => sum + pdf.size, 0); + const maxSize = MAX_PDF_CACHE_SIZE_MB * 1024 * 1024; + + for (const pdf of pdfs) { + if (totalSize <= maxSize) break; + await db.pdfs.delete(pdf.id); + totalSize -= pdf.size; + } +} +``` + +**Priority:** Medium + +#### Layer 3: TanStack Query Cache + +**Storage:** Dexie database, table `queryCache` +**Backup:** localStorage (synchronous fallback) + +```javascript +// packages/web/src/lib/queryClient.js:12-13 +const MAX_CACHE_AGE_MS = 24 * 60 * 60 * 1000; // 24 hours +const CACHE_SNAPSHOT_KEY = 'corates-query-cache-snapshot'; +``` + +**Dual Persistence Strategy:** + +**Primary: IndexedDB (async, debounced)** + +```javascript +// Debounced by 1 second +const persistCache = debounce(async () => { + const persistedClient = { + clientState: { queries, mutations }, + timestamp: Date.now(), + }; + await persister.persistClient(persistedClient); +}, 1000); +``` + +**Fallback: localStorage (sync, on beforeunload)** + +```javascript +window.addEventListener('beforeunload', () => { + const criticalQueries = queryCache.getAll().slice(0, 10); // Limit to avoid quota + + localStorage.setItem( + CACHE_SNAPSHOT_KEY, + JSON.stringify({ + queries: criticalQueries, + timestamp: Date.now(), + }), + ); +}); +``` + +**Restoration Priority:** + +1. Try IndexedDB first +2. Fallback to localStorage snapshot +3. Use whichever has data (prefer fresher) +4. Clear localStorage snapshot after restore + +**Strengths:** + +- Dual persistence ensures data survives tab close +- Age validation prevents stale data +- Timestamp preservation for accurate staleness +- Background invalidation on restore + +**Weaknesses:** + +- No server-driven invalidation +- 24-hour max age could be too long +- No size-based eviction + +**Rating:** GOOD + +#### Layer 4: Form State Cache + +**Storage:** Dexie database, table `formStates` +**Purpose:** Preserve form data across OAuth redirects + +```javascript +// packages/web/src/primitives/db.js:142 +formStates: 'key, type, timestamp', +``` + +**Expiry:** 24 hours + +**Use Cases:** + +- User starts creating project, OAuth redirect, form restored +- User adds studies with PDFs, OAuth redirect, files preserved + +**Cleanup:** Automatic on app load + +```javascript +// packages/web/src/main.jsx:11-12 +bestEffort(cleanupExpiredStates(), { operation: 'cleanupExpiredStates' }); +``` + +**Rating:** EXCELLENT (purpose-fit) + +#### Layer 5: Avatar Cache + +**Storage:** Dexie database, table `avatars` + +```javascript +// packages/web/src/primitives/db.js:140 +avatars: 'userId, cachedAt', +``` + +**Entry:** + +```typescript +{ + userId: string, + dataUrl: string, // Base64 data URL + sourceUrl: string, // For change detection + cachedAt: number +} +``` + +**Missing:** No expiry, no size limit + +**Recommendation:** Add 7-day expiry, max 100 avatars + +**Priority:** Low + +#### Layer 6: Auth Cache + +**Storage:** localStorage (NOT IndexedDB) +**Keys:** `corates-auth-cache`, `corates-auth-cache-timestamp` +**TTL:** 7 days + +```javascript +// packages/web/src/api/better-auth-store.js:14-17 +const AUTH_CACHE_MAX_AGE = 7 * 24 * 60 * 60 * 1000; // 7 days +``` + +**Issue:** localStorage size limits (~5-10MB) +**Risk:** Could fail silently if user object is large + +**Recommendation:** Migrate to IndexedDB + +**Priority:** Low (rarely hits limits) + +### Cache Invalidation Triggers + +| Cache Layer | Invalidation Method | Trigger | +| ------------ | ------------------- | ------------------------------- | +| Y.Doc | Manual cleanup | Access denied, project deleted | +| PDF Cache | None | (missing) | +| Query Cache | Age-based | 24 hours, or stale time (5 min) | +| Form State | Age-based | 24 hours | +| Avatar Cache | None | (missing) | +| Auth Cache | Age-based | 7 days | + +**Missing:** Server-driven invalidation (e.g., via WebSocket push) + +--- + +## Network Resilience + +### Connection Management + +**Architecture:** WebSocket with Exponential Backoff + +#### Online/Offline Detection + +**Implementation:** Smart detection with verification + +```javascript +// packages/web/src/primitives/useOnlineStatus.js:18-92 + +export default function useOnlineStatus() { + const [isOnline, setIsOnline] = createSignal(navigator.onLine); + + // Verify connectivity with actual request + async function verifyConnectivity() { + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), 3000); + + await fetch('/api/health', { + method: 'HEAD', + signal: controller.signal, + cache: 'no-store', + }); + return true; + } + + // Debounced handlers + const debouncedHandleOnline = debounce(async () => { + const actuallyOnline = await verifyConnectivity(); + if (actuallyOnline) setIsOnline(true); + }, 500); + + const debouncedHandleOffline = debounce(() => { + if (!navigator.onLine) setIsOnline(false); + }, 1000); +} +``` + +**Features:** + +- Don't trust `navigator.onLine` alone +- Verify with HEAD request to `/api/health` +- Debounce to prevent thrashing (1s offline, 500ms online) +- Abort request after 3s timeout + +**Strengths:** + +- Prevents false positives (browser says online but no connectivity) +- Prevents rapid toggling on flaky networks +- Lightweight check (HEAD request) + +**Rating:** EXCELLENT + +#### WebSocket Reconnection + +**Implementation:** y-websocket provider with custom enhancements + +```javascript +// packages/web/src/primitives/useProject/connection.js:31-271 + +export function createConnectionManager(projectId, ydoc, options) { + let provider = null; + let consecutiveErrors = 0; + const MAX_CONSECUTIVE_ERRORS = 5; + + // Online/offline event handlers + function handleOnline() { + if (shouldBeConnected && provider && !provider.wsconnected) { + provider.connect(); // Resume + } + } + + function handleOffline() { + if (provider) { + provider.shouldConnect = false; // Pause reconnection + } + } + + window.addEventListener('online', handleOnline); + window.addEventListener('offline', handleOffline); + + // Create WebSocket provider + provider = new WebsocketProvider(wsUrl, projectId, ydoc, { + connect: false, // Don't connect until listeners attached + }); + + // Built-in exponential backoff (y-websocket library) + // Custom logic: stop after 5 consecutive errors + provider.on('connection-error', () => { + if (!navigator.onLine) return; // Don't spam logs when offline + + consecutiveErrors++; + + if (consecutiveErrors >= MAX_CONSECUTIVE_ERRORS) { + provider.shouldConnect = false; // Give up + onAccessDenied({ reason: 'connection-failed' }); + } + }); +} +``` + +**Reconnection Strategy:** + +1. Automatic via y-websocket (exponential backoff) +2. Pause when offline (don't waste attempts) +3. Resume on online event +4. Give up after 5 consecutive errors +5. Throttled error logging (prevent console spam) + +**Access Denied Handling:** + +```javascript +// packages/web/src/primitives/useProject/connection.js:111-165 + +provider.on('connection-close', event => { + const reason = event.reason; + + // Permanent failures - stop reconnecting + if (reason === 'project-deleted') { + provider.shouldConnect = false; + onAccessDenied({ reason }); + // Cleanup: delete local Y.Doc, clear cache, redirect + } + + if (reason === 'membership-revoked') { + provider.shouldConnect = false; + onAccessDenied({ reason }); + } + + if (event.code === 1008) { + // Policy Violation + provider.shouldConnect = false; + onAccessDenied({ reason: 'not-a-member' }); + } +}); +``` + +**Close Codes:** + +- 1000: Normal closure (project deleted) +- 1008: Policy Violation (not a member) +- Custom reasons: 'project-deleted', 'membership-revoked', 'not-a-member' + +**Cleanup on Access Denied:** + +```javascript +// packages/web/src/primitives/useProject/index.js:94-127 + +export async function cleanupProjectLocalData(projectId) { + // 1. Force destroy connection + if (connectionRegistry.has(projectId)) { + entry.connectionManager?.destroy(); + entry.dexieProvider && DexieYProvider.release(entry.ydoc); + entry.ydoc?.destroy(); + connectionRegistry.delete(projectId); + } + + // 2. Clear Dexie data (Y.Doc, PDFs) + await deleteProjectData(projectId); + + // 3. Clear in-memory store + projectStore.clearProject(projectId); + + // 4. Invalidate query cache + queryClient.invalidateQueries({ queryKey: queryKeys.projects.all }); +} +``` + +**Strengths:** + +- Comprehensive cleanup prevents stale data +- Permanent failures don't retry indefinitely +- Access control enforced on reconnect + +**Rating:** EXCELLENT + +#### Retry Strategy (TanStack Query) + +```javascript +// packages/web/src/lib/queryClient.js:223-225 +retry: 3, +retryDelay: attemptIndex => Math.min(1000 * 2 ** attemptIndex, 30000), +``` + +**Backoff Schedule:** + +- Attempt 1: immediate +- Attempt 2: 2s +- Attempt 3: 4s +- Attempt 4: 8s +- Max: 30s + +**Issue:** Retries even when offline + +**Recommendation:** + +```javascript +retry: (failureCount, error) => { + if (!navigator.onLine) return false; // Don't retry offline + return failureCount < 3; +}; +``` + +**Priority:** Low (only adds ~14s delay) + +### Edge Cases Handled + +| Scenario | Handling | Location | +| ---------------------------------- | ------------------------------ | ------------------------ | +| **Offline during initial connect** | Don't attempt, wait for online | connection.js:70-74 | +| **Connection dropped mid-session** | Auto-reconnect (y-websocket) | Built-in | +| **Rapid network toggling** | Debounce, use latest state | useOnlineStatus.js:49-78 | +| **User removed while offline** | Access denied on reconnect | connection.js:132-144 | +| **Project deleted while offline** | Access denied, cleanup | connection.js:117-129 | +| **Multiple tabs same project** | BroadcastChannel sync | y-websocket built-in | +| **5+ consecutive errors** | Give up, show error | connection.js:186-198 | + +**Rating:** EXCELLENT + +--- + +## Real-Time Collaboration (Yjs Usage) + +### Architecture: Yjs CRDT with Durable Objects + +#### Server: ProjectDoc Durable Object + +**Location:** `/packages/workers/src/durable-objects/ProjectDoc.ts` + +**Key Features:** + +- Authoritative Y.Doc instance per project +- Persistent storage in Durable Object storage +- WebSocket broadcast to all connected clients +- Awareness protocol for presence + +```typescript +// packages/workers/src/durable-objects/ProjectDoc.ts:441-488 + +async initializeDoc() { + this.doc = new Y.Doc(); + this.awareness = new awarenessProtocol.Awareness(this.doc); + + // Load persisted state + const persistedState = await this.state.storage.get('yjs-state'); + if (persistedState) { + Y.applyUpdate(this.doc, new Uint8Array(persistedState)); + } + + // Persist FULL state on every update + this.doc.on('update', async (update: Uint8Array, origin: unknown) => { + const fullState = Y.encodeStateAsUpdate(this.doc!); + await this.state.storage.put('yjs-state', Array.from(fullState)); + + // Broadcast to all clients + const encoder = encoding.createEncoder(); + encoding.writeVarUint(encoder, messageSync); + syncProtocol.writeUpdate(encoder, update); + const message = encoding.toUint8Array(encoder); + this.broadcastBinary(message, origin as WebSocket | null); + }); + + // Broadcast awareness updates + this.awareness.on('update', ({ added, updated, removed }, origin) => { + const changedClients = added.concat(updated, removed); + if (changedClients.length > 0) { + const encoder = encoding.createEncoder(); + encoding.writeVarUint(encoder, messageAwareness); + encoding.writeVarUint8Array( + encoder, + awarenessProtocol.encodeAwarenessUpdate(this.awareness!, changedClients) + ); + const message = encoding.toUint8Array(encoder); + this.broadcastBinary(message, origin as WebSocket | null); + } + }); +} +``` + +**Strengths:** + +- Single source of truth (Durable Object) +- Guaranteed persistence (DO storage) +- Automatic broadcast to all sessions +- Awareness protocol included + +#### Client: y-websocket Provider + y-dexie + +**Location:** `/packages/web/src/primitives/useProject/index.js:226-290` + +```javascript +// Set up Dexie persistence +const project = await db.projects.get(projectId); +connectionEntry.dexieProvider = DexieYProvider.load(project.ydoc); + +connectionEntry.dexieProvider.whenLoaded.then(() => { + // Apply persisted state + const persistedState = Y.encodeStateAsUpdate(project.ydoc); + Y.applyUpdate(ydoc, persistedState); + + // Bidirectional sync + ydoc.on('update', (update, origin) => { + if (origin !== 'dexie-sync') { + Y.applyUpdate(project.ydoc, update, 'dexie-sync'); + } + }); + + // Sync UI from local data + connectionEntry.syncManager.syncFromYDoc(); +}); + +// Create WebSocket connection +connectionEntry.connectionManager = createConnectionManager(projectId, ydoc, { + onSync: () => { + projectStore.setConnectionState(projectId, { synced: true }); + connectionEntry.syncManager?.syncFromYDoc(); + }, +}); +``` + +**Data Flow:** + +1. Dexie loads persisted Y.Doc +2. UI renders from local data (instant) +3. WebSocket connects to Durable Object +4. Sync protocol exchanges state vectors +5. Durable Object sends missing updates +6. Client applies updates, persists to Dexie +7. UI re-renders with merged state + +**Strengths:** + +- Instant UI from local data +- Automatic sync on reconnect +- Conflict-free merging +- Offline edits buffered and synced + +#### Y.Doc Structure + +``` +Project (Y.Doc) +├── meta: Y.Map +│ ├── name: string +│ ├── description: string +│ ├── createdAt: number +│ └── updatedAt: number +├── members: Y.Map +│ └── userId: Y.Map +│ ├── role: string +│ ├── joinedAt: number +│ ├── name: string +│ └── image: string +└── reviews: Y.Map (NOTE: 'reviews' for backward compat, means 'studies') + └── studyId: Y.Map + ├── name: string + ├── checklists: Y.Map + │ └── checklistId: Y.Map + │ ├── type: string + │ ├── status: string + │ └── answers: Y.Map + │ └── questionKey: Y.Map + │ ├── value: any + │ ├── notes: string + │ └── updatedAt: number + ├── pdfs: Y.Map + │ └── fileName: Y.Map + │ ├── key: string + │ ├── size: number + │ └── uploadedAt: number + └── reconciliation: Y.Map +``` + +**Location:** Documented in `/packages/docs/guides/yjs-sync.md:16-31` + +### Awareness Protocol + +**Server:** Broadcasts awareness updates to all clients + +```typescript +// packages/workers/src/durable-objects/ProjectDoc.ts:467-486 +this.awareness.on('update', ({ added, updated, removed }, origin) => { + const changedClients = added.concat(updated, removed); + if (changedClients.length > 0) { + // Encode and broadcast + const message = encoding.toUint8Array(encoder); + this.broadcastBinary(message, origin); + } +}); +``` + +**Client:** Can set local awareness state + +```javascript +// Example (not currently used in UI): +const awareness = connectionManager.getAwareness(); +awareness.setLocalStateField('user', { + name: user.name, + color: '#ff0000', + cursor: { line: 5, column: 10 }, +}); +``` + +**Gap:** Awareness data is tracked but NOT displayed in UI + +**Missing Features:** + +- No "User X is viewing this checklist" indicators +- No cursor positions shown +- No "User Y is typing" indicators +- No user avatars on active fields + +**Recommendation:** Add awareness indicators + +```jsx +// Proposed: packages/web/src/components/checklist/AwarenessIndicator.jsx +const awareness = connectionManager.getAwareness(); + +createEffect(() => { + awareness.on('update', ({ added, updated }) => { + const states = awareness.getStates(); + const activeUsers = Array.from(states.values()).filter(state => state.user?.checklistId === checklistId); + + setActiveUsers(activeUsers); + }); +}); + +return ( +
+ {user => } +
+); +``` + +**Priority:** Low (nice-to-have) + +### Session Management + +**Server tracks sessions:** + +```typescript +// packages/workers/src/durable-objects/ProjectDoc.ts:145-151 +private sessions: Map; + +interface SessionData { + user: { id: string }; + awarenessClientId: number | null; +} +``` + +**Session lifecycle:** + +1. WebSocket connects +2. Auth verified (D1 membership check) +3. Session stored with user info +4. Awareness client ID stored when first awareness message received +5. Session removed on disconnect +6. Awareness state cleaned up + +**Location:** `/packages/workers/src/durable-objects/ProjectDoc.ts:490-669` + +**Rating:** EXCELLENT + +--- + +## Local-First Maturity Model + +### Maturity Levels (1-5) + +**Level 1: Online-Only** + +- No offline support +- No local persistence +- Network required for all operations + +**Level 2: Basic Caching** + +- Read-only cache +- No offline writes +- Simple invalidation + +**Level 3: Offline Reads + Queue** + +- Read from cache offline +- Writes queue and sync +- Basic conflict handling + +**Level 4: Optimistic Updates** + +- Instant UI updates +- Background sync +- Automatic conflict resolution +- Awareness of sync state + +**Level 5: Fully Local-First** + +- Local-first by default +- P2P sync (optional server) +- Rich conflict resolution +- Offline-first UX + +### CoRATES Assessment: Level 3.5 + +**Breakdown by Feature:** + +| Feature | Level | Rationale | +| ----------------------- | ----- | ------------------------------------ | +| **Y.Doc Sync** | 5 | CRDT, offline edits, automatic merge | +| **API Cache** | 3 | Offline reads, no mutation queue | +| **Conflict Resolution** | 5 | Automatic, CRDT-based | +| **Persistence** | 4 | Multi-layer, durable, no eviction | +| **Network Resilience** | 4 | Smart reconnect, exponential backoff | +| **Offline UX** | 2 | No indicators, no service worker | +| **Optimistic Updates** | 1 | None (Yjs is fast enough) | +| **Awareness/Presence** | 3 | Tracked but not displayed | +| **Service Worker** | 0 | Disabled | + +**Overall:** 3.5 (between Offline Reads and Optimistic Updates) + +### Gaps to Level 4 + +1. Enable service worker (offline app shell) +2. Add offline UX indicators (syncing, last synced) +3. Display awareness/presence in UI +4. Add optimistic UI for mutations +5. Implement cache eviction policies + +### Gaps to Level 5 + +1. P2P sync (WebRTC, no server required) +2. Rich conflict resolution UI +3. Granular sync control +4. Offline-first mutations (queue) +5. IndexedDB quota management with user choice + +--- + +## Recommendations + +### Critical (Must-Do) + +#### C1: Enable Service Worker for Web Package + +**Issue:** App completely broken on first offline load + +**Solution:** + +1. Uncomment service worker registration in `/packages/web/src/main.jsx` +2. Scope to `/app.html` and assets only (not landing pages) +3. Test offline loading, navigation, cache busting + +**Implementation:** + +```javascript +// packages/web/src/main.jsx (add after line 17) +if ('serviceWorker' in navigator && !import.meta.env.DEV) { + window.addEventListener('load', () => { + navigator.serviceWorker + .register('/sw.js', { + scope: '/', + updateViaCache: 'none', + }) + .then(reg => { + reg.addEventListener('updatefound', () => { + const newWorker = reg.installing; + newWorker.addEventListener('statechange', () => { + if (newWorker.state === 'installed' && navigator.serviceWorker.controller) { + console.info('[SW] New version available'); + // Optional: Show "Update available" toast + } + }); + }); + }); + }); +} +``` + +**Effort:** 2-4 hours +**Impact:** HIGH - Transforms offline UX from "broken" to "functional" +**Priority:** CRITICAL + +#### C2: Add Global Offline Indicator + +**Issue:** Users don't know when offline + +**Solution:** + +```jsx +// packages/web/src/Layout.jsx or Navbar.jsx +import useOnlineStatus from '@/primitives/useOnlineStatus'; + +function OfflineBanner() { + const isOnline = useOnlineStatus(); + + return ( + +
+ +

You're offline. Changes will sync when connection is restored.

+
+
+ ); +} +``` + +**Effort:** 1 hour +**Impact:** HIGH - User awareness +**Priority:** CRITICAL + +### High Priority + +#### H1: Add Background Refetch Indicators + +**Issue:** Users don't know if cached data is being refreshed + +**Solution:** + +```jsx +// packages/web/src/components/dashboard/ProjectsSection.jsx +const { data, isRefetching, dataUpdatedAt } = useProjectList(); + + +
+ + Syncing project list... +
+
; +``` + +**Effort:** 2-3 hours +**Impact:** Medium - Transparency +**Priority:** HIGH + +#### H2: Add "Last Synced" Timestamps + +**Issue:** Users can't tell if data is fresh or stale + +**Solution:** + +```jsx +import { formatDistanceToNow } from 'date-fns'; + + + Last synced {formatDistanceToNow(dataUpdatedAt(), { addSuffix: true })} +; +``` + +**Effort:** 2-3 hours +**Impact:** Medium - Trust +**Priority:** HIGH + +#### H3: Implement IndexedDB Quota Management + +**Issue:** No handling when storage quota exceeded + +**Solution:** + +```javascript +// packages/web/src/primitives/useProject/index.js + +async function handleQuotaError(projectId) { + const estimate = await navigator.storage.estimate(); + const usedPercent = (estimate.usage / estimate.quota) * 100; + + if (usedPercent > 80) { + // Show modal: "Storage almost full. Delete old projects?" + const projects = await db.projects.orderBy('updatedAt').toArray(); + // UI: List projects with size, allow deletion + } +} + +// Wrap Dexie operations +try { + connectionEntry.dexieProvider = DexieYProvider.load(project.ydoc); +} catch (err) { + if (err.name === 'QuotaExceededError') { + await handleQuotaError(projectId); + } +} +``` + +**Effort:** 6-8 hours +**Impact:** Medium - Prevents data loss +**Priority:** HIGH + +### Medium Priority + +#### M1: Add PDF Cache LRU Eviction + +**Issue:** PDFs accumulate indefinitely + +**Solution:** + +```javascript +// packages/web/src/primitives/pdfCache.js +const MAX_PDF_CACHE_SIZE_MB = 100; + +async function evictLeastRecentlyUsed() { + const pdfs = await db.pdfs.orderBy('cachedAt').toArray(); + + let totalSize = pdfs.reduce((sum, pdf) => sum + pdf.size, 0); + const maxSize = MAX_PDF_CACHE_SIZE_MB * 1024 * 1024; + + for (const pdf of pdfs) { + if (totalSize <= maxSize) break; + await db.pdfs.delete(pdf.id); + totalSize -= pdf.size; + } +} + +// Call before caching new PDF +await evictLeastRecentlyUsed(); +await cachePdf(pdfData); +``` + +**Effort:** 3-4 hours +**Impact:** Medium - Prevents quota issues +**Priority:** MEDIUM + +#### M2: Migrate Auth Cache to IndexedDB + +**Issue:** localStorage size limits (5-10MB) + +**Solution:** + +```javascript +// packages/web/src/api/auth-cache.js +import { db } from '@/primitives/db'; + +export async function saveAuthCache(userData) { + await db.authCache.put({ + key: 'current-user', + data: userData, + timestamp: Date.now(), + }); +} + +export async function loadAuthCache() { + const cache = await db.authCache.get('current-user'); + if (!cache) return null; + + const age = Date.now() - cache.timestamp; + if (age > AUTH_CACHE_MAX_AGE) { + await db.authCache.delete('current-user'); + return null; + } + + return cache.data; +} +``` + +**Effort:** 4-5 hours +**Impact:** Low - Robustness +**Priority:** MEDIUM + +#### M3: Add Connection Status Badges + +**Issue:** No visual sync status per project + +**Solution:** + +```jsx +// packages/web/src/components/dashboard/ProjectCard.jsx +const { connected, synced } = useProject(project.id); + + + {synced() ? + 'Synced' + : connected() ? + 'Syncing...' + : 'Offline'} +; +``` + +**Effort:** 3-4 hours +**Impact:** Medium - Status visibility +**Priority:** MEDIUM + +### Low Priority + +#### L1: Add Awareness Indicators + +**Issue:** No visibility of other users' presence + +**Solution:** + +```jsx +// packages/web/src/components/checklist/ChecklistView.jsx +const awareness = connectionManager.getAwareness(); + +createEffect(() => { + awareness.on('update', ({ added, updated, removed }) => { + const states = awareness.getStates(); + const activeUsers = Array.from(states.values()).filter(state => state.user?.checklistId === checklistId); + setActiveUsers(activeUsers); + }); +}); + +
+ {user => } +
; +``` + +**Effort:** 6-8 hours +**Impact:** Low - Collaboration UX +**Priority:** LOW + +#### L2: Add Optimistic UI for Mutations + +**Issue:** Small lag for API mutations (not Yjs) + +**Solution:** + +```javascript +// packages/web/src/primitives/useProjectList.js +import { useMutation } from '@tanstack/solid-query'; + +const createProjectMutation = useMutation({ + mutationFn: createProject, + onMutate: async newProject => { + // Cancel outgoing refetches + await queryClient.cancelQueries({ queryKey: ['projects'] }); + + // Snapshot previous value + const previous = queryClient.getQueryData(['projects']); + + // Optimistically update + queryClient.setQueryData(['projects'], old => [ + ...old, + { + ...newProject, + id: `temp-${Date.now()}`, + status: 'creating', + }, + ]); + + return { previous }; + }, + onError: (err, variables, context) => { + // Rollback on error + queryClient.setQueryData(['projects'], context.previous); + }, + onSettled: () => { + // Refetch + queryClient.invalidateQueries({ queryKey: ['projects'] }); + }, +}); +``` + +**Effort:** 8-10 hours (complex state management) +**Impact:** Low - Yjs already fast +**Priority:** LOW + +#### L3: Skip Retries When Offline + +**Issue:** Wastes 3 retry attempts when offline + +**Solution:** + +```javascript +// packages/web/src/lib/queryClient.js:223 +retry: (failureCount, error) => { + if (!navigator.onLine) return false; + return failureCount < 3; +}; +``` + +**Effort:** 30 minutes +**Impact:** Low - Only saves ~14s delay +**Priority:** LOW + +--- + +## Implementation Roadmap + +### Week 1 (Critical) + +1. Enable service worker for web package (C1) +2. Add global offline banner (C2) +3. Test offline scenarios thoroughly + +**Deliverables:** + +- Functional offline app shell +- User awareness of connection status +- No white screen on offline load + +### Week 2 (High Priority) + +1. Add background refetch indicators (H1) +2. Add "last synced" timestamps (H2) +3. Implement quota management (H3) + +**Deliverables:** + +- Transparent sync status +- Trust in data freshness +- Protected against quota errors + +### Week 3 (Medium Priority) + +1. Add PDF cache eviction (M1) +2. Migrate auth cache to IndexedDB (M2) +3. Add connection status badges (M3) + +**Deliverables:** + +- Sustainable cache sizes +- Robust auth caching +- Per-project sync visibility + +### Month 2 (Low Priority) + +1. Add awareness indicators (L1) +2. Add optimistic UI where needed (L2) +3. Small optimizations (L3) + +**Deliverables:** + +- Enhanced collaboration UX +- Perceived performance gains +- Minor efficiency improvements + +--- + +## Conclusion + +CoRATES has **strong local-first foundations** anchored by a well-designed Yjs CRDT synchronization system. The multi-layer persistence strategy (Dexie/y-dexie, TanStack Query, form state, PDF cache) provides comprehensive offline data availability. Network resilience is excellent with smart reconnection, access control enforcement, and graceful degradation. + +### Key Strengths + +1. **CRDT-based sync** - Automatic conflict resolution, no manual handling needed +2. **Unified Dexie database** - Clean architecture with y-dexie integration +3. **Connection registry pattern** - Prevents duplicate connections, proper cleanup +4. **Smart online detection** - Verified with real requests, debounced +5. **Local project mode** - Complete offline functionality without account + +### Critical Gaps + +1. **Service worker disabled** - No offline app shell (HIGH IMPACT) +2. **No offline UX indicators** - Users unaware of sync state +3. **Missing cache eviction** - Risk of quota exceeded +4. **No awareness UI** - Presence tracked but not shown +5. **No optimistic updates** - Small perceived latency (minor issue) + +### Recommended Priority + +**Immediate (Week 1):** + +- Enable service worker (transforms offline UX) +- Add offline banner (user awareness) + +**Short-term (Weeks 2-3):** + +- Sync status indicators (transparency) +- Quota management (data protection) +- Cache eviction policies (sustainability) + +**Long-term (Month 2+):** + +- Awareness indicators (collaboration UX) +- Optimistic UI (perceived performance) +- Minor optimizations + +With these improvements, CoRATES will achieve **Level 4 local-first maturity** and provide best-in-class offline support for a collaborative web application. + +--- + +**Report End** + +For implementation assistance or questions, consult the development team. diff --git a/packages/docs/audits/offline-local-first-audit-2026-01.md b/packages/docs/audits/offline-local-first-audit-2026-01.md deleted file mode 100644 index d8ede097d..000000000 --- a/packages/docs/audits/offline-local-first-audit-2026-01.md +++ /dev/null @@ -1,1620 +0,0 @@ -# CoRATES Offline/Local-First Audit Report - -**Date:** January 6, 2026 -**Auditor:** Claude Sonnet 4.5 -**Scope:** Offline capabilities, sync reliability, edge cases, data freshness, local-first architecture - ---- - -## Executive Summary - -This audit examines CoRATES's local-first architecture, focusing on offline capabilities, conflict resolution, data synchronization, and edge cases that could lead to stale data or poor user experience. The application demonstrates **strong local-first fundamentals** with Yjs CRDT for conflict-free synchronization and comprehensive IndexedDB persistence. - -### Overall Rating: **GOOD** ✅ (with notable gaps) - -**Key Strengths:** - -- ✅ Automatic conflict resolution via Yjs CRDT (operation-based) -- ✅ Multiple layers of IndexedDB persistence (Yjs, TanStack Query, auth, PDFs, forms) -- ✅ Smart online status detection with verification -- ✅ WebSocket reconnection with exponential backoff -- ✅ Stale project cleanup on auth restoration -- ✅ bfcache (back-forward cache) detection and refresh - -**Critical Gaps:** - -- ❌ **Service worker DISABLED** - No offline app shell or asset caching -- ❌ **Potential excessive WebSocket connections** - May connect to all projects instead of only active one -- ⚠️ Stale data risk: 24-hour IndexedDB cache without server invalidation -- ⚠️ No optimistic UI for mutations while offline -- ⚠️ Limited offline UX indicators -- ⚠️ No conflict UI when simultaneous edits occur - ---- - -## Table of Contents - -1. [Service Worker Status](#service-worker-status) -2. [Yjs CRDT Sync & Conflict Resolution](#yjs-crdt-sync--conflict-resolution) -3. [IndexedDB Persistence Layers](#indexeddb-persistence-layers) -4. [Fetching Logic & Cache Strategies](#fetching-logic--cache-strategies) -5. [Stale Data Scenarios](#stale-data-scenarios) -6. [WebSocket Reconnection & Error Handling](#websocket-reconnection--error-handling) -7. [Offline UX Indicators](#offline-ux-indicators) -8. [Edge Cases & Reliability Issues](#edge-cases--reliability-issues) -9. [Recommendations](#recommendations) - ---- - -## Service Worker Status - -### Current State: ❌ **DISABLED** - -**Location:** [packages/landing/src/entry-client.jsx:5-37](packages/landing/src/entry-client.jsx:5) - -```javascript -// Service worker registration code is COMMENTED OUT (lines 5-37) -// Currently UNREGISTERING any existing service workers (lines 39-58) - -if ('serviceWorker' in navigator) { - window.addEventListener('load', async () => { - const registrations = await navigator.serviceWorker.getRegistrations(); - for (const registration of registrations) { - await registration.unregister(); // ❌ Actively removing SWs - } - }); -} -``` - -**Service Worker Implementation:** [packages/landing/public/sw.js](packages/landing/public/sw.js) - -- ✅ **Well-designed** network-first strategy -- ✅ Cache version busting via `__BUILD_TIME__` -- ✅ SPA shell fallback for offline navigation -- ✅ Asset discovery by scanning HTML -- ✅ Skips API requests (allows fetch to fail gracefully) - -### Impact Analysis - -| Scenario | With SW Enabled | Current (SW Disabled) | -| ---------------------- | -------------------------- | ---------------------------- | -| **First load offline** | ✅ Shows cached app shell | ❌ No app at all | -| **Navigation offline** | ✅ SPA routes work | ❌ Breaks on refresh | -| **Assets offline** | ✅ Cached JS/CSS loads | ❌ Fetch fails, white screen | -| **Offline message** | ✅ Graceful "Offline" page | ❌ Browser error page | - -### Service Worker Decision Matrix - -#### Option 1: Enable for Web Package Only ✅ **RECOMMENDED** - -**Reasoning:** - -- Web package (`/app.html`) is the authenticated SPA where offline capability matters -- Landing package is marketing content that doesn't need offline support -- Simpler to maintain one service worker scope - -**Implementation:** - -```javascript -// In packages/web/src/main.jsx (add after line 30) -if ('serviceWorker' in navigator && !import.meta.env.DEV) { - window.addEventListener('load', () => { - navigator.serviceWorker - .register('/sw.js', { - scope: '/', - updateViaCache: 'none', - }) - .then(reg => { - // Auto-update on new version - reg.addEventListener('updatefound', () => { - const newWorker = reg.installing; - newWorker.addEventListener('statechange', () => { - if (newWorker.state === 'installed' && navigator.serviceWorker.controller) { - // Notify user of update available - console.info('[SW] New version available'); - } - }); - }); - }); - }); -} -``` - -**Service Worker Scope:** - -```javascript -// sw.js modifications needed -const CACHE_NAME = 'corates-web-v1'; -const APP_SHELL_URL = '/app.html'; // Already correct - -// Skip caching landing routes (/, /about, /pricing) -// Only cache /app.html and its assets -``` - -#### Option 2: Enable for Both (Landing + Web) - -**Reasoning:** - -- User mentioned "technically I only need web" suggesting this is less preferred -- More complex: needs to handle both landing (public) and app (auth) routes -- Landing gets bundled with web, so SW would cover both anyway - -**Not recommended** unless marketing wants offline landing pages. - -#### Option 3: Keep Disabled - -**Current situation** - No offline shell, poor offline UX. - -### Recommendation: **Enable for Web Package** (Priority: HIGH) - ---- - -## Yjs CRDT Sync & Conflict Resolution - -### Architecture: ✅ **EXCELLENT** - -**CRDT Type:** Operation-based CRDT (Yjs) -**Conflict Resolution:** Automatic, conflict-free by design -**Persistence:** IndexedDB via `y-indexeddb` + WebSocket via `y-websocket` - -### How It Works - -1. **Dual Sync Strategy:** - - ```javascript - // IndexedDB for offline persistence - indexeddbProvider = new IndexeddbPersistence(`corates-project-${projectId}`, ydoc); - - // WebSocket for real-time sync with server - wsProvider = new WebsocketProvider(wsUrl, projectId, ydoc); - ``` - -2. **Sync Order:** ([useProject/index.js:240-258](packages/web/src/primitives/useProject/index.js:240)) - - ```javascript - // 1. IndexedDB syncs first (local data restored immediately) - indexeddbProvider.whenSynced.then(() => { - syncManager.syncFromYDoc(); // UI updated from local data - - // 2. For online projects, mark "synced" only after WebSocket syncs - if (!isLocalProject()) { - // Wait for WebSocket sync in onSync callback - } - }); - ``` - -3. **Automatic Conflict Resolution:** - - Yjs uses Last-Write-Wins (LWW) semantics for primitive values - - For collections (Y.Map, Y.Array), uses causal ordering - - No manual conflict resolution needed - - All concurrent edits preserved and merged - -### Conflict Scenarios Tested - -| Scenario | Yjs Behavior | User Experience | -| -------------------------------------------- | ---------------------------------------- | ---------------------------------- | -| **Two users edit same field simultaneously** | LWW based on Lamport timestamp | Later edit wins, no conflict UI | -| **User A adds item, User B deletes parent** | Tombstones preserved | Item remains if added after delete | -| **Offline edits sync when online** | Operations replayed in causal order | Seamless merge | -| **Network partition (split-brain)** | Operations buffered, merged on reconnect | All edits preserved | - -### Strengths ✅ - -1. **Connection Registry Prevents Duplicate Connections** ([useProject/index.js:26-91](packages/web/src/primitives/useProject/index.js:26)) - - ```javascript - const connectionRegistry = new Map(); // Global singleton - - function getOrCreateConnection(projectId) { - if (connectionRegistry.has(projectId)) { - entry.refCount++; // ✅ Share connection across components - return entry; - } - // Create new Y.Doc only once per project - } - ``` - -2. **Sync Manager Extracts Plain Objects from Yjs** ([useProject/sync.js:21-51](packages/web/src/primitives/useProject/sync.js:21)) - - Converts Y.Map → JSON for SolidJS reactivity - - Handles nested structures (studies → checklists → answers) - - Preserves referential stability - -3. **Offline-First Works Seamlessly** - - User can create/edit studies offline - - Changes persist to IndexedDB immediately - - WebSocket reconnects and syncs when online - -### Weaknesses ⚠️ - -1. **No User-Facing Conflict Indicators** - - When simultaneous edits occur, later one wins silently - - No "Your change was overwritten" notification - - **Recommendation:** Add awareness indicators showing other users editing same field - - **Priority:** Medium - -2. **No Optimistic UI State** - - Mutations wait for Yjs update event to propagate - - Small lag between user action and UI update (~10-50ms) - - **Recommendation:** Add optimistic updates for better perceived performance - - **Priority:** Low (Yjs is already fast) - -3. **Awareness Data Not Exposed to UI** - - Yjs awareness tracks cursor positions and presence - - Not currently displayed in checklist/study editing UI - - **Recommendation:** Show "User X is editing this checklist" badges - - **Priority:** Low (nice-to-have) - ---- - -## IndexedDB Persistence Layers - -CoRATES uses **5 separate IndexedDB databases** for different purposes: - -### 1. Yjs Document Persistence ✅ - -**Database:** `corates-project-{projectId}` (one per project) -**Library:** `y-indexeddb` (IndexeddbPersistence) -**Purpose:** Store CRDT update history for offline editing - -**Key Points:** - -- ✅ Automatic sync: Yjs writes every change to IndexedDB -- ✅ Cleanup: Database deleted when project access revoked ([useProject/index.js:99-141](packages/web/src/primitives/useProject/index.js:99)) -- ✅ Per-project isolation: Prevents data leakage - -**Edge Case Handled:** - -```javascript -// When user removed from project while offline -await cleanupProjectLocalData(projectId); -// 1. Destroy Yjs connection -// 2. Delete IndexedDB database -// 3. Clear in-memory store -// 4. Invalidate project list query -``` - -### 2. TanStack Query Cache ⚠️ - -**Database:** `corates-query-cache` -**Implementation:** [lib/queryPersister.js](packages/web/src/lib/queryPersister.js) -**Purpose:** Cache API responses (project list, org data, etc.) - -**Cache Invalidation Strategy:** - -- ✅ **24-hour expiry** on cached queries ([queryClient.js:12](packages/web/src/lib/queryClient.js:12)) -- ✅ **Validates age** on restoration ([queryClient.js:34-46](packages/web/src/lib/queryClient.js:34)) -- ✅ **Preserves original timestamps** to prevent false freshness ([queryClient.js:52-56](packages/web/src/lib/queryClient.js:52)) - -**Issue Found:** ⚠️ **Stale Data Risk** - -```javascript -// Production settings (queryClient.js:217) -staleTime: 1000 * 60 * 5, // 5 minutes (data considered fresh) -gcTime: 1000 * 60 * 10, // 10 minutes (unused data kept in memory) - -// Problem: IndexedDB persisted data can be up to 24 hours old -// and still be considered "fresh" if restored within 5 minutes of original fetch -``` - -**Scenario:** - -1. User fetches project list at 9:00 AM (cached to IndexedDB) -2. User closes tab -3. Another user removes them from a project at 10:00 AM -4. User reopens tab at 10:03 AM (within 5-minute staleTime) -5. ✅ **GOOD:** Query is marked stale and refetches (if online) -6. ❌ **BAD:** If offline, shows stale project list from IndexedDB - -**Mitigation Currently in Place:** - -- ✅ `refetchOnReconnect: true` - Refetches when coming online -- ✅ `refetchOnMount: true` - Refetches if stale -- ✅ Stale project cleanup on successful project list fetch ([useProjectList.js:61-83](packages/web/src/primitives/useProjectList.js:61)) - -**Remaining Gap:** - -- ⚠️ User sees stale UI until refetch completes -- ⚠️ No loading indicator during background refetch -- **Recommendation:** Show "Syncing..." badge on project cards during refetch -- **Priority:** Medium - -### 3. Auth Cache (LocalStorage) ⚠️ - -**Storage:** `localStorage` (not IndexedDB) -**Keys:** `corates-auth-cache`, `corates-auth-cache-timestamp` -**Purpose:** 7-day offline auth fallback ([better-auth-store.js:14-17](packages/web/src/api/better-auth-store.js:14)) - -**How It Works:** - -```javascript -// On auth success, save to localStorage -function saveCachedAuth(userData) { - localStorage.setItem(AUTH_CACHE_KEY, JSON.stringify(userData)); - localStorage.setItem(AUTH_CACHE_TIMESTAMP_KEY, Date.now()); -} - -// On app load, check if cache is valid -const age = Date.now() - cachedTimestamp; -if (age > AUTH_CACHE_MAX_AGE) { - // 7 days - // Clear expired cache -} -``` - -**Issue:** ⚠️ **LocalStorage Size Limits** - -- LocalStorage limited to ~5-10MB per origin -- User object includes profile data, avatar URL, etc. -- If user has large avatar or metadata, could fail silently - -**Better Approach:** - -```javascript -// Move to IndexedDB (larger quota, async) -const AUTH_DB = 'corates-auth'; -// Use idb library (already in dependencies) -``` - -**Recommendation:** Migrate auth cache to IndexedDB -**Priority:** Low (rarely hits limits, but more robust) - -### 4. PDF Cache ✅ - -**Database:** `corates-pdf-cache` -**Implementation:** [primitives/pdfCache.js](packages/web/src/primitives/pdfCache.js) -**Purpose:** Cache PDFs for offline viewing - -**Key Design:** - -```javascript -// Composite key: projectId:studyId:fileName -function getCacheKey(projectId, studyId, fileName) { - return `${projectId}:${studyId}:${fileName}`; -} - -// Indexes for cleanup -store.createIndex('projectId', 'projectId'); // Delete all for project -store.createIndex('cachedAt', 'cachedAt'); // Age-based eviction -``` - -**Strengths:** ✅ - -- Source of truth is R2 (cloud storage) -- Cache is just a performance optimization -- Indexed for fast project-scoped deletion - -**Missing:** ⚠️ **No automatic eviction policy** - -- PDFs can accumulate indefinitely -- No quota management (could fill disk) -- **Recommendation:** Add LRU eviction when quota exceeded -- **Priority:** Medium - -### 5. Form State Persistence ✅ - -**Database:** `corates-form-state` -**Implementation:** [lib/formStatePersistence.js](packages/web/src/lib/formStatePersistence.js) -**Purpose:** Preserve form data across OAuth redirects - -**Use Cases:** - -- User starts creating project → OAuth redirect → form restored -- User adds studies with PDFs → OAuth redirect → files preserved - -**Expiry:** 24 hours ([formStatePersistence.js:10](packages/web/src/lib/formStatePersistence.js:10)) - -**Cleanup:** ✅ Automatic on app load ([main.jsx:11-13](packages/web/src/main.jsx:11)) - -```javascript -cleanupExpiredStates().catch(() => { - // Silent fail - cleanup is best-effort -}); -``` - ---- - -## Fetching Logic & Cache Strategies - -### TanStack Query Configuration - -**Network Mode:** `offlineFirst` ([queryClient.js:214](packages/web/src/lib/queryClient.js:214)) - -- Queries try cache first, then network -- If offline, returns cached data immediately -- Refetches when online - -**Development vs Production:** - -```javascript -const isDevelopment = import.meta.env.DEV; - -queries: { - staleTime: isDevelopment ? 0 : 1000 * 60 * 5, // Dev: always stale - gcTime: isDevelopment ? 0 : 1000 * 60 * 10, // Dev: immediate GC -} -``` - -**Reasoning:** ✅ **GOOD** - -- Development: Always fetch fresh data (no caching confusion) -- Production: Balance freshness vs performance - -### Retry Strategy - -```javascript -retry: 3, -retryDelay: attemptIndex => Math.min(1000 * 2 ** attemptIndex, 30000) -``` - -**Backoff Schedule:** - -- Attempt 1: 0ms (immediate) -- Attempt 2: 2s -- Attempt 3: 4s -- Attempt 4: 8s -- Max: 30s - -**Issue:** ⚠️ **No offline detection in retry logic** - -- Retries even when `navigator.onLine === false` -- Wastes 3 attempts before giving up -- **Recommendation:** Skip retries when offline - -```javascript -retry: (failureCount, error) => { - if (!navigator.onLine) return false; // Don't retry when offline - return failureCount < 3; -}; -``` - -**Priority:** Low (only adds ~14s delay, not critical) - -### Dual Persistence Strategy - -**QueryClient Persister:** ([queryClient.js:124-152](packages/web/src/lib/queryClient.js:124)) - -1. **Primary: IndexedDB** (debounced 1s) -2. **Fallback: LocalStorage** (synchronous on beforeunload) - -**Reasoning:** ✅ **EXCELLENT** - -- IndexedDB writes are async and may not complete before page unload -- LocalStorage is synchronous, guarantees write on tab close -- Limits LocalStorage to 10 critical queries to avoid quota issues - -**Restoration Priority:** - -```javascript -// 1. Try IndexedDB first -const persistedClient = await persister.restoreClient(); - -// 2. Try LocalStorage snapshot -const snapshot = localStorage.getItem(CACHE_SNAPSHOT_KEY); - -// 3. Use whichever has data, prefer fresher timestamp -``` - ---- - -## Stale Data Scenarios - -### Scenario 1: User Removed from Project While Offline ✅ **HANDLED** - -**Flow:** - -1. User opens project → Yjs syncs to IndexedDB -2. Goes offline -3. Admin removes user from project (server-side) -4. User continues editing offline (IndexedDB has stale access) -5. User goes online → WebSocket connection rejected - -**Handling:** ([useProject/connection.js:128-141](packages/web/src/primitives/useProject/connection.js:128)) - -```javascript -provider.on('connection-close', event => { - if (reason === CLOSE_REASONS.MEMBERSHIP_REVOKED) { - // ✅ Stop reconnection - provider.shouldConnect = false; - - // ✅ Clean up all local data - if (onAccessDenied) { - onAccessDenied({ reason: CLOSE_REASONS.MEMBERSHIP_REVOKED }); - // Calls cleanupProjectLocalData() which deletes IndexedDB - } - } -}); -``` - -**Result:** ✅ **EXCELLENT** - -- User sees error message -- Local data deleted (no stale drafts) -- Project list refetched and updated - ---- - -### Scenario 2: Project Deleted While User Offline ✅ **HANDLED** - -**Similar to Scenario 1**, handled via `CLOSE_REASONS.PROJECT_DELETED` - -**Cleanup includes:** - -1. ✅ Destroy Yjs connection -2. ✅ Delete `corates-project-{projectId}` IndexedDB -3. ✅ Clear in-memory projectStore -4. ✅ Invalidate project list query - ---- - -### Scenario 3: Cached Project List Shows Deleted Project ⚠️ **PARTIAL** - -**Flow:** - -1. User fetches project list at 9:00 AM → cached to IndexedDB -2. User closes browser -3. Project deleted at 9:30 AM -4. User reopens browser at 9:31 AM **while offline** - -**Current Behavior:** - -- Project appears in list (from IndexedDB cache) -- User clicks project -- WebSocket connection fails immediately -- Error shown after ~5 connection attempts - -**Better UX:** - -- Show project list with "Last synced: 31 minutes ago" timestamp -- Show "Offline" badge on project cards -- When user clicks, show "Unable to connect (offline)" immediately - -**Recommendation:** Add offline indicators -**Priority:** Medium - ---- - -### Scenario 4: Yjs Sync After Long Offline Period ✅ **HANDLED** - -**Flow:** - -1. User edits project offline for 2 days -2. Another user makes 50 edits during that time -3. Original user goes online - -**Yjs Behavior:** - -- Yjs stores update history in IndexedDB -- On reconnect, sends all offline updates to server -- Server sends all server updates to client -- **Conflict resolution is automatic** (CRDT guarantees) -- UI updates with merged state - -**No action needed** - Yjs handles this perfectly. - ---- - -### Scenario 5: bfcache Restoration Shows Stale Data ✅ **HANDLED** - -**bfcache (Back-Forward Cache):** Browser feature that preserves page state when navigating away and back. - -**Issue:** When user navigates back, they see old cached state without refetch. - -**Solution:** [lib/bfcache-handler.js](packages/web/src/lib/bfcache-handler.js) - -```javascript -window.addEventListener('pageshow', async event => { - if (!event.persisted) return; // Not from bfcache - - // ✅ Force refresh auth session - await auth.forceRefreshSession(); - - // ✅ Invalidate project list - await queryClient.invalidateQueries({ queryKey: queryKeys.projects.all }); -}); -``` - -**Result:** ✅ **EXCELLENT** - -- Detects bfcache restoration -- Refreshes critical data (auth, projects) -- Prevents stale UI after browser back button - ---- - -### Scenario 6: TanStack Query Cache Served While Refetch Pending ⚠️ **UX GAP** - -**Flow:** - -1. User loads app → project list cached -2. User closes app -3. 10 minutes later, user reopens app -4. Query marked as stale, but cached data shown immediately -5. Background refetch starts (no loading indicator) -6. 2 seconds later, fresh data arrives and updates UI - -**Issue:** User doesn't know if they're seeing stale data during those 2 seconds. - -**Recommendation:** - -```jsx -// Add to project list UI -const { data, isRefetching, dataUpdatedAt } = useProjectList(); - -{ - isRefetching && ( -
Syncing... (last updated {formatDistanceToNow(dataUpdatedAt)})
- ); -} -``` - -**Priority:** Medium - ---- - -## WebSocket Reconnection & Error Handling - -### Reconnection Strategy: ✅ **ROBUST** - -**Library:** `y-websocket` (WebsocketProvider) -**Built-in Features:** - -- Automatic reconnection with exponential backoff -- Max delay capped at library defaults - -**Custom Enhancements:** ([useProject/connection.js](packages/web/src/primitives/useProject/connection.js)) - -1. **Online/Offline Detection** ([connection.js:42-61](packages/web/src/primitives/useProject/connection.js:42)) - - ```javascript - window.addEventListener('online', handleOnline); - window.addEventListener('offline', handleOffline); - - function handleOffline() { - provider.shouldConnect = false; // ✅ Stop reconnection attempts - } - - function handleOnline() { - if (shouldBeConnected && !provider.wsconnected) { - provider.connect(); // ✅ Resume connection - } - } - ``` - -2. **Error Throttling** ([connection.js:34-37](packages/web/src/primitives/useProject/connection.js:34)) - - ```javascript - const ERROR_LOG_THROTTLE = 5000; // ✅ Prevent console spam - const MAX_CONSECUTIVE_ERRORS = 5; // ✅ Stop after persistent failures - ``` - -3. **Access Denial Detection** ([connection.js:107-167](packages/web/src/primitives/useProject/connection.js:107)) - - ```javascript - provider.on('connection-close', event => { - const reason = event.reason; - - // ✅ Permanent failures: Stop reconnecting - if (reason === 'project-deleted' || reason === 'membership-revoked' || event.code === 1008) { - // Policy Violation - provider.shouldConnect = false; - onAccessDenied({ reason }); - } - }); - ``` - -### Connection State Machine - -**States:** `connecting` → `connected` → `synced` - -| State | Meaning | UI Indication | -| ------------------ | ------------------------------- | ------------------------- | -| `connecting: true` | WebSocket handshake in progress | "Connecting..." | -| `connected: true` | WebSocket open | "Connected" | -| `synced: true` | Yjs sync protocol completed | "Synced" (hide indicator) | - -**State Transitions:** - -```javascript -// Initial connection -setConnectionState({ connecting: true }); - -// WebSocket opens -provider.on('status', ({ status }) => { - if (status === 'connected') { - setConnectionState({ connected: true, connecting: false }); - } -}); - -// Yjs sync completes -provider.on('sync', isSynced => { - if (isSynced) { - setConnectionState({ synced: true }); - } -}); -``` - -### Edge Cases Handled ✅ - -1. **Offline During Initial Connection** - - ```javascript - function connect() { - if (!navigator.onLine) { - shouldBeConnected = true; // ✅ Remember intent - return; // ✅ Don't attempt connection - } - } - ``` - -2. **Connection Dropped Mid-Session** - - y-websocket automatically reconnects - - UI shows `connected: false` until restored - -3. **Rapid Network Toggling** - - Event listeners prevent simultaneous connect/disconnect - - Only latest intent (online/offline) is respected - ---- - -## Offline UX Indicators - -### Current Indicators - -#### 1. useOnlineStatus Hook ✅ - -**Implementation:** [primitives/useOnlineStatus.js](packages/web/src/primitives/useOnlineStatus.js) - -**Features:** - -- ✅ Debouncing (1s) to prevent flapping -- ✅ **Network verification** - doesn't trust `navigator.onLine` alone -- ✅ HEAD request to `/api/health` to confirm connectivity - -```javascript -async function verifyConnectivity() { - const controller = new AbortController(); - setTimeout(() => controller.abort(), 3000); // 3s timeout - - await fetch('/api/health', { - method: 'HEAD', - signal: controller.signal, - cache: 'no-store', - }); - return true; // Only returns true if fetch succeeds -} -``` - -**Usage in UI:** - -```jsx -const isOnline = useOnlineStatus(); - -// Somewhere in UI (example - not currently implemented) -{ - !isOnline() && ; -} -``` - -#### 2. Connection State Per Project ✅ - -**Stored in:** `projectStore.getConnectionState(projectId)` - -**Includes:** - -- `connected: boolean` - WebSocket open -- `connecting: boolean` - Connection in progress -- `synced: boolean` - Yjs sync complete -- `error: string | null` - Connection error message - -**Example Usage:** - -```jsx -const { connected, synced, error } = useProject(projectId); - -{ - !connected() && Offline; -} -{ - error() && {error()}; -} -``` - -### Missing UX Indicators ⚠️ - -1. **No Global Offline Banner** - - Users don't know if they're offline - - **Recommendation:** Add persistent offline indicator - - ```jsx - // In Navbar.jsx or App root - { - !isOnline() && ( -
-

You're offline. Changes will sync when connection is restored.

-
- ); - } - ``` - - **Priority:** High - -2. **No "Syncing..." Indicator for Background Refetch** - - TanStack Query refetches in background - - User doesn't know if data is stale or being updated - - **Recommendation:** Add spinner/badge when `isRefetching` - - **Priority:** Medium - -3. **No "Last Synced" Timestamp** - - User doesn't know how old cached data is - - **Recommendation:** Show `dataUpdatedAt` from query state - - ```jsx - { - dataUpdatedAt && Last synced {formatDistanceToNow(dataUpdatedAt)}; - } - ``` - - **Priority:** Medium - -4. **No Awareness Indicators (Who's Editing)** - - Yjs awareness tracks user presence - - Not displayed in UI - - **Recommendation:** Show avatars of users editing same checklist - - **Priority:** Low (nice-to-have) - ---- - -## Edge Cases & Reliability Issues - -### Edge Case 1: IndexedDB Quota Exceeded ❌ **NOT HANDLED** - -**Scenario:** - -- User has 100 projects, each with 1000 studies -- Each Yjs document stores full update history -- IndexedDB quota exceeded (browser typically 50MB-1GB) - -**Current Behavior:** - -- IndexedDB write fails silently -- Yjs continues in-memory -- On page reload, offline changes lost - -**Recommendation:** Add quota management - -```javascript -// In IndexeddbPersistence initialization -try { - indexeddbProvider = new IndexeddbPersistence(dbName, ydoc); -} catch (err) { - if (err.name === 'QuotaExceededError') { - // 1. Notify user - // 2. Offer to clear old projects - // 3. Implement LRU eviction - } -} -``` - -**Priority:** Medium (rare but data-loss risk) - ---- - -### Edge Case 2: Yjs Update History Grows Unbounded ⚠️ **POTENTIAL ISSUE** - -**Scenario:** - -- Long-lived project with thousands of edits -- Yjs stores every single update in IndexedDB -- Database size grows indefinitely - -**Yjs Behavior:** - -- No automatic compaction -- Update history needed for offline conflict resolution -- Can be manually compacted via `Y.encodeStateAsUpdate()` - -**Recommendation:** Periodic compaction - -```javascript -// After successful sync, compact old updates -provider.on('sync', async isSynced => { - if (isSynced) { - // Compact updates older than 30 days - const stateVector = Y.encodeStateVector(ydoc); - const update = Y.encodeStateAsUpdate(ydoc, stateVector); - // Store compacted update, delete old updates - } -}); -``` - -**Priority:** Low (only matters for very active projects) - ---- - -### Edge Case 3: LocalStorage Auth Cache Falls Back to Stale Data ⚠️ - -**Scenario:** - -1. User authenticates at 9:00 AM (cached to LocalStorage) -2. Admin revokes user's access at 10:00 AM -3. User's auth session expires at 10:05 AM -4. User refreshes page **while offline** at 10:10 AM -5. LocalStorage cache still valid (7-day TTL) -6. User sees app as if still authenticated - -**Current Mitigation:** - -- ✅ API requests will fail (403 Forbidden) -- ✅ Query errors will show in UI - -**Gap:** - -- User sees authenticated UI until first API call fails -- Confusing UX - -**Recommendation:** Add server-validated "auth check" on app load - -```javascript -// On app load (when online) -if (navigator.onLine) { - await auth.forceRefreshSession(); // Already exists - // If fails, clear cached auth -} -``` - -**Already implemented** via bfcache handler! ✅ - -**Priority:** Low (edge case, already mitigated) - ---- - -### Edge Case 4: User Edits Project While Connection Flapping ⚠️ - -**Scenario:** - -- User on unreliable network -- Connection drops and reconnects every 10 seconds -- User continues editing during disconnections - -**Yjs Behavior:** - -- ✅ All edits buffered in IndexedDB -- ✅ Sent to server on reconnection -- ✅ No data loss - -**UX Issue:** - -- User doesn't know if edits are saved to server -- **Recommendation:** Show "Synced" checkmark after successful sync - -```jsx -{ - synced() ? -
- Synced -
- :
- Syncing... -
; -} -``` - -**Priority:** Medium - ---- - -### Edge Case 5: Multiple Tabs Editing Same Project ✅ **HANDLED** - -**Scenario:** - -- User opens project in 2 browser tabs -- Edits in both tabs simultaneously - -**Yjs Behavior:** - -- ✅ BroadcastChannel syncs Y.Docs across tabs -- ✅ Both tabs share same IndexedDB -- ✅ Edits merged correctly - -**No issue** - Yjs handles this natively. - ---- - -### Edge Case 6: Unnecessary WebSocket Connections for All Projects ❌ **CRITICAL ISSUE** - -**Current Behavior:** - -- `useProject()` hook called in `ProjectView.jsx` (line 47) -- Creates WebSocket connection immediately when component mounts -- Connection registry prevents duplicate connections per project -- **BUT:** If user has project list visible (e.g., Dashboard), all projects are rendered in the list - -**Problem Scenario:** - -1. User has 50 projects -2. User views Dashboard page -3. Each project card potentially mounts and calls `useProject()` -4. **Result:** 50+ WebSocket connections to server simultaneously - -**Investigation Needed:** - -```bash -# Check where useProject is called -grep -r "useProject(" packages/web/src/components/ -``` - -**Current Findings:** - -- [ProjectView.jsx:47](packages/web/src/components/project/ProjectView.jsx:47) - Main project view (✅ **CORRECT**) -- [ChecklistYjsWrapper.jsx](packages/web/src/components/checklist/ChecklistYjsWrapper.jsx) - Checklist editing (✅ **CORRECT**) -- [ReconciliationWrapper.jsx](packages/web/src/components/project/reconcile-tab/amstar2-reconcile/ReconciliationWrapper.jsx) - Reconciliation (✅ **CORRECT**) -- [CompletedTab.jsx](packages/web/src/components/project/completed-tab/CompletedTab.jsx) - Needs verification -- [PreviousReviewersView.jsx](packages/web/src/components/project/completed-tab/PreviousReviewersView.jsx) - Needs verification - -**Recommended Architecture:** - -Only maintain **2 WebSocket connections** maximum: - -1. **Notifications WebSocket** - User-scoped ([useNotifications.js](packages/web/src/primitives/useNotifications.js)) - - ✅ Already implemented correctly - - Connected when user is authenticated - - Used by [useMembershipSync.js](packages/web/src/primitives/useMembershipSync.js) - - Receives project membership change notifications - -2. **Active Project WebSocket** - Project-scoped (via y-websocket) - - ✅ Currently connects only when ProjectView mounts - - Should **only** connect when user actively viewing/editing a project - - Should **disconnect** when navigating away from project - -**Current State Assessment:** - -✅ **GOOD:** Connection registry prevents duplicate connections per project -✅ **GOOD:** Notifications WebSocket is user-scoped (only 1 per user) -⚠️ **CONCERN:** Need to verify project list doesn't mount useProject for all projects - -**Verification Steps:** - -1. Check if Dashboard/project list renders project cards with `useProject()` -2. Check if project cards only fetch metadata via TanStack Query (correct approach) -3. Ensure `useProject()` WebSocket only connects when ProjectView is active - -**If Issue Confirmed:** - -**Solution 1: Lazy Connection (Recommended)** - -```javascript -// In useProject/index.js -export function useProject(projectId, options = {}) { - const { autoConnect = true } = options; - - // Only auto-connect if explicitly requested - if (autoConnect) { - createEffect(() => { - if (projectId) { - connect(); - } - }); - } - - return { - ...projectConnection, - connect, // Expose for manual connection - }; -} - -// In ProjectView.jsx -const projectConnection = useProject(params.projectId, { autoConnect: true }); - -// In project card (if needed) -const projectConnection = useProject(project.id, { autoConnect: false }); -``` - -**Solution 2: Route-Based Connection** - -```javascript -// Only allow useProject() on project detail routes -// Dashboard/list should use TanStack Query for metadata only -``` - -**Priority:** HIGH ⚠️ -**Impact:** Server load, browser memory, unnecessary bandwidth -**Estimated Effort:** 4-6 hours (verification + fix) - ---- - -### Edge Case 7: Service Worker Update During Active Session ⚠️ **NOT APPLICABLE** - -**Currently:** Service worker disabled, so no update issues. - -**When SW enabled:** - -- New version detected while user editing -- Old SW serves old assets -- New code expects new data format - -**Recommendation (when SW enabled):** - -```javascript -// Notify user of update, allow them to choose when to reload -registration.addEventListener('updatefound', () => { - const newWorker = registration.installing; - newWorker.addEventListener('statechange', () => { - if (newWorker.state === 'installed' && navigator.serviceWorker.controller) { - // Show notification: "Update available. Click to refresh." - // Don't force reload while user editing - } - }); -}); -``` - -**Priority:** High (when SW enabled) - ---- - -## Recommendations - -### Critical Priority (Implement Immediately) - -#### C1: Enable Service Worker for Web Package ❌ → ✅ - -**Issue:** No offline app shell means app completely broken when offline. - -**Solution:** - -1. Uncomment service worker registration in `packages/web/src/main.jsx` -2. Scope service worker to `/app.html` and assets -3. Test offline loading, navigation, and cache busting - -**Effort:** 2-4 hours -**Impact:** High - transforms offline UX from "broken" to "functional" - ---- - -#### C2: Verify and Fix WebSocket Connection Strategy ⚠️ → ✅ - -**Issue:** Potentially connecting to multiple project WebSockets simultaneously instead of only active project + notifications. - -**Required Architecture:** - -- **1 Notifications WebSocket** - User-scoped for membership changes (✅ already correct) -- **1 Project WebSocket** - Only for actively viewed/edited project -- **0 WebSockets** - For project list/dashboard (use TanStack Query only) - -**Verification Steps:** - -1. Open browser DevTools → Network → WS filter -2. Navigate to Dashboard with 10+ projects -3. Count active WebSocket connections -4. **Expected:** 1 (notifications only) -5. **If More:** Projects list is mounting `useProject()` - needs fix - -**Solution (if issue confirmed):** - -```javascript -// Option 1: Add autoConnect flag to useProject -export function useProject(projectId, options = {}) { - const { autoConnect = false } = options; // Default to false - - if (autoConnect) { - createEffect(() => { - if (projectId) connect(); - }); - } - - return { ...projectConnection, connect }; // Allow manual connect -} - -// In ProjectView.jsx (active project detail) -const project = useProject(projectId, { autoConnect: true }); - -// In CompletedTab.jsx (if using useProject for read-only data) -const project = useProject(projectId, { autoConnect: false }); -// Use projectStore or TanStack Query instead -``` - -**Effort:** 4-6 hours (verification + implementation + testing) -**Impact:** HIGH - Reduces server load, improves battery life, prevents connection limits - ---- - -#### C3: Add Global Offline Indicator ❌ → ✅ - -**Issue:** Users don't know when they're offline. - -**Solution:** - -```jsx -// In App.jsx or Navbar.jsx -import useOnlineStatus from '@/primitives/useOnlineStatus'; - -function OfflineBanner() { - const isOnline = useOnlineStatus(); - - return ( - -
- -

You're offline. Changes will sync when connection is restored.

-
-
- ); -} -``` - -**Effort:** 1 hour -**Impact:** High - prevents user confusion - ---- - -### High Priority (Next 2 Weeks) - -#### H1: Add Background Refetch Indicators ⚠️ → ✅ - -**Issue:** Users don't know if cached data is being refreshed. - -**Solution:** - -```jsx -const { data, isRefetching, dataUpdatedAt } = useProjectList(); - -{ - isRefetching() && ( -
- - Syncing project list... -
- ); -} -``` - -**Effort:** 2-3 hours (across multiple components) -**Impact:** Medium - improves perceived reliability - ---- - -#### H2: Add "Last Synced" Timestamps ⚠️ → ✅ - -**Issue:** Users can't tell if data is fresh or stale. - -**Solution:** - -```jsx -import { formatDistanceToNow } from 'date-fns'; - -{ - dataUpdatedAt && ( - Last synced {formatDistanceToNow(dataUpdatedAt, { addSuffix: true })} - ); -} -``` - -**Effort:** 2-3 hours -**Impact:** Medium - transparency builds trust - ---- - -#### H3: Implement IndexedDB Quota Management ❌ → ✅ - -**Issue:** No handling when storage quota exceeded. - -**Solution:** - -```javascript -// Add to useProject/index.js -async function handleQuotaError(projectId) { - // 1. Estimate current usage - const estimate = await navigator.storage.estimate(); - const usedPercent = (estimate.usage / estimate.quota) * 100; - - // 2. If >80%, offer to clean up - if (usedPercent > 80) { - // Show modal: "Storage almost full. Delete old projects?" - // List projects by size, allow user to delete - } -} - -// Wrap IndexedDB operations -try { - indexeddbProvider = new IndexeddbPersistence(dbName, ydoc); -} catch (err) { - if (err.name === 'QuotaExceededError') { - await handleQuotaError(projectId); - } -} -``` - -**Effort:** 6-8 hours -**Impact:** Medium - prevents rare data loss - ---- - -### Medium Priority (Next Month) - -#### M1: Add Connection Status Indicators on Project Cards ⚠️ → ✅ - -**Issue:** No visual indication of sync status per project. - -**Solution:** - -```jsx -// In ProjectCard.jsx -const { connected, synced } = useProject(project.id); - - - {synced() ? - 'Synced' - : connected() ? - 'Syncing...' - : 'Offline'} -; -``` - -**Effort:** 3-4 hours -**Impact:** Medium - better status visibility - ---- - -#### M2: Migrate Auth Cache from LocalStorage to IndexedDB ⚠️ → ✅ - -**Issue:** LocalStorage has small quota, synchronous blocking. - -**Solution:** - -```javascript -// Create new auth-cache.js using idb library -import { openDB } from 'idb'; - -const AUTH_DB = 'corates-auth-cache'; - -export async function saveAuthCache(userData) { - const db = await openDB(AUTH_DB, 1, { - upgrade(db) { - db.createObjectStore('auth'); - }, - }); - await db.put('auth', userData, 'current-user'); -} - -export async function loadAuthCache() { - const db = await openDB(AUTH_DB, 1); - return await db.get('auth', 'current-user'); -} -``` - -**Effort:** 4-5 hours -**Impact:** Low - improves robustness - ---- - -#### M3: Add LRU Eviction for PDF Cache ❌ → ✅ - -**Issue:** PDFs accumulate indefinitely. - -**Solution:** - -```javascript -// In pdfCache.js -const MAX_PDF_CACHE_SIZE_MB = 100; - -async function evictLeastRecentlyUsed() { - const db = await getDb(); - const tx = db.transaction(PDF_STORE_NAME, 'readwrite'); - const store = tx.objectStore(PDF_STORE_NAME); - const index = store.index('cachedAt'); - - // Get all PDFs sorted by cachedAt (oldest first) - const pdfs = await index.getAll(); - - let totalSize = pdfs.reduce((sum, pdf) => sum + pdf.size, 0); - const maxSize = MAX_PDF_CACHE_SIZE_MB * 1024 * 1024; - - // Delete oldest until under quota - for (const pdf of pdfs) { - if (totalSize <= maxSize) break; - await store.delete(pdf.id); - totalSize -= pdf.size; - } -} -``` - -**Effort:** 3-4 hours -**Impact:** Medium - prevents quota issues - ---- - -### Low Priority (Nice-to-Have) - -#### L1: Add Yjs Awareness Indicators (Who's Editing) ⚠️ → ✅ - -**Issue:** No visibility into other users' presence. - -**Solution:** - -```jsx -// In ChecklistView.jsx -const awareness = connectionManager.getAwareness(); - -createEffect(() => { - awareness.on('update', ({ added, updated, removed }) => { - // Show user avatars who are viewing same checklist - }); -}); - -
- {user => } -
; -``` - -**Effort:** 6-8 hours -**Impact:** Low - collaboration UX improvement - ---- - -#### L2: Add Optimistic UI for Mutations ⚠️ → ✅ - -**Issue:** Small lag between user action and Yjs update. - -**Solution:** - -```jsx -// In study creation -function createStudy(data) { - // Optimistic update - const tempId = `temp-${Date.now()}`; - projectStore.addStudy({ id: tempId, ...data, status: 'creating' }); - - // Actual Yjs mutation - studyOps.createStudy(data); - - // Remove temp study after Yjs update - createEffect(() => { - if (studies().find(s => s.id !== tempId && s.name === data.name)) { - projectStore.removeStudy(tempId); - } - }); -} -``` - -**Effort:** 8-10 hours (complex state management) -**Impact:** Low - Yjs is already fast (~10-50ms) - ---- - -#### L3: Implement Yjs History Compaction ⚠️ → ✅ - -**Issue:** Update history grows unbounded for long-lived projects. - -**Solution:** - -```javascript -// Compact every 7 days or 10k updates -let updateCount = 0; -const COMPACT_INTERVAL = 10000; - -ydoc.on('update', () => { - updateCount++; - if (updateCount >= COMPACT_INTERVAL) { - compactHistory(); - updateCount = 0; - } -}); - -async function compactHistory() { - const stateVector = Y.encodeStateVector(ydoc); - const compactedUpdate = Y.encodeStateAsUpdate(ydoc); - - // Clear old updates, store compacted version - await indexeddbProvider.clearUpdateLog(); - await indexeddbProvider.storeUpdate(compactedUpdate); -} -``` - -**Effort:** 6-8 hours (research + implementation) -**Impact:** Low - only affects very active projects - ---- - -## Service Worker Implementation Checklist - -When enabling the service worker, follow this checklist: - -### Pre-Deployment - -- [ ] **Scope Decision Made:** Web package only or both landing + web -- [ ] **Cache Strategy Finalized:** - - [ ] Network-first for navigations ✅ (already in sw.js) - - [ ] Cache-first for assets ✅ - - [ ] Skip API requests ✅ -- [ ] **Version Busting:** - - [ ] `CACHE_VERSION` replaced at build time - - [ ] Old caches deleted on activate ✅ - -### Implementation - -- [ ] **Uncomment registration code** in `packages/web/src/main.jsx` -- [ ] **Add update notification:** - ```javascript - registration.addEventListener('updatefound', () => { - // Show "Update available" toast - }); - ``` -- [ ] **Test offline scenarios:** - - [ ] First load while offline (should fail gracefully) - - [ ] Subsequent loads while offline (should work) - - [ ] Navigation while offline (SPA routes work) - - [ ] Asset loading while offline (cached) -- [ ] **Test update scenarios:** - - [ ] New deployment detected - - [ ] Old SW continues serving until user refreshes - - [ ] Cache cleared on update - -### Monitoring - -- [ ] **Add analytics for:** - - [ ] SW registration success/failure - - [ ] Offline usage (navigator.onLine events) - - [ ] Cache hit/miss rates -- [ ] **Error tracking:** - - [ ] SW registration errors - - [ ] Cache storage errors - ---- - -## Conclusion - -CoRATES has **strong local-first foundations** with Yjs CRDT providing automatic conflict resolution and comprehensive IndexedDB persistence across multiple layers. The architecture handles most edge cases gracefully, especially around access revocation and offline editing. - -### Critical Gaps to Address: - -1. **Service worker disabled** - No offline app shell (HIGH PRIORITY) -2. **Potential excessive WebSocket connections** - May connect all projects instead of only active one (HIGH PRIORITY - needs verification) -3. **No offline UX indicators** - Users don't know connection status (HIGH PRIORITY) -4. **No quota management** - Risk of silent data loss (MEDIUM PRIORITY) - -### Strengths to Maintain: - -1. ✅ Yjs CRDT sync is excellent -2. ✅ Multiple IndexedDB layers provide deep persistence -3. ✅ WebSocket reconnection is robust -4. ✅ Stale data cleanup works well -5. ✅ bfcache handling prevents stale UI - -### Recommended Implementation Order: - -**Week 1 (Critical):** - -1. **Verify WebSocket connection count** - Check if multiple projects connect simultaneously -2. **Fix connection strategy** (if needed) - Only active project + notifications -3. Enable service worker for web package -4. Add global offline banner - -**Week 2 (High Priority):** - -- Add background refetch indicators -- Add "last synced" timestamps - -**Week 3 (High Priority):** - -- Implement quota management -- Add connection status badges - -**Month 2 (Medium Priority):** - -- Migrate auth cache to IndexedDB -- Add LRU PDF eviction -- Add awareness indicators (optional) - -With these improvements, CoRATES will have **best-in-class offline support** for a web-based collaborative application. - ---- - -**End of Report** - -For questions or implementation assistance, please consult the development team. diff --git a/packages/docs/audits/prod-audit-2026-01-19-full.md b/packages/docs/audits/prod-audit-2026-01-19-full.md new file mode 100644 index 000000000..70732a021 --- /dev/null +++ b/packages/docs/audits/prod-audit-2026-01-19-full.md @@ -0,0 +1,690 @@ +# CoRATES Production Readiness Audit - Full Report + +**Date:** 2026-01-19 +**Auditor:** Claude Code Production Audit +**Launch Type:** First public launch (invited beta with paid component) +**Expected Traffic:** Low (<100 MAU) +**Audit Strictness:** MEDIUM-HIGH + +--- + +## Table of Contents + +1. [Executive Summary](#executive-summary) +2. [Launch Context](#launch-context) +3. [System Architecture](#system-architecture) +4. [Codebase Health Findings](#codebase-health-findings) +5. [Data Safety Findings](#data-safety-findings) +6. [Security Findings](#security-findings) +7. [Performance Findings](#performance-findings) +8. [Observability Findings](#observability-findings) +9. [Operational Readiness Findings](#operational-readiness-findings) +10. [Verdict and Remediation Plan](#verdict-and-remediation-plan) + +--- + +## Executive Summary + +### Verdict: NOT READY + +CoRATES has solid code architecture and security fundamentals, but critical gaps in data protection and operational infrastructure prevent a safe production launch with real user data and payments. + +**5 Stop-Ship Issues** must be resolved before launch: + +1. No documented D1 rollback procedure (backups exist via Time Travel) +2. Hard deletes only (no soft delete) +3. D1/Durable Object sync failures silently ignored +4. Error monitoring disabled +5. Quota race condition allows exceeding limits + +**No Critical Security Vulnerabilities** were found. Authentication, authorization, and input validation are well-implemented. + +--- + +## Launch Context + +| Factor | Value | +| ------------------- | ------------------------------------------------ | +| Launch Type | Invited beta with paid component | +| User Type | Invited beta users | +| Data Sensitivity | Mixed real/test - data loss mitigation important | +| Payment Integration | Yes - paid product | +| Traffic Level | Low (<100 MAU) | +| Risk Tolerance | Data loss > Downtime priority | +| Audit Strictness | MEDIUM-HIGH | + +**Stop-Ship Criteria Established:** + +- Exploitable security vulnerabilities +- Data loss or corruption risks +- Payment/billing integrity issues +- Missing critical authorization checks + +--- + +## System Architecture + +### Package Structure + +``` +packages/ + web/ - Frontend SolidJS application + workers/ - Backend Hono API + Durable Objects + landing/ - Marketing site (embeds web at build) + shared/ - Common utilities and error definitions + mcp/ - MCP server for development tools + mcp-memory/ - Persistent agent memory + docs/ - Internal documentation +``` + +### Entry Points + +| Type | Path | Auth | Purpose | +| --------- | -------------------------------- | ----------------- | ---------------------------- | +| REST API | `/api/*` | Varies | Core application APIs | +| WebSocket | `/api/project-doc/:projectId` | Cookie + D1 check | Real-time Y.js collaboration | +| WebSocket | `/api/sessions/:sessionId` | Cookie | User notifications | +| Webhook | `/api/auth/stripe/webhook` | Stripe signature | Subscription events | +| Webhook | `/api/billing/purchases/webhook` | Stripe signature | One-time purchases | + +### Persistence Layer + +| Store | Type | Purpose | +| --------------- | ---------------- | ---------------------------------------------------------- | +| D1 (SQLite) | Primary database | Users, sessions, orgs, projects, subscriptions | +| R2 | Object storage | PDF files, avatars | +| Durable Objects | Stateful workers | ProjectDoc (Y.js), UserSession (notifications), EmailQueue | + +### External Services + +| Service | Purpose | Failure Impact | +| ------------ | ---------------------- | ------------------------ | +| Stripe | Payments/subscriptions | Billing broken | +| Google OAuth | Primary login | Login impacted | +| ORCID OAuth | Researcher login | ORCID login unavailable | +| Postmark | Email delivery | Magic links fail | +| Google Drive | PDF import | Drive import unavailable | + +### Trust Boundaries + +| Boundary | Verification | +| ------------- | ------------------------------ | +| Client/API | Cookie session via Better Auth | +| API/Database | Drizzle ORM (no raw SQL) | +| Admin/Regular | Role-based + CSRF protection | +| Project/Org | Separate membership tables | +| Webhooks | Stripe signature verification | + +**Critical Multi-tenancy Note:** Project access requires explicit `projectMembers` entry. Org membership does NOT automatically grant project access. + +--- + +## Codebase Health Findings + +### Error Handling Issues + +#### HIGH: Quota Check-Then-Insert Race Condition + +**Location:** `packages/workers/src/lib/quotaTransaction.ts:157-227` + +The `insertWithQuotaCheck` function performs a classic check-then-act pattern vulnerable to race conditions: + +1. Counts existing records +2. Checks if count >= limit +3. Performs batch insert +4. Verifies count again (but only logs warning, still returns success) + +**Scenario:** Two concurrent requests to add members: + +1. Request A checks quota: 4/5 members used +2. Request B checks quota: 4/5 members used +3. Request A inserts member (5/5) +4. Request B inserts member (6/5 - exceeds quota) +5. Both return success + +**Impact:** Organizations can exceed subscription quotas, losing revenue. + +#### HIGH: Non-Atomic Member Addition with DO Sync Failure + +**Location:** `packages/workers/src/commands/members/addMember.ts:117-154` + +Member addition performs D1 inserts atomically, but DO sync is done afterwards and failures are silently logged: + +```typescript +// Execute all inserts atomically +await db.batch(insertOperations); + +// Sync member to DO - failures logged but ignored +try { + await syncMemberToDO(env, projectId, 'add', { ... }); +} catch (err) { + console.error('Failed to sync member to DO:', err); +} +``` + +**Impact:** Users may be "members" in database but unable to access real-time collaboration. + +#### HIGH: Non-Atomic Member Removal with Potential Stale Access + +**Location:** `packages/workers/src/commands/members/removeMember.ts:51-62` + +Member removal deletes from D1 first, then syncs to DO. If DO sync fails: + +1. WebSocket connection may not be terminated +2. Local IndexedDB cache still has the project +3. User can continue editing until they disconnect + +**Impact:** Removed users can continue editing documents temporarily. + +#### HIGH: Missing Timeouts on External API Calls + +Multiple locations lack explicit timeout protection: + +- Google Drive API calls +- PDF proxy fetches +- Avatar copy operations + +Cloudflare Workers has 30-second implicit limit, but no graceful handling. + +#### MEDIUM: Project Deletion Cleanup Order Issue + +**Location:** `packages/workers/src/commands/projects/deleteProject.ts:49-71` + +Deletion order creates window for orphaned files: + +1. Disconnect all users +2. Clean up R2 storage (failures logged, not thrown) +3. Delete from D1 + +If R2 cleanup fails silently, orphaned PDFs remain indefinitely. + +#### MEDIUM: PDF Upload Non-Atomic R2/D1 Operations + +**Location:** `packages/workers/src/routes/orgs/pdfs.ts:639-704` + +PDF upload stores in R2 first, then inserts to D1. If D1 fails, cleanup is attempted but can also fail, leaving orphaned files. + +#### MEDIUM: PDF Filename Uniqueness Race Condition + +**Location:** `packages/workers/src/routes/orgs/pdfs.ts:372-433` + +`generateUniqueFileName` queries for existing names but another request could create the same name between check and insert. + +**Impact:** One user's PDF could overwrite another's. + +#### MEDIUM: Grant Extension Race Condition + +**Location:** `packages/workers/src/commands/billing/processCheckoutSession.ts:126-158` + +When extending grants, read-modify-write is not atomic. Concurrent purchases could result in lost access time. + +#### MEDIUM: Y.js State Persistence Not Debounced + +**Location:** `packages/workers/src/durable-objects/ProjectDoc.ts:454-465` + +Every Y.js update triggers full state serialization and storage write. Under heavy collaboration, this causes performance issues and potential data loss during high-frequency updates. + +### Code Quality Issues + +#### HIGH: Missing Error Monitoring Integration + +**Location:** `packages/web/src/lib/errorLogger.js:72-94` + +Sentry integration is commented out. Production errors only go to browser console. + +#### LOW: Known Bug in PDF Title Extraction + +**Location:** `packages/web/src/lib/__tests__/pdfUtils.test.js:107-109` + +Test documents that title-cleaning regex only works for prefixes, not mid-title occurrences. + +#### LOW: Incomplete Contact Page FAQ + +**Location:** `packages/landing/src/routes/contact.jsx:111` + +```jsx +{ + /* TODO FAQ */ +} +``` + +#### LOW: Multiple Commented-Out Features + +- Service worker registration (disabled) +- CSV import feature (incomplete) +- PDF redaction commands (disabled) +- Sidebar recents feature (disabled) + +### Console Logging + +564+ console statements found across the codebase. Most are appropriate for development but should be reviewed for production: + +- Convert security-relevant logs to structured logging +- Remove debug logging from authentication flows + +--- + +## Data Safety Findings + +### Write Paths and Destructive Operations + +| Operation | Type | Protection | Risk | +| ---------------- | ----------- | -------------------- | --------------------------------- | +| User deletion | HARD DELETE | Auth + Confirmation | Permanent data loss | +| Project deletion | HARD DELETE | Owner only + cascade | Project + members + files deleted | +| Member removal | HARD DELETE | Owner only | Member access removed | +| PDF deletion | HARD DELETE | Project member | File and metadata deleted | +| Account deletion | HARD DELETE | Self + Confirmation | All user data permanently lost | + +### Cascade Delete Behavior + +- `organization` delete cascades to: `member`, `invitation`, `projects`, `mediaFiles`, `orgAccessGrants`, `projectInvitations` +- `projects` delete cascades to: `projectMembers`, `mediaFiles`, `projectInvitations` +- `user` delete cascades to: `member`, `session`, `account`, `projectMembers`, `twoFactor`, `invitation`, `projectInvitations` + +### Critical Findings + +| Finding | Severity | Description | +| ------------------------ | -------- | --------------------------------------------------- | +| No soft deletes | CRITICAL | All deletions are permanent with no recovery path | +| No D1 rollback procedure | HIGH | D1 Time Travel exists but no documented procedure | +| No migration directory | HIGH | Migrations appear to be run manually via DrizzleKit | +| R2 orphans on D1 failure | MEDIUM | No cleanup job for orphaned files | + +### Backup/Recovery Status + +| Store | Backup Strategy | Recovery Capability | +| --------------- | ----------------------- | -------------------------------------- | +| D1 | Time Travel (automatic) | Available but procedure not documented | +| R2 | None documented | Manual only | +| Durable Objects | State persisted | No backup mechanism | + +--- + +## Security Findings + +### Authentication Audit + +**No Critical Vulnerabilities Found** + +#### HIGH: Sensitive URLs Logged to Console + +**Files:** Multiple in `packages/workers/src/auth/` + +Magic link URLs, password reset URLs, and verification URLs logged to console: + +```typescript +console.log('[Auth] Queuing magic link email to:', email, 'URL:', url); +``` + +**Impact:** Account takeover via intercepted tokens if logs are accessible. + +#### MEDIUM: Rate Limiting Only Active in Production + +**Location:** `packages/workers/src/middleware/rateLimit.ts:51-55` + +```typescript +if (c.env?.ENVIRONMENT !== 'production') { + await next(); + return; +} +``` + +**Impact:** Staging environments can be brute-forced. + +#### MEDIUM: In-Memory Rate Limiting Not Distributed + +**Location:** `packages/workers/src/middleware/rateLimit.ts:8` + +Rate limiting uses in-memory Map not shared across Workers isolates. + +**Impact:** Attackers can bypass by distributing requests. + +#### MEDIUM: Cookie Cache May Delay Session Revocation + +**Location:** `packages/workers/src/auth/config.ts:485-488` + +5-minute session cache means revoked sessions remain valid briefly. + +#### MEDIUM: Stack Trace Exposure in Non-Production + +**Location:** `packages/workers/src/middleware/errorHandler.ts:83-89` + +Stack traces exposed if ENVIRONMENT is not set to "production". + +#### MEDIUM: Missing Rate Limiting on Magic Link Endpoint + +**Location:** `packages/workers/src/auth/routes.ts` + +Magic link send endpoint not rate limited. Email bombing possible. + +#### LOW: ORCID Email Fallback Creates Synthetic Emails + +**Location:** `packages/workers/src/auth/config.ts:138-140` + +When ORCID doesn't provide email, synthetic email created: `${profile.sub}@orcid.org` + +#### LOW: Admin Impersonation Audit Trail Limited + +No explicit audit logging for impersonation start/stop events. + +#### LOW: Password Minimum Length Could Be Stronger + +8 characters minimum. Consider 10-12. + +### Authorization Audit + +**No Critical Vulnerabilities Found** + +#### MEDIUM: Billing Endpoint Authorization Relies on Active Session Org + +**Location:** `packages/workers/src/routes/billing/subscription.ts` + +Billing endpoints don't enforce org membership through middleware. Rely on session's active organization which could be stale. + +#### MEDIUM: No CSRF Protection on Billing Subscription Routes + +Billing routes only apply `requireAuth` but not `requireTrustedOrigin`. + +#### MEDIUM: Member ID vs User ID Confusion + +**Location:** `packages/workers/src/routes/orgs/index.ts:838-904` + +Route parameter `memberId` compared directly against `authUser.id` (user ID). Logic error, though UUID collision unlikely. + +#### LOW: Dev Routes Only Protected by DEV_MODE Flag + +**Location:** `packages/workers/src/routes/orgs/dev-routes.ts:24-29` + +If DEV_MODE accidentally enabled in production, project members could manipulate Y.js state. + +#### LOW: Invitation Token in URL Query Parameter + +Tokens in URLs appear in logs, browser history, referrer headers. + +### Authorization Matrix + +| Resource | Auth | Org Member | Project Member | Admin | Owner | +| ------------------------------------------- | ---- | ---------- | -------------- | ----- | ----- | +| GET /api/orgs | Yes | - | - | No | No | +| GET /api/orgs/:orgId | Yes | Yes | - | No | No | +| DELETE /api/orgs/:orgId | Yes | Owner | - | No | Yes | +| GET /api/orgs/:orgId/projects/:projectId | Yes | Yes | Yes | No | No | +| DELETE /api/orgs/:orgId/projects/:projectId | Yes | Yes | Owner | No | Yes | +| GET /api/billing/subscription | Yes | \* | - | No | No | +| GET /api/admin/\* | Yes | - | - | Yes | No | +| WS /api/project-doc/:projectId | Yes | - | Yes | No | No | + +\*Billing endpoints rely on session's active org without explicit membership verification + +### Input Validation Audit + +**No Critical Vulnerabilities Found** + +#### MEDIUM: SSRF Protection Missing DNS Rebinding Defense + +**Location:** `packages/workers/src/lib/ssrf-protection.ts:140-190` + +Validates hostnames but doesn't verify resolved IP addresses. DNS rebinding attack possible. + +#### MEDIUM: PDF Proxy URL Input Not Validated with Zod + +**Location:** `packages/workers/src/index.ts:167-174` + +URL accepted from request body without Zod schema validation. + +#### MEDIUM: Google Drive File ID Not Validated for Format + +**Location:** `packages/workers/src/routes/google-drive.ts:331-333` + +Only validates non-empty string, not Google Drive ID format. + +#### LOW: IPv6 Addresses Blocked but Not Comprehensively + +**Location:** `packages/workers/src/lib/ssrf-protection.ts:177-179` + +Only blocks bracket notation `[::1]`. Other IPv6 representations may bypass. + +### Positive Security Findings + +1. **Proper CSRF Protection** - Admin routes use `requireTrustedOrigin` +2. **Session Management** - Proper expiry, revocation, IP/user-agent tracking +3. **Password Hashing** - Better Auth uses secure bcrypt +4. **2FA Implementation** - TOTP with 10 backup codes +5. **OAuth Security** - Secure state parameter handling +6. **Security Headers** - HSTS, X-Frame-Options, CSP configured +7. **Email Verification** - Required for email/password signup +8. **Impersonation Safeguards** - Cannot impersonate self, 1-hour limit +9. **Ban Implementation** - Properly invalidates all sessions +10. **Webhook Security** - Two-phase trust model, signature verification +11. **SSRF Protection** - Domain allowlist, private IP blocking +12. **Input Validation** - Consistent Zod usage throughout + +### Secrets Audit + +**No hardcoded secrets found.** All sensitive values loaded from environment variables. + +--- + +## Performance Findings + +### Positive Findings + +- Proper pagination limits (DEFAULT_PAGE_SIZE: 50, MAX_PAGE_SIZE: 100) +- File size limits enforced (PDF: 50MB, Avatar: 2MB) +- R2 list operations batched (1000 items) +- Database queries mostly use `.limit()` appropriately + +### Areas of Concern + +| Finding | Severity | Description | +| ------------------------------------ | -------- | ------------------------------------------------------------- | +| No timeout on external fetches | MEDIUM | PDF proxy, Google Drive, avatar copy have no explicit timeout | +| Y.js state persistence not debounced | MEDIUM | Every keystroke triggers full state write | +| In-memory rate limiting | LOW | Not shared across isolates | +| Admin queries without limits | LOW | Some fetch all records (acceptable for admin) | + +Given low traffic expectation (<100 MAU), these are acceptable risks for launch. + +--- + +## Observability Findings + +### Positive Findings + +| Capability | Status | Location | +| ------------------- | ------ | -------------------------------------------------- | +| Health endpoints | YES | `/health`, `/health/live`, `/healthz` | +| Dependency checks | YES | D1, R2, Durable Objects verified | +| Structured logging | YES | `packages/workers/src/lib/observability/logger.ts` | +| Request ID tracking | YES | X-Request-Id header propagated | +| Webhook audit trail | YES | `stripeEventLedger` table | + +### Gaps Identified + +| Gap | Severity | Description | +| ---------------------- | -------- | ----------------------------------------------- | +| No error monitoring | HIGH | Sentry integration commented out in frontend | +| No alerting | MEDIUM | No documentation for setting up alerts | +| Console-only logging | MEDIUM | Backend logs to console only, no aggregation | +| No metrics collection | LOW | No business metrics or KPIs tracked | +| Mixed logging patterns | LOW | Some code uses console.log vs structured logger | + +--- + +## Operational Readiness Findings + +### Deployment Assessment + +| Attribute | Value | +| ------------- | --------------------------------------------------- | +| Process | Semi-manual (CLI-based) | +| Duration | Unknown (not documented) | +| Rollback Time | Unknown (no documented procedure) | +| Manual Steps | Multiple (secret setup, migration, deploy commands) | +| Risk Level | HIGH | + +### Current Deployment Process + +```bash +# From package.json +pnpm deploy # runs deploy:workers && deploy:landing +``` + +Deployment via `wrangler deploy --env production`. No CI/CD pipeline. + +### CI/CD Status + +| Area | Status | +| ------------------- | -------------------------------- | +| Deployment Pipeline | MISSING | +| Build Pipeline | MISSING | +| Test Pipeline | MISSING | +| Prettier Check | Present (non-main branches only) | + +### Environment Configuration + +| Environment | Status | +| ----------- | ----------------------- | +| Development | Local `.env` files | +| Staging | DOES NOT EXIST | +| Production | Wrangler vars + secrets | + +### Migration Safety + +| Aspect | Status | +| ------------------- | -------------------- | +| Reversible | NO | +| Idempotent | Partial (DrizzleKit) | +| Tested procedure | NO | +| Rollback documented | NO | + +**Critical:** `reset-db-prod.mjs` script is destructive - drops ALL tables and data. + +### Rollback Capability + +| Capability | Status | +| -------------------- | --------------------------------------- | +| Code rollback | Manual via `wrangler versions rollback` | +| Database rollback | NONE | +| Documented procedure | NO | + +### Operational Kill Switches + +| Kill Switch | Status | +| ---------------- | ------------------- | +| Feature flags | NONE | +| Maintenance mode | NONE | +| Rate limiting | Present (per-route) | +| User blocking | Partial (ban field) | + +### Documentation Status + +| Document | Status | +| ----------------- | ------------------------ | +| README | Basic setup only | +| CONTRIBUTING.md | Development workflow | +| Deployment guide | MISSING | +| Runbooks | MISSING | +| Incident response | MISSING | +| Architecture docs | Present (packages/docs/) | + +### Failure Scenarios + +| Scenario | Current Handling | Gap | +| ----------------- | ---------------------------------------- | ----------------------- | +| Bad deploy | No documented rollback | No tested procedure | +| Failed migration | Only `reset-db-prod.mjs` (destroys data) | No incremental rollback | +| Stripe down | Users cannot purchase | No graceful degradation | +| Traffic spike | Workers auto-scale | Adequate | +| Security incident | No procedure | No playbook | + +--- + +## Verdict and Remediation Plan + +### Final Verdict: NOT READY + +The application has solid code architecture and security fundamentals, but critical gaps in data protection and operational infrastructure prevent a safe production launch with real user data and payments. + +### Stop-Ship Issues (Must Fix Before Launch) + +| ID | Issue | Risk | Location | Effort | +| ---- | ------------------------------------ | ------------------- | ------------------------- | --------- | +| SS-1 | No documented D1 rollback procedure | Data Loss (ops) | Documentation | 1-2 hours | +| SS-2 | Hard deletes without soft delete | Data Loss | `db/schema.ts` | 4-8 hours | +| SS-3 | D1/DO sync failures silently ignored | Data Integrity | `commands/members/*.ts` | 4-8 hours | +| SS-4 | Missing error monitoring | Undetected Failures | `lib/errorLogger.js` | 2-4 hours | +| SS-5 | Quota race condition | Payment Integrity | `lib/quotaTransaction.ts` | 4-8 hours | + +### Fix Soon Issues (First Week After Launch) + +| ID | Issue | Location | Effort | +| ---- | --------------------------------------- | ------------------------- | --------- | +| FS-1 | Rate limiting not distributed | `middleware/rateLimit.ts` | 4-8 hours | +| FS-2 | 5-minute session cache delay | `auth/config.ts` | 1 hour | +| FS-3 | Sensitive URLs logged | `auth/` directory | 1 hour | +| FS-4 | No staging environment | `wrangler.jsonc` | 4-8 hours | +| FS-5 | Missing magic link rate limit | `auth/routes.ts` | 1 hour | +| FS-6 | Billing endpoints lack org verification | `billing/subscription.ts` | 2 hours | +| FS-7 | Orphaned R2 files on failures | Multiple | 4-8 hours | +| FS-8 | Y.js state persistence not debounced | `ProjectDoc.ts` | 2-4 hours | + +### Acceptable Risk (Document and Proceed) + +| ID | Issue | Rationale | +| ---- | ------------------------------ | ------------------------------------------- | +| AR-1 | No external fetch timeouts | Workers 30-second limit provides protection | +| AR-2 | In-memory rate limiting resets | Acceptable for beta traffic | +| AR-3 | No feature flags | Acceptable for beta; implement before GA | +| AR-4 | Console-only logging | Cloudflare dashboard provides access | +| AR-5 | Missing CI/CD pipeline | Acceptable for single-operator beta | + +### Recommended Launch Sequence + +1. **Fix stop-ship issues** (estimated 2-3 days) + - Enable Sentry error monitoring (SS-4) - quickest win + - Document D1 backup procedure (SS-1) + - Add soft delete to projects/mediaFiles (SS-2) + - Add retry/compensation for DO sync failures (SS-3) + - Fix quota race condition with database constraints (SS-5) + +2. **Test deployment with rollback verification** + +3. **Launch to invited beta users** + +4. **Address fix-soon items during first week** + +5. **Plan GA launch after operational improvements** + +### Files Requiring Immediate Attention + +| File | Issue | +| ------------------------------------------------------- | ---------------------- | +| `packages/workers/src/lib/quotaTransaction.ts` | Race condition | +| `packages/workers/src/commands/members/addMember.ts` | Silent sync failure | +| `packages/workers/src/commands/members/removeMember.ts` | Silent sync failure | +| `packages/web/src/lib/errorLogger.js` | Enable Sentry | +| `packages/workers/src/db/schema.ts` | Add soft delete fields | + +--- + +## Architecture Highlights (Positive) + +The audit identified several well-implemented patterns: + +1. **Clean separation of concerns** - Org membership separate from project membership +2. **WebSocket auth verifies against D1** - Not trusting Y.js state for authorization +3. **Two-phase webhook trust model** - Stripe events logged before/after verification +4. **Last owner protection** - Cannot orphan projects or orgs +5. **Structured logging infrastructure** - Request IDs, correlation, JSON format +6. **Health check endpoints** - Verify D1, R2, Durable Objects +7. **SSRF protection** - Academic publisher allowlist, private IP blocking +8. **Quota system** - Plan-based limits with entitlement checks +9. **Domain error system** - Typed errors with consistent handling +10. **Multi-layer authorization** - Middleware chain with role checks + +--- + +_Report generated by Claude Code Production Audit_ +_Full audit completed in 9 phases across architecture, code health, data safety, security, performance, observability, and operations._ diff --git a/packages/docs/audits/security-analysis.md b/packages/docs/audits/security-analysis.md new file mode 100644 index 000000000..33f405241 --- /dev/null +++ b/packages/docs/audits/security-analysis.md @@ -0,0 +1,1017 @@ +# Security Analysis Report - CoRATES + +**Date:** 2026-01-19 +**Application:** CoRATES (Collaborative Research Appraisal Tool for Evidence Synthesis) +**Architecture:** SolidJS frontend + Cloudflare Workers backend +**Status:** Pre-production (no active users) + +## Executive Summary + +This security audit examines the CoRATES codebase against OWASP Top 10 vulnerabilities and general security best practices. The application demonstrates **strong security foundations** with comprehensive authentication, input validation, and CSRF protection. However, several areas require attention before production deployment. + +### Overall Security Posture: **GOOD** (7.5/10) + +**Strengths:** + +- Comprehensive authentication via Better-Auth with 2FA support +- Strong CSRF protection via origin validation +- Consistent Zod-based input validation +- Drizzle ORM prevents SQL injection +- Rate limiting on sensitive endpoints +- SSRF protection for external URL fetching +- Webhook signature verification for Stripe +- WebSocket authentication with D1 verification + +**Critical Findings:** 1 +**High Severity:** 3 +**Medium Severity:** 5 +**Low Severity:** 4 +**Informational:** 6 + +--- + +## Findings by Severity + +### CRITICAL + +#### C1. Missing HSTS in Development Environment + +**Severity:** CRITICAL +**OWASP Category:** A05:2021 - Security Misconfiguration +**File:** `/packages/workers/src/middleware/securityHeaders.ts:13-15` + +**Issue:** +HSTS header is only added for HTTPS URLs, but in development with HTTP, this leaves connections unprotected. While appropriate for dev, there's no clear indication production will enforce HTTPS. + +```typescript +try { + const url = new URL(c.req.url); + if (url.protocol === 'https:') { + c.header('Strict-Transport-Security', 'max-age=15552000; includeSubDomains'); + } +} catch { + // Ignore invalid URLs +} +``` + +**Recommendation:** + +1. Add explicit HTTPS enforcement in production environment +2. Add CSP `upgrade-insecure-requests` directive +3. Document HTTPS requirement clearly in deployment docs + +**Impact:** High - Man-in-the-middle attacks possible if HTTPS not enforced + +--- + +### HIGH SEVERITY + +#### H1. Admin Privileges Based Solely on Email Domain + +**Severity:** HIGH +**OWASP Category:** A01:2021 - Broken Access Control +**File:** `/packages/workers/src/auth/admin.ts:11-14` + +**Issue:** +Admin privileges are granted based only on role field in user object. The role assignment mechanism is not visible in this audit scope. If role can be modified through any user-facing API, this is a critical privilege escalation vulnerability. + +```typescript +export function isAdminUser(user: { role?: string | null } | null | undefined): boolean { + if (!user) return false; + return user.role === 'admin'; +} +``` + +**Recommendation:** + +1. Verify role assignment is strictly controlled server-side +2. Add audit logging for all role changes +3. Implement multi-factor authentication requirement for admin actions +4. Consider environment-based admin email allowlist as backup check + +**Impact:** High - Unauthorized admin access could compromise entire system + +--- + +#### H2. Raw SQL Query in Migration Check + +**Severity:** HIGH +**OWASP Category:** A03:2021 - Injection +**File:** `/packages/workers/src/routes/database.ts:164-166` + +**Issue:** +Direct SQL query without parameterization. While the table name is hardcoded (no user input), this violates the project's "ALWAYS use Drizzle ORM" principle and sets a bad precedent. + +```typescript +const tableCheck = await c.env.DB.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='user'").first(); +``` + +**Recommendation:** + +1. Use Drizzle ORM query builder instead +2. Remove this exception and document why Drizzle can't be used (if applicable) +3. Add code review checklist item to catch raw SQL usage + +**Impact:** Medium - No immediate vulnerability but architectural violation + +--- + +#### H3. Incomplete Content Security Policy + +**Severity:** HIGH +**OWASP Category:** A05:2021 - Security Misconfiguration +**File:** `/packages/workers/src/middleware/securityHeaders.ts:43-57` + +**Issue:** +Production CSP allows `unsafe-inline` for styles, which can enable XSS attacks via style injection. The docs page has even weaker CSP with `unsafe-eval`. + +```typescript +} else if (isHtmlResponse) { + c.header( + 'Content-Security-Policy', + [ + "default-src 'self'", + "script-src 'self'", + "style-src 'self' 'unsafe-inline'", // UNSAFE + "img-src 'self' data:", + // ... + ].join('; '), + ); +} +``` + +**Recommendation:** + +1. Remove `unsafe-inline` for styles - use CSS files or nonce-based inline styles +2. Implement CSP nonce generation for legitimate inline scripts +3. Add CSP reporting endpoint to monitor violations +4. Separate docs CSP to dedicated middleware + +**Impact:** High - XSS attacks possible via style injection + +--- + +### MEDIUM SEVERITY + +#### M1. Rate Limiting Disabled in Non-Production + +**Severity:** MEDIUM +**OWASP Category:** A04:2021 - Insecure Design +**File:** `/packages/workers/src/middleware/rateLimit.ts:52-55` + +**Issue:** +Rate limiting is completely bypassed in non-production environments. This makes development testing less representative of production behavior and could mask abuse patterns. + +```typescript +return async (c, next) => { + if (c.env?.ENVIRONMENT !== 'production') { + await next(); + return; + } + // ... rate limiting logic +``` + +**Recommendation:** + +1. Use higher limits in development instead of disabling completely +2. Add environment variable to explicitly disable rate limiting when needed +3. Document that rate limiting is production-only + +**Impact:** Medium - Development testing doesn't match production behavior + +--- + +#### M2. In-Memory Rate Limit Store + +**Severity:** MEDIUM +**OWASP Category:** A04:2021 - Insecure Design +**File:** `/packages/workers/src/middleware/rateLimit.ts:8` + +**Issue:** +Rate limits use in-memory Map storage which is lost on worker restarts and not shared across worker instances. Attackers can bypass limits by triggering worker restarts or distributing requests across instances. + +```typescript +const rateLimitStore = new Map(); +``` + +**Recommendation:** + +1. Use Durable Objects for persistent, distributed rate limiting +2. Document current limitations in security docs +3. Consider Cloudflare Rate Limiting API for production + +**Impact:** Medium - Rate limits can be bypassed + +--- + +#### M3. Webhook Signature Timing Attack Risk + +**Severity:** MEDIUM +**OWASP Category:** A02:2021 - Cryptographic Failures +**File:** `/packages/workers/src/routes/billing/webhooks.ts:186-190` + +**Issue:** +While Stripe's webhook verification is used correctly, error handling doesn't use constant-time comparison. This is mitigated by using Stripe's library which should handle timing attacks internally. + +```typescript +event = await stripe.webhooks.constructEventAsync(rawBody, signature!, c.env.STRIPE_WEBHOOK_SECRET_PURCHASES); +``` + +**Recommendation:** + +1. Verify Stripe SDK uses constant-time comparison (it does) +2. Document reliance on Stripe SDK security +3. Add additional entropy check if implementing custom webhook handlers + +**Impact:** Low - Stripe SDK likely handles this correctly + +--- + +#### M4. Missing Security Headers on WebSocket Upgrade + +**Severity:** MEDIUM +**OWASP Category:** A05:2021 - Security Misconfiguration +**File:** `/packages/workers/src/middleware/securityHeaders.ts:7-9` + +**Issue:** +Security headers are skipped for WebSocket connections (status 101). While WebSockets don't use headers the same way, this bypasses CORS and origin checks in the middleware. + +```typescript +if (c.res.status === 101) { + return; +} +``` + +**Recommendation:** + +1. Verify WebSocket origin validation happens elsewhere (it does in ProjectDoc) +2. Add explicit comment explaining why 101 is skipped +3. Consider adding WebSocket-specific security headers + +**Impact:** Low - Origin validation exists in WebSocket handler + +--- + +#### M5. Potential Email Header Injection + +**Severity:** MEDIUM +**OWASP Category:** A03:2021 - Injection +**File:** `/packages/workers/src/routes/contact.ts:193` + +**Issue:** +Email subject construction concatenates user input. While input is validated via Zod, the subject field could contain newline characters enabling header injection if not sanitized. + +```typescript +Subject: `[Contact Form] ${subject || 'New Inquiry'}`, +``` + +**Recommendation:** + +1. Use `sanitizeEmailSubject()` function already present in codebase +2. Apply sanitization to all email header fields +3. Add test cases for newline injection + +**Impact:** Medium - Email header injection possible + +--- + +### LOW SEVERITY + +#### L1. Overly Permissive CORS for WebSockets + +**Severity:** LOW +**OWASP Category:** A05:2021 - Security Misconfiguration +**File:** `/packages/workers/src/middleware/cors.ts:30-34` + +**Issue:** +CORS middleware explicitly bypasses WebSocket connections, assuming origin validation happens elsewhere. This is correct but not clearly documented. + +```typescript +const upgradeHeader = c.req.raw.headers.get('Upgrade'); +if (upgradeHeader === 'websocket') { + return next(); +} +``` + +**Recommendation:** + +1. Add comment explaining WebSocket origin validation location +2. Link to WebSocket auth implementation +3. Consider extracting WebSocket-specific middleware + +**Impact:** Low - Origin validation exists in WebSocket handler + +--- + +#### L2. Console Logging of Sensitive Operations + +**Severity:** LOW +**OWASP Category:** A09:2021 - Security Logging and Monitoring Failures +**Files:** Multiple (see below) + +**Issue:** +Extensive console.error and console.log usage throughout codebase. Some log statements may expose sensitive data in production logs. + +Examples: + +- `/packages/workers/src/auth/config.ts:158` - Logs email and URL +- `/packages/workers/src/routes/billing/webhooks.ts:223` - Logs webhook errors + +**Recommendation:** + +1. Audit all console.log/error statements for sensitive data +2. Implement structured logging with severity levels +3. Use logger abstraction to control log output by environment +4. Redact sensitive fields (emails, tokens, IDs) in logs + +**Impact:** Low - Information disclosure in logs + +--- + +#### L3. Weak Session Expiry Configuration + +**Severity:** LOW +**OWASP Category:** A07:2021 - Identification and Authentication Failures +**File:** `/packages/workers/src/auth/config.ts:482-488` + +**Issue:** +7-day session expiry is longer than recommended for sensitive applications. While updateAge provides automatic renewal, compromised sessions remain valid for extended periods. + +```typescript +session: { + expiresIn: 60 * 60 * 24 * 7, // 7 days + updateAge: 60 * 60 * 24, // 1 day + cookieCache: { + enabled: true, + maxAge: 60 * 5, // 5 minutes + }, +}, +``` + +**Recommendation:** + +1. Consider reducing session expiry to 24-48 hours +2. Document session management policy +3. Implement "remember me" option for extended sessions +4. Add session revocation on password change + +**Impact:** Low - Extended session lifetime + +--- + +#### L4. Missing X-Frame-Options Exception for Embeds + +**Severity:** LOW +**OWASP Category:** A05:2021 - Security Misconfiguration +**File:** `/packages/workers/src/middleware/securityHeaders.ts:20` + +**Issue:** +X-Frame-Options: DENY prevents all iframe embedding. If PDF viewer or other components need embedding, this could break functionality. + +```typescript +c.header('X-Frame-Options', 'DENY'); +``` + +**Recommendation:** + +1. Verify no legitimate iframe usage exists +2. If needed, use `SAMEORIGIN` instead of `DENY` +3. Use CSP `frame-ancestors` as primary defense +4. Document iframe policy + +**Impact:** Low - May break legitimate functionality + +--- + +### INFORMATIONAL + +#### I1. Better-Auth Configuration + +**File:** `/packages/workers/src/auth/config.ts` + +**Positive Findings:** + +- 2FA support with backup codes +- Email verification required +- Account linking with trusted providers +- 8-character minimum password length +- Admin impersonation with 1-hour limit +- OAuth with offline access for Google Drive + +**Recommendations:** + +1. Consider increasing minimum password length to 10-12 characters +2. Add password complexity requirements +3. Implement password breach checking (Have I Been Pwned API) +4. Add rate limiting to password reset + +--- + +#### I2. Input Validation Strategy + +**File:** `/packages/workers/src/config/validation.ts` + +**Positive Findings:** + +- Comprehensive Zod schemas for all inputs +- Centralized validation middleware +- Proper error mapping to domain errors +- Query parameter validation +- Transform functions for sanitization + +**Recommendations:** + +1. Add validation unit tests for edge cases +2. Document validation patterns in security guidelines +3. Consider max file size validation for uploads +4. Add regex validation for specific formats (phone, postal codes) + +--- + +#### I3. SSRF Protection Implementation + +**File:** `/packages/workers/src/lib/ssrf-protection.ts` + +**Positive Findings:** + +- Blocks private IP ranges (RFC 1918, link-local, etc.) +- Blocks metadata endpoints (AWS, GCP, Azure) +- Domain allowlist for PDF fetching +- IPv6 blocking +- Direct IP address restrictions + +**Recommendations:** + +1. Add DNS rebinding protection +2. Implement URL redirect following limits +3. Add timeout for external requests +4. Log SSRF blocking attempts + +--- + +#### I4. WebSocket Authentication + +**File:** `/packages/workers/src/durable-objects/ProjectDoc.ts:490-556` + +**Positive Findings:** + +- Cookie-based authentication required +- D1 database verification on every connection +- Project membership checked against authoritative source +- No trust of Yjs member map for authorization + +**Recommendations:** + +1. Add connection rate limiting per user +2. Implement connection timeout +3. Add heartbeat/keepalive mechanism +4. Log suspicious connection patterns + +--- + +#### I5. Stripe Webhook Security + +**File:** `/packages/workers/src/routes/billing/webhooks.ts` + +**Positive Findings:** + +- Two-phase trust model (receipt then verification) +- Signature verification required +- Duplicate event detection via payload hash +- Stripe event ID deduplication +- Test mode rejection in production +- Comprehensive ledger for observability + +**Recommendations:** + +1. Add webhook replay attack detection (timestamp check) +2. Implement webhook retry limits +3. Add alerting for verification failures +4. Document webhook security model + +--- + +#### I6. Error Handling and Logging + +**Files:** Multiple + +**Positive Findings:** + +- Centralized error handling via @corates/shared +- Domain-specific error types +- Error context preservation +- HTTP status code mapping + +**Recommendations:** + +1. Implement error monitoring service (Sentry, etc.) +2. Add error correlation IDs +3. Sanitize error messages for production +4. Create error handling documentation + +--- + +## OWASP Top 10 Coverage + +### A01:2021 - Broken Access Control + +**Status:** GOOD +**Findings:** H1 + +- Role-based access control implemented +- Organization and project scoping enforced +- Middleware chain validates permissions +- WebSocket connections verify D1 membership +- **Risk:** Admin role assignment mechanism needs verification + +### A02:2021 - Cryptographic Failures + +**Status:** GOOD +**Findings:** M3 + +- Better-Auth handles password hashing +- HTTPS enforced (assumed in production) +- Secrets stored in environment variables +- Stripe webhook signatures verified +- **Risk:** HSTS not enforced by code + +### A03:2021 - Injection + +**Status:** EXCELLENT +**Findings:** H2, M5 + +- Drizzle ORM prevents SQL injection +- Zod validation on all inputs +- HTML escaping used in email templates +- **Risk:** One raw SQL query in migration check + +### A04:2021 - Insecure Design + +**Status:** GOOD +**Findings:** M1, M2 + +- Rate limiting implemented +- SSRF protection for external URLs +- CSRF protection via origin validation +- **Risk:** Rate limits in-memory and production-only + +### A05:2021 - Security Misconfiguration + +**Status:** FAIR +**Findings:** C1, H3, M4, L1, L4 + +- Security headers implemented +- Default deny CORS policy +- X-Frame-Options set to DENY +- **Risk:** CSP allows unsafe-inline styles, HSTS not guaranteed + +### A06:2021 - Vulnerable and Outdated Components + +**Status:** NOT AUDITED + +**Recommendation:** Run `npm audit` and dependency scanning tools + +### A07:2021 - Identification and Authentication Failures + +**Status:** EXCELLENT +**Findings:** L3 + +- Better-Auth library with 2FA +- Email verification required +- OAuth with multiple providers +- Session management with secure cookies +- **Risk:** 7-day session expiry may be too long + +### A08:2021 - Software and Data Integrity Failures + +**Status:** GOOD + +- Webhook signature verification +- No eval() or dynamic code execution +- Trusted npm packages only +- **Recommendation:** Add dependency integrity checks + +### A09:2021 - Security Logging and Monitoring Failures + +**Status:** FAIR +**Findings:** L2 + +- Extensive logging via console +- Webhook ledger for payment observability +- **Risk:** No centralized log monitoring, potential sensitive data in logs + +### A10:2021 - Server-Side Request Forgery + +**Status:** EXCELLENT +**Findings:** I3 + +- Comprehensive SSRF protection +- Domain allowlist for PDF fetching +- Private IP blocking +- Metadata endpoint blocking + +--- + +## Production Deployment Checklist + +### CRITICAL (Must Fix Before Production) + +- [ ] **C1:** Enforce HTTPS in production configuration +- [ ] **H1:** Verify and document admin role assignment security +- [ ] **H2:** Replace raw SQL with Drizzle ORM +- [ ] **H3:** Remove `unsafe-inline` from CSP + +### HIGH PRIORITY + +- [ ] **M1:** Configure production rate limiting strategy +- [ ] **M2:** Implement Durable Object-based rate limiting +- [ ] **M5:** Add email header sanitization +- [ ] Set up error monitoring (Sentry/similar) +- [ ] Configure HTTPS/HSTS at Cloudflare level +- [ ] Run dependency audit (`npm audit fix`) + +### MEDIUM PRIORITY + +- [ ] **L2:** Audit and sanitize all log statements +- [ ] **L3:** Review session expiry configuration +- [ ] Implement centralized logging system +- [ ] Add CSP violation reporting +- [ ] Set up security headers testing + +### LOW PRIORITY + +- [ ] Add password complexity requirements +- [ ] Implement password breach checking +- [ ] Add connection rate limiting for WebSockets +- [ ] Document all security configurations + +--- + +## Secrets Management + +### Current State + +**Secrets stored in environment variables:** + +- AUTH_SECRET (session encryption) +- STRIPE_SECRET_KEY +- STRIPE_WEBHOOK_SECRET_AUTH +- STRIPE_WEBHOOK_SECRET_PURCHASES +- GOOGLE_CLIENT_SECRET +- ORCID_CLIENT_SECRET +- POSTMARK_SERVER_TOKEN +- SMTP_PASS + +**Storage Method:** + +- Local: `.env` file (gitignored) +- Production: Wrangler secrets (`wrangler secret put`) + +**Issues:** + +- No secret rotation policy documented +- No audit trail for secret access +- Placeholder values in code comments + +**Recommendations:** + +1. Document secret rotation procedure +2. Use Cloudflare's secret management API +3. Implement secret versioning +4. Add secret expiry monitoring +5. Remove placeholder comments from code + +--- + +## Database Security + +### SQL Injection Prevention + +**Status:** EXCELLENT + +- All queries use Drizzle ORM +- Parameterized queries only +- One exception (migration check) needs fix + +### Schema Security + +**File:** `/packages/workers/src/db/schema.ts` + +**Findings:** + +- Password field exists for email/password auth (handled by Better-Auth) +- UUIDs used for primary keys +- Proper foreign key relationships +- Soft delete via banned/deletedAt fields + +**Recommendations:** + +1. Add database-level constraints +2. Implement row-level security if supported +3. Add audit logging triggers +4. Document data retention policy + +--- + +## Authentication Deep Dive + +### Session Management + +**Cookie Configuration:** + +- HttpOnly: Assumed (Better-Auth default) +- Secure: Assumed for HTTPS +- SameSite: Not explicitly configured +- Domain: Set to AUTH_BASE_URL + +**Recommendations:** + +1. Explicitly configure SameSite=Strict or Lax +2. Verify HttpOnly and Secure flags +3. Add CSRF token for state-changing operations +4. Document cookie configuration + +### OAuth Security + +**Providers:** + +- Google OAuth (drive.readonly scope) +- ORCID (researcher authentication) + +**Security:** + +- Account linking enabled with trusted providers +- Email verification not required for Google (trusted) +- Offline access for Drive integration + +**Recommendations:** + +1. Add OAuth state parameter validation +2. Implement PKCE for public clients +3. Limit OAuth scopes to minimum required +4. Add OAuth token revocation on account deletion + +--- + +## API Security + +### Rate Limiting Configuration + +```typescript +authRateLimit: 20 requests / 15 minutes +sessionRateLimit: 200 requests / 1 minute +emailRateLimit: 5 requests / 1 hour +contactRateLimit: 5 requests / 15 minutes +billingCheckoutRateLimit: 10 requests / 15 minutes +``` + +**Analysis:** + +- Reasonable limits for most endpoints +- Session endpoint has high limit (needed for frequent checks) +- Email/contact heavily restricted (good) +- **Issue:** All disabled in non-production + +### CORS Configuration + +**Allowed Origins:** + +- Static: localhost:5173, localhost:8787, corates.org +- Pattern: `*.jacobamaynard.workers.dev` +- Environment: ALLOWED_ORIGINS env var + +**Security:** + +- Credentials allowed (required for cookies) +- Origin validation before allowing +- WebSockets bypass CORS middleware + +**Recommendations:** + +1. Remove wildcard workers.dev in production +2. Add origin validation logging +3. Document CORS policy + +--- + +## Frontend Security Considerations + +**Note:** This audit focused on backend. Frontend security should cover: + +- XSS prevention in SolidJS components +- Client-side input validation +- Secure storage of sensitive data +- CSP compliance in inline scripts +- Third-party script integrity (SRI) + +**Recommendation:** Conduct separate frontend security audit + +--- + +## Monitoring and Incident Response + +### Current Monitoring + +- Cloudflare Workers logs +- Console logging throughout application +- Webhook event ledger +- Rate limit tracking + +### Gaps + +- No centralized error tracking +- No security event alerting +- No anomaly detection +- No incident response plan + +### Recommendations + +1. Implement security monitoring: + - Failed authentication attempts + - Rate limit violations + - CSRF token failures + - Webhook signature failures + - Admin action audit trail + +2. Set up alerting: + - Multiple failed logins + - Unusual API usage patterns + - Database errors + - Third-party service failures + +3. Create incident response plan: + - Security breach procedures + - Secret rotation process + - User notification protocol + - Forensics data collection + +--- + +## Testing Recommendations + +### Security Testing Gaps + +1. **Penetration Testing:** + - SQL injection attempts + - XSS payload testing + - CSRF attack simulation + - Rate limit bypass attempts + +2. **Authentication Testing:** + - Session fixation + - Session hijacking + - OAuth flow manipulation + - Password reset flow + +3. **Authorization Testing:** + - Horizontal privilege escalation + - Vertical privilege escalation + - IDOR vulnerabilities + - Mass assignment + +4. **Input Validation Testing:** + - Boundary value testing + - Malformed input handling + - File upload security + - SQL injection vectors + +### Test Coverage + +**Current State:** + +- Backend routes: Good coverage +- Middleware: Partial coverage +- Frontend: Sparse coverage (per STATUS.md) + +**Recommendations:** + +1. Add security-focused test cases +2. Implement fuzz testing +3. Add integration tests for auth flows +4. Test rate limiting behavior + +--- + +## Compliance Considerations + +### GDPR (if applicable) + +- [ ] Data minimization review +- [ ] Right to erasure implementation +- [ ] Data portability features +- [ ] Privacy policy +- [ ] Cookie consent mechanism +- [ ] Data processing agreements + +### HIPAA (if handling health data) + +- [ ] Encryption at rest +- [ ] Access logging +- [ ] Audit trails +- [ ] Business associate agreements + +**Note:** Confirm regulatory requirements based on deployment region and data types. + +--- + +## Positive Security Practices + +### Architecture Strengths + +1. **Separation of Concerns:** Clear middleware chain for auth, validation, authorization +2. **Defense in Depth:** Multiple layers of security (CORS, CSRF, auth, validation) +3. **Principle of Least Privilege:** Project membership requires explicit invitation +4. **Secure by Default:** Authentication required for most endpoints +5. **Comprehensive Validation:** Zod schemas for all inputs + +### Code Quality + +1. **Consistent Error Handling:** Domain error types throughout +2. **Type Safety:** TypeScript with strict mode +3. **Dependency Management:** Package.json with specific versions +4. **Code Organization:** Clear module boundaries + +### Documentation + +1. **Inline Comments:** Security-critical code well-documented +2. **Pattern Guides:** Extensive .mdc files for common patterns +3. **Status Tracking:** STATUS.md documents implementation state + +--- + +## Recommendations Summary + +### Immediate Actions (Pre-Production) + +1. Fix all CRITICAL and HIGH severity findings +2. Configure HTTPS enforcement +3. Replace raw SQL query +4. Fix CSP to remove unsafe-inline +5. Verify admin role assignment security +6. Add email header sanitization +7. Run dependency audit + +### Short-term (First Month) + +1. Implement Durable Object rate limiting +2. Set up error monitoring +3. Audit and sanitize log statements +4. Configure production rate limits +5. Add CSP violation reporting +6. Document security configurations + +### Medium-term (First Quarter) + +1. Conduct penetration testing +2. Implement security monitoring +3. Create incident response plan +4. Add password breach checking +5. Implement secret rotation +6. Frontend security audit + +### Long-term (Ongoing) + +1. Regular security audits +2. Dependency updates and scanning +3. Security training for developers +4. Threat modeling exercises +5. Bug bounty program consideration + +--- + +## Conclusion + +CoRATES demonstrates a **strong security foundation** with comprehensive authentication, input validation, and protection against common web vulnerabilities. The use of established libraries (Better-Auth, Drizzle ORM, Zod) and security middleware shows good security awareness. + +The critical and high-severity findings are primarily **configuration and hardening issues** rather than fundamental architectural flaws. All identified issues are addressable before production deployment. + +**Primary Risks:** + +1. Production HTTPS/HSTS enforcement not guaranteed +2. Admin role assignment security needs verification +3. CSP weaknesses (unsafe-inline) +4. Rate limiting limitations (in-memory, production-only) + +**Next Steps:** + +1. Address all CRITICAL and HIGH severity findings +2. Complete production deployment security checklist +3. Conduct penetration testing +4. Implement security monitoring +5. Document security configurations and policies + +With the recommended fixes implemented, CoRATES will have a **robust security posture** suitable for production deployment in a research/academic environment. + +--- + +## Appendix: Security Contacts + +- Report security issues to: [Define security contact] +- Security policy: [Create SECURITY.md] +- Responsible disclosure: [Define disclosure policy] + +--- + +**Report prepared by:** Security Audit Agent +**Review status:** Pending human verification +**Next review date:** [Define review schedule] diff --git a/packages/docs/audits/solidjs-best-practices-audit-2026-01.md b/packages/docs/audits/solidjs-best-practices-audit-2026-01.md deleted file mode 100644 index b8b3bbcc1..000000000 --- a/packages/docs/audits/solidjs-best-practices-audit-2026-01.md +++ /dev/null @@ -1,242 +0,0 @@ -# SolidJS Best Practices Audit - -**Date:** 2026-01-17 -**Reference:** Brenley Dueck's SolidJS Best Practices (January 12, 2026) -**Scope:** packages/web/src codebase and documentation - ---- - -## Executive Summary - -The CoRATES codebase demonstrates **excellent adherence** to SolidJS best practices. The team has done strong work on prop handling, control-flow components, store usage, and effect patterns. One minor signal passing issue was fixed. Remaining items are low-priority optimizations. - -| Category | Status | Priority | -| -------------------------- | --------------------- | -------- | -| Props (no destructuring) | Excellent | N/A | -| Control-flow (Show/For) | Excellent | N/A | -| Stores for complex objects | Good | Low | -| createEffect usage | Good | N/A | -| Derived state patterns | Good | N/A | -| Signal passing in JSX | Fixed | N/A | -| Documentation | Good, gaps identified | Low | - ---- - -## Code Findings - -### 1. Prop Destructuring - EXCELLENT - -**Status:** No violations found - -All ~100+ components properly access props via `props.propertyName` without destructuring. Examples of correct patterns used throughout: - -```js -// Correct pattern used everywhere -export default function ScoreTag(props) { - const showRatingOnly = () => props.showRatingOnly ?? false; - const checklistType = () => props.checklistType || DEFAULT_CHECKLIST_TYPE; -} -``` - -### 2. Control-Flow Components (Show/For) - EXCELLENT - -**Status:** No violations found - -The codebase consistently uses: - -- `` instead of `{condition && }` -- `}>` instead of ternary operators -- `` instead of `.map()` in JSX - -### 3. createEffect Usage - GOOD - -**Status:** No high-priority anti-patterns found. ~70 total effects reviewed, all legitimate. - -#### High Priority Fixes - -None - all identified issues were determined to be legitimate patterns on closer inspection. - -#### Medium Priority - -| File | Line | Issue | Fix | -| -------------------------- | ----- | ------------------------- | -------------------------- | -| `SplitScreenLayout.jsx` | 24-26 | Simple prop sync | Use prop directly | -| `EditPdfMetadataModal.jsx` | 31-40 | Form reset on prop change | Consider dedicated handler | - -#### Legitimate Uses (No changes needed) - -Most createEffect instances are legitimate: - -- DOM manipulation and event listeners (Sidebar, DevPanel) -- Browser API integration (localStorage, online status) -- Third-party library integration (PDF loading, blob URLs) -- Resource cleanup with onCleanup -- Controlled/uncontrolled component initialization (AMSTAR2Checklist:797-812, MultiPartQuestionPage:38-64 - initializes mutable local state from props with fallback) -- Legacy data normalization (SignallingQuestion:21-29 - coerces invalid 'NA' values to 'NI' when NA is not a valid option for the response type) - -### 4. Store vs Signal for Complex Objects - GOOD - -**Status:** 3 files should use createStore instead of createSignal - -#### Should Convert to createStore - -| File | Lines | Current Pattern | Benefit | -| ---------------- | ------------- | ----------------------------------------- | -------------------------------------- | -| `Sidebar.jsx` | 37-38, 95-105 | `createSignal({})` for expandedProjects | Fine-grained reactivity per project ID | -| `DevPanel.jsx` | 46-50 | Separate position/size/dragOffset signals | Eliminate cross-property re-renders | -| `form-errors.js` | 136, 157-170 | `createSignal({})` for fieldErrors | Only affected fields re-render | - -### 5. Derived State Patterns - NEEDS ATTENTION - -**Status:** 2 files use createEffect for state sync instead of derivation - -#### Anti-Patterns Found - -**RobinsIReconciliation.jsx** (Lines 89-98): - -```js -// ANTI-PATTERN: Using effect to set derived state -createEffect(() => { - const items = navItems(); - const page = currentPage(); - if (items.length > 0 && expandedDomain() === null) { - const sectionKey = getSectionKeyForPage(items, page); - if (sectionKey) { - setExpandedDomain(sectionKey); - } - } -}); - -// SHOULD BE: Derived with createMemo -const expandedDomain = createMemo(() => { - const items = navItems(); - const page = currentPage(); - if (items.length > 0) { - return getSectionKeyForPage(items, page); - } - return null; -}); -``` - -**ChecklistReconciliation.jsx** has similar patterns (Lines 154-170). - -### 6. Signal Passing in JSX - MINOR ISSUES - -**Status:** 2 instances found - -| File | Line | Issue | Fix | -| ------------------- | ---- | ------------------------------------------------------------------ | ------------------------------------------------------ | -| `MagicLinkForm.jsx` | 103 | Passes `error` instead of `displayError` accessor | Change to `displayError={displayError}` | -| `SignUp.jsx` | 138 | Passes raw `error` signal, inconsistent with other auth components | Create `displayError` accessor with authError fallback | - ---- - -## Documentation Findings - -### Well Documented - -1. **Prop destructuring** - Explicitly called out in CLAUDE.md, solidjs.mdc, and guides -2. **Control-flow components** - Good examples in components.md -3. **createEffect sparingly** - Mentioned in multiple guides -4. **Stores for complex objects** - Comprehensive coverage in state-management.md - -### Documentation Gaps - -| Topic | Current State | Recommendation | -| ------------------------------- | ------------------------------- | -------------------------------------------------- | -| Derive vs Sync | Implicit in examples | Add dedicated section with anti-pattern comparison | -| Function wrappers vs createMemo | Both shown, no guidance on when | Explain performance tradeoffs | -| Effects anti-patterns | Says "use sparingly" | Add common mistakes section | -| Signal props pattern | Minimal coverage | Add examples of receiving signals as props | - -### Recommended Documentation Additions - -#### 1. Add to state-management.md: "Derive Instead of Sync" - -````md -## Derive Instead of Sync - -Prefer deriving values over synchronizing signals with effects. - -### Anti-Pattern (DON'T DO THIS) - -```js -const [items, setItems] = createSignal([]); -const [filtered, setFiltered] = createSignal([]); - -// BAD: Syncing with effect -createEffect(() => { - setFiltered(items().filter(i => i.active)); -}); -``` -```` - -### Correct Pattern - -```js -const [items, setItems] = createSignal([]); - -// GOOD: Derive with createMemo -const filtered = createMemo(() => items().filter(i => i.active)); -``` - -**Why:** Derivation is declarative, has no race conditions, and automatically re-computes when dependencies change. - -```` - -#### 2. Add to components.md: "Function Wrappers vs createMemo" - -```md -## When to Use Function Wrappers vs createMemo - -- **Function wrapper** `() => value`: Lightweight, re-evaluates on every access -- **createMemo**: Tracks dependencies, caches result, re-evaluates only when deps change - -Use createMemo when: -- The computation is expensive -- The value is accessed multiple times per render -- You need memoization for referential stability -```` - ---- - -## Architecture Note: SolidStart Patterns - -The project uses **SolidJS Router** (SPA architecture), not TanStack Start. Therefore: - -- `createAsync`, `action()`, `query()`, `preload` patterns do not apply -- Data fetching via TanStack Query (useQuery) is appropriate -- Mutations via `apiFetch` are functional but could benefit from `useMutation` wrapper for consistent loading/error state - ---- - -## Priority Action Items - -### High Priority (Fix These) - -1. **MagicLinkForm.jsx:103** - Change `displayError={error}` to `displayError={displayError}` [FIXED] - -### Medium Priority (Should Fix) - -2. **Sidebar.jsx:37-38** - Convert expandedProjects/Studies to createStore -3. **DevPanel.jsx:46-50** - Consolidate position/size/dragOffset into single store - -### Low Priority (Nice to Have) - -4. **form-errors.js** - Convert fieldErrors to createStore -5. **SignUp.jsx:138** - Add displayError accessor for consistency -6. Update documentation with "Derive vs Sync" section - ---- - -## Files Referenced - -- `/packages/web/src/components/project/reconcile-tab/amstar2-reconcile/MultiPartQuestionPage.jsx` -- `/packages/web/src/components/checklist/ROBINSIChecklist/SignallingQuestion.jsx` -- `/packages/web/src/components/auth/MagicLinkForm.jsx` -- `/packages/web/src/components/auth/SignUp.jsx` -- `/packages/web/src/components/sidebar/Sidebar.jsx` -- `/packages/web/src/components/dev/DevPanel.jsx` -- `/packages/web/src/lib/form-errors.js` -- `/packages/web/src/components/project/reconcile-tab/robins-i-reconcile/RobinsIReconciliation.jsx` -- `/packages/web/src/components/project/reconcile-tab/amstar2-reconcile/ChecklistReconciliation.jsx` diff --git a/packages/docs/audits/third-party-libraries-audit-2026-01.md b/packages/docs/audits/third-party-libraries-audit-2026-01.md deleted file mode 100644 index 382483e7b..000000000 --- a/packages/docs/audits/third-party-libraries-audit-2026-01.md +++ /dev/null @@ -1,414 +0,0 @@ -# Third-Party Libraries Audit - -**Date:** 2026-01-06 -**Scope:** Identify useful 3rd party libraries that could benefit the CoRATES codebase - ---- - -## Executive Summary - -This audit analyzes the current CoRATES codebase to identify areas where third-party libraries could improve code quality, reduce maintenance burden, or add valuable functionality. The recommendations are prioritized by impact and effort. - ---- - -## Current Stack Overview - -### Frontend (packages/web) - -- **UI Framework:** SolidJS + Ark UI -- **Routing:** @solidjs/router -- **State/Data:** TanStack Query, Yjs, IndexedDB (idb) -- **Styling:** TailwindCSS -- **Charts:** D3.js -- **PDF:** embedpdf ecosystem -- **Icons:** solid-icons - -### Backend (packages/workers) - -- **Runtime:** Cloudflare Workers -- **Framework:** Hono -- **Database:** Drizzle ORM + D1 -- **Auth:** Better-Auth -- **Payments:** Stripe -- **Real-time:** Yjs + Durable Objects -- **Email:** Postmark -- **Validation:** Zod - ---- - -## High Priority Recommendations - -### 1. Error Monitoring: Sentry or Toucan - -**Problem:** The codebase has a TODO comment in [ErrorBoundary.jsx](packages/web/src/components/ErrorBoundary.jsx#L115) noting the need for error monitoring. Unknown errors are caught but only logged to console. - -**Recommendation:** **@sentry/browser** or **toucan-js** (Cloudflare-native) - -**Benefits:** - -- Automatic error tracking with stack traces -- Session replay for debugging user issues -- Performance monitoring -- Release tracking - -**Implementation:** - -```js -// Frontend: @sentry/browser (lightweight, works with SolidJS) -// Backend: toucan-js (designed for Cloudflare Workers) -``` - -**Effort:** Low-Medium -**Impact:** High - ---- - -### 2. Schema Validation: Zod Frontend - -**Problem:** While Zod is used extensively in the workers package for validation, the frontend relies on manual validation or lacks structured validation entirely. Form validation is inconsistent. - -**Recommendation:** Use existing **zod** dependency on frontend - -**Benefits:** - -- Type-safe form validation -- Consistent error messages -- Reuse schemas between frontend/backend - -**Implementation:** - -- Export shared schemas from `@corates/shared` -- Use `@tanstack/solid-form` or `@modular-forms/solid` with Zod adapter - -**Effort:** Medium -**Impact:** High - ---- - -### 3. Date/Time Handling: date-fns or Temporal - -**Problem:** The codebase uses raw `Date.now()`, `new Date()` throughout. No consistent date formatting or timezone handling. Examples found in: - -- [referenceLookup.js](packages/web/src/lib/referenceLookup.js) -- [studies.js](packages/web/src/primitives/useProject/studies.js) -- [queryClient.js](packages/web/src/lib/queryClient.js) - -**Recommendation:** **date-fns** (tree-shakeable) or **@formkit/tempo** (smaller) - -**Benefits:** - -- Consistent date formatting across the app -- Relative time display ("2 hours ago") -- Timezone-safe operations -- Internationalization support - -**Implementation:** - -```js -// date-fns is tree-shakeable, only import what you need -import { formatDistanceToNow, format, parseISO } from 'date-fns'; -``` - -**Effort:** Low -**Impact:** Medium - ---- - -### 4. Reference Parsing: Better BibTeX/RIS Libraries - -**Problem:** Custom RIS and BibTeX parsers in [referenceParser.js](packages/web/src/lib/referenceParser.js) (400+ lines). These are complex format-specific parsers that likely have edge cases. - -**Recommendation:** **bibtex-parse** or **citation-js** - -**Benefits:** - -- Battle-tested parsing for academic formats -- Support for more citation styles (CSL) -- Better handling of edge cases -- Citation formatting capabilities - -**Consideration:** Evaluate bundle size vs. custom solution. Your current implementation may be sufficient if working well. - -**Effort:** Medium -**Impact:** Medium - ---- - -### 5. Debounce/Throttle Utilities: @solid-primitives/scheduled - -**Problem:** Manual debounce implementations scattered throughout the codebase: - -- [queryClient.js](packages/web/src/lib/queryClient.js) - manual setTimeout debounce -- [useOnlineStatus.js](packages/web/src/primitives/useOnlineStatus.js) - manual timers - -**Recommendation:** **@solid-primitives/scheduled** - -**Benefits:** - -- SolidJS-native reactivity integration -- Proper cleanup handling -- `debounce`, `throttle`, `scheduleIdle`, `leading` variants -- Already maintained by SolidJS community - -**Implementation:** - -```js -import { debounce, throttle } from '@solid-primitives/scheduled'; -const debouncedSave = debounce(save, 1000); -``` - -**Effort:** Low -**Impact:** Medium - ---- - -## Medium Priority Recommendations - -### 6. HTTP Client: ky or ofetch - -**Problem:** Raw `fetch()` calls throughout [api/](packages/web/src/api/) with repeated boilerplate for: - -- Error handling -- JSON parsing -- Headers management -- Retry logic - -**Recommendation:** **ky** (browser) or **ofetch** (universal) - -**Benefits:** - -- Automatic JSON parsing -- Retry with backoff -- Request/response hooks -- Simpler error handling -- Timeout support built-in - -**Implementation:** - -```js -import ky from 'ky'; -const api = ky.create({ prefixUrl: API_BASE, credentials: 'include' }); -const data = await api.get('billing/subscription').json(); -``` - -**Effort:** Medium -**Impact:** Medium - ---- - -### 7. Image Processing: Sharp (Backend) - -**Problem:** [imageUtils.js](packages/web/src/lib/imageUtils.js) does client-side image compression using Canvas API. This works but: - -- Inconsistent results across browsers -- No WebP/AVIF support -- CPU-intensive on client - -**Recommendation:** Move to server-side with **@cf-wasm/photon** or process uploads via **Cloudflare Images** - -**Benefits:** - -- Consistent compression quality -- Modern format support (WebP, AVIF) -- Reduced client CPU usage -- Better compression ratios - -**Consideration:** This requires architectural change. Current solution is functional. - -**Effort:** High -**Impact:** Low-Medium - ---- - -### 8. UUID Generation: nanoid - -**Problem:** Using `crypto.randomUUID()` throughout. While this works, nanoid offers advantages. - -**Recommendation:** **nanoid** - -**Benefits:** - -- 21 chars vs 36 chars (smaller storage/URLs) -- URL-safe by default -- Customizable alphabet -- Slightly faster - -**Implementation:** - -```js -import { nanoid } from 'nanoid'; -const studyId = nanoid(); // "V1StGXR8_Z5jdHi6B-myT" -``` - -**Effort:** Low -**Impact:** Low - ---- - -### 9. Statistical Functions: simple-statistics - -**Problem:** [inter-rater-reliability.js](packages/web/src/lib/inter-rater-reliability.js) implements Cohen's Kappa manually. Academic tools often need additional statistical measures. - -**Recommendation:** **simple-statistics** - -**Benefits:** - -- Cohen's Kappa, Fleiss' Kappa -- Standard deviation, variance, percentiles -- Regression analysis -- Well-tested implementations -- Small bundle size (tree-shakeable) - -**Implementation:** - -```js -import { cohensKappa, standardDeviation } from 'simple-statistics'; -``` - -**Effort:** Low -**Impact:** Medium (for future statistical features) - ---- - -### 10. Form State Management: @modular-forms/solid - -**Problem:** Complex form state handling in components like AddStudiesForm. Manual persistence logic in [formStatePersistence.js](packages/web/src/lib/formStatePersistence.js). - -**Recommendation:** **@modular-forms/solid** with Zod adapter - -**Benefits:** - -- Built for SolidJS reactivity -- Zod schema integration -- Field-level validation -- Dirty/touched tracking -- Built-in array fields (for study lists) - -**Effort:** Medium-High -**Impact:** Medium - ---- - -## Low Priority / Nice-to-Have - -### 11. DOMPurify for User Content - -**Problem:** Basic HTML escaping in [escapeHtml.js](packages/workers/src/lib/escapeHtml.js). If user-generated HTML content is ever displayed (markdown, etc.), XSS risks increase. - -**Recommendation:** **isomorphic-dompurify** or **sanitize-html** - -**Benefits:** - -- Comprehensive XSS protection -- Configurable allowed tags/attributes -- Works server and client side - -**When needed:** If implementing markdown rendering, rich text, or displaying user HTML. - -**Effort:** Low -**Impact:** Low (currently not rendering user HTML) - ---- - -### 12. Distributed Rate Limiting: @upstash/ratelimit - -**Problem:** Current rate limiting in [rateLimit.js](packages/workers/src/middleware/rateLimit.js) is per-worker-instance memory-based. Comment notes this limitation. - -**Recommendation:** **@upstash/ratelimit** or implement with Durable Objects - -**Benefits:** - -- True distributed rate limiting -- Works across all worker instances -- Multiple algorithms (sliding window, token bucket) - -**When needed:** When scaling to high traffic or strict rate limiting requirements. - -**Effort:** Medium -**Impact:** Low (current solution works for moderate traffic) - ---- - -### 13. Charting Alternative: @observablehq/plot or Chart.js - -**Problem:** D3 is powerful but low-level. Current charts in [AMSTARDistribution.jsx](packages/web/src/components/charts/AMSTARDistribution.jsx) require significant code. - -**Recommendation:** Keep D3 for complex visualizations, consider **@observablehq/plot** for simpler charts - -**Benefits:** - -- Higher-level API -- Still D3-based (familiar) -- Good for academic visualizations -- Less boilerplate - -**Consideration:** D3 gives full control. Only switch if chart requirements are simple. - -**Effort:** Medium -**Impact:** Low - ---- - -### 14. Feature Flags: @vercel/flags or LaunchDarkly - -**Problem:** No feature flag system for gradual rollouts, A/B testing, or disabling features. - -**Recommendation:** **@vercel/flags** (if using Vercel) or **@happykit/flags** or custom with KV - -**Benefits:** - -- Gradual feature rollouts -- User segment targeting -- Kill switches for features -- A/B testing capability - -**When needed:** When approaching production with real users. - -**Effort:** Medium -**Impact:** Low (pre-production) - ---- - -## Libraries to Avoid - -### moment.js - -- Too large (300kb+), use date-fns instead - -### lodash (full) - -- Use native methods or lodash-es with specific imports - -### axios - -- fetch is standard, ky/ofetch are smaller and modern - -### jQuery - -- Not needed with SolidJS - ---- - -## Summary Matrix - -| Library | Priority | Effort | Impact | Category | -| --------------------------- | -------- | -------- | ------ | -------------- | -| Sentry/Toucan | High | Low-Med | High | Monitoring | -| Zod (frontend) | High | Medium | High | Validation | -| date-fns | High | Low | Medium | Utilities | -| @solid-primitives/scheduled | High | Low | Medium | Utilities | -| ky/ofetch | Medium | Medium | Medium | HTTP | -| simple-statistics | Medium | Low | Medium | Statistics | -| @modular-forms/solid | Medium | Med-High | Medium | Forms | -| nanoid | Low | Low | Low | Utilities | -| DOMPurify | Low | Low | Low | Security | -| @upstash/ratelimit | Low | Medium | Low | Infrastructure | - ---- - -## Recommended Next Steps - -1. **Immediate:** Add error monitoring (Sentry/Toucan) - catches production issues early -2. **Short-term:** Integrate date-fns and @solid-primitives/scheduled - quick wins -3. **Medium-term:** Evaluate form library needs based on complexity growth -4. **Ongoing:** Review this audit quarterly as requirements evolve diff --git a/packages/docs/audits/typescript-backend-conversion-plan.md b/packages/docs/audits/typescript-backend-conversion-plan.md deleted file mode 100644 index c53d62902..000000000 --- a/packages/docs/audits/typescript-backend-conversion-plan.md +++ /dev/null @@ -1,563 +0,0 @@ -# TypeScript Backend Conversion Plan - -Plan to convert the `@corates/workers` package from JavaScript to TypeScript. - -## Current State Analysis - -### Scope - -- **Total files**: 155 JavaScript files -- **Source files**: ~80 implementation files -- **Test files**: ~75 test files -- **Entry point**: `src/index.js` -- **Build tool**: Wrangler (Cloudflare Workers) - -### Directory Structure - -``` -packages/workers/src/ - index.js # Main entry, exports Durable Objects - docs.js # OpenAPI documentation generator - auth/ # Authentication (6 files) - config/ # Constants, validation, origins (3 files) - db/ # Drizzle schema and client (4 files) - durable-objects/ # UserSession, ProjectDoc, EmailQueue (4 files) - lib/ # Utilities (12 files) - middleware/ # Hono middleware (11 files) - routes/ # API routes (15+ files) - schemas/ # Zod schemas (1 file) - commands/ # CLI commands - __tests__/ # Test files and helpers -``` - -### Existing TypeScript Assets - -- `drizzle.config.ts` - Drizzle configuration -- `worker-configuration.d.ts` - Generated Cloudflare types (12k+ lines) -- `jsconfig.json` - Path aliases (`@/*` -> `src/*`) - -### Dependencies Already Type-Safe - -- `@hono/zod-openapi` - TypeScript-first -- `drizzle-orm` - Full TypeScript support -- `better-auth` - TypeScript -- `zod` - TypeScript-first validation -- `@corates/shared` - Already TypeScript -- `@cloudflare/workers-types` - Cloudflare type definitions - ---- - -## Conversion Strategy - -### Approach: Incremental Migration - -Convert files incrementally while maintaining a working codebase. Use TypeScript's `allowJs: true` during transition to allow mixed JS/TS. - -### Guiding Principles - -1. **Keep the codebase working** - All tests must pass after each phase -2. **Convert leaf nodes first** - Start with files that have no internal dependencies -3. **Maximize type inference** - Let TypeScript infer types where possible -4. **Add explicit types where valuable** - Focus on public APIs and function signatures -5. **Preserve runtime behavior** - No functional changes during conversion - ---- - -## Phase 1: Infrastructure Setup - -**Duration**: 1 day -**Risk**: Low - -### Tasks - -1. **Create `tsconfig.json`** - - ```jsonc - { - "compilerOptions": { - "target": "ES2022", - "module": "ES2022", - "lib": ["ES2022"], - "moduleResolution": "bundler", - "strict": true, - "esModuleInterop": true, - "skipLibCheck": true, - "forceConsistentCasingInFileNames": true, - "resolveJsonModule": true, - "declaration": false, - "noEmit": true, - "allowJs": true, - "checkJs": false, - "types": ["@cloudflare/workers-types", "vitest/globals"], - "paths": { - "@/*": ["./src/*"], - }, - "baseUrl": ".", - }, - "include": ["src/**/*"], - "exclude": ["node_modules", "migrations", ".wrangler"], - } - ``` - -2. **Update `package.json`** - - Add `typescript` dev dependency - - Add `typecheck` script: `tsc --noEmit` - - Keep `main` as `src/index.js` (Wrangler handles compilation) - -3. **Update `wrangler.jsonc`** - - Change `main` from `src/index.js` to `src/index.ts` (after Phase 2) - -4. **Update `vitest.config.js` -> `vitest.config.ts`** - - Update test include pattern: `['src/**/*.{test,spec}.{js,ts}']` - -5. **Update `drizzle.config.ts`** - - Change schema path from `.js` to `.ts` (after db files converted) - -6. **Delete `jsconfig.json`** (superseded by tsconfig.json) - -### Verification - -- [ ] `pnpm typecheck` runs without errors (with `checkJs: false`) -- [ ] `pnpm test` passes -- [ ] `pnpm dev` starts successfully - ---- - -## Phase 2: Core Infrastructure - -**Duration**: 2 days -**Risk**: Medium - -Convert foundational files that other modules depend on. - -### Conversion Order - -1. **Database Layer** (`src/db/`) - - `schema.js` -> `schema.ts` - - `client.js` -> `client.ts` - - `orgAccessGrants.js` -> `orgAccessGrants.ts` - - `stripeEventLedger.js` -> `stripeEventLedger.ts` - -2. **Configuration** (`src/config/`) - - `constants.js` -> `constants.ts` - - `origins.js` -> `origins.ts` - - `validation.js` -> `validation.ts` - -3. **Schemas** (`src/schemas/`) - - `common.js` -> `common.ts` - -### Type Definitions to Add - -```typescript -// src/types/env.ts - Re-export from generated types -export type { Env } from '../../worker-configuration'; - -// src/types/context.ts - Hono context with auth -import type { Context } from 'hono'; -import type { Env } from './env'; - -export interface AuthUser { - id: string; - email: string; - name: string; - role?: string; - // ... other fields -} - -export interface AuthSession { - id: string; - userId: string; - token: string; - // ... other fields -} - -export interface AppVariables { - user: AuthUser | null; - session: AuthSession | null; -} - -export type AppContext = Context<{ Bindings: Env; Variables: AppVariables }>; -``` - -### Verification - -- [ ] `pnpm typecheck` passes -- [ ] `pnpm test` passes -- [ ] Drizzle migrations still generate correctly - ---- - -## Phase 3: Middleware Layer - -**Duration**: 2 days -**Risk**: Medium - -Convert all middleware with proper Hono typing. - -### Conversion Order - -1. `errorHandler.js` -> `errorHandler.ts` -2. `cors.js` -> `cors.ts` -3. `securityHeaders.js` -> `securityHeaders.ts` -4. `auth.js` -> `auth.ts` -5. `csrf.js` -> `csrf.ts` -6. `rateLimit.js` -> `rateLimit.ts` -7. `requireAuth.js` (if separate) -8. `requireOrg.js` -> `requireOrg.ts` -9. `requireOrgWriteAccess.js` -> `requireOrgWriteAccess.ts` -10. `requireAdmin.js` -> `requireAdmin.ts` -11. `requireEntitlement.js` -> `requireEntitlement.ts` -12. `requireQuota.js` -> `requireQuota.ts` -13. `subscription.js` -> `subscription.ts` - -### Type Pattern for Middleware - -```typescript -import type { MiddlewareHandler } from 'hono'; -import type { Env } from '../types/env'; -import type { AppVariables } from '../types/context'; - -export const requireAuth: MiddlewareHandler<{ - Bindings: Env; - Variables: AppVariables; -}> = async (c, next) => { - // implementation -}; -``` - -### Verification - -- [ ] All middleware tests pass -- [ ] `pnpm typecheck` passes - ---- - -## Phase 4: Library Utilities - -**Duration**: 2 days -**Risk**: Low - -Convert utility functions and helpers. - -### Conversion Order - -1. `lib/escapeHtml.js` -> `lib/escapeHtml.ts` -2. `lib/ssrf-protection.js` -> `lib/ssrf-protection.ts` -3. `lib/project-doc-id.js` -> `lib/project-doc-id.ts` -4. `lib/project-sync.js` -> `lib/project-sync.ts` -5. `lib/access.js` -> `lib/access.ts` -6. `lib/entitlements.js` -> `lib/entitlements.ts` -7. `lib/quotaTransaction.js` -> `lib/quotaTransaction.ts` -8. `lib/billingResolver.js` -> `lib/billingResolver.ts` -9. `lib/notify.js` -> `lib/notify.ts` -10. `lib/observability/*` -> `lib/observability/*.ts` -11. `lib/dev-seed/*` -> `lib/dev-seed/*.ts` -12. `lib/mock-templates.js` -> `lib/mock-templates.ts` - -### Verification - -- [ ] All lib tests pass -- [ ] `pnpm typecheck` passes - ---- - -## Phase 5: Authentication - -**Duration**: 1 day -**Risk**: Medium (auth is critical) - -### Conversion Order - -1. `auth/emailTemplates.js` -> `auth/emailTemplates.ts` -2. `auth/templates.js` -> `auth/templates.ts` -3. `auth/email.js` -> `auth/email.ts` -4. `auth/config.js` -> `auth/config.ts` -5. `auth/admin.js` -> `auth/admin.ts` -6. `auth/routes.js` -> `auth/routes.ts` - -### Type Considerations - -```typescript -// Better Auth types -import type { Auth } from 'better-auth'; -import type { BetterAuthOptions } from 'better-auth/types'; - -export function createAuth(env: Env): Auth { - // implementation -} -``` - -### Verification - -- [ ] Auth tests pass -- [ ] Manual login/logout testing -- [ ] OAuth flows work - ---- - -## Phase 6: Durable Objects - -**Duration**: 2 days -**Risk**: High (complex async state) - -### Conversion Order - -1. `durable-objects/EmailQueue.js` -> `durable-objects/EmailQueue.ts` -2. `durable-objects/UserSession.js` -> `durable-objects/UserSession.ts` -3. `durable-objects/ProjectDoc.js` -> `durable-objects/ProjectDoc.ts` -4. `durable-objects/dev-handlers.js` -> `durable-objects/dev-handlers.ts` - -### Type Pattern for Durable Objects - -```typescript -import { DurableObject } from 'cloudflare:workers'; -import type { Env } from '../types/env'; - -export class UserSession extends DurableObject { - constructor(ctx: DurableObjectState, env: Env) { - super(ctx, env); - } - - async fetch(request: Request): Promise { - // implementation - } -} -``` - -### Verification - -- [ ] Durable Object tests pass -- [ ] WebSocket connections work -- [ ] Yjs sync works - ---- - -## Phase 7: API Routes - -**Duration**: 3-4 days -**Risk**: Medium - -Largest phase - convert all route handlers. - -### Conversion Order (by complexity, ascending) - -**Simple routes (no complex dependencies)** - -1. `routes/health.js` -> `routes/health.ts` -2. `routes/contact.js` -> `routes/contact.ts` -3. `routes/avatars.js` -> `routes/avatars.ts` -4. `routes/database.js` -> `routes/database.ts` - -**Medium complexity** 5. `routes/email.js` -> `routes/email.ts` 6. `routes/users.js` -> `routes/users.ts` 7. `routes/invitations.js` -> `routes/invitations.ts` 8. `routes/members.js` -> `routes/members.ts` 9. `routes/google-drive.js` -> `routes/google-drive.ts` 10. `routes/account-merge.js` -> `routes/account-merge.ts` - -**Complex routes** 11. `routes/projects.js` -> `routes/projects.ts` 12. `routes/orgs/index.js` -> `routes/orgs/index.ts` 13. `routes/orgs/*.js` -> `routes/orgs/*.ts` 14. `routes/billing/index.js` -> `routes/billing/index.ts` 15. `routes/billing/*.js` -> `routes/billing/*.ts` 16. `routes/admin/index.js` -> `routes/admin/index.ts` 17. `routes/admin/*.js` -> `routes/admin/*.ts` - -### Type Pattern for Routes - -```typescript -import { OpenAPIHono, createRoute, z } from '@hono/zod-openapi'; -import type { Env } from '../types/env'; -import type { AppVariables } from '../types/context'; - -const routes = new OpenAPIHono<{ - Bindings: Env; - Variables: AppVariables; -}>(); - -// Route with typed request/response -const getProjectRoute = createRoute({ - method: 'get', - path: '/:projectId', - request: { - params: z.object({ - projectId: z.string(), - }), - }, - responses: { - 200: { - content: { - 'application/json': { - schema: ProjectSchema, - }, - }, - description: 'Project details', - }, - }, -}); - -routes.openapi(getProjectRoute, async c => { - const { projectId } = c.req.valid('param'); - // implementation - fully typed! -}); -``` - -### Verification - -- [ ] All route tests pass -- [ ] API responses match expected schemas -- [ ] `pnpm typecheck` passes - ---- - -## Phase 8: Entry Point and Final Cleanup - -**Duration**: 1 day -**Risk**: Low - -### Tasks - -1. **Convert entry point** - - `src/index.js` -> `src/index.ts` - - `src/docs.js` -> `src/docs.ts` - -2. **Update Wrangler config** - - ```jsonc - { - "main": "src/index.ts", - } - ``` - -3. **Remove `allowJs` from tsconfig** - - ```jsonc - { - "compilerOptions": { - "allowJs": false, - "checkJs": false, - }, - } - ``` - -4. **Enable strict checks** (if not already) - - `noUnusedLocals: true` - - `noUnusedParameters: true` - - `noImplicitReturns: true` - -### Verification - -- [ ] `pnpm typecheck` passes with strict mode -- [ ] All tests pass -- [ ] Dev server starts -- [ ] Production deployment works - ---- - -## Phase 9: Test Files - -**Duration**: 2-3 days -**Risk**: Low - -Convert test files last since they depend on implementation files. - -### Conversion Order - -1. `__tests__/helpers.js` -> `__tests__/helpers.ts` -2. `__tests__/factories/*.js` -> `__tests__/factories/*.ts` -3. `__tests__/setup.js` -> `__tests__/setup.ts` -4. Individual test files (can be done incrementally) - -### Test Type Pattern - -```typescript -import { describe, it, expect, beforeEach, vi } from 'vitest'; -import type { MockInstance } from 'vitest'; - -describe('Feature', () => { - let mockFn: MockInstance; - - beforeEach(() => { - mockFn = vi.fn(); - }); - - it('should work', async () => { - // implementation - }); -}); -``` - -### Verification - -- [ ] `pnpm test` passes -- [ ] Test coverage unchanged - ---- - -## Estimated Timeline - -| Phase | Duration | Cumulative | -| ---------------------- | -------- | ---------- | -| 1. Infrastructure | 1 day | 1 day | -| 2. Core Infrastructure | 2 days | 3 days | -| 3. Middleware | 2 days | 5 days | -| 4. Library Utilities | 2 days | 7 days | -| 5. Authentication | 1 day | 8 days | -| 6. Durable Objects | 2 days | 10 days | -| 7. API Routes | 4 days | 14 days | -| 8. Entry Point | 1 day | 15 days | -| 9. Test Files | 3 days | 18 days | - -**Total estimated time**: 15-18 working days (~3-4 weeks) - ---- - -## Risk Mitigation - -### High-Risk Areas - -1. **Durable Objects** - Complex async state, WebSocket handling - - Mitigation: Extensive manual testing, keep JS fallback ready - -2. **Authentication** - Critical path, OAuth complexity - - Mitigation: Test auth flows thoroughly before/after - -3. **Yjs Integration** - Real-time sync with external library - - Mitigation: Test WebSocket connections and sync - -### Rollback Strategy - -- Each phase should be a separate PR -- Keep the main branch stable -- Feature flag TypeScript entry point if needed - -### Testing Strategy - -- Run full test suite after each file conversion -- Manual smoke testing after each phase -- Test in staging before production deployment - ---- - -## Success Criteria - -1. [ ] All 155 files converted to TypeScript -2. [ ] `pnpm typecheck` passes with strict mode -3. [ ] All tests pass (same coverage) -4. [ ] No runtime behavior changes -5. [ ] Dev server works -6. [ ] Production deployment successful -7. [ ] No increase in bundle size (minimal) - ---- - -## Post-Conversion Improvements - -After conversion is complete, consider: - -1. **Add return type annotations** to all exported functions -2. **Create barrel exports** (`index.ts`) for each directory -3. **Add JSDoc** for public APIs -4. **Enable `strictNullChecks`** fully -5. **Add path aliases** for common imports -6. **Consider `zod` inference** for request/response types -7. **Add API client generation** from OpenAPI spec - ---- - -## Notes - -- Wrangler handles TypeScript compilation natively - no separate build step needed -- Vitest supports TypeScript out of the box -- Drizzle schema types are automatically inferred -- Better Auth provides full TypeScript support -- The `worker-configuration.d.ts` file provides all Cloudflare bindings types diff --git a/packages/docs/guides/database.md b/packages/docs/guides/database.md index 7f72d9580..1395d21fd 100644 --- a/packages/docs/guides/database.md +++ b/packages/docs/guides/database.md @@ -811,6 +811,143 @@ Check the Zod schema in `src/__tests__/seed-schemas.js`: - `scripts/generate-test-sql.mjs` - Script to generate test SQL constant - `drizzle.config.ts` - DrizzleKit configuration +## Database Recovery + +Cloudflare D1 provides automatic point-in-time recovery through Time Travel. This section documents recovery procedures for common scenarios. + +### Time Travel Overview + +D1 automatically maintains a history of your database, allowing you to: + +- Restore the entire database to a previous point in time +- Export data from a specific point in time +- Query historical data without restoring + +**Retention:** 30 days on paid plans, 7 days on free tier. + +### Recovery Commands + +#### List Available Restore Points + +```bash +# View recent bookmarks (automatic snapshots) +wrangler d1 time-travel info corates-db-prod --env production +``` + +#### Restore to a Point in Time + +```bash +# Restore to a specific timestamp (ISO 8601 format) +wrangler d1 time-travel restore corates-db-prod \ + --timestamp "2026-01-19T10:30:00Z" \ + --env production + +# Restore to a specific bookmark +wrangler d1 time-travel restore corates-db-prod \ + --bookmark \ + --env production +``` + +#### Export Data from a Point in Time + +```bash +# Export SQL from a specific timestamp (for inspection before restore) +wrangler d1 time-travel export corates-db-prod \ + --timestamp "2026-01-19T10:30:00Z" \ + --output backup.sql \ + --env production +``` + +### Common Recovery Scenarios + +#### Scenario 1: Accidental Project Deletion + +A user accidentally deletes an important project. + +**Steps:** + +1. Identify when the deletion occurred (check logs, ask user) +2. Export data from just before the deletion: + ```bash + wrangler d1 time-travel export corates-db-prod \ + --timestamp "2026-01-19T14:25:00Z" \ + --output pre-deletion.sql \ + --env production + ``` +3. Review the export to find the project data +4. Either restore the entire database or selectively re-insert the project + +**Note:** If the project had associated R2 files (PDFs), those may not be recoverable as R2 does not have Time Travel. + +#### Scenario 2: Bad Migration Rollback + +A migration causes data corruption or unexpected behavior. + +**Steps:** + +1. Identify the timestamp just before the migration was applied +2. Restore to that point: + ```bash + wrangler d1 time-travel restore corates-db-prod \ + --timestamp "2026-01-19T09:00:00Z" \ + --env production + ``` +3. Fix the migration in code +4. Redeploy with the corrected migration + +**Important:** After restoring, you may need to redeploy the workers to ensure schema compatibility. + +#### Scenario 3: Data Corruption Investigation + +You suspect data was corrupted but need to investigate before restoring. + +**Steps:** + +1. Export data from multiple points in time: + + ```bash + # Export from yesterday + wrangler d1 time-travel export corates-db-prod \ + --timestamp "2026-01-18T00:00:00Z" \ + --output yesterday.sql \ + --env production + + # Export from current + wrangler d1 export corates-db-prod \ + --output current.sql \ + --env production + ``` + +2. Compare the exports to identify what changed +3. Decide whether to restore or manually fix + +### Pre-Deployment Backup Procedure + +Before risky deployments (migrations, major updates), create a manual bookmark: + +```bash +# Create a bookmark before deployment +wrangler d1 time-travel bookmark corates-db-prod \ + --description "Pre-deployment backup 2026-01-19" \ + --env production +``` + +This creates a named restore point you can easily reference later. + +### Limitations + +- **R2 files are not covered:** Time Travel only applies to D1. PDFs and avatars in R2 cannot be restored. +- **Durable Objects are not covered:** Y.js state in ProjectDoc Durable Objects has no Time Travel. +- **Restore is database-wide:** You cannot restore individual tables or rows; the entire database is restored. +- **Workers must be compatible:** After restoring to an older schema, ensure your deployed workers are compatible. + +### Emergency Contacts + +For D1 issues beyond Time Travel capabilities: + +- Cloudflare Support (if on paid plan) +- Cloudflare Discord community + ## Related Guides - [Organizations Guide](/guides/organizations) - For org model and membership patterns diff --git a/packages/docs/plans/backend-typescript.md b/packages/docs/plans/backend-typescript.md deleted file mode 100644 index 6350efc62..000000000 --- a/packages/docs/plans/backend-typescript.md +++ /dev/null @@ -1,74 +0,0 @@ -⏺ Backend Assessment Report - -Current State Summary -┌───────────────────────────┬───────────────────────────────────────────┐ -│ Metric │ Status │ -├───────────────────────────┼───────────────────────────────────────────┤ -│ TypeScript coverage │ 65.5% (74/113 files) │ -├───────────────────────────┼───────────────────────────────────────────┤ -│ Route files migrated │ 17.8% (8/45 files) │ -├───────────────────────────┼───────────────────────────────────────────┤ -│ Command pattern adoption │ Partial (projects, members, some billing) │ -├───────────────────────────┼───────────────────────────────────────────┤ -│ Duplicated code instances │ 23+ (validation hooks across routes) │ -└───────────────────────────┴───────────────────────────────────────────┘ - ---- - -Key Findings - -1. Files Still Requiring Migration (39 .js files) - -Critical path blockers: - -- src/index.js - Main entry point -- src/docs.js - Documentation endpoint - -Largest files needing attention: - -- /routes/account-merge.js (22K lines) -- /routes/billing/webhooks.js (12K lines) -- /routes/google-drive.js (15K lines) -- /routes/projects.js (14K lines) - -2. Duplicated Validation Hook Pattern - -23 route files contain identical defaultHook implementations (~50 lines each). This should be extracted to /lib/honoValidationHook.ts. - -3. Command Pattern Status - -Already using commands: - -- /routes/orgs/members.js - uses addMember, removeMember, updateMemberRole -- /routes/orgs/projects.js - uses createProject, updateProject, deleteProject -- /routes/billing/checkout.ts - uses createSingleProjectCheckout - -Need command extraction: - -- Billing webhook handlers (9 files in /routes/billing/handlers/) -- Admin operations (users, billing, orgs) -- Account merge logic -- Invitation flows - ---- - -Recommended Priority Order - -Phase 1: Foundation (First) - -1. Extract shared validation hook to /lib/honoValidationHook.ts -2. Migrate index.js to TypeScript -3. Migrate docs.js to TypeScript - -Phase 2: Command Pattern Expansion - -1. Extract billing event handlers to commands -2. Create admin commands (users, billing, orgs) -3. Create account merge command - -Phase 3: Route Migration - -1. Admin routes (6 files) -2. Billing routes (9+ files) -3. Org routes (6 files) -4. Utility routes (remaining) diff --git a/packages/docs/plans/project-creation-wizard.md b/packages/docs/plans/project-creation-wizard.md deleted file mode 100644 index 2d6fe9aad..000000000 --- a/packages/docs/plans/project-creation-wizard.md +++ /dev/null @@ -1,487 +0,0 @@ -# Plan: Project Creation Wizard - -**Status:** Draft -**Created:** 2026-01-18 -**Last Updated:** 2026-01-18 - ---- - -## Overview - -Project creation uses a progressive setup flow where the project is created immediately (Step 1), then the user is guided through optional configuration steps (Team, Studies, Assignment). Each step uses the same component that appears in project management, ensuring consistent UX and no duplicated code. - -Users can exit the wizard at any point - the project exists and can be configured later through the standard project UI. - -## Architecture Principle - -**One component, two contexts.** Each configuration panel (Team, Studies, Assignment) is a standalone component that: - -- Renders identically in the wizard flow and in project settings -- Operates on a real project (not wizard state) -- Saves changes immediately (no "submit all at once") - -The wizard is simply a guided wrapper that presents these components in sequence with a stepper UI. - -## Prerequisites - -- Existing team/organization membership system -- Study import infrastructure (PDF upload, reference parsing) -- Google Drive integration for PDF import -- DOI/PMID metadata lookup service - -## Goals - -1. Guide new users through complete project setup with a stepper UI -2. Allow experienced users to skip wizard and configure manually -3. Reuse components between wizard and project management (no duplication) -4. Enable flexible, incremental project configuration -5. Reduce time-to-first-review while remaining non-prescriptive - -## Non-Goals - -- Forcing users through the wizard (it's optional guidance) -- Atomic/transactional project creation (project exists after step 1) -- Draft projects (project is real immediately) -- Bulk project creation or templates (future enhancement) - ---- - -## User Flows - -### Flow A: Guided Wizard (New Users) - -``` -Create Project → [Project Created] → Team Panel → Studies Panel → Assignment Panel → Done - ↓ ↓ ↓ - (can exit) (can exit) (can exit) -``` - -### Flow B: Manual Setup (Experienced Users) - -``` -Create Project → [Project Created] → Exit to Project View → Configure via Settings/Tabs -``` - -### Flow C: Hybrid - -``` -Create Project → Team Panel → Exit → (later) → Studies Tab → Add Studies -``` - -All flows result in the same project state - the wizard is just one path to get there. - ---- - -## Component Architecture - -### Shared Panels - -These components are used in both the wizard and project management: - -| Component | Wizard Context | Management Context | -| -------------------- | ---------------- | -------------------------------- | -| `TeamPanel` | Step 2 of wizard | Project Settings > Team | -| `StudiesImportPanel` | Step 3 of wizard | Studies Tab > Add Studies button | -| `AssignmentPanel` | Step 4 of wizard | Studies Tab > Manage Assignments | - -Each panel: - -- Receives `projectId` as prop -- Fetches/mutates project data directly -- Has no wizard-specific logic -- Can be used standalone or within wizard wrapper - -### Wizard Wrapper - -```tsx - - - {/* Only used here, creates project */} - - - - - - - - - - - -``` - -The wrapper provides: - -- Stepper UI showing progress -- Next/Back/Skip navigation -- "Exit to Project" option on every step -- Completion celebration on final step - ---- - -## Step Specifications - -### Step 1: Create Project - -**Purpose:** Create the project shell. This is the only wizard-specific step. - -**Fields:** - -| Field | Type | Required | Notes | -| --------------- | ------------ | -------- | ------------------------------------ | -| Project Name | text | Yes | 3-100 characters | -| Description | textarea | No | Optional, supports markdown | -| Checklist Types | multi-select | Yes | One or more: AMSTAR2, ROBINS-I, etc. | -| Registration ID | text | No | PROSPERO or other registry ID | - -**Behavior:** - -- On submit, project is created via API -- User becomes Owner automatically -- Wizard advances to Step 2 with real `projectId` - -**Validation:** - -- Name must be unique within the organization -- At least one checklist type must be selected - -**UI Notes:** - -- Clean, focused form -- Checklist type selector shows brief description of each option -- No "skip" option - project must be created to proceed - ---- - -### Step 2: Team Panel - -**Purpose:** Add team members and assign roles. - -**This is the same `TeamPanel` component used in Project Settings > Team.** - -**Roles:** - -| Role | Permissions | -| ------ | ------------------------------------------------------------------ | -| Owner | Full access: manage team, reconcile, edit settings, delete project | -| Member | Can review assigned studies, view all project data | - -**Features:** - -1. **Add Members** - - Email input with role selector (Owner/Member) - - Autocomplete for organization members - - Batch add support (comma-separated) - - Invites sent immediately when added - -2. **Member List** - - Shows all members with role badges - - Pending invite indicator for non-org members - - Role change dropdown - - Remove button - -3. **Wizard-Specific UI** - - "Skip for now" button (can add members later) - - Helper text: "You can always add more team members later" - -**Validation:** - -- Warning if fewer than 2 members for dual-review workflow -- Warning is non-blocking (user can proceed) - ---- - -### Step 3: Studies Import Panel - -**Purpose:** Import studies from various sources. - -**This is the same `StudiesImportPanel` component used in Studies Tab > Add Studies.** - -#### Import Sources (Tabs) - -**Tab: PDF Upload** - -- Drag-and-drop zone for PDF files -- Multi-file upload support -- Extracts metadata from PDF (title, authors, DOI if available) -- Progress indicator for uploads -- Studies created immediately as uploads complete - -**Tab: DOI/PMID Lookup** - -- Text input for DOI or PMID (one per line or comma-separated) -- "Lookup" button fetches metadata from CrossRef/PubMed -- Results shown with confirm/add action -- Option to attach PDF after lookup - -**Tab: Reference File** - -- Upload RIS, BibTeX, EndNote XML, or CSV -- Parser extracts all references -- Preview table before import -- Field mapping UI for CSV -- Bulk add to project - -**Tab: Google Drive** - -- "Connect Google Drive" button (OAuth if not connected) -- Folder browser for selecting PDFs -- Multi-select with "Import Selected" button -- PDFs downloaded to CoRATES storage on import - -#### Deduplication - -- Automatic detection on import (exact DOI, fuzzy title match) -- Warning shown before adding duplicate -- User chooses: "Add anyway" or "Skip" - -#### PDF Matching - -- For references without PDFs, suggest matches from uploaded PDFs -- Auto-match by DOI or title similarity -- Manual drag-drop matching - -#### Primary vs Secondary PDFs - -- Per-study PDF list with drag to reorder -- First position = primary (shown in viewer) -- Secondary PDFs available as attachments - -**Wizard-Specific UI:** - -- "Skip for now" button -- Counter showing studies added this session -- Helper text: "You can import more studies anytime from the Studies tab" - ---- - -### Step 4: Assignment Panel - -**Purpose:** Configure and run reviewer assignment. - -**This is the same `AssignmentPanel` component used in Studies Tab > Manage Assignments.** - -#### Assignment Strategy - -| Strategy | Description | -| ----------- | ---------------------------------------------- | -| Random | Randomly assign members, balanced distribution | -| Round-Robin | Cycle through members in order | -| Manual | Assign individually per study | - -#### Configuration Options - -| Option | Type | Default | Notes | -| ------------------- | -------- | ------- | ----------------------------------------- | -| Reviewers per Study | number | 2 | Standard dual-review; 1 for single-review | -| Include Owners | checkbox | true | Whether owners are in assignment pool | -| Balanced Load | checkbox | true | Ensure equal distribution | - -#### Assignment Preview - -Before applying, show: - -**Summary Table:** - -``` -Member | Assigned | Percentage ---------------------|----------|------------ -sarah@example.com | 12 | 50% -michael@example.com | 12 | 50% -``` - -**Per-Study View:** - -- Table showing each study and assigned members -- Click to manually adjust -- "Regenerate" button to re-randomize - -**Actions:** - -- "Apply Assignments" - saves to project -- "Clear All" - removes all assignments - -**Wizard-Specific UI:** - -- "Skip for now" button -- Disabled state if no studies or fewer than 2 members -- Helper text explaining why assignment is disabled (if applicable) - ---- - -### Wizard Completion - -After Step 4 (or when user clicks "Finish" on any step): - -- Show completion message with summary -- "Go to Project" button -- Optional: brief tour of project view highlighting where to find each panel - ---- - -## Technical Details - -### No Wizard State - -Since each step operates on the real project, there's no wizard-specific state to manage. The only "state" is: - -- Current step number (URL param or local state) -- Project ID (from step 1 creation) - -```typescript -// Wizard just tracks position, not data -interface WizardNavState { - projectId: string; - currentStep: 1 | 2 | 3 | 4; -} -``` - -### Panel Props Interface - -Each shared panel follows this pattern: - -```typescript -interface PanelProps { - projectId: string; - // Optional wizard context - wizardMode?: boolean; // Enables "Skip" button, helper text - onComplete?: () => void; // Called when user clicks "Continue" in wizard -} -``` - -### API Endpoints - -Panels use existing project APIs where possible: - -| Panel | Endpoints Used | -| ------------------ | ---------------------------------------------------------------------------------- | -| TeamPanel | `POST /projects/:id/members`, `DELETE /projects/:id/members/:memberId` | -| StudiesImportPanel | `POST /projects/:id/studies`, `POST /api/lookup-doi`, `POST /api/parse-references` | -| AssignmentPanel | `POST /projects/:id/assignments`, `GET /projects/:id/assignment-preview` | - -**New endpoints needed:** - -| Method | Path | Purpose | -| ------ | ---------------------------------- | ------------------------------------------ | -| POST | `/api/lookup-doi` | Fetch metadata for DOI/PMID batch | -| POST | `/api/parse-references` | Parse uploaded reference file | -| POST | `/api/extract-pdf-metadata` | Extract metadata from PDF | -| POST | `/projects/:id/assignment-preview` | Generate assignment preview without saving | - -### PDF Handling - -PDFs are uploaded directly to permanent storage: - -1. User uploads PDF -2. PDF stored in R2: `projects/{projectId}/studies/{studyId}/{filename}` -3. Metadata extracted and study created -4. No temp storage needed (project exists) - -For Google Drive: - -1. User selects files -2. Backend downloads from Drive to R2 -3. Study created with PDF reference - ---- - -## UI/UX Considerations - -### Wizard Stepper - -Horizontal stepper at top: - -``` -[1. Create] ---- [2. Team] ---- [3. Studies] ---- [4. Assignment] - done current o o -``` - -- Completed steps show checkmark -- Current step highlighted -- Future steps shown but not clickable -- "Exit to Project" link always visible - -### Navigation - -Each step has: - -- "Back" button (except step 1) -- "Continue" / "Skip" buttons -- "Exit to Project" link - -Browser back button navigates wizard steps (not browser history). - -### Empty States - -Each panel handles empty state gracefully: - -- Team: "No members yet. Add team members to enable dual-review." -- Studies: "No studies yet. Import studies to get started." -- Assignment: "Add studies and team members before assigning reviewers." - -### Responsive Design - -- Desktop: Full panel width, side help text -- Tablet: Stacked layout -- Mobile: Full-screen panels, simplified import (PDF upload + DOI primarily) - ---- - -## Component Reuse Matrix - -| Component | Wizard Step 2 | Wizard Step 3 | Wizard Step 4 | Project Settings | Studies Tab | -| ------------------ | ------------- | ------------- | ------------- | ---------------- | ----------- | -| TeamPanel | X | | | X | | -| StudiesImportPanel | | X | | | X | -| AssignmentPanel | | | X | | X | - ---- - -## Success Criteria - -Before considering this plan complete: - -- [ ] Project creation (Step 1) functional -- [ ] TeamPanel works in both wizard and settings context -- [ ] StudiesImportPanel works in both wizard and studies tab context -- [ ] AssignmentPanel works in both wizard and studies tab context -- [ ] All import methods functional (PDF, DOI, reference file, Google Drive) -- [ ] Deduplication detection working -- [ ] PDF matching (auto and manual) implemented -- [ ] Assignment strategies implemented with preview -- [ ] Wizard navigation (next/back/skip/exit) working -- [ ] Responsive design for all panels -- [ ] Tests for shared panel components -- [ ] Tests for assignment algorithm - ---- - -## Resolved Questions - -1. **Draft Projects:** No. Project is created immediately in Step 1. - -2. **Study Limit:** No limits for initial implementation. - -3. **Checklist Selection:** Multiple checklist types allowed per project. - -4. **Roles:** Two roles - "Owner" and "Member". - -5. **Post-Creation Editing:** All wizard steps map to management UI. Same components, same capabilities. - -6. **Google Drive:** One-time import. PDFs downloaded to CoRATES storage. - -7. **Reference Manager Integration:** File import only (RIS, BibTeX, EndNote). No direct API integration. - -8. **Invite Timing:** Invites sent immediately when members are added (project exists). - -9. **Wizard Flexibility:** Users can skip steps, exit early, or bypass wizard entirely. Wizard is guidance, not enforcement. - ---- - -## Related Documents - -- [State Management Guide](../guides/state-management.md) - For panel state patterns -- [PDF Handling](../../.cursor/rules/pdf-handling.mdc) - PDF upload and storage patterns -- [Authentication Guide](../guides/authentication.md) - For Google OAuth (Drive integration) -- [API Development Guide](../guides/api-development.md) - For endpoint patterns -- [Add Studies Mocks](../../web/src/components/mocks/) - Existing UI explorations diff --git a/packages/docs/plans/study-counts-architecture.md b/packages/docs/plans/study-counts-architecture.md deleted file mode 100644 index d4d77b40e..000000000 --- a/packages/docs/plans/study-counts-architecture.md +++ /dev/null @@ -1,261 +0,0 @@ -# Study Counts Architecture - -## Problem - -The dashboard shows "0/0 studies" for all projects because: - -1. Studies are stored in Yjs durable objects (`ProjectDoc`), not SQL -2. The `/api/users/me/projects` endpoint only returns SQL data (no study counts) -3. Getting accurate counts requires connecting to each ProjectDoc - -## Solution Overview - -Two-tier approach: - -1. **Lazy Load (Authoritative)**: Fetch true counts when user opens a project via the existing Yjs WebSocket connection -2. **Notifications (Preview)**: Push approximate counts to dashboard for quick previews (can be stale, max 50 queued) - ---- - -## Phase 1: Lazy Load (Authoritative Source) - -When a user opens a project, they connect to `ProjectDoc` via WebSocket. At that point, we have access to the Yjs document and can compute accurate study counts. - -### 1.1 Add Stats Computation to ProjectDoc - -**File**: `packages/workers/src/durable-objects/ProjectDoc.js` - -Add a method to compute stats from the Yjs document: - -```javascript -getProjectStats() { - const reviews = this.doc.getMap('reviews'); - const studyCount = reviews.size; - - let completedCount = 0; - reviews.forEach((study) => { - // Check if study has completed status - // Structure depends on checklist type - need to verify - if (study.status === 'completed' || study.completed) { - completedCount++; - } - }); - - return { studyCount, completedCount }; -} -``` - -### 1.2 Send Stats on WebSocket Connect - -When a client connects to ProjectDoc, send stats as an initial message: - -```javascript -// In WebSocket connection handler -ws.send( - JSON.stringify({ - type: 'project-stats', - stats: this.getProjectStats(), - }), -); -``` - -### 1.3 Frontend: Store Stats in Project Store - -**File**: `packages/web/src/stores/projectStore.js` - -Add stats storage: - -```javascript -// Add to store -projectStats: {}, // { [projectId]: { studyCount, completedCount, lastUpdated } } - -setProjectStats(projectId, stats) { - setStore('projectStats', projectId, { - ...stats, - lastUpdated: Date.now() - }); -} -``` - -### 1.4 Frontend: Handle Stats Message in useProject - -**File**: `packages/web/src/primitives/useProject.js` - -Listen for `project-stats` message type: - -```javascript -// In WebSocket message handler -if (data.type === 'project-stats') { - projectStore.setProjectStats(projectId, data.stats); -} -``` - -### 1.5 Frontend: Update Stats on Study Changes - -When studies are added/removed locally, update the cached stats: - -```javascript -// After adding a study -const currentStats = projectStore.projectStats[projectId]; -projectStore.setProjectStats(projectId, { - studyCount: currentStats.studyCount + 1, - completedCount: currentStats.completedCount, -}); -``` - -### 1.6 Dashboard: Display Cached Stats - -**File**: `packages/web/src/components/dashboard/ProjectCard.jsx` - -Read from projectStore for cards of previously-opened projects: - -```javascript -const cachedStats = () => projectStore.projectStats[props.project.id]; -const studyCount = () => cachedStats()?.studyCount ?? props.project.studyCount ?? 0; -const completedCount = () => cachedStats()?.completedCount ?? props.project.completedCount ?? 0; -``` - -Priority: `projectStore cache > props from API > 0` - ---- - -## Phase 2: Notifications (Preview/Hints) - -Notifications provide approximate stats for the dashboard before a user opens a project. These are secondary and may be incomplete (max 50 pending notifications, can be lost). - -### 2.1 Emit Stats Updates from ProjectDoc - -**File**: `packages/workers/src/durable-objects/ProjectDoc.js` - -When studies change, notify project members via their UserSession: - -```javascript -async broadcastStatsToMembers(env) { - const stats = this.getProjectStats(); - const members = await this.getProjectMembers(env); // Need to implement - - for (const member of members) { - const session = env.USER_SESSION.idFromName(member.userId); - await session.get().fetch('https://internal/notify', { - method: 'POST', - body: JSON.stringify({ - type: 'project-stats-updated', - projectId: this.projectId, - stats, - timestamp: Date.now() - }) - }); - } -} -``` - -### 2.2 Call Stats Broadcast on Study Changes - -Hook into Yjs observers to detect changes: - -```javascript -this.doc.getMap('reviews').observe((event) => { - // Debounce to avoid spamming on bulk imports - this.scheduleStatsBroadcast(); -}); - -scheduleStatsBroadcast() { - if (this.statsBroadcastTimer) return; - this.statsBroadcastTimer = setTimeout(() => { - this.broadcastStatsToMembers(this.env); - this.statsBroadcastTimer = null; - }, 5000); // 5 second debounce -} -``` - -### 2.3 Frontend: Handle Stats Notifications - -**File**: `packages/web/src/primitives/useMembershipSync.js` - -Add handler for stats updates: - -```javascript -if (notificationType === 'project-stats-updated') { - // Store as hint in projectStore (lower priority than lazy-loaded) - projectStore.setProjectStatsHint(notification.projectId, notification.stats); -} -``` - -### 2.4 API Enhancement (Optional) - -Could add stats hints to `/api/users/me/projects` response by: - -1. Storing last-known stats in SQL when ProjectDoc broadcasts -2. Joining that data in the projects query - -This avoids N+1 queries to ProjectDoc DOs at dashboard load time. - ---- - -## Data Flow Summary - -``` -Dashboard Load: - API returns projects (no stats or stale stats from SQL cache) - -> Show cached stats from projectStore if available - -> Show notification hints if available - -> Show "-- studies" if no data - -User Opens Project: - WebSocket connects to ProjectDoc - -> ProjectDoc sends project-stats message - -> Frontend stores authoritative stats in projectStore - -> Dashboard updates to show true counts - -Study Added/Removed (while in project): - Frontend updates local stats immediately - ProjectDoc broadcasts to other members (5s debounce) - -> Other members see updated hints on dashboard -``` - ---- - -## Implementation Order - -### Phase 1 Tasks (Lazy Load) - -1. [ ] Add `getProjectStats()` method to ProjectDoc -2. [ ] Send stats on WebSocket connect in ProjectDoc -3. [ ] Add `projectStats` to projectStore -4. [ ] Handle `project-stats` message in useProject -5. [ ] Update Dashboard/ProjectCard to read from projectStore -6. [ ] Update local stats on study add/remove - -### Phase 2 Tasks (Notifications) - -1. [ ] Add `broadcastStatsToMembers()` to ProjectDoc -2. [ ] Add Yjs observer with debounce for stats changes -3. [ ] Add `project-stats-updated` handler to useMembershipSync -4. [ ] Add `projectStatsHints` to projectStore (separate from authoritative) -5. [ ] Optional: Cache stats in SQL for API response - ---- - -## Files to Modify - -**Phase 1:** - -- `packages/workers/src/durable-objects/ProjectDoc.js` - Stats computation + WS message -- `packages/web/src/stores/projectStore.js` - Stats storage -- `packages/web/src/primitives/useProject.js` - Handle stats message -- `packages/web/src/components/dashboard/ProjectCard.jsx` - Display cached stats -- `packages/web/src/components/dashboard/Dashboard.jsx` - Stats aggregation - -**Phase 2:** - -- `packages/workers/src/durable-objects/ProjectDoc.js` - Stats broadcast -- `packages/web/src/primitives/useMembershipSync.js` - Handle notification -- `packages/web/src/stores/projectStore.js` - Stats hints storage - ---- - -## Open Questions - -1. **Study completion criteria**: What field indicates a study is "completed"? Need to check Yjs document structure. -2. **Member list access**: How does ProjectDoc get the list of project members? May need to query SQL or store in Yjs. -3. **Performance**: For projects with many members, broadcasting could be expensive. May want to limit or batch. diff --git a/packages/landing/wrangler.jsonc b/packages/landing/wrangler.jsonc index 5a13c1b62..230715f8b 100644 --- a/packages/landing/wrangler.jsonc +++ b/packages/landing/wrangler.jsonc @@ -19,6 +19,7 @@ "vars": { "VITE_BASEPATH": "/", "VITE_PUBLIC_APP_URL": "https://corates.org", - "VITE_API_URL": "https://corates.org" + "VITE_API_URL": "https://corates.org", + "VITE_SENTRY_DSN": "https://8ff0e3ce7f93715c9971b2c6518cf285@o4510738063818752.ingest.us.sentry.io/4510738383110144" } } diff --git a/packages/web/.env.example b/packages/web/.env.example index faefa3201..788c37c11 100644 --- a/packages/web/.env.example +++ b/packages/web/.env.example @@ -8,3 +8,10 @@ VITE_BASEPATH="/" # For Google Picker integration VITE_GOOGLE_PICKER_API_KEY="your-google-picker-api-key-here" VITE_GOOGLE_PICKER_APP_ID="your-google-project-number" + +# Sentry error monitoring (optional) +# Get DSN from https://sentry.io/settings/projects/YOUR_PROJECT/keys/ +VITE_SENTRY_DSN="" + +# Set to "true" to send errors to Sentry in development +VITE_SENTRY_DEV=false diff --git a/packages/web/package.json b/packages/web/package.json index e93c98c24..fb6ae45cf 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -47,6 +47,7 @@ "@embedpdf/plugin-view-manager": "^2.2.0", "@embedpdf/plugin-viewport": "^2.2.0", "@embedpdf/plugin-zoom": "^2.2.0", + "@sentry/solid": "^10.35.0", "@solid-primitives/scheduled": "^1.5.2", "@solidjs/router": "^0.15.4", "@tanstack/solid-query": "^5.90.22", diff --git a/packages/web/src/components/project/ProjectView.jsx b/packages/web/src/components/project/ProjectView.jsx index f1af34e86..8086365f6 100644 --- a/packages/web/src/components/project/ProjectView.jsx +++ b/packages/web/src/components/project/ProjectView.jsx @@ -16,6 +16,7 @@ import projectActionsStore from '@/stores/projectActionsStore'; import { useBetterAuth } from '@api/better-auth-store.js'; import { uploadPdf, deletePdf } from '@api/pdf-api.js'; import { cachePdf } from '@primitives/pdfCache.js'; +import { bestEffort } from '@lib/errorLogger.js'; import { importFromGoogleDrive } from '@api/google-drive.js'; import { showToast } from '@/components/ui/toast'; import { Tabs, TabsList, TabsTrigger, TabsContent, TabsIndicator } from '@/components/ui/tabs'; @@ -122,7 +123,12 @@ export default function ProjectView(props) { const arrayBuffer = new Uint8Array(pdf.data).buffer; uploadPdf(oid, params.projectId, studyId, arrayBuffer, pdf.fileName) .then(result => { - cachePdf(params.projectId, studyId, result.fileName, arrayBuffer).catch(console.warn); + bestEffort(cachePdf(params.projectId, studyId, result.fileName, arrayBuffer), { + operation: 'cachePdf (pending upload)', + projectId: params.projectId, + studyId, + fileName: result.fileName, + }); try { // Extract PDF metadata from pdf.metadata to pass to the PDF object const pdfMetadata = pdf.metadata || {}; @@ -142,7 +148,12 @@ export default function ProjectView(props) { } catch (metaErr) { console.error('Failed to add PDF metadata:', metaErr); // Clean up orphaned file - deletePdf(oid, params.projectId, studyId, result.fileName).catch(console.warn); + bestEffort(deletePdf(oid, params.projectId, studyId, result.fileName), { + operation: 'deletePdf (pending upload rollback)', + projectId: params.projectId, + studyId, + fileName: result.fileName, + }); } }) .catch(err => console.error('Error uploading PDF for new study:', err)); @@ -207,7 +218,12 @@ export default function ProjectView(props) { } catch (metaErr) { console.error('Failed to add PDF metadata:', metaErr); // Clean up orphaned file - deletePdf(oid, params.projectId, studyId, result.file.fileName).catch(console.warn); + bestEffort(deletePdf(oid, params.projectId, studyId, result.file.fileName), { + operation: 'deletePdf (Google Drive rollback)', + projectId: params.projectId, + studyId, + fileName: result.file.fileName, + }); } }) .catch(err => console.error('Error importing Google Drive file:', err)); diff --git a/packages/web/src/config/sentry.js b/packages/web/src/config/sentry.js new file mode 100644 index 000000000..227c333a7 --- /dev/null +++ b/packages/web/src/config/sentry.js @@ -0,0 +1,129 @@ +/** + * Sentry Configuration + * Frontend error monitoring setup with SolidJS integration + */ + +import * as Sentry from '@sentry/solid'; +import { solidRouterBrowserTracingIntegration } from '@sentry/solid/solidrouter'; +import { useBeforeLeave, useLocation } from '@solidjs/router'; + +const SENTRY_DSN = import.meta.env.VITE_SENTRY_DSN || ''; +const ENVIRONMENT = import.meta.env.MODE || 'development'; + +/** + * Initialize Sentry for frontend error monitoring + * Call this early in app initialization (before render) + */ +export function initSentry() { + // Only initialize if DSN is configured + if (!SENTRY_DSN) { + if (ENVIRONMENT === 'development') { + console.info('[Sentry] No DSN configured, skipping initialization'); + } + return; + } + + Sentry.init({ + dsn: SENTRY_DSN, + environment: ENVIRONMENT, + + integrations: [ + // Solid router integration for automatic route tracking + solidRouterBrowserTracingIntegration({ useBeforeLeave, useLocation }), + ], + + // Capture 100% of errors + // Adjust in production if volume is high + tracesSampleRate: ENVIRONMENT === 'production' ? 0.1 : 1.0, + + // Don't send PII by default + sendDefaultPii: false, + + // Filter out common noise + ignoreErrors: [ + // Browser extensions + /^chrome-extension:\/\//, + /^moz-extension:\/\//, + // Network errors that are usually transient + 'Network request failed', + 'Failed to fetch', + 'Load failed', + // ResizeObserver noise + 'ResizeObserver loop', + ], + + // Add context before sending + beforeSend(event) { + // Don't send in development unless explicitly enabled + if (ENVIRONMENT === 'development' && !import.meta.env.VITE_SENTRY_DEV) { + console.info('[Sentry] Would send event:', event); + return null; + } + return event; + }, + }); + + console.info('[Sentry] Initialized for', ENVIRONMENT); +} + +/** + * Capture an exception with optional context + */ +export function captureException(error, context = {}) { + if (!SENTRY_DSN) { + return; + } + + Sentry.captureException(error, { + tags: { + component: context.component, + action: context.action, + }, + extra: context, + }); +} + +/** + * Capture a message with optional context + */ +export function captureMessage(message, level = 'info', context = {}) { + if (!SENTRY_DSN) { + return; + } + + Sentry.captureMessage(message, { + level, + tags: { + component: context.component, + action: context.action, + }, + extra: context, + }); +} + +/** + * Set user context for error tracking + */ +export function setUser(user) { + if (!SENTRY_DSN) { + return; + } + + if (user) { + Sentry.setUser({ + id: user.id, + email: user.email, + }); + } else { + Sentry.setUser(null); + } +} + +/** + * Check if Sentry is enabled + */ +export function isSentryEnabled() { + return !!SENTRY_DSN; +} + +export { Sentry }; diff --git a/packages/web/src/lib/errorLogger.js b/packages/web/src/lib/errorLogger.js index 8362c39cb..bd0e57d62 100644 --- a/packages/web/src/lib/errorLogger.js +++ b/packages/web/src/lib/errorLogger.js @@ -19,6 +19,7 @@ */ import { normalizeError } from '@corates/shared'; +import { captureException, captureMessage, isSentryEnabled } from '@config/sentry.js'; /** * Log levels for categorizing messages @@ -46,7 +47,7 @@ function formatErrorData(error) { /** * Core logging function - * Handles both console output and future monitoring service integration + * Handles both console output and Sentry error monitoring */ function log(level, message, context = {}) { const timestamp = new Date().toISOString(); @@ -69,29 +70,18 @@ function log(level, message, context = {}) { console.info(`[Info] ${message}`, logData); } - // Future Sentry integration point - // When Sentry is configured, add integration here: - // - // if (typeof window !== 'undefined' && window.Sentry) { - // if (level === LogLevel.ERROR && context.error) { - // window.Sentry.captureException(context.error, { - // tags: { - // component: context.component, - // action: context.action, - // }, - // extra: context, - // }); - // } else { - // window.Sentry.captureMessage(message, { - // level: level === LogLevel.ERROR ? 'error' : level, - // tags: { - // component: context.component, - // action: context.action, - // }, - // extra: context, - // }); - // } - // } + // Send to Sentry if enabled + if (isSentryEnabled()) { + if (level === LogLevel.ERROR && context.error) { + // For errors, capture the original error object if available + const originalError = context.originalError || new Error(message); + captureException(originalError, context); + } else if (level === LogLevel.ERROR || level === LogLevel.WARNING) { + // For warnings and errors without an error object, capture as message + captureMessage(message, level, context); + } + // INFO level messages are not sent to Sentry to reduce noise + } } /** @@ -111,6 +101,8 @@ export function logError(error, context = {}) { log(LogLevel.ERROR, message, { ...context, error: errorData, + // Pass original error for Sentry stack trace + originalError: error instanceof Error ? error : new Error(errorData.message), }); } diff --git a/packages/web/src/main.jsx b/packages/web/src/main.jsx index 6a1f9a237..21745e20c 100644 --- a/packages/web/src/main.jsx +++ b/packages/web/src/main.jsx @@ -7,6 +7,10 @@ import AppErrorBoundary from './components/ErrorBoundary.jsx'; import { QueryClientProvider } from '@tanstack/solid-query'; import { queryClient } from '@lib/queryClient.js'; import { bestEffort } from '@lib/errorLogger.js'; +import { initSentry } from '@config/sentry.js'; + +// Initialize Sentry early, before any other code runs +initSentry(); // Clean up any expired form state entries from IndexedDB on app load bestEffort(cleanupExpiredStates(), { operation: 'cleanupExpiredStates' }); diff --git a/packages/web/src/stores/projectActionsStore/pdfs.js b/packages/web/src/stores/projectActionsStore/pdfs.js index c9abd22ac..2655ddbcf 100644 --- a/packages/web/src/stores/projectActionsStore/pdfs.js +++ b/packages/web/src/stores/projectActionsStore/pdfs.js @@ -4,6 +4,7 @@ import { uploadPdf, downloadPdf, deletePdf } from '@api/pdf-api.js'; import { cachePdf, removeCachedPdf, getCachedPdf } from '@primitives/pdfCache.js'; +import { bestEffort } from '@lib/errorLogger.js'; import { extractPdfDoi, extractPdfTitle } from '@/lib/pdfUtils.js'; import { fetchFromDOI } from '@/lib/referenceLookup.js'; import projectStore from '../projectStore.js'; @@ -85,7 +86,12 @@ export function createPdfActions( if (!data) { data = await downloadPdf(orgId, projectId, studyId, pdf.fileName); - await cachePdf(projectId, studyId, pdf.fileName, data).catch(console.warn); + await bestEffort(cachePdf(projectId, studyId, pdf.fileName, data), { + operation: 'cachePdf (view)', + projectId, + studyId, + fileName: pdf.fileName, + }); } pdfPreviewStore.setData(data); @@ -164,9 +170,12 @@ export function createPdfActions( } catch (err) { console.warn('Failed to convert file to ArrayBuffer:', err.message); } - cachePdf(projectId, studyId, uploadResult.fileName, arrayBuffer).catch(err => - console.warn('Failed to cache PDF:', err), - ); + bestEffort(cachePdf(projectId, studyId, uploadResult.fileName, arrayBuffer), { + operation: 'cachePdf (upload)', + projectId, + studyId, + fileName: uploadResult.fileName, + }); // Extract PDF metadata const pdfMetadata = await extractPdfMetadata(arrayBuffer); @@ -193,9 +202,12 @@ export function createPdfActions( } catch (err) { console.error('Error uploading PDF:', err); if (uploadResult?.fileName) { - deletePdf(orgId, projectId, studyId, uploadResult.fileName).catch(cleanupErr => - console.warn('Failed to clean up orphaned PDF:', cleanupErr), - ); + bestEffort(deletePdf(orgId, projectId, studyId, uploadResult.fileName), { + operation: 'deletePdf (upload rollback)', + projectId, + studyId, + fileName: uploadResult.fileName, + }); } throw err; } @@ -299,9 +311,12 @@ export function createPdfActions( let arrayBuffer = null; try { arrayBuffer = await downloadPdf(orgId, projectId, studyId, file.fileName); - cachePdf(projectId, studyId, file.fileName, arrayBuffer).catch(err => - console.warn('Failed to cache Google Drive PDF:', err), - ); + bestEffort(cachePdf(projectId, studyId, file.fileName, arrayBuffer), { + operation: 'cachePdf (Google Drive import)', + projectId, + studyId, + fileName: file.fileName, + }); } catch (downloadErr) { console.warn( 'Failed to download/cache Google Drive PDF for metadata extraction:', @@ -333,7 +348,12 @@ export function createPdfActions( ); } catch (err) { console.error('Failed to add Google Drive PDF metadata:', err); - deletePdf(orgId, projectId, studyId, file.fileName).catch(console.warn); + bestEffort(deletePdf(orgId, projectId, studyId, file.fileName), { + operation: 'deletePdf (Google Drive rollback)', + projectId, + studyId, + fileName: file.fileName, + }); throw err; } } diff --git a/packages/web/src/stores/projectActionsStore/studies.js b/packages/web/src/stores/projectActionsStore/studies.js index 8b6b67dcd..6ed3b6515 100644 --- a/packages/web/src/stores/projectActionsStore/studies.js +++ b/packages/web/src/stores/projectActionsStore/studies.js @@ -4,6 +4,7 @@ import { uploadPdf, fetchPdfViaProxy, downloadPdf, deletePdf } from '@api/pdf-api.js'; import { cachePdf, clearStudyCache } from '@primitives/pdfCache.js'; +import { bestEffort } from '@lib/errorLogger.js'; import { showToast } from '@/components/ui/toast'; import { importFromGoogleDrive } from '@api/google-drive.js'; import { extractPdfDoi, extractPdfTitle } from '@/lib/pdfUtils.js'; @@ -88,7 +89,12 @@ async function addPdfMetadataToStudy(ops, studyId, pdfInfo, orgId, projectId, ta } catch (err) { console.error('Failed to add PDF metadata:', err); // Rollback: delete the uploaded PDF - deletePdf(orgId, projectId, studyId, pdfInfo.fileName).catch(console.warn); + bestEffort(deletePdf(orgId, projectId, studyId, pdfInfo.fileName), { + operation: 'deletePdf (rollback)', + projectId, + studyId, + fileName: pdfInfo.fileName, + }); throw err; } } @@ -123,9 +129,12 @@ async function handleGoogleDrivePdf(ops, study, studyId, orgId, projectId, userI // Extract and apply metadata from the PDF try { const arrayBuffer = await downloadPdf(orgId, projectId, studyId, importedFile.fileName); - cachePdf(projectId, studyId, importedFile.fileName, arrayBuffer).catch(err => - console.warn('Failed to cache Google Drive PDF:', err), - ); + bestEffort(cachePdf(projectId, studyId, importedFile.fileName, arrayBuffer), { + operation: 'cachePdf (Google Drive)', + projectId, + studyId, + fileName: importedFile.fileName, + }); const metadataUpdates = await extractPdfMetadata(arrayBuffer, study.doi, study.title); if (importedFile.fileName) { @@ -173,9 +182,12 @@ async function fetchPdfFromUrl(study) { async function uploadAndAttachPdf(ops, pdfData, pdfFileName, studyId, orgId, projectId, userId) { try { const result = await uploadPdf(orgId, projectId, studyId, pdfData, pdfFileName); - cachePdf(projectId, studyId, result.fileName, pdfData).catch(err => - console.warn('Failed to cache PDF:', err), - ); + bestEffort(cachePdf(projectId, studyId, result.fileName, pdfData), { + operation: 'cachePdf', + projectId, + studyId, + fileName: result.fileName, + }); // Extract PDF metadata (title, DOI, and fetch DOI metadata if available) let pdfMetadata = {}; @@ -286,18 +298,21 @@ export function createStudyActions( if (pdfs.length > 0) { const deletePromises = pdfs.map(pdf => { if (!pdf?.fileName) return Promise.resolve(); - return deletePdf(orgId, projectId, studyId, pdf.fileName).catch(err => { - // Log but don't block - continue with other PDFs - console.warn(`Failed to delete PDF ${pdf.fileName} from R2:`, err); + return bestEffort(deletePdf(orgId, projectId, studyId, pdf.fileName), { + operation: 'deletePdf (study cleanup)', + projectId, + studyId, + fileName: pdf.fileName, }); }); await Promise.all(deletePromises); } // Clear all PDFs for this study from IndexedDB - await clearStudyCache(projectId, studyId).catch(err => { - // Log but don't block - continue with Y.js deletion - console.warn('Failed to clear study PDF cache from IndexedDB:', err); + await bestEffort(clearStudyCache(projectId, studyId), { + operation: 'clearStudyCache', + projectId, + studyId, }); // Finally, delete the study from Y.js diff --git a/packages/workers/package.json b/packages/workers/package.json index ee0b1d180..2c4f4ae59 100644 --- a/packages/workers/package.json +++ b/packages/workers/package.json @@ -34,6 +34,7 @@ "@cloudflare/workers-types": "^4.20260118.0", "@corates/shared": "workspace:*", "@hono/zod-openapi": "^1.2.0", + "@sentry/cloudflare": "^10.35.0", "better-auth": "^1.4.15", "drizzle-orm": "^0.45.1", "hono": "^4.11.4", diff --git a/packages/workers/src/auth/routes.ts b/packages/workers/src/auth/routes.ts index 40cdb3fbd..5a7547baa 100644 --- a/packages/workers/src/auth/routes.ts +++ b/packages/workers/src/auth/routes.ts @@ -54,6 +54,7 @@ auth.use('/sign-in/*', authRateLimit); auth.use('/sign-up/*', authRateLimit); auth.use('/forget-password/*', authRateLimit); auth.use('/reset-password/*', authRateLimit); +auth.use('/magic-link/*', authRateLimit); /** * GET /api/auth/session diff --git a/packages/workers/src/commands/members/addMember.ts b/packages/workers/src/commands/members/addMember.ts index 61ff0a98d..97fccd7b9 100644 --- a/packages/workers/src/commands/members/addMember.ts +++ b/packages/workers/src/commands/members/addMember.ts @@ -9,7 +9,7 @@ import { createDb } from '@/db/client'; import { projectMembers, projects, member } from '@/db/schema'; import { eq, and } from 'drizzle-orm'; import { createDomainError, PROJECT_ERRORS } from '@corates/shared'; -import { syncMemberToDO } from '@/commands/lib/doSync'; +import { syncMemberWithRetry } from '@/lib/syncWithRetry'; import { notifyUser, NotificationTypes } from '@/commands/lib/notifications'; import { checkCollaboratorQuota } from '@/lib/quotaTransaction'; import type { Env } from '@/types'; @@ -137,21 +137,17 @@ export async function addMember( console.error('Failed to send project membership notification:', err); } - // Sync member to DO - try { - await syncMemberToDO(env, projectId, 'add', { - userId: userToAdd.id, - role, - joinedAt: now.getTime(), - name: userToAdd.name, - email: userToAdd.email, - givenName: userToAdd.givenName, - familyName: userToAdd.familyName, - image: userToAdd.image, - }); - } catch (err) { - console.error('Failed to sync member to DO:', err); - } + // Sync member to DO with automatic retry + await syncMemberWithRetry(env, projectId, 'add', { + userId: userToAdd.id, + role, + joinedAt: now.getTime(), + name: userToAdd.name, + email: userToAdd.email, + givenName: userToAdd.givenName, + familyName: userToAdd.familyName, + image: userToAdd.image, + }); return { member: { diff --git a/packages/workers/src/commands/members/removeMember.ts b/packages/workers/src/commands/members/removeMember.ts index 189d1d4e9..59ebdf2c4 100644 --- a/packages/workers/src/commands/members/removeMember.ts +++ b/packages/workers/src/commands/members/removeMember.ts @@ -9,7 +9,7 @@ import { createDb } from '@/db/client'; import { projectMembers, projects } from '@/db/schema'; import { eq, and } from 'drizzle-orm'; import { createDomainError, PROJECT_ERRORS } from '@corates/shared'; -import { syncMemberToDO } from '@/commands/lib/doSync'; +import { syncMemberWithRetry } from '@/lib/syncWithRetry'; import { notifyUser, NotificationTypes } from '@/commands/lib/notifications'; import { getProjectMembership, requireSafeRemoval } from '@/policies'; import type { Env } from '@/types'; @@ -52,14 +52,8 @@ export async function removeMember( .delete(projectMembers) .where(and(eq(projectMembers.projectId, projectId), eq(projectMembers.userId, userId))); - // Sync member removal to DO - try { - await syncMemberToDO(env, projectId, 'remove', { - userId, - }); - } catch (err) { - console.error('Failed to sync member removal to DO:', err); - } + // Sync member removal to DO with automatic retry + await syncMemberWithRetry(env, projectId, 'remove', { userId }); // Send notification to removed user (if not self-removal) if (!isSelfRemoval) { diff --git a/packages/workers/src/commands/members/updateMemberRole.ts b/packages/workers/src/commands/members/updateMemberRole.ts index d2cb04324..1597fa807 100644 --- a/packages/workers/src/commands/members/updateMemberRole.ts +++ b/packages/workers/src/commands/members/updateMemberRole.ts @@ -7,7 +7,7 @@ import { createDb } from '@/db/client'; import { projectMembers } from '@/db/schema'; import { eq, and } from 'drizzle-orm'; -import { syncMemberToDO } from '@/commands/lib/doSync'; +import { syncMemberWithRetry } from '@/lib/syncWithRetry'; import { notifyUser, NotificationTypes } from '@/commands/lib/notifications'; import { requireSafeRoleChange } from '@/policies'; import { createDomainError, SYSTEM_ERRORS } from '@corates/shared'; @@ -52,15 +52,8 @@ export async function updateMemberRole( }); } - // Sync role update to DO - try { - await syncMemberToDO(env, projectId, 'update', { - userId, - role, - }); - } catch (err) { - console.error('Failed to sync member update to DO:', err); - } + // Sync role update to DO with automatic retry + await syncMemberWithRetry(env, projectId, 'update', { userId, role }); // Send notification to the user whose role was updated try { diff --git a/packages/workers/src/commands/projects/createProject.ts b/packages/workers/src/commands/projects/createProject.ts index d31d1e3b9..13d2a53f3 100644 --- a/packages/workers/src/commands/projects/createProject.ts +++ b/packages/workers/src/commands/projects/createProject.ts @@ -9,7 +9,7 @@ import { createDb } from '@/db/client'; import { projects, projectMembers, user } from '@/db/schema'; import { eq } from 'drizzle-orm'; -import { insertWithQuotaCheck } from '@/lib/quotaTransaction'; +import { insertWithQuotaCheck, type InsertRollbackMeta } from '@/lib/quotaTransaction'; import { syncProjectToDO } from '@/commands/lib/doSync'; import { createValidationError, VALIDATION_ERRORS } from '@corates/shared'; import type { Env } from '@/types'; @@ -74,12 +74,20 @@ export async function createProject( }), ]; + // Rollback metadata for race condition handling + // Array is processed in reverse order: projectMembers deleted first, then projects (FK constraint) + const rollbackMeta: InsertRollbackMeta[] = [ + { table: projects, idColumn: projects.id, id: projectId }, + { table: projectMembers, idColumn: projectMembers.id, id: memberId }, + ]; + const quotaResult = await insertWithQuotaCheck(db, { orgId, quotaKey: 'projects.max', countTable: projects, countColumn: projects.orgId, insertStatements, + rollbackMeta, }); if (!quotaResult.success) { diff --git a/packages/workers/src/index.ts b/packages/workers/src/index.ts index 7f182c27f..d36cf43da 100644 --- a/packages/workers/src/index.ts +++ b/packages/workers/src/index.ts @@ -7,6 +7,7 @@ import { OpenAPIHono } from '@hono/zod-openapi'; import type { Context } from 'hono'; import type { ContentfulStatusCode } from 'hono/utils/http-status'; +import * as Sentry from '@sentry/cloudflare'; import { UserSession } from './durable-objects/UserSession'; import { ProjectDoc } from './durable-objects/ProjectDoc'; import { EmailQueue } from './durable-objects/EmailQueue'; @@ -367,4 +368,18 @@ app.notFound(c => { // Global error handler - catches all uncaught errors in routes app.onError(errorHandler); -export default app; +// Wrap with Sentry for error monitoring (only if DSN is configured) +export default Sentry.withSentry( + (env: Env) => ({ + dsn: env.SENTRY_DSN || '', + release: env.CF_VERSION_METADATA?.id, + environment: env.ENVIRONMENT, + // Only enable if DSN is set + enabled: !!env.SENTRY_DSN, + // Capture 100% of errors + tracesSampleRate: env.ENVIRONMENT === 'production' ? 0.1 : 1.0, + // Add request data to error reports + sendDefaultPii: true, + }), + app, +); diff --git a/packages/workers/src/lib/quotaTransaction.ts b/packages/workers/src/lib/quotaTransaction.ts index 1c28d9b08..ada17cc1b 100644 --- a/packages/workers/src/lib/quotaTransaction.ts +++ b/packages/workers/src/lib/quotaTransaction.ts @@ -92,12 +92,29 @@ export async function checkQuotaForInsert( return { allowed: true, used, limit }; } +/** + * Metadata for rollback in case of race condition + * Each entry represents one inserted record that can be rolled back + */ +export interface InsertRollbackMeta { + table: SQLiteTable; + idColumn: SQLiteColumn; + id: string; +} + interface InsertWithQuotaOptions { orgId: string; quotaKey: string; countTable: SQLiteTable; countColumn: SQLiteColumn; insertStatements: unknown[]; + /** + * Optional rollback metadata for race condition handling. + * If provided and a race condition is detected (post-insert count exceeds limit), + * these records will be deleted and an error returned. + * Order matters: records are deleted in reverse order to respect FK constraints. + */ + rollbackMeta?: InsertRollbackMeta[]; } interface InsertWithQuotaResult { @@ -109,7 +126,7 @@ export async function insertWithQuotaCheck( db: Database, options: InsertWithQuotaOptions, ): Promise { - const { orgId, quotaKey, countTable, countColumn, insertStatements } = options; + const { orgId, quotaKey, countTable, countColumn, insertStatements, rollbackMeta } = options; let orgBilling; try { @@ -217,9 +234,50 @@ export async function insertWithQuotaCheck( const newCount = verifyResult?.count || 0; if (newCount > limit) { - console.warn( + // Race condition detected - quota exceeded after insert + console.error( `[QuotaTransaction] Race condition detected: ${quotaKey} exceeded for org ${orgId}. ` + - `Count: ${newCount}, Limit: ${limit}. Consider rollback or manual intervention.`, + `Count: ${newCount}, Limit: ${limit}. Attempting rollback.`, + ); + + // If rollback metadata provided, delete the inserted records + if (rollbackMeta && rollbackMeta.length > 0) { + // Delete in reverse order to respect FK constraints (e.g., projectMembers before projects) + for (let i = rollbackMeta.length - 1; i >= 0; i--) { + const { table, idColumn, id } = rollbackMeta[i]; + try { + await db.delete(table).where(eq(idColumn, id)); + } catch (deleteErr) { + // Log but continue - best effort rollback + console.error( + `[QuotaTransaction] Failed to rollback record during race condition cleanup:`, + { table: table._.name, id, error: deleteErr }, + ); + } + } + + return { + success: false, + error: createDomainError( + AUTH_ERRORS.FORBIDDEN, + { + reason: 'quota_exceeded_race', + quotaKey, + used: newCount, + limit, + requested: 1, + retryable: true, + }, + `Quota exceeded due to concurrent request: ${quotaKey}. ` + + `Current usage: ${newCount}, Limit: ${limit}. Please retry your request.`, + ), + }; + } + + // No rollback metadata - log warning but allow (legacy behavior for backward compatibility) + console.warn( + `[QuotaTransaction] No rollback metadata provided. ` + + `Over-quota records will remain. Consider providing rollbackMeta.`, ); } diff --git a/packages/workers/src/lib/retry.ts b/packages/workers/src/lib/retry.ts new file mode 100644 index 000000000..045485fe9 --- /dev/null +++ b/packages/workers/src/lib/retry.ts @@ -0,0 +1,170 @@ +/** + * Generic retry utility with exponential backoff and structured logging + * + * Use this utility to wrap operations that may fail transiently (network errors, + * temporary unavailability, etc.) and should be retried automatically. + */ + +import type { Logger } from './observability/logger'; + +export interface RetryOptions { + /** The async operation to retry */ + operation: () => Promise; + /** Maximum number of attempts (default: 3) */ + maxAttempts?: number; + /** Initial delay in milliseconds before first retry (default: 100) */ + initialDelayMs?: number; + /** Maximum delay in milliseconds between retries (default: 5000) */ + maxDelayMs?: number; + /** Multiplier for exponential backoff (default: 2) */ + backoffMultiplier?: number; + /** Custom predicate to determine if error is retryable (default: always retry) */ + shouldRetry?: (_error: unknown, _attempt: number) => boolean; + /** Logger for structured logging of retry attempts */ + logger?: Logger; + /** Name of operation for logging (default: 'operation') */ + operationName?: string; +} + +export interface RetryResult { + /** Whether the operation succeeded */ + success: boolean; + /** The returned value on success */ + value?: T; + /** The final error if all attempts failed */ + error?: unknown; + /** Number of attempts made */ + attempts: number; +} + +const DEFAULT_MAX_ATTEMPTS = 3; +const DEFAULT_INITIAL_DELAY_MS = 100; +const DEFAULT_MAX_DELAY_MS = 5000; +const DEFAULT_BACKOFF_MULTIPLIER = 2; + +/** + * Executes an async operation with automatic retry on failure + * + * @example + * ```typescript + * const result = await withRetry({ + * operation: () => fetchExternalApi(), + * maxAttempts: 3, + * initialDelayMs: 100, + * logger, + * operationName: 'fetch-api', + * }); + * + * if (!result.success) { + * console.error('Operation failed after retries', result.error); + * } + * ``` + */ +export async function withRetry(options: RetryOptions): Promise> { + const { + operation, + maxAttempts = DEFAULT_MAX_ATTEMPTS, + initialDelayMs = DEFAULT_INITIAL_DELAY_MS, + maxDelayMs = DEFAULT_MAX_DELAY_MS, + backoffMultiplier = DEFAULT_BACKOFF_MULTIPLIER, + shouldRetry = defaultShouldRetry, + logger, + operationName = 'operation', + } = options; + + let lastError: unknown; + + for (let attempt = 1; attempt <= maxAttempts; attempt++) { + try { + const value = await operation(); + + // Log success after retries (not on first attempt) + if (attempt > 1 && logger) { + logger.info(`${operationName} succeeded after retries`, { + attempt, + totalAttempts: attempt, + }); + } + + return { success: true, value, attempts: attempt }; + } catch (error) { + lastError = error; + + const errorMessage = error instanceof Error ? error.message : String(error); + const willRetry = attempt < maxAttempts && shouldRetry(error, attempt); + + if (!willRetry) { + // Final failure - log at error level + if (logger) { + logger.error(`${operationName} failed after ${attempt} attempt(s)`, { + attempts: attempt, + maxAttempts, + error: errorMessage, + willRetry: false, + }); + } + return { success: false, error: lastError, attempts: attempt }; + } + + // Calculate delay with exponential backoff and jitter + const delayMs = calculateDelay(attempt, { + initialDelayMs, + maxDelayMs, + backoffMultiplier, + }); + + // Log retry attempt at warn level + if (logger) { + logger.warn(`${operationName} failed, retrying`, { + attempt, + maxAttempts, + nextDelayMs: delayMs, + error: errorMessage, + }); + } + + await sleep(delayMs); + } + } + + // Should never reach here, but TypeScript needs it + return { success: false, error: lastError, attempts: maxAttempts }; +} + +/** + * Calculate delay with exponential backoff and jitter + * Jitter helps prevent thundering herd when multiple operations fail simultaneously + */ +function calculateDelay( + attempt: number, + config: { + initialDelayMs: number; + maxDelayMs: number; + backoffMultiplier: number; + }, +): number { + // Exponential backoff: delay = initial * multiplier^(attempt-1) + const exponential = Math.min( + config.initialDelayMs * Math.pow(config.backoffMultiplier, attempt - 1), + config.maxDelayMs, + ); + + // Add 0-20% jitter to prevent thundering herd + const jitter = exponential * 0.2 * Math.random(); + + return Math.round(exponential + jitter); +} + +/** + * Default retry predicate - always retry unless we've exhausted attempts + */ +function defaultShouldRetry(_error: unknown, _attempt: number): boolean { + return true; +} + +/** + * Simple sleep utility + */ +function sleep(ms: number): Promise { + return new Promise(resolve => setTimeout(resolve, ms)); +} diff --git a/packages/workers/src/lib/syncWithRetry.ts b/packages/workers/src/lib/syncWithRetry.ts new file mode 100644 index 000000000..a473bd109 --- /dev/null +++ b/packages/workers/src/lib/syncWithRetry.ts @@ -0,0 +1,161 @@ +/** + * Retry wrappers for Durable Object sync operations + * + * These functions wrap the DO sync calls with automatic retry logic. + * They handle transient failures gracefully without blocking the main operation, + * since D1 is the source of truth and DO state will eventually sync on reconnect. + */ + +import { syncMemberToDO, syncProjectToDO } from './project-sync'; +import { withRetry } from './retry'; +import { createLogger, type Logger } from './observability/logger'; +import type { Env } from '../types'; + +interface MemberData { + userId: string; + role?: string; + joinedAt?: number; + name?: string | null; + email?: string | null; + givenName?: string | null; + familyName?: string | null; + image?: string | null; + [key: string]: unknown; +} + +interface ProjectMeta { + name?: string; + description?: string | null; + updatedAt?: number; + createdAt?: number; + [key: string]: unknown; +} + +interface ProjectMember { + userId: string; + role: string; + [key: string]: unknown; +} + +/** Retry configuration for DO sync operations */ +const DO_SYNC_RETRY_CONFIG = { + maxAttempts: 3, + initialDelayMs: 100, + maxDelayMs: 5000, + backoffMultiplier: 2, +} as const; + +/** + * Sync member to Durable Object with automatic retry + * + * This function wraps `syncMemberToDO` with retry logic. If all retries fail, + * the error is logged but not thrown - the D1 write has already succeeded + * and the DO will sync on next client connection. + * + * @example + * ```typescript + * await syncMemberWithRetry(env, projectId, 'add', { + * userId: user.id, + * role: 'member', + * joinedAt: Date.now(), + * }); + * ``` + */ +export async function syncMemberWithRetry( + env: Env, + projectId: string, + action: 'add' | 'update' | 'remove', + memberData: MemberData, + logger?: Logger, +): Promise { + const log = logger || createLogger({ service: 'sync-member', env }); + + const syncLogger = log.child({ + projectId, + operation: `member-${action}`, + userId: memberData.userId, + }); + + const result = await withRetry({ + operation: () => syncMemberToDO(env, projectId, action, memberData), + ...DO_SYNC_RETRY_CONFIG, + shouldRetry: shouldRetryDOSync, + logger: syncLogger, + operationName: `DO sync-member (${action})`, + }); + + if (!result.success) { + // Log final failure but don't throw - D1 is source of truth + // DO will sync on next client connection + syncLogger.error('DO member sync exhausted all retries', { + attempts: result.attempts, + finalError: result.error instanceof Error ? result.error.message : String(result.error), + }); + } +} + +/** + * Sync project metadata and members to Durable Object with automatic retry + * + * Similar to `syncMemberWithRetry` but for full project sync operations. + * Used when creating projects or performing bulk updates. + */ +export async function syncProjectWithRetry( + env: Env, + projectId: string, + meta: ProjectMeta | null, + members: ProjectMember[] | null, + logger?: Logger, +): Promise { + const log = logger || createLogger({ service: 'sync-project', env }); + + const syncLogger = log.child({ + projectId, + operation: 'project-sync', + }); + + const result = await withRetry({ + operation: () => syncProjectToDO(env, projectId, meta, members), + ...DO_SYNC_RETRY_CONFIG, + shouldRetry: shouldRetryDOSync, + logger: syncLogger, + operationName: 'DO sync-project', + }); + + if (!result.success) { + syncLogger.error('DO project sync exhausted all retries', { + attempts: result.attempts, + finalError: result.error instanceof Error ? result.error.message : String(result.error), + }); + } +} + +/** + * Determine if a DO sync error should be retried + * + * Only retries on server errors (5xx) and network errors. + * Does NOT retry on client errors (4xx) as those indicate bugs in our code. + */ +function shouldRetryDOSync(error: unknown, _attempt: number): boolean { + if (!(error instanceof Error)) { + // Unknown error type - retry as it might be transient + return true; + } + + const message = error.message; + + // Extract status code from sync error messages + // Format: "[ProjectSync] sync-member failed for project xxx: 503 ..." + const statusMatch = message.match(/:\s*(\d{3})\s/); + if (statusMatch) { + const status = parseInt(statusMatch[1], 10); + + // Only retry 5xx errors (server/DO errors) + // Don't retry 4xx (client errors - indicates bug in our code) + return status >= 500 && status < 600; + } + + // For network errors, timeouts, etc. - retry + // These typically don't have HTTP status codes in the message + return true; +} diff --git a/packages/workers/src/routes/billing/helpers/orgContext.ts b/packages/workers/src/routes/billing/helpers/orgContext.ts index 336ee170b..77e829029 100644 --- a/packages/workers/src/routes/billing/helpers/orgContext.ts +++ b/packages/workers/src/routes/billing/helpers/orgContext.ts @@ -4,6 +4,7 @@ */ import type { DrizzleD1Database } from 'drizzle-orm/d1'; import type * as schema from '@/db/schema.js'; +import { createDomainError, SYSTEM_ERRORS } from '@corates/shared'; interface Session { activeOrganizationId?: string | null; @@ -22,28 +23,45 @@ interface OrgIdWithRole { /** * Get orgId from session's activeOrganizationId or user's first org + * Verifies user is still a member of the org before returning */ export async function resolveOrgId({ db, session, userId, }: ResolveOrgParams): Promise { - let orgId = session?.activeOrganizationId; + const { member } = await import('@/db/schema.js'); + const { eq, and } = await import('drizzle-orm'); - // If no active org in session, get user's first org - if (!orgId) { - const { member } = await import('@/db/schema.js'); - const { eq } = await import('drizzle-orm'); + const activeOrgId = session?.activeOrganizationId; + + try { + if (activeOrgId) { + // Verify user is still a member of the active org + const membership = await db + .select({ organizationId: member.organizationId }) + .from(member) + .where(and(eq(member.organizationId, activeOrgId), eq(member.userId, userId))) + .get(); + + if (membership) { + return activeOrgId; + } + // User is no longer a member of the active org, fall through to first membership + } + + // Get user's first org membership const firstMembership = await db .select({ organizationId: member.organizationId }) .from(member) .where(eq(member.userId, userId)) .limit(1) .get(); - orgId = firstMembership?.organizationId; - } - return orgId || null; + return firstMembership?.organizationId || null; + } catch (err) { + throw createDomainError(SYSTEM_ERRORS.DB_ERROR, { cause: err }); + } } /** diff --git a/packages/workers/worker-configuration.d.ts b/packages/workers/worker-configuration.d.ts deleted file mode 100644 index e541dee8a..000000000 --- a/packages/workers/worker-configuration.d.ts +++ /dev/null @@ -1,12025 +0,0 @@ -/* eslint-disable */ -// Generated by Wrangler by running `wrangler types` (hash: 6e0f4421200915145614a4f75305a057) -// Runtime types generated with workerd@1.20251217.0 2025-12-01 nodejs_compat -declare namespace Cloudflare { - interface GlobalProps { - mainModule: typeof import('./src/index'); - durableNamespaces: 'UserSession' | 'ProjectDoc' | 'EmailQueue'; - } - interface Env { - ENVIRONMENT: 'development' | 'production'; - DEV_MODE: true; - AUTH_BASE_URL: 'http://localhost:8787' | 'https://corates.org'; - AUTH_SECRET: string; - EMAIL_FROM: string; - RESEND_API_KEY: string; - POSTMARK_SERVER_TOKEN: string; - APP_URL: string; - SEND_EMAILS_IN_DEV: string; - GOOGLE_CLIENT_ID: string; - GOOGLE_CLIENT_SECRET: string; - ORCID_CLIENT_ID: string; - ORCID_CLIENT_SECRET: string; - ADMIN_EMAIL: string; - STRIPE_SECRET_KEY: string; - STRIPE_WEBHOOK_SECRET_AUTH: string; - STRIPE_WEBHOOK_SECRET_PURCHASES: string; - STRIPE_PRICE_ID_STARTER_TEAM_MONTHLY: string; - STRIPE_PRICE_ID_STARTER_TEAM_YEARLY: string; - STRIPE_PRICE_ID_TEAM_MONTHLY: string; - STRIPE_PRICE_ID_TEAM_YEARLY: string; - STRIPE_PRICE_ID_UNLIMITED_TEAM_MONTHLY: string; - STRIPE_PRICE_ID_UNLIMITED_TEAM_YEARLY: string; - STRIPE_PRICE_ID_SINGLE_PROJECT: string; - USER_SESSION: DurableObjectNamespace; - PROJECT_DOC: DurableObjectNamespace; - EMAIL_QUEUE: DurableObjectNamespace; - PDF_BUCKET: R2Bucket; - DB: D1Database; - } -} -interface Env extends Cloudflare.Env {} -type StringifyValues> = { - [Binding in keyof EnvType]: EnvType[Binding] extends string ? EnvType[Binding] : string; -}; -declare namespace NodeJS { - interface ProcessEnv extends StringifyValues< - Pick< - Cloudflare.Env, - | 'ENVIRONMENT' - | 'DEV_MODE' - | 'AUTH_BASE_URL' - | 'AUTH_SECRET' - | 'EMAIL_FROM' - | 'RESEND_API_KEY' - | 'POSTMARK_SERVER_TOKEN' - | 'APP_URL' - | 'SEND_EMAILS_IN_DEV' - | 'GOOGLE_CLIENT_ID' - | 'GOOGLE_CLIENT_SECRET' - | 'ORCID_CLIENT_ID' - | 'ORCID_CLIENT_SECRET' - | 'ADMIN_EMAIL' - | 'STRIPE_SECRET_KEY' - | 'STRIPE_WEBHOOK_SECRET_AUTH' - | 'STRIPE_WEBHOOK_SECRET_PURCHASES' - | 'STRIPE_PRICE_ID_STARTER_TEAM_MONTHLY' - | 'STRIPE_PRICE_ID_STARTER_TEAM_YEARLY' - | 'STRIPE_PRICE_ID_TEAM_MONTHLY' - | 'STRIPE_PRICE_ID_TEAM_YEARLY' - | 'STRIPE_PRICE_ID_UNLIMITED_TEAM_MONTHLY' - | 'STRIPE_PRICE_ID_UNLIMITED_TEAM_YEARLY' - | 'STRIPE_PRICE_ID_SINGLE_PROJECT' - > - > {} -} - -// Begin runtime types -/*! ***************************************************************************** -Copyright (c) Cloudflare. All rights reserved. -Copyright (c) Microsoft Corporation. All rights reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); you may not use -this file except in compliance with the License. You may obtain a copy of the -License at http://www.apache.org/licenses/LICENSE-2.0 -THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED -WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, -MERCHANTABLITY OR NON-INFRINGEMENT. -See the Apache Version 2.0 License for specific language governing permissions -and limitations under the License. -***************************************************************************** */ -/* eslint-disable */ -// noinspection JSUnusedGlobalSymbols -declare var onmessage: never; -/** - * The **`DOMException`** interface represents an abnormal event (called an **exception**) that occurs as a result of calling a method or accessing a property of a web API. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/DOMException) - */ -declare class DOMException extends Error { - constructor(message?: string, name?: string); - /** - * The **`message`** read-only property of the a message or description associated with the given error name. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/DOMException/message) - */ - readonly message: string; - /** - * The **`name`** read-only property of the one of the strings associated with an error name. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/DOMException/name) - */ - readonly name: string; - /** - * The **`code`** read-only property of the DOMException interface returns one of the legacy error code constants, or `0` if none match. - * @deprecated - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/DOMException/code) - */ - readonly code: number; - static readonly INDEX_SIZE_ERR: number; - static readonly DOMSTRING_SIZE_ERR: number; - static readonly HIERARCHY_REQUEST_ERR: number; - static readonly WRONG_DOCUMENT_ERR: number; - static readonly INVALID_CHARACTER_ERR: number; - static readonly NO_DATA_ALLOWED_ERR: number; - static readonly NO_MODIFICATION_ALLOWED_ERR: number; - static readonly NOT_FOUND_ERR: number; - static readonly NOT_SUPPORTED_ERR: number; - static readonly INUSE_ATTRIBUTE_ERR: number; - static readonly INVALID_STATE_ERR: number; - static readonly SYNTAX_ERR: number; - static readonly INVALID_MODIFICATION_ERR: number; - static readonly NAMESPACE_ERR: number; - static readonly INVALID_ACCESS_ERR: number; - static readonly VALIDATION_ERR: number; - static readonly TYPE_MISMATCH_ERR: number; - static readonly SECURITY_ERR: number; - static readonly NETWORK_ERR: number; - static readonly ABORT_ERR: number; - static readonly URL_MISMATCH_ERR: number; - static readonly QUOTA_EXCEEDED_ERR: number; - static readonly TIMEOUT_ERR: number; - static readonly INVALID_NODE_TYPE_ERR: number; - static readonly DATA_CLONE_ERR: number; - get stack(): any; - set stack(value: any); -} -type WorkerGlobalScopeEventMap = { - fetch: FetchEvent; - scheduled: ScheduledEvent; - queue: QueueEvent; - unhandledrejection: PromiseRejectionEvent; - rejectionhandled: PromiseRejectionEvent; -}; -declare abstract class WorkerGlobalScope extends EventTarget { - EventTarget: typeof EventTarget; -} -/* The **`console`** object provides access to the debugging console (e.g., the Web console in Firefox). * - * The **`console`** object provides access to the debugging console (e.g., the Web console in Firefox). - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console) - */ -interface Console { - 'assert'(condition?: boolean, ...data: any[]): void; - /** - * The **`console.clear()`** static method clears the console if possible. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/clear_static) - */ - clear(): void; - /** - * The **`console.count()`** static method logs the number of times that this particular call to `count()` has been called. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/count_static) - */ - count(label?: string): void; - /** - * The **`console.countReset()`** static method resets counter used with console/count_static. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/countReset_static) - */ - countReset(label?: string): void; - /** - * The **`console.debug()`** static method outputs a message to the console at the 'debug' log level. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/debug_static) - */ - debug(...data: any[]): void; - /** - * The **`console.dir()`** static method displays a list of the properties of the specified JavaScript object. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/dir_static) - */ - dir(item?: any, options?: any): void; - /** - * The **`console.dirxml()`** static method displays an interactive tree of the descendant elements of the specified XML/HTML element. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/dirxml_static) - */ - dirxml(...data: any[]): void; - /** - * The **`console.error()`** static method outputs a message to the console at the 'error' log level. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/error_static) - */ - error(...data: any[]): void; - /** - * The **`console.group()`** static method creates a new inline group in the Web console log, causing any subsequent console messages to be indented by an additional level, until console/groupEnd_static is called. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/group_static) - */ - group(...data: any[]): void; - /** - * The **`console.groupCollapsed()`** static method creates a new inline group in the console. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/groupCollapsed_static) - */ - groupCollapsed(...data: any[]): void; - /** - * The **`console.groupEnd()`** static method exits the current inline group in the console. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/groupEnd_static) - */ - groupEnd(): void; - /** - * The **`console.info()`** static method outputs a message to the console at the 'info' log level. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/info_static) - */ - info(...data: any[]): void; - /** - * The **`console.log()`** static method outputs a message to the console. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/log_static) - */ - log(...data: any[]): void; - /** - * The **`console.table()`** static method displays tabular data as a table. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/table_static) - */ - table(tabularData?: any, properties?: string[]): void; - /** - * The **`console.time()`** static method starts a timer you can use to track how long an operation takes. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/time_static) - */ - time(label?: string): void; - /** - * The **`console.timeEnd()`** static method stops a timer that was previously started by calling console/time_static. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/timeEnd_static) - */ - timeEnd(label?: string): void; - /** - * The **`console.timeLog()`** static method logs the current value of a timer that was previously started by calling console/time_static. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/timeLog_static) - */ - timeLog(label?: string, ...data: any[]): void; - timeStamp(label?: string): void; - /** - * The **`console.trace()`** static method outputs a stack trace to the console. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/trace_static) - */ - trace(...data: any[]): void; - /** - * The **`console.warn()`** static method outputs a warning message to the console at the 'warning' log level. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/warn_static) - */ - warn(...data: any[]): void; -} -declare const console: Console; -type BufferSource = ArrayBufferView | ArrayBuffer; -type TypedArray = - | Int8Array - | Uint8Array - | Uint8ClampedArray - | Int16Array - | Uint16Array - | Int32Array - | Uint32Array - | Float32Array - | Float64Array - | BigInt64Array - | BigUint64Array; -declare namespace WebAssembly { - class CompileError extends Error { - constructor(message?: string); - } - class RuntimeError extends Error { - constructor(message?: string); - } - type ValueType = 'anyfunc' | 'externref' | 'f32' | 'f64' | 'i32' | 'i64' | 'v128'; - interface GlobalDescriptor { - value: ValueType; - mutable?: boolean; - } - class Global { - constructor(descriptor: GlobalDescriptor, value?: any); - value: any; - valueOf(): any; - } - type ImportValue = ExportValue | number; - type ModuleImports = Record; - type Imports = Record; - type ExportValue = Function | Global | Memory | Table; - type Exports = Record; - class Instance { - constructor(module: Module, imports?: Imports); - readonly exports: Exports; - } - interface MemoryDescriptor { - initial: number; - maximum?: number; - shared?: boolean; - } - class Memory { - constructor(descriptor: MemoryDescriptor); - readonly buffer: ArrayBuffer; - grow(delta: number): number; - } - type ImportExportKind = 'function' | 'global' | 'memory' | 'table'; - interface ModuleExportDescriptor { - kind: ImportExportKind; - name: string; - } - interface ModuleImportDescriptor { - kind: ImportExportKind; - module: string; - name: string; - } - abstract class Module { - static customSections(module: Module, sectionName: string): ArrayBuffer[]; - static exports(module: Module): ModuleExportDescriptor[]; - static imports(module: Module): ModuleImportDescriptor[]; - } - type TableKind = 'anyfunc' | 'externref'; - interface TableDescriptor { - element: TableKind; - initial: number; - maximum?: number; - } - class Table { - constructor(descriptor: TableDescriptor, value?: any); - readonly length: number; - get(index: number): any; - grow(delta: number, value?: any): number; - set(index: number, value?: any): void; - } - function instantiate(module: Module, imports?: Imports): Promise; - function validate(bytes: BufferSource): boolean; -} -/** - * The **`ServiceWorkerGlobalScope`** interface of the Service Worker API represents the global execution context of a service worker. - * Available only in secure contexts. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ServiceWorkerGlobalScope) - */ -interface ServiceWorkerGlobalScope extends WorkerGlobalScope { - DOMException: typeof DOMException; - WorkerGlobalScope: typeof WorkerGlobalScope; - btoa(data: string): string; - atob(data: string): string; - setTimeout(callback: (...args: any[]) => void, msDelay?: number): number; - setTimeout( - callback: (...args: Args) => void, - msDelay?: number, - ...args: Args - ): number; - clearTimeout(timeoutId: number | null): void; - setInterval(callback: (...args: any[]) => void, msDelay?: number): number; - setInterval( - callback: (...args: Args) => void, - msDelay?: number, - ...args: Args - ): number; - clearInterval(timeoutId: number | null): void; - queueMicrotask(task: Function): void; - structuredClone(value: T, options?: StructuredSerializeOptions): T; - reportError(error: any): void; - fetch(input: RequestInfo | URL, init?: RequestInit): Promise; - self: ServiceWorkerGlobalScope; - crypto: Crypto; - caches: CacheStorage; - scheduler: Scheduler; - performance: Performance; - Cloudflare: Cloudflare; - readonly origin: string; - Event: typeof Event; - ExtendableEvent: typeof ExtendableEvent; - CustomEvent: typeof CustomEvent; - PromiseRejectionEvent: typeof PromiseRejectionEvent; - FetchEvent: typeof FetchEvent; - TailEvent: typeof TailEvent; - TraceEvent: typeof TailEvent; - ScheduledEvent: typeof ScheduledEvent; - MessageEvent: typeof MessageEvent; - CloseEvent: typeof CloseEvent; - ReadableStreamDefaultReader: typeof ReadableStreamDefaultReader; - ReadableStreamBYOBReader: typeof ReadableStreamBYOBReader; - ReadableStream: typeof ReadableStream; - WritableStream: typeof WritableStream; - WritableStreamDefaultWriter: typeof WritableStreamDefaultWriter; - TransformStream: typeof TransformStream; - ByteLengthQueuingStrategy: typeof ByteLengthQueuingStrategy; - CountQueuingStrategy: typeof CountQueuingStrategy; - ErrorEvent: typeof ErrorEvent; - MessageChannel: typeof MessageChannel; - MessagePort: typeof MessagePort; - EventSource: typeof EventSource; - ReadableStreamBYOBRequest: typeof ReadableStreamBYOBRequest; - ReadableStreamDefaultController: typeof ReadableStreamDefaultController; - ReadableByteStreamController: typeof ReadableByteStreamController; - WritableStreamDefaultController: typeof WritableStreamDefaultController; - TransformStreamDefaultController: typeof TransformStreamDefaultController; - CompressionStream: typeof CompressionStream; - DecompressionStream: typeof DecompressionStream; - TextEncoderStream: typeof TextEncoderStream; - TextDecoderStream: typeof TextDecoderStream; - Headers: typeof Headers; - Body: typeof Body; - Request: typeof Request; - Response: typeof Response; - WebSocket: typeof WebSocket; - WebSocketPair: typeof WebSocketPair; - WebSocketRequestResponsePair: typeof WebSocketRequestResponsePair; - AbortController: typeof AbortController; - AbortSignal: typeof AbortSignal; - TextDecoder: typeof TextDecoder; - TextEncoder: typeof TextEncoder; - navigator: Navigator; - Navigator: typeof Navigator; - URL: typeof URL; - URLSearchParams: typeof URLSearchParams; - URLPattern: typeof URLPattern; - Blob: typeof Blob; - File: typeof File; - FormData: typeof FormData; - Crypto: typeof Crypto; - SubtleCrypto: typeof SubtleCrypto; - CryptoKey: typeof CryptoKey; - CacheStorage: typeof CacheStorage; - Cache: typeof Cache; - FixedLengthStream: typeof FixedLengthStream; - IdentityTransformStream: typeof IdentityTransformStream; - HTMLRewriter: typeof HTMLRewriter; -} -declare function addEventListener( - type: Type, - handler: EventListenerOrEventListenerObject, - options?: EventTargetAddEventListenerOptions | boolean, -): void; -declare function removeEventListener( - type: Type, - handler: EventListenerOrEventListenerObject, - options?: EventTargetEventListenerOptions | boolean, -): void; -/** - * The **`dispatchEvent()`** method of the EventTarget sends an Event to the object, (synchronously) invoking the affected event listeners in the appropriate order. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventTarget/dispatchEvent) - */ -declare function dispatchEvent( - event: WorkerGlobalScopeEventMap[keyof WorkerGlobalScopeEventMap], -): boolean; -/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/btoa) */ -declare function btoa(data: string): string; -/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/atob) */ -declare function atob(data: string): string; -/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/setTimeout) */ -declare function setTimeout(callback: (...args: any[]) => void, msDelay?: number): number; -/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/setTimeout) */ -declare function setTimeout( - callback: (...args: Args) => void, - msDelay?: number, - ...args: Args -): number; -/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/clearTimeout) */ -declare function clearTimeout(timeoutId: number | null): void; -/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/setInterval) */ -declare function setInterval(callback: (...args: any[]) => void, msDelay?: number): number; -/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/setInterval) */ -declare function setInterval( - callback: (...args: Args) => void, - msDelay?: number, - ...args: Args -): number; -/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/clearInterval) */ -declare function clearInterval(timeoutId: number | null): void; -/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/queueMicrotask) */ -declare function queueMicrotask(task: Function): void; -/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/structuredClone) */ -declare function structuredClone(value: T, options?: StructuredSerializeOptions): T; -/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/reportError) */ -declare function reportError(error: any): void; -/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/fetch) */ -declare function fetch( - input: RequestInfo | URL, - init?: RequestInit, -): Promise; -declare const self: ServiceWorkerGlobalScope; -/** - * The Web Crypto API provides a set of low-level functions for common cryptographic tasks. - * The Workers runtime implements the full surface of this API, but with some differences in - * the [supported algorithms](https://developers.cloudflare.com/workers/runtime-apis/web-crypto/#supported-algorithms) - * compared to those implemented in most browsers. - * - * [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/web-crypto/) - */ -declare const crypto: Crypto; -/** - * The Cache API allows fine grained control of reading and writing from the Cloudflare global network cache. - * - * [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/cache/) - */ -declare const caches: CacheStorage; -declare const scheduler: Scheduler; -/** - * The Workers runtime supports a subset of the Performance API, used to measure timing and performance, - * as well as timing of subrequests and other operations. - * - * [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/performance/) - */ -declare const performance: Performance; -declare const Cloudflare: Cloudflare; -declare const origin: string; -declare const navigator: Navigator; -interface TestController {} -interface ExecutionContext { - waitUntil(promise: Promise): void; - passThroughOnException(): void; - readonly exports: Cloudflare.Exports; - readonly props: Props; -} -type ExportedHandlerFetchHandler = ( - request: Request>, - env: Env, - ctx: ExecutionContext, -) => Response | Promise; -type ExportedHandlerTailHandler = ( - events: TraceItem[], - env: Env, - ctx: ExecutionContext, -) => void | Promise; -type ExportedHandlerTraceHandler = ( - traces: TraceItem[], - env: Env, - ctx: ExecutionContext, -) => void | Promise; -type ExportedHandlerTailStreamHandler = ( - event: TailStream.TailEvent, - env: Env, - ctx: ExecutionContext, -) => TailStream.TailEventHandlerType | Promise; -type ExportedHandlerScheduledHandler = ( - controller: ScheduledController, - env: Env, - ctx: ExecutionContext, -) => void | Promise; -type ExportedHandlerQueueHandler = ( - batch: MessageBatch, - env: Env, - ctx: ExecutionContext, -) => void | Promise; -type ExportedHandlerTestHandler = ( - controller: TestController, - env: Env, - ctx: ExecutionContext, -) => void | Promise; -interface ExportedHandler { - fetch?: ExportedHandlerFetchHandler; - tail?: ExportedHandlerTailHandler; - trace?: ExportedHandlerTraceHandler; - tailStream?: ExportedHandlerTailStreamHandler; - scheduled?: ExportedHandlerScheduledHandler; - test?: ExportedHandlerTestHandler; - email?: EmailExportedHandler; - queue?: ExportedHandlerQueueHandler; -} -interface StructuredSerializeOptions { - transfer?: any[]; -} -declare abstract class Navigator { - sendBeacon(url: string, body?: BodyInit): boolean; - readonly userAgent: string; - readonly hardwareConcurrency: number; - readonly language: string; - readonly languages: string[]; -} -interface AlarmInvocationInfo { - readonly isRetry: boolean; - readonly retryCount: number; -} -interface Cloudflare { - readonly compatibilityFlags: Record; -} -interface DurableObject { - fetch(request: Request): Response | Promise; - alarm?(alarmInfo?: AlarmInvocationInfo): void | Promise; - webSocketMessage?(ws: WebSocket, message: string | ArrayBuffer): void | Promise; - webSocketClose?( - ws: WebSocket, - code: number, - reason: string, - wasClean: boolean, - ): void | Promise; - webSocketError?(ws: WebSocket, error: unknown): void | Promise; -} -type DurableObjectStub = Fetcher< - T, - 'alarm' | 'webSocketMessage' | 'webSocketClose' | 'webSocketError' -> & { - readonly id: DurableObjectId; - readonly name?: string; -}; -interface DurableObjectId { - toString(): string; - equals(other: DurableObjectId): boolean; - readonly name?: string; -} -declare abstract class DurableObjectNamespace< - T extends Rpc.DurableObjectBranded | undefined = undefined, -> { - newUniqueId(options?: DurableObjectNamespaceNewUniqueIdOptions): DurableObjectId; - idFromName(name: string): DurableObjectId; - idFromString(id: string): DurableObjectId; - get( - id: DurableObjectId, - options?: DurableObjectNamespaceGetDurableObjectOptions, - ): DurableObjectStub; - getByName( - name: string, - options?: DurableObjectNamespaceGetDurableObjectOptions, - ): DurableObjectStub; - jurisdiction(jurisdiction: DurableObjectJurisdiction): DurableObjectNamespace; -} -type DurableObjectJurisdiction = 'eu' | 'fedramp' | 'fedramp-high'; -interface DurableObjectNamespaceNewUniqueIdOptions { - jurisdiction?: DurableObjectJurisdiction; -} -type DurableObjectLocationHint = - | 'wnam' - | 'enam' - | 'sam' - | 'weur' - | 'eeur' - | 'apac' - | 'oc' - | 'afr' - | 'me'; -interface DurableObjectNamespaceGetDurableObjectOptions { - locationHint?: DurableObjectLocationHint; -} -interface DurableObjectClass<_T extends Rpc.DurableObjectBranded | undefined = undefined> {} -interface DurableObjectState { - waitUntil(promise: Promise): void; - readonly exports: Cloudflare.Exports; - readonly props: Props; - readonly id: DurableObjectId; - readonly storage: DurableObjectStorage; - container?: Container; - blockConcurrencyWhile(callback: () => Promise): Promise; - acceptWebSocket(ws: WebSocket, tags?: string[]): void; - getWebSockets(tag?: string): WebSocket[]; - setWebSocketAutoResponse(maybeReqResp?: WebSocketRequestResponsePair): void; - getWebSocketAutoResponse(): WebSocketRequestResponsePair | null; - getWebSocketAutoResponseTimestamp(ws: WebSocket): Date | null; - setHibernatableWebSocketEventTimeout(timeoutMs?: number): void; - getHibernatableWebSocketEventTimeout(): number | null; - getTags(ws: WebSocket): string[]; - abort(reason?: string): void; -} -interface DurableObjectTransaction { - get(key: string, options?: DurableObjectGetOptions): Promise; - get(keys: string[], options?: DurableObjectGetOptions): Promise>; - list(options?: DurableObjectListOptions): Promise>; - put(key: string, value: T, options?: DurableObjectPutOptions): Promise; - put(entries: Record, options?: DurableObjectPutOptions): Promise; - delete(key: string, options?: DurableObjectPutOptions): Promise; - delete(keys: string[], options?: DurableObjectPutOptions): Promise; - rollback(): void; - getAlarm(options?: DurableObjectGetAlarmOptions): Promise; - setAlarm(scheduledTime: number | Date, options?: DurableObjectSetAlarmOptions): Promise; - deleteAlarm(options?: DurableObjectSetAlarmOptions): Promise; -} -interface DurableObjectStorage { - get(key: string, options?: DurableObjectGetOptions): Promise; - get(keys: string[], options?: DurableObjectGetOptions): Promise>; - list(options?: DurableObjectListOptions): Promise>; - put(key: string, value: T, options?: DurableObjectPutOptions): Promise; - put(entries: Record, options?: DurableObjectPutOptions): Promise; - delete(key: string, options?: DurableObjectPutOptions): Promise; - delete(keys: string[], options?: DurableObjectPutOptions): Promise; - deleteAll(options?: DurableObjectPutOptions): Promise; - transaction(closure: (txn: DurableObjectTransaction) => Promise): Promise; - getAlarm(options?: DurableObjectGetAlarmOptions): Promise; - setAlarm(scheduledTime: number | Date, options?: DurableObjectSetAlarmOptions): Promise; - deleteAlarm(options?: DurableObjectSetAlarmOptions): Promise; - sync(): Promise; - sql: SqlStorage; - kv: SyncKvStorage; - transactionSync(closure: () => T): T; - getCurrentBookmark(): Promise; - getBookmarkForTime(timestamp: number | Date): Promise; - onNextSessionRestoreBookmark(bookmark: string): Promise; -} -interface DurableObjectListOptions { - start?: string; - startAfter?: string; - end?: string; - prefix?: string; - reverse?: boolean; - limit?: number; - allowConcurrency?: boolean; - noCache?: boolean; -} -interface DurableObjectGetOptions { - allowConcurrency?: boolean; - noCache?: boolean; -} -interface DurableObjectGetAlarmOptions { - allowConcurrency?: boolean; -} -interface DurableObjectPutOptions { - allowConcurrency?: boolean; - allowUnconfirmed?: boolean; - noCache?: boolean; -} -interface DurableObjectSetAlarmOptions { - allowConcurrency?: boolean; - allowUnconfirmed?: boolean; -} -declare class WebSocketRequestResponsePair { - constructor(request: string, response: string); - get request(): string; - get response(): string; -} -interface AnalyticsEngineDataset { - writeDataPoint(event?: AnalyticsEngineDataPoint): void; -} -interface AnalyticsEngineDataPoint { - indexes?: ((ArrayBuffer | string) | null)[]; - doubles?: number[]; - blobs?: ((ArrayBuffer | string) | null)[]; -} -/** - * The **`Event`** interface represents an event which takes place on an `EventTarget`. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event) - */ -declare class Event { - constructor(type: string, init?: EventInit); - /** - * The **`type`** read-only property of the Event interface returns a string containing the event's type. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/type) - */ - get type(): string; - /** - * The **`eventPhase`** read-only property of the being evaluated. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/eventPhase) - */ - get eventPhase(): number; - /** - * The read-only **`composed`** property of the or not the event will propagate across the shadow DOM boundary into the standard DOM. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/composed) - */ - get composed(): boolean; - /** - * The **`bubbles`** read-only property of the Event interface indicates whether the event bubbles up through the DOM tree or not. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/bubbles) - */ - get bubbles(): boolean; - /** - * The **`cancelable`** read-only property of the Event interface indicates whether the event can be canceled, and therefore prevented as if the event never happened. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/cancelable) - */ - get cancelable(): boolean; - /** - * The **`defaultPrevented`** read-only property of the Event interface returns a boolean value indicating whether or not the call to Event.preventDefault() canceled the event. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/defaultPrevented) - */ - get defaultPrevented(): boolean; - /** - * The Event property **`returnValue`** indicates whether the default action for this event has been prevented or not. - * @deprecated - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/returnValue) - */ - get returnValue(): boolean; - /** - * The **`currentTarget`** read-only property of the Event interface identifies the element to which the event handler has been attached. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/currentTarget) - */ - get currentTarget(): EventTarget | undefined; - /** - * The read-only **`target`** property of the dispatched. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/target) - */ - get target(): EventTarget | undefined; - /** - * The deprecated **`Event.srcElement`** is an alias for the Event.target property. - * @deprecated - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/srcElement) - */ - get srcElement(): EventTarget | undefined; - /** - * The **`timeStamp`** read-only property of the Event interface returns the time (in milliseconds) at which the event was created. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/timeStamp) - */ - get timeStamp(): number; - /** - * The **`isTrusted`** read-only property of the when the event was generated by the user agent (including via user actions and programmatic methods such as HTMLElement.focus()), and `false` when the event was dispatched via The only exception is the `click` event, which initializes the `isTrusted` property to `false` in user agents. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/isTrusted) - */ - get isTrusted(): boolean; - /** - * The **`cancelBubble`** property of the Event interface is deprecated. - * @deprecated - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/cancelBubble) - */ - get cancelBubble(): boolean; - /** - * The **`cancelBubble`** property of the Event interface is deprecated. - * @deprecated - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/cancelBubble) - */ - set cancelBubble(value: boolean); - /** - * The **`stopImmediatePropagation()`** method of the If several listeners are attached to the same element for the same event type, they are called in the order in which they were added. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/stopImmediatePropagation) - */ - stopImmediatePropagation(): void; - /** - * The **`preventDefault()`** method of the Event interface tells the user agent that if the event does not get explicitly handled, its default action should not be taken as it normally would be. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/preventDefault) - */ - preventDefault(): void; - /** - * The **`stopPropagation()`** method of the Event interface prevents further propagation of the current event in the capturing and bubbling phases. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/stopPropagation) - */ - stopPropagation(): void; - /** - * The **`composedPath()`** method of the Event interface returns the event's path which is an array of the objects on which listeners will be invoked. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/composedPath) - */ - composedPath(): EventTarget[]; - static readonly NONE: number; - static readonly CAPTURING_PHASE: number; - static readonly AT_TARGET: number; - static readonly BUBBLING_PHASE: number; -} -interface EventInit { - bubbles?: boolean; - cancelable?: boolean; - composed?: boolean; -} -type EventListener = (event: EventType) => void; -interface EventListenerObject { - handleEvent(event: EventType): void; -} -type EventListenerOrEventListenerObject = - | EventListener - | EventListenerObject; -/** - * The **`EventTarget`** interface is implemented by objects that can receive events and may have listeners for them. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventTarget) - */ -declare class EventTarget = Record> { - constructor(); - /** - * The **`addEventListener()`** method of the EventTarget interface sets up a function that will be called whenever the specified event is delivered to the target. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventTarget/addEventListener) - */ - addEventListener( - type: Type, - handler: EventListenerOrEventListenerObject, - options?: EventTargetAddEventListenerOptions | boolean, - ): void; - /** - * The **`removeEventListener()`** method of the EventTarget interface removes an event listener previously registered with EventTarget.addEventListener() from the target. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventTarget/removeEventListener) - */ - removeEventListener( - type: Type, - handler: EventListenerOrEventListenerObject, - options?: EventTargetEventListenerOptions | boolean, - ): void; - /** - * The **`dispatchEvent()`** method of the EventTarget sends an Event to the object, (synchronously) invoking the affected event listeners in the appropriate order. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventTarget/dispatchEvent) - */ - dispatchEvent(event: EventMap[keyof EventMap]): boolean; -} -interface EventTargetEventListenerOptions { - capture?: boolean; -} -interface EventTargetAddEventListenerOptions { - capture?: boolean; - passive?: boolean; - once?: boolean; - signal?: AbortSignal; -} -interface EventTargetHandlerObject { - handleEvent: (event: Event) => any | undefined; -} -/** - * The **`AbortController`** interface represents a controller object that allows you to abort one or more Web requests as and when desired. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortController) - */ -declare class AbortController { - constructor(); - /** - * The **`signal`** read-only property of the AbortController interface returns an AbortSignal object instance, which can be used to communicate with/abort an asynchronous operation as desired. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortController/signal) - */ - get signal(): AbortSignal; - /** - * The **`abort()`** method of the AbortController interface aborts an asynchronous operation before it has completed. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortController/abort) - */ - abort(reason?: any): void; -} -/** - * The **`AbortSignal`** interface represents a signal object that allows you to communicate with an asynchronous operation (such as a fetch request) and abort it if required via an AbortController object. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortSignal) - */ -declare abstract class AbortSignal extends EventTarget { - /** - * The **`AbortSignal.abort()`** static method returns an AbortSignal that is already set as aborted (and which does not trigger an AbortSignal/abort_event event). - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortSignal/abort_static) - */ - static abort(reason?: any): AbortSignal; - /** - * The **`AbortSignal.timeout()`** static method returns an AbortSignal that will automatically abort after a specified time. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortSignal/timeout_static) - */ - static timeout(delay: number): AbortSignal; - /** - * The **`AbortSignal.any()`** static method takes an iterable of abort signals and returns an AbortSignal. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortSignal/any_static) - */ - static any(signals: AbortSignal[]): AbortSignal; - /** - * The **`aborted`** read-only property returns a value that indicates whether the asynchronous operations the signal is communicating with are aborted (`true`) or not (`false`). - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortSignal/aborted) - */ - get aborted(): boolean; - /** - * The **`reason`** read-only property returns a JavaScript value that indicates the abort reason. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortSignal/reason) - */ - get reason(): any; - /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortSignal/abort_event) */ - get onabort(): any | null; - /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortSignal/abort_event) */ - set onabort(value: any | null); - /** - * The **`throwIfAborted()`** method throws the signal's abort AbortSignal.reason if the signal has been aborted; otherwise it does nothing. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortSignal/throwIfAborted) - */ - throwIfAborted(): void; -} -interface Scheduler { - wait(delay: number, maybeOptions?: SchedulerWaitOptions): Promise; -} -interface SchedulerWaitOptions { - signal?: AbortSignal; -} -/** - * The **`ExtendableEvent`** interface extends the lifetime of the `install` and `activate` events dispatched on the global scope as part of the service worker lifecycle. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ExtendableEvent) - */ -declare abstract class ExtendableEvent extends Event { - /** - * The **`ExtendableEvent.waitUntil()`** method tells the event dispatcher that work is ongoing. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ExtendableEvent/waitUntil) - */ - waitUntil(promise: Promise): void; -} -/** - * The **`CustomEvent`** interface represents events initialized by an application for any purpose. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CustomEvent) - */ -declare class CustomEvent extends Event { - constructor(type: string, init?: CustomEventCustomEventInit); - /** - * The read-only **`detail`** property of the CustomEvent interface returns any data passed when initializing the event. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CustomEvent/detail) - */ - get detail(): T; -} -interface CustomEventCustomEventInit { - bubbles?: boolean; - cancelable?: boolean; - composed?: boolean; - detail?: any; -} -/** - * The **`Blob`** interface represents a blob, which is a file-like object of immutable, raw data; they can be read as text or binary data, or converted into a ReadableStream so its methods can be used for processing the data. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob) - */ -declare class Blob { - constructor(type?: ((ArrayBuffer | ArrayBufferView) | string | Blob)[], options?: BlobOptions); - /** - * The **`size`** read-only property of the Blob interface returns the size of the Blob or File in bytes. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/size) - */ - get size(): number; - /** - * The **`type`** read-only property of the Blob interface returns the MIME type of the file. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/type) - */ - get type(): string; - /** - * The **`slice()`** method of the Blob interface creates and returns a new `Blob` object which contains data from a subset of the blob on which it's called. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/slice) - */ - slice(start?: number, end?: number, type?: string): Blob; - /** - * The **`arrayBuffer()`** method of the Blob interface returns a Promise that resolves with the contents of the blob as binary data contained in an ArrayBuffer. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/arrayBuffer) - */ - arrayBuffer(): Promise; - /** - * The **`bytes()`** method of the Blob interface returns a Promise that resolves with a Uint8Array containing the contents of the blob as an array of bytes. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/bytes) - */ - bytes(): Promise; - /** - * The **`text()`** method of the string containing the contents of the blob, interpreted as UTF-8. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/text) - */ - text(): Promise; - /** - * The **`stream()`** method of the Blob interface returns a ReadableStream which upon reading returns the data contained within the `Blob`. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/stream) - */ - stream(): ReadableStream; -} -interface BlobOptions { - type?: string; -} -/** - * The **`File`** interface provides information about files and allows JavaScript in a web page to access their content. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/File) - */ -declare class File extends Blob { - constructor( - bits: ((ArrayBuffer | ArrayBufferView) | string | Blob)[] | undefined, - name: string, - options?: FileOptions, - ); - /** - * The **`name`** read-only property of the File interface returns the name of the file represented by a File object. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/File/name) - */ - get name(): string; - /** - * The **`lastModified`** read-only property of the File interface provides the last modified date of the file as the number of milliseconds since the Unix epoch (January 1, 1970 at midnight). - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/File/lastModified) - */ - get lastModified(): number; -} -interface FileOptions { - type?: string; - lastModified?: number; -} -/** - * The Cache API allows fine grained control of reading and writing from the Cloudflare global network cache. - * - * [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/cache/) - */ -declare abstract class CacheStorage { - /** - * The **`open()`** method of the the Cache object matching the `cacheName`. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CacheStorage/open) - */ - open(cacheName: string): Promise; - readonly default: Cache; -} -/** - * The Cache API allows fine grained control of reading and writing from the Cloudflare global network cache. - * - * [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/cache/) - */ -declare abstract class Cache { - /* [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/cache/#delete) */ - delete(request: RequestInfo | URL, options?: CacheQueryOptions): Promise; - /* [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/cache/#match) */ - match(request: RequestInfo | URL, options?: CacheQueryOptions): Promise; - /* [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/cache/#put) */ - put(request: RequestInfo | URL, response: Response): Promise; -} -interface CacheQueryOptions { - ignoreMethod?: boolean; -} -/** - * The Web Crypto API provides a set of low-level functions for common cryptographic tasks. - * The Workers runtime implements the full surface of this API, but with some differences in - * the [supported algorithms](https://developers.cloudflare.com/workers/runtime-apis/web-crypto/#supported-algorithms) - * compared to those implemented in most browsers. - * - * [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/web-crypto/) - */ -declare abstract class Crypto { - /** - * The **`Crypto.subtle`** read-only property returns a cryptographic operations. - * Available only in secure contexts. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Crypto/subtle) - */ - get subtle(): SubtleCrypto; - /** - * The **`Crypto.getRandomValues()`** method lets you get cryptographically strong random values. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Crypto/getRandomValues) - */ - getRandomValues< - T extends - | Int8Array - | Uint8Array - | Int16Array - | Uint16Array - | Int32Array - | Uint32Array - | BigInt64Array - | BigUint64Array, - >(buffer: T): T; - /** - * The **`randomUUID()`** method of the Crypto interface is used to generate a v4 UUID using a cryptographically secure random number generator. - * Available only in secure contexts. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Crypto/randomUUID) - */ - randomUUID(): string; - DigestStream: typeof DigestStream; -} -/** - * The **`SubtleCrypto`** interface of the Web Crypto API provides a number of low-level cryptographic functions. - * Available only in secure contexts. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto) - */ -declare abstract class SubtleCrypto { - /** - * The **`encrypt()`** method of the SubtleCrypto interface encrypts data. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/encrypt) - */ - encrypt( - algorithm: string | SubtleCryptoEncryptAlgorithm, - key: CryptoKey, - plainText: ArrayBuffer | ArrayBufferView, - ): Promise; - /** - * The **`decrypt()`** method of the SubtleCrypto interface decrypts some encrypted data. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/decrypt) - */ - decrypt( - algorithm: string | SubtleCryptoEncryptAlgorithm, - key: CryptoKey, - cipherText: ArrayBuffer | ArrayBufferView, - ): Promise; - /** - * The **`sign()`** method of the SubtleCrypto interface generates a digital signature. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/sign) - */ - sign( - algorithm: string | SubtleCryptoSignAlgorithm, - key: CryptoKey, - data: ArrayBuffer | ArrayBufferView, - ): Promise; - /** - * The **`verify()`** method of the SubtleCrypto interface verifies a digital signature. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/verify) - */ - verify( - algorithm: string | SubtleCryptoSignAlgorithm, - key: CryptoKey, - signature: ArrayBuffer | ArrayBufferView, - data: ArrayBuffer | ArrayBufferView, - ): Promise; - /** - * The **`digest()`** method of the SubtleCrypto interface generates a _digest_ of the given data, using the specified hash function. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/digest) - */ - digest( - algorithm: string | SubtleCryptoHashAlgorithm, - data: ArrayBuffer | ArrayBufferView, - ): Promise; - /** - * The **`generateKey()`** method of the SubtleCrypto interface is used to generate a new key (for symmetric algorithms) or key pair (for public-key algorithms). - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/generateKey) - */ - generateKey( - algorithm: string | SubtleCryptoGenerateKeyAlgorithm, - extractable: boolean, - keyUsages: string[], - ): Promise; - /** - * The **`deriveKey()`** method of the SubtleCrypto interface can be used to derive a secret key from a master key. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/deriveKey) - */ - deriveKey( - algorithm: string | SubtleCryptoDeriveKeyAlgorithm, - baseKey: CryptoKey, - derivedKeyAlgorithm: string | SubtleCryptoImportKeyAlgorithm, - extractable: boolean, - keyUsages: string[], - ): Promise; - /** - * The **`deriveBits()`** method of the key. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/deriveBits) - */ - deriveBits( - algorithm: string | SubtleCryptoDeriveKeyAlgorithm, - baseKey: CryptoKey, - length?: number | null, - ): Promise; - /** - * The **`importKey()`** method of the SubtleCrypto interface imports a key: that is, it takes as input a key in an external, portable format and gives you a CryptoKey object that you can use in the Web Crypto API. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/importKey) - */ - importKey( - format: string, - keyData: (ArrayBuffer | ArrayBufferView) | JsonWebKey, - algorithm: string | SubtleCryptoImportKeyAlgorithm, - extractable: boolean, - keyUsages: string[], - ): Promise; - /** - * The **`exportKey()`** method of the SubtleCrypto interface exports a key: that is, it takes as input a CryptoKey object and gives you the key in an external, portable format. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/exportKey) - */ - exportKey(format: string, key: CryptoKey): Promise; - /** - * The **`wrapKey()`** method of the SubtleCrypto interface 'wraps' a key. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/wrapKey) - */ - wrapKey( - format: string, - key: CryptoKey, - wrappingKey: CryptoKey, - wrapAlgorithm: string | SubtleCryptoEncryptAlgorithm, - ): Promise; - /** - * The **`unwrapKey()`** method of the SubtleCrypto interface 'unwraps' a key. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/unwrapKey) - */ - unwrapKey( - format: string, - wrappedKey: ArrayBuffer | ArrayBufferView, - unwrappingKey: CryptoKey, - unwrapAlgorithm: string | SubtleCryptoEncryptAlgorithm, - unwrappedKeyAlgorithm: string | SubtleCryptoImportKeyAlgorithm, - extractable: boolean, - keyUsages: string[], - ): Promise; - timingSafeEqual(a: ArrayBuffer | ArrayBufferView, b: ArrayBuffer | ArrayBufferView): boolean; -} -/** - * The **`CryptoKey`** interface of the Web Crypto API represents a cryptographic key obtained from one of the SubtleCrypto methods SubtleCrypto.generateKey, SubtleCrypto.deriveKey, SubtleCrypto.importKey, or SubtleCrypto.unwrapKey. - * Available only in secure contexts. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CryptoKey) - */ -declare abstract class CryptoKey { - /** - * The read-only **`type`** property of the CryptoKey interface indicates which kind of key is represented by the object. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CryptoKey/type) - */ - readonly type: string; - /** - * The read-only **`extractable`** property of the CryptoKey interface indicates whether or not the key may be extracted using `SubtleCrypto.exportKey()` or `SubtleCrypto.wrapKey()`. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CryptoKey/extractable) - */ - readonly extractable: boolean; - /** - * The read-only **`algorithm`** property of the CryptoKey interface returns an object describing the algorithm for which this key can be used, and any associated extra parameters. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CryptoKey/algorithm) - */ - readonly algorithm: - | CryptoKeyKeyAlgorithm - | CryptoKeyAesKeyAlgorithm - | CryptoKeyHmacKeyAlgorithm - | CryptoKeyRsaKeyAlgorithm - | CryptoKeyEllipticKeyAlgorithm - | CryptoKeyArbitraryKeyAlgorithm; - /** - * The read-only **`usages`** property of the CryptoKey interface indicates what can be done with the key. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CryptoKey/usages) - */ - readonly usages: string[]; -} -interface CryptoKeyPair { - publicKey: CryptoKey; - privateKey: CryptoKey; -} -interface JsonWebKey { - kty: string; - use?: string; - key_ops?: string[]; - alg?: string; - ext?: boolean; - crv?: string; - x?: string; - y?: string; - d?: string; - n?: string; - e?: string; - p?: string; - q?: string; - dp?: string; - dq?: string; - qi?: string; - oth?: RsaOtherPrimesInfo[]; - k?: string; -} -interface RsaOtherPrimesInfo { - r?: string; - d?: string; - t?: string; -} -interface SubtleCryptoDeriveKeyAlgorithm { - name: string; - salt?: ArrayBuffer | ArrayBufferView; - iterations?: number; - hash?: string | SubtleCryptoHashAlgorithm; - $public?: CryptoKey; - info?: ArrayBuffer | ArrayBufferView; -} -interface SubtleCryptoEncryptAlgorithm { - name: string; - iv?: ArrayBuffer | ArrayBufferView; - additionalData?: ArrayBuffer | ArrayBufferView; - tagLength?: number; - counter?: ArrayBuffer | ArrayBufferView; - length?: number; - label?: ArrayBuffer | ArrayBufferView; -} -interface SubtleCryptoGenerateKeyAlgorithm { - name: string; - hash?: string | SubtleCryptoHashAlgorithm; - modulusLength?: number; - publicExponent?: ArrayBuffer | ArrayBufferView; - length?: number; - namedCurve?: string; -} -interface SubtleCryptoHashAlgorithm { - name: string; -} -interface SubtleCryptoImportKeyAlgorithm { - name: string; - hash?: string | SubtleCryptoHashAlgorithm; - length?: number; - namedCurve?: string; - compressed?: boolean; -} -interface SubtleCryptoSignAlgorithm { - name: string; - hash?: string | SubtleCryptoHashAlgorithm; - dataLength?: number; - saltLength?: number; -} -interface CryptoKeyKeyAlgorithm { - name: string; -} -interface CryptoKeyAesKeyAlgorithm { - name: string; - length: number; -} -interface CryptoKeyHmacKeyAlgorithm { - name: string; - hash: CryptoKeyKeyAlgorithm; - length: number; -} -interface CryptoKeyRsaKeyAlgorithm { - name: string; - modulusLength: number; - publicExponent: ArrayBuffer | ArrayBufferView; - hash?: CryptoKeyKeyAlgorithm; -} -interface CryptoKeyEllipticKeyAlgorithm { - name: string; - namedCurve: string; -} -interface CryptoKeyArbitraryKeyAlgorithm { - name: string; - hash?: CryptoKeyKeyAlgorithm; - namedCurve?: string; - length?: number; -} -declare class DigestStream extends WritableStream { - constructor(algorithm: string | SubtleCryptoHashAlgorithm); - readonly digest: Promise; - get bytesWritten(): number | bigint; -} -/** - * The **`TextDecoder`** interface represents a decoder for a specific text encoding, such as `UTF-8`, `ISO-8859-2`, `KOI8-R`, `GBK`, etc. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TextDecoder) - */ -declare class TextDecoder { - constructor(label?: string, options?: TextDecoderConstructorOptions); - /** - * The **`TextDecoder.decode()`** method returns a string containing text decoded from the buffer passed as a parameter. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TextDecoder/decode) - */ - decode(input?: ArrayBuffer | ArrayBufferView, options?: TextDecoderDecodeOptions): string; - get encoding(): string; - get fatal(): boolean; - get ignoreBOM(): boolean; -} -/** - * The **`TextEncoder`** interface takes a stream of code points as input and emits a stream of UTF-8 bytes. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TextEncoder) - */ -declare class TextEncoder { - constructor(); - /** - * The **`TextEncoder.encode()`** method takes a string as input, and returns a Global_Objects/Uint8Array containing the text given in parameters encoded with the specific method for that TextEncoder object. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TextEncoder/encode) - */ - encode(input?: string): Uint8Array; - /** - * The **`TextEncoder.encodeInto()`** method takes a string to encode and a destination Uint8Array to put resulting UTF-8 encoded text into, and returns a dictionary object indicating the progress of the encoding. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TextEncoder/encodeInto) - */ - encodeInto(input: string, buffer: Uint8Array): TextEncoderEncodeIntoResult; - get encoding(): string; -} -interface TextDecoderConstructorOptions { - fatal: boolean; - ignoreBOM: boolean; -} -interface TextDecoderDecodeOptions { - stream: boolean; -} -interface TextEncoderEncodeIntoResult { - read: number; - written: number; -} -/** - * The **`ErrorEvent`** interface represents events providing information related to errors in scripts or in files. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ErrorEvent) - */ -declare class ErrorEvent extends Event { - constructor(type: string, init?: ErrorEventErrorEventInit); - /** - * The **`filename`** read-only property of the ErrorEvent interface returns a string containing the name of the script file in which the error occurred. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ErrorEvent/filename) - */ - get filename(): string; - /** - * The **`message`** read-only property of the ErrorEvent interface returns a string containing a human-readable error message describing the problem. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ErrorEvent/message) - */ - get message(): string; - /** - * The **`lineno`** read-only property of the ErrorEvent interface returns an integer containing the line number of the script file on which the error occurred. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ErrorEvent/lineno) - */ - get lineno(): number; - /** - * The **`colno`** read-only property of the ErrorEvent interface returns an integer containing the column number of the script file on which the error occurred. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ErrorEvent/colno) - */ - get colno(): number; - /** - * The **`error`** read-only property of the ErrorEvent interface returns a JavaScript value, such as an Error or DOMException, representing the error associated with this event. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ErrorEvent/error) - */ - get error(): any; -} -interface ErrorEventErrorEventInit { - message?: string; - filename?: string; - lineno?: number; - colno?: number; - error?: any; -} -/** - * The **`MessageEvent`** interface represents a message received by a target object. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessageEvent) - */ -declare class MessageEvent extends Event { - constructor(type: string, initializer: MessageEventInit); - /** - * The **`data`** read-only property of the The data sent by the message emitter; this can be any data type, depending on what originated this event. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessageEvent/data) - */ - readonly data: any; - /** - * The **`origin`** read-only property of the origin of the message emitter. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessageEvent/origin) - */ - readonly origin: string | null; - /** - * The **`lastEventId`** read-only property of the unique ID for the event. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessageEvent/lastEventId) - */ - readonly lastEventId: string; - /** - * The **`source`** read-only property of the a WindowProxy, MessagePort, or a `MessageEventSource` (which can be a WindowProxy, message emitter. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessageEvent/source) - */ - readonly source: MessagePort | null; - /** - * The **`ports`** read-only property of the containing all MessagePort objects sent with the message, in order. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessageEvent/ports) - */ - readonly ports: MessagePort[]; -} -interface MessageEventInit { - data: ArrayBuffer | string; -} -/** - * The **`PromiseRejectionEvent`** interface represents events which are sent to the global script context when JavaScript Promises are rejected. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/PromiseRejectionEvent) - */ -declare abstract class PromiseRejectionEvent extends Event { - /** - * The PromiseRejectionEvent interface's **`promise`** read-only property indicates the JavaScript rejected. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/PromiseRejectionEvent/promise) - */ - readonly promise: Promise; - /** - * The PromiseRejectionEvent **`reason`** read-only property is any JavaScript value or Object which provides the reason passed into Promise.reject(). - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/PromiseRejectionEvent/reason) - */ - readonly reason: any; -} -/** - * The **`FormData`** interface provides a way to construct a set of key/value pairs representing form fields and their values, which can be sent using the Window/fetch, XMLHttpRequest.send() or navigator.sendBeacon() methods. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FormData) - */ -declare class FormData { - constructor(); - /** - * The **`append()`** method of the FormData interface appends a new value onto an existing key inside a `FormData` object, or adds the key if it does not already exist. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FormData/append) - */ - append(name: string, value: string): void; - /** - * The **`append()`** method of the FormData interface appends a new value onto an existing key inside a `FormData` object, or adds the key if it does not already exist. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FormData/append) - */ - append(name: string, value: Blob, filename?: string): void; - /** - * The **`delete()`** method of the FormData interface deletes a key and its value(s) from a `FormData` object. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FormData/delete) - */ - delete(name: string): void; - /** - * The **`get()`** method of the FormData interface returns the first value associated with a given key from within a `FormData` object. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FormData/get) - */ - get(name: string): (File | string) | null; - /** - * The **`getAll()`** method of the FormData interface returns all the values associated with a given key from within a `FormData` object. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FormData/getAll) - */ - getAll(name: string): (File | string)[]; - /** - * The **`has()`** method of the FormData interface returns whether a `FormData` object contains a certain key. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FormData/has) - */ - has(name: string): boolean; - /** - * The **`set()`** method of the FormData interface sets a new value for an existing key inside a `FormData` object, or adds the key/value if it does not already exist. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FormData/set) - */ - set(name: string, value: string): void; - /** - * The **`set()`** method of the FormData interface sets a new value for an existing key inside a `FormData` object, or adds the key/value if it does not already exist. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FormData/set) - */ - set(name: string, value: Blob, filename?: string): void; - /* Returns an array of key, value pairs for every entry in the list. */ - entries(): IterableIterator<[key: string, value: File | string]>; - /* Returns a list of keys in the list. */ - keys(): IterableIterator; - /* Returns a list of values in the list. */ - values(): IterableIterator; - forEach( - callback: (this: This, value: File | string, key: string, parent: FormData) => void, - thisArg?: This, - ): void; - [Symbol.iterator](): IterableIterator<[key: string, value: File | string]>; -} -interface ContentOptions { - html?: boolean; -} -declare class HTMLRewriter { - constructor(); - on(selector: string, handlers: HTMLRewriterElementContentHandlers): HTMLRewriter; - onDocument(handlers: HTMLRewriterDocumentContentHandlers): HTMLRewriter; - transform(response: Response): Response; -} -interface HTMLRewriterElementContentHandlers { - element?(element: Element): void | Promise; - comments?(comment: Comment): void | Promise; - text?(element: Text): void | Promise; -} -interface HTMLRewriterDocumentContentHandlers { - doctype?(doctype: Doctype): void | Promise; - comments?(comment: Comment): void | Promise; - text?(text: Text): void | Promise; - end?(end: DocumentEnd): void | Promise; -} -interface Doctype { - readonly name: string | null; - readonly publicId: string | null; - readonly systemId: string | null; -} -interface Element { - tagName: string; - readonly attributes: IterableIterator; - readonly removed: boolean; - readonly namespaceURI: string; - getAttribute(name: string): string | null; - hasAttribute(name: string): boolean; - setAttribute(name: string, value: string): Element; - removeAttribute(name: string): Element; - before(content: string | ReadableStream | Response, options?: ContentOptions): Element; - after(content: string | ReadableStream | Response, options?: ContentOptions): Element; - prepend(content: string | ReadableStream | Response, options?: ContentOptions): Element; - append(content: string | ReadableStream | Response, options?: ContentOptions): Element; - replace(content: string | ReadableStream | Response, options?: ContentOptions): Element; - remove(): Element; - removeAndKeepContent(): Element; - setInnerContent(content: string | ReadableStream | Response, options?: ContentOptions): Element; - onEndTag(handler: (tag: EndTag) => void | Promise): void; -} -interface EndTag { - name: string; - before(content: string | ReadableStream | Response, options?: ContentOptions): EndTag; - after(content: string | ReadableStream | Response, options?: ContentOptions): EndTag; - remove(): EndTag; -} -interface Comment { - text: string; - readonly removed: boolean; - before(content: string, options?: ContentOptions): Comment; - after(content: string, options?: ContentOptions): Comment; - replace(content: string, options?: ContentOptions): Comment; - remove(): Comment; -} -interface Text { - readonly text: string; - readonly lastInTextNode: boolean; - readonly removed: boolean; - before(content: string | ReadableStream | Response, options?: ContentOptions): Text; - after(content: string | ReadableStream | Response, options?: ContentOptions): Text; - replace(content: string | ReadableStream | Response, options?: ContentOptions): Text; - remove(): Text; -} -interface DocumentEnd { - append(content: string, options?: ContentOptions): DocumentEnd; -} -/** - * This is the event type for `fetch` events dispatched on the ServiceWorkerGlobalScope. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FetchEvent) - */ -declare abstract class FetchEvent extends ExtendableEvent { - /** - * The **`request`** read-only property of the the event handler. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FetchEvent/request) - */ - readonly request: Request; - /** - * The **`respondWith()`** method of allows you to provide a promise for a Response yourself. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FetchEvent/respondWith) - */ - respondWith(promise: Response | Promise): void; - passThroughOnException(): void; -} -type HeadersInit = Headers | Iterable> | Record; -/** - * The **`Headers`** interface of the Fetch API allows you to perform various actions on HTTP request and response headers. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Headers) - */ -declare class Headers { - constructor(init?: HeadersInit); - /** - * The **`get()`** method of the Headers interface returns a byte string of all the values of a header within a `Headers` object with a given name. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Headers/get) - */ - get(name: string): string | null; - getAll(name: string): string[]; - /** - * The **`getSetCookie()`** method of the Headers interface returns an array containing the values of all Set-Cookie headers associated with a response. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Headers/getSetCookie) - */ - getSetCookie(): string[]; - /** - * The **`has()`** method of the Headers interface returns a boolean stating whether a `Headers` object contains a certain header. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Headers/has) - */ - has(name: string): boolean; - /** - * The **`set()`** method of the Headers interface sets a new value for an existing header inside a `Headers` object, or adds the header if it does not already exist. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Headers/set) - */ - set(name: string, value: string): void; - /** - * The **`append()`** method of the Headers interface appends a new value onto an existing header inside a `Headers` object, or adds the header if it does not already exist. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Headers/append) - */ - append(name: string, value: string): void; - /** - * The **`delete()`** method of the Headers interface deletes a header from the current `Headers` object. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Headers/delete) - */ - delete(name: string): void; - forEach( - callback: (this: This, value: string, key: string, parent: Headers) => void, - thisArg?: This, - ): void; - /* Returns an iterator allowing to go through all key/value pairs contained in this object. */ - entries(): IterableIterator<[key: string, value: string]>; - /* Returns an iterator allowing to go through all keys of the key/value pairs contained in this object. */ - keys(): IterableIterator; - /* Returns an iterator allowing to go through all values of the key/value pairs contained in this object. */ - values(): IterableIterator; - [Symbol.iterator](): IterableIterator<[key: string, value: string]>; -} -type BodyInit = - | ReadableStream - | string - | ArrayBuffer - | ArrayBufferView - | Blob - | URLSearchParams - | FormData; -declare abstract class Body { - /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/body) */ - get body(): ReadableStream | null; - /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/bodyUsed) */ - get bodyUsed(): boolean; - /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/arrayBuffer) */ - arrayBuffer(): Promise; - /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/bytes) */ - bytes(): Promise; - /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/text) */ - text(): Promise; - /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/json) */ - json(): Promise; - /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/formData) */ - formData(): Promise; - /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/blob) */ - blob(): Promise; -} -/** - * The **`Response`** interface of the Fetch API represents the response to a request. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Response) - */ -declare var Response: { - prototype: Response; - new (body?: BodyInit | null, init?: ResponseInit): Response; - error(): Response; - redirect(url: string, status?: number): Response; - json(any: any, maybeInit?: ResponseInit | Response): Response; -}; -/** - * The **`Response`** interface of the Fetch API represents the response to a request. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Response) - */ -interface Response extends Body { - /** - * The **`clone()`** method of the Response interface creates a clone of a response object, identical in every way, but stored in a different variable. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Response/clone) - */ - clone(): Response; - /** - * The **`status`** read-only property of the Response interface contains the HTTP status codes of the response. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Response/status) - */ - status: number; - /** - * The **`statusText`** read-only property of the Response interface contains the status message corresponding to the HTTP status code in Response.status. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Response/statusText) - */ - statusText: string; - /** - * The **`headers`** read-only property of the with the response. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Response/headers) - */ - headers: Headers; - /** - * The **`ok`** read-only property of the Response interface contains a Boolean stating whether the response was successful (status in the range 200-299) or not. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Response/ok) - */ - ok: boolean; - /** - * The **`redirected`** read-only property of the Response interface indicates whether or not the response is the result of a request you made which was redirected. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Response/redirected) - */ - redirected: boolean; - /** - * The **`url`** read-only property of the Response interface contains the URL of the response. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Response/url) - */ - url: string; - webSocket: WebSocket | null; - cf: any | undefined; - /** - * The **`type`** read-only property of the Response interface contains the type of the response. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Response/type) - */ - type: 'default' | 'error'; -} -interface ResponseInit { - status?: number; - statusText?: string; - headers?: HeadersInit; - cf?: any; - webSocket?: WebSocket | null; - encodeBody?: 'automatic' | 'manual'; -} -type RequestInfo> = - | Request - | string; -/** - * The **`Request`** interface of the Fetch API represents a resource request. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request) - */ -declare var Request: { - prototype: Request; - new >( - input: RequestInfo | URL, - init?: RequestInit, - ): Request; -}; -/** - * The **`Request`** interface of the Fetch API represents a resource request. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request) - */ -interface Request> extends Body { - /** - * The **`clone()`** method of the Request interface creates a copy of the current `Request` object. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/clone) - */ - clone(): Request; - /** - * The **`method`** read-only property of the `POST`, etc.) A String indicating the method of the request. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/method) - */ - method: string; - /** - * The **`url`** read-only property of the Request interface contains the URL of the request. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/url) - */ - url: string; - /** - * The **`headers`** read-only property of the with the request. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/headers) - */ - headers: Headers; - /** - * The **`redirect`** read-only property of the Request interface contains the mode for how redirects are handled. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/redirect) - */ - redirect: string; - fetcher: Fetcher | null; - /** - * The read-only **`signal`** property of the Request interface returns the AbortSignal associated with the request. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/signal) - */ - signal: AbortSignal; - cf: Cf | undefined; - /** - * The **`integrity`** read-only property of the Request interface contains the subresource integrity value of the request. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/integrity) - */ - integrity: string; - /** - * The **`keepalive`** read-only property of the Request interface contains the request's `keepalive` setting (`true` or `false`), which indicates whether the browser will keep the associated request alive if the page that initiated it is unloaded before the request is complete. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/keepalive) - */ - keepalive: boolean; - /** - * The **`cache`** read-only property of the Request interface contains the cache mode of the request. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/cache) - */ - cache?: 'no-store' | 'no-cache'; -} -interface RequestInit { - /* A string to set request's method. */ - method?: string; - /* A Headers object, an object literal, or an array of two-item arrays to set request's headers. */ - headers?: HeadersInit; - /* A BodyInit object or null to set request's body. */ - body?: BodyInit | null; - /* A string indicating whether request follows redirects, results in an error upon encountering a redirect, or returns the redirect (in an opaque fashion). Sets request's redirect. */ - redirect?: string; - fetcher?: Fetcher | null; - cf?: Cf; - /* A string indicating how the request will interact with the browser's cache to set request's cache. */ - cache?: 'no-store' | 'no-cache'; - /* A cryptographic hash of the resource to be fetched by request. Sets request's integrity. */ - integrity?: string; - /* An AbortSignal to set request's signal. */ - signal?: AbortSignal | null; - encodeResponseBody?: 'automatic' | 'manual'; -} -type Service< - T extends - | (new (...args: any[]) => Rpc.WorkerEntrypointBranded) - | Rpc.WorkerEntrypointBranded - | ExportedHandler - | undefined = undefined, -> = - T extends new (...args: any[]) => Rpc.WorkerEntrypointBranded ? Fetcher> - : T extends Rpc.WorkerEntrypointBranded ? Fetcher - : T extends Exclude ? never - : Fetcher; -type Fetcher< - T extends Rpc.EntrypointBranded | undefined = undefined, - Reserved extends string = never, -> = (T extends Rpc.EntrypointBranded ? Rpc.Provider -: unknown) & { - fetch(input: RequestInfo | URL, init?: RequestInit): Promise; - connect(address: SocketAddress | string, options?: SocketOptions): Socket; -}; -interface KVNamespaceListKey { - name: Key; - expiration?: number; - metadata?: Metadata; -} -type KVNamespaceListResult = - | { - list_complete: false; - keys: KVNamespaceListKey[]; - cursor: string; - cacheStatus: string | null; - } - | { - list_complete: true; - keys: KVNamespaceListKey[]; - cacheStatus: string | null; - }; -interface KVNamespace { - get(key: Key, options?: Partial>): Promise; - get(key: Key, type: 'text'): Promise; - get(key: Key, type: 'json'): Promise; - get(key: Key, type: 'arrayBuffer'): Promise; - get(key: Key, type: 'stream'): Promise; - get(key: Key, options?: KVNamespaceGetOptions<'text'>): Promise; - get( - key: Key, - options?: KVNamespaceGetOptions<'json'>, - ): Promise; - get(key: Key, options?: KVNamespaceGetOptions<'arrayBuffer'>): Promise; - get(key: Key, options?: KVNamespaceGetOptions<'stream'>): Promise; - get(key: Array, type: 'text'): Promise>; - get( - key: Array, - type: 'json', - ): Promise>; - get( - key: Array, - options?: Partial>, - ): Promise>; - get( - key: Array, - options?: KVNamespaceGetOptions<'text'>, - ): Promise>; - get( - key: Array, - options?: KVNamespaceGetOptions<'json'>, - ): Promise>; - list( - options?: KVNamespaceListOptions, - ): Promise>; - put( - key: Key, - value: string | ArrayBuffer | ArrayBufferView | ReadableStream, - options?: KVNamespacePutOptions, - ): Promise; - getWithMetadata( - key: Key, - options?: Partial>, - ): Promise>; - getWithMetadata( - key: Key, - type: 'text', - ): Promise>; - getWithMetadata( - key: Key, - type: 'json', - ): Promise>; - getWithMetadata( - key: Key, - type: 'arrayBuffer', - ): Promise>; - getWithMetadata( - key: Key, - type: 'stream', - ): Promise>; - getWithMetadata( - key: Key, - options: KVNamespaceGetOptions<'text'>, - ): Promise>; - getWithMetadata( - key: Key, - options: KVNamespaceGetOptions<'json'>, - ): Promise>; - getWithMetadata( - key: Key, - options: KVNamespaceGetOptions<'arrayBuffer'>, - ): Promise>; - getWithMetadata( - key: Key, - options: KVNamespaceGetOptions<'stream'>, - ): Promise>; - getWithMetadata( - key: Array, - type: 'text', - ): Promise>>; - getWithMetadata( - key: Array, - type: 'json', - ): Promise>>; - getWithMetadata( - key: Array, - options?: Partial>, - ): Promise>>; - getWithMetadata( - key: Array, - options?: KVNamespaceGetOptions<'text'>, - ): Promise>>; - getWithMetadata( - key: Array, - options?: KVNamespaceGetOptions<'json'>, - ): Promise>>; - delete(key: Key): Promise; -} -interface KVNamespaceListOptions { - limit?: number; - prefix?: string | null; - cursor?: string | null; -} -interface KVNamespaceGetOptions { - type: Type; - cacheTtl?: number; -} -interface KVNamespacePutOptions { - expiration?: number; - expirationTtl?: number; - metadata?: any | null; -} -interface KVNamespaceGetWithMetadataResult { - value: Value | null; - metadata: Metadata | null; - cacheStatus: string | null; -} -type QueueContentType = 'text' | 'bytes' | 'json' | 'v8'; -interface Queue { - send(message: Body, options?: QueueSendOptions): Promise; - sendBatch( - messages: Iterable>, - options?: QueueSendBatchOptions, - ): Promise; -} -interface QueueSendOptions { - contentType?: QueueContentType; - delaySeconds?: number; -} -interface QueueSendBatchOptions { - delaySeconds?: number; -} -interface MessageSendRequest { - body: Body; - contentType?: QueueContentType; - delaySeconds?: number; -} -interface QueueRetryOptions { - delaySeconds?: number; -} -interface Message { - readonly id: string; - readonly timestamp: Date; - readonly body: Body; - readonly attempts: number; - retry(options?: QueueRetryOptions): void; - ack(): void; -} -interface QueueEvent extends ExtendableEvent { - readonly messages: readonly Message[]; - readonly queue: string; - retryAll(options?: QueueRetryOptions): void; - ackAll(): void; -} -interface MessageBatch { - readonly messages: readonly Message[]; - readonly queue: string; - retryAll(options?: QueueRetryOptions): void; - ackAll(): void; -} -interface R2Error extends Error { - readonly name: string; - readonly code: number; - readonly message: string; - readonly action: string; - readonly stack: any; -} -interface R2ListOptions { - limit?: number; - prefix?: string; - cursor?: string; - delimiter?: string; - startAfter?: string; - include?: ('httpMetadata' | 'customMetadata')[]; -} -declare abstract class R2Bucket { - head(key: string): Promise; - get( - key: string, - options: R2GetOptions & { - onlyIf: R2Conditional | Headers; - }, - ): Promise; - get(key: string, options?: R2GetOptions): Promise; - put( - key: string, - value: ReadableStream | ArrayBuffer | ArrayBufferView | string | null | Blob, - options?: R2PutOptions & { - onlyIf: R2Conditional | Headers; - }, - ): Promise; - put( - key: string, - value: ReadableStream | ArrayBuffer | ArrayBufferView | string | null | Blob, - options?: R2PutOptions, - ): Promise; - createMultipartUpload(key: string, options?: R2MultipartOptions): Promise; - resumeMultipartUpload(key: string, uploadId: string): R2MultipartUpload; - delete(keys: string | string[]): Promise; - list(options?: R2ListOptions): Promise; -} -interface R2MultipartUpload { - readonly key: string; - readonly uploadId: string; - uploadPart( - partNumber: number, - value: ReadableStream | (ArrayBuffer | ArrayBufferView) | string | Blob, - options?: R2UploadPartOptions, - ): Promise; - abort(): Promise; - complete(uploadedParts: R2UploadedPart[]): Promise; -} -interface R2UploadedPart { - partNumber: number; - etag: string; -} -declare abstract class R2Object { - readonly key: string; - readonly version: string; - readonly size: number; - readonly etag: string; - readonly httpEtag: string; - readonly checksums: R2Checksums; - readonly uploaded: Date; - readonly httpMetadata?: R2HTTPMetadata; - readonly customMetadata?: Record; - readonly range?: R2Range; - readonly storageClass: string; - readonly ssecKeyMd5?: string; - writeHttpMetadata(headers: Headers): void; -} -interface R2ObjectBody extends R2Object { - get body(): ReadableStream; - get bodyUsed(): boolean; - arrayBuffer(): Promise; - bytes(): Promise; - text(): Promise; - json(): Promise; - blob(): Promise; -} -type R2Range = - | { - offset: number; - length?: number; - } - | { - offset?: number; - length: number; - } - | { - suffix: number; - }; -interface R2Conditional { - etagMatches?: string; - etagDoesNotMatch?: string; - uploadedBefore?: Date; - uploadedAfter?: Date; - secondsGranularity?: boolean; -} -interface R2GetOptions { - onlyIf?: R2Conditional | Headers; - range?: R2Range | Headers; - ssecKey?: ArrayBuffer | string; -} -interface R2PutOptions { - onlyIf?: R2Conditional | Headers; - httpMetadata?: R2HTTPMetadata | Headers; - customMetadata?: Record; - md5?: (ArrayBuffer | ArrayBufferView) | string; - sha1?: (ArrayBuffer | ArrayBufferView) | string; - sha256?: (ArrayBuffer | ArrayBufferView) | string; - sha384?: (ArrayBuffer | ArrayBufferView) | string; - sha512?: (ArrayBuffer | ArrayBufferView) | string; - storageClass?: string; - ssecKey?: ArrayBuffer | string; -} -interface R2MultipartOptions { - httpMetadata?: R2HTTPMetadata | Headers; - customMetadata?: Record; - storageClass?: string; - ssecKey?: ArrayBuffer | string; -} -interface R2Checksums { - readonly md5?: ArrayBuffer; - readonly sha1?: ArrayBuffer; - readonly sha256?: ArrayBuffer; - readonly sha384?: ArrayBuffer; - readonly sha512?: ArrayBuffer; - toJSON(): R2StringChecksums; -} -interface R2StringChecksums { - md5?: string; - sha1?: string; - sha256?: string; - sha384?: string; - sha512?: string; -} -interface R2HTTPMetadata { - contentType?: string; - contentLanguage?: string; - contentDisposition?: string; - contentEncoding?: string; - cacheControl?: string; - cacheExpiry?: Date; -} -type R2Objects = { - objects: R2Object[]; - delimitedPrefixes: string[]; -} & ( - | { - truncated: true; - cursor: string; - } - | { - truncated: false; - } -); -interface R2UploadPartOptions { - ssecKey?: ArrayBuffer | string; -} -declare abstract class ScheduledEvent extends ExtendableEvent { - readonly scheduledTime: number; - readonly cron: string; - noRetry(): void; -} -interface ScheduledController { - readonly scheduledTime: number; - readonly cron: string; - noRetry(): void; -} -interface QueuingStrategy { - highWaterMark?: number | bigint; - size?: (chunk: T) => number | bigint; -} -interface UnderlyingSink { - type?: string; - start?: (controller: WritableStreamDefaultController) => void | Promise; - write?: (chunk: W, controller: WritableStreamDefaultController) => void | Promise; - abort?: (reason: any) => void | Promise; - close?: () => void | Promise; -} -interface UnderlyingByteSource { - type: 'bytes'; - autoAllocateChunkSize?: number; - start?: (controller: ReadableByteStreamController) => void | Promise; - pull?: (controller: ReadableByteStreamController) => void | Promise; - cancel?: (reason: any) => void | Promise; -} -interface UnderlyingSource { - type?: '' | undefined; - start?: (controller: ReadableStreamDefaultController) => void | Promise; - pull?: (controller: ReadableStreamDefaultController) => void | Promise; - cancel?: (reason: any) => void | Promise; - expectedLength?: number | bigint; -} -interface Transformer { - readableType?: string; - writableType?: string; - start?: (controller: TransformStreamDefaultController) => void | Promise; - transform?: (chunk: I, controller: TransformStreamDefaultController) => void | Promise; - flush?: (controller: TransformStreamDefaultController) => void | Promise; - cancel?: (reason: any) => void | Promise; - expectedLength?: number; -} -interface StreamPipeOptions { - /** - * Pipes this readable stream to a given writable stream destination. The way in which the piping process behaves under various error conditions can be customized with a number of passed options. It returns a promise that fulfills when the piping process completes successfully, or rejects if any errors were encountered. - * - * Piping a stream will lock it for the duration of the pipe, preventing any other consumer from acquiring a reader. - * - * Errors and closures of the source and destination streams propagate as follows: - * - * An error in this source readable stream will abort destination, unless preventAbort is truthy. The returned promise will be rejected with the source's error, or with any error that occurs during aborting the destination. - * - * An error in destination will cancel this source readable stream, unless preventCancel is truthy. The returned promise will be rejected with the destination's error, or with any error that occurs during canceling the source. - * - * When this source readable stream closes, destination will be closed, unless preventClose is truthy. The returned promise will be fulfilled once this process completes, unless an error is encountered while closing the destination, in which case it will be rejected with that error. - * - * If destination starts out closed or closing, this source readable stream will be canceled, unless preventCancel is true. The returned promise will be rejected with an error indicating piping to a closed stream failed, or with any error that occurs during canceling the source. - * - * The signal option can be set to an AbortSignal to allow aborting an ongoing pipe operation via the corresponding AbortController. In this case, this source readable stream will be canceled, and destination aborted, unless the respective options preventCancel or preventAbort are set. - */ - preventClose?: boolean; - preventAbort?: boolean; - preventCancel?: boolean; - signal?: AbortSignal; -} -type ReadableStreamReadResult = - | { - done: false; - value: R; - } - | { - done: true; - value?: undefined; - }; -/** - * The `ReadableStream` interface of the Streams API represents a readable stream of byte data. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStream) - */ -interface ReadableStream { - /** - * The **`locked`** read-only property of the ReadableStream interface returns whether or not the readable stream is locked to a reader. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStream/locked) - */ - get locked(): boolean; - /** - * The **`cancel()`** method of the ReadableStream interface returns a Promise that resolves when the stream is canceled. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStream/cancel) - */ - cancel(reason?: any): Promise; - /** - * The **`getReader()`** method of the ReadableStream interface creates a reader and locks the stream to it. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStream/getReader) - */ - getReader(): ReadableStreamDefaultReader; - /** - * The **`getReader()`** method of the ReadableStream interface creates a reader and locks the stream to it. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStream/getReader) - */ - getReader(options: ReadableStreamGetReaderOptions): ReadableStreamBYOBReader; - /** - * The **`pipeThrough()`** method of the ReadableStream interface provides a chainable way of piping the current stream through a transform stream or any other writable/readable pair. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStream/pipeThrough) - */ - pipeThrough( - transform: ReadableWritablePair, - options?: StreamPipeOptions, - ): ReadableStream; - /** - * The **`pipeTo()`** method of the ReadableStream interface pipes the current `ReadableStream` to a given WritableStream and returns a Promise that fulfills when the piping process completes successfully, or rejects if any errors were encountered. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStream/pipeTo) - */ - pipeTo(destination: WritableStream, options?: StreamPipeOptions): Promise; - /** - * The **`tee()`** method of the two-element array containing the two resulting branches as new ReadableStream instances. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStream/tee) - */ - tee(): [ReadableStream, ReadableStream]; - values(options?: ReadableStreamValuesOptions): AsyncIterableIterator; - [Symbol.asyncIterator](options?: ReadableStreamValuesOptions): AsyncIterableIterator; -} -/** - * The `ReadableStream` interface of the Streams API represents a readable stream of byte data. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStream) - */ -declare const ReadableStream: { - prototype: ReadableStream; - new ( - underlyingSource: UnderlyingByteSource, - strategy?: QueuingStrategy, - ): ReadableStream; - new ( - underlyingSource?: UnderlyingSource, - strategy?: QueuingStrategy, - ): ReadableStream; -}; -/** - * The **`ReadableStreamDefaultReader`** interface of the Streams API represents a default reader that can be used to read stream data supplied from a network (such as a fetch request). - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamDefaultReader) - */ -declare class ReadableStreamDefaultReader { - constructor(stream: ReadableStream); - get closed(): Promise; - cancel(reason?: any): Promise; - /** - * The **`read()`** method of the ReadableStreamDefaultReader interface returns a Promise providing access to the next chunk in the stream's internal queue. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamDefaultReader/read) - */ - read(): Promise>; - /** - * The **`releaseLock()`** method of the ReadableStreamDefaultReader interface releases the reader's lock on the stream. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamDefaultReader/releaseLock) - */ - releaseLock(): void; -} -/** - * The `ReadableStreamBYOBReader` interface of the Streams API defines a reader for a ReadableStream that supports zero-copy reading from an underlying byte source. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamBYOBReader) - */ -declare class ReadableStreamBYOBReader { - constructor(stream: ReadableStream); - get closed(): Promise; - cancel(reason?: any): Promise; - /** - * The **`read()`** method of the ReadableStreamBYOBReader interface is used to read data into a view on a user-supplied buffer from an associated readable byte stream. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamBYOBReader/read) - */ - read(view: T): Promise>; - /** - * The **`releaseLock()`** method of the ReadableStreamBYOBReader interface releases the reader's lock on the stream. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamBYOBReader/releaseLock) - */ - releaseLock(): void; - readAtLeast( - minElements: number, - view: T, - ): Promise>; -} -interface ReadableStreamBYOBReaderReadableStreamBYOBReaderReadOptions { - min?: number; -} -interface ReadableStreamGetReaderOptions { - /** - * Creates a ReadableStreamBYOBReader and locks the stream to the new reader. - * - * This call behaves the same way as the no-argument variant, except that it only works on readable byte streams, i.e. streams which were constructed specifically with the ability to handle "bring your own buffer" reading. The returned BYOB reader provides the ability to directly read individual chunks from the stream via its read() method, into developer-supplied buffers, allowing more precise control over allocation. - */ - mode: 'byob'; -} -/** - * The **`ReadableStreamBYOBRequest`** interface of the Streams API represents a 'pull request' for data from an underlying source that will made as a zero-copy transfer to a consumer (bypassing the stream's internal queues). - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamBYOBRequest) - */ -declare abstract class ReadableStreamBYOBRequest { - /** - * The **`view`** getter property of the ReadableStreamBYOBRequest interface returns the current view. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamBYOBRequest/view) - */ - get view(): Uint8Array | null; - /** - * The **`respond()`** method of the ReadableStreamBYOBRequest interface is used to signal to the associated readable byte stream that the specified number of bytes were written into the ReadableStreamBYOBRequest.view. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamBYOBRequest/respond) - */ - respond(bytesWritten: number): void; - /** - * The **`respondWithNewView()`** method of the ReadableStreamBYOBRequest interface specifies a new view that the consumer of the associated readable byte stream should write to instead of ReadableStreamBYOBRequest.view. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamBYOBRequest/respondWithNewView) - */ - respondWithNewView(view: ArrayBuffer | ArrayBufferView): void; - get atLeast(): number | null; -} -/** - * The **`ReadableStreamDefaultController`** interface of the Streams API represents a controller allowing control of a ReadableStream's state and internal queue. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamDefaultController) - */ -declare abstract class ReadableStreamDefaultController { - /** - * The **`desiredSize`** read-only property of the required to fill the stream's internal queue. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamDefaultController/desiredSize) - */ - get desiredSize(): number | null; - /** - * The **`close()`** method of the ReadableStreamDefaultController interface closes the associated stream. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamDefaultController/close) - */ - close(): void; - /** - * The **`enqueue()`** method of the ```js-nolint enqueue(chunk) ``` - `chunk` - : The chunk to enqueue. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamDefaultController/enqueue) - */ - enqueue(chunk?: R): void; - /** - * The **`error()`** method of the with the associated stream to error. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamDefaultController/error) - */ - error(reason: any): void; -} -/** - * The **`ReadableByteStreamController`** interface of the Streams API represents a controller for a readable byte stream. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableByteStreamController) - */ -declare abstract class ReadableByteStreamController { - /** - * The **`byobRequest`** read-only property of the ReadableByteStreamController interface returns the current BYOB request, or `null` if there are no pending requests. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableByteStreamController/byobRequest) - */ - get byobRequest(): ReadableStreamBYOBRequest | null; - /** - * The **`desiredSize`** read-only property of the ReadableByteStreamController interface returns the number of bytes required to fill the stream's internal queue to its 'desired size'. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableByteStreamController/desiredSize) - */ - get desiredSize(): number | null; - /** - * The **`close()`** method of the ReadableByteStreamController interface closes the associated stream. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableByteStreamController/close) - */ - close(): void; - /** - * The **`enqueue()`** method of the ReadableByteStreamController interface enqueues a given chunk on the associated readable byte stream (the chunk is copied into the stream's internal queues). - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableByteStreamController/enqueue) - */ - enqueue(chunk: ArrayBuffer | ArrayBufferView): void; - /** - * The **`error()`** method of the ReadableByteStreamController interface causes any future interactions with the associated stream to error with the specified reason. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableByteStreamController/error) - */ - error(reason: any): void; -} -/** - * The **`WritableStreamDefaultController`** interface of the Streams API represents a controller allowing control of a WritableStream's state. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultController) - */ -declare abstract class WritableStreamDefaultController { - /** - * The read-only **`signal`** property of the WritableStreamDefaultController interface returns the AbortSignal associated with the controller. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultController/signal) - */ - get signal(): AbortSignal; - /** - * The **`error()`** method of the with the associated stream to error. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultController/error) - */ - error(reason?: any): void; -} -/** - * The **`TransformStreamDefaultController`** interface of the Streams API provides methods to manipulate the associated ReadableStream and WritableStream. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TransformStreamDefaultController) - */ -declare abstract class TransformStreamDefaultController { - /** - * The **`desiredSize`** read-only property of the TransformStreamDefaultController interface returns the desired size to fill the queue of the associated ReadableStream. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TransformStreamDefaultController/desiredSize) - */ - get desiredSize(): number | null; - /** - * The **`enqueue()`** method of the TransformStreamDefaultController interface enqueues the given chunk in the readable side of the stream. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TransformStreamDefaultController/enqueue) - */ - enqueue(chunk?: O): void; - /** - * The **`error()`** method of the TransformStreamDefaultController interface errors both sides of the stream. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TransformStreamDefaultController/error) - */ - error(reason: any): void; - /** - * The **`terminate()`** method of the TransformStreamDefaultController interface closes the readable side and errors the writable side of the stream. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TransformStreamDefaultController/terminate) - */ - terminate(): void; -} -interface ReadableWritablePair { - /** - * Provides a convenient, chainable way of piping this readable stream through a transform stream (or any other { writable, readable } pair). It simply pipes the stream into the writable side of the supplied pair, and returns the readable side for further use. - * - * Piping a stream will lock it for the duration of the pipe, preventing any other consumer from acquiring a reader. - */ - writable: WritableStream; - readable: ReadableStream; -} -/** - * The **`WritableStream`** interface of the Streams API provides a standard abstraction for writing streaming data to a destination, known as a sink. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStream) - */ -declare class WritableStream { - constructor(underlyingSink?: UnderlyingSink, queuingStrategy?: QueuingStrategy); - /** - * The **`locked`** read-only property of the WritableStream interface returns a boolean indicating whether the `WritableStream` is locked to a writer. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStream/locked) - */ - get locked(): boolean; - /** - * The **`abort()`** method of the WritableStream interface aborts the stream, signaling that the producer can no longer successfully write to the stream and it is to be immediately moved to an error state, with any queued writes discarded. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStream/abort) - */ - abort(reason?: any): Promise; - /** - * The **`close()`** method of the WritableStream interface closes the associated stream. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStream/close) - */ - close(): Promise; - /** - * The **`getWriter()`** method of the WritableStream interface returns a new instance of WritableStreamDefaultWriter and locks the stream to that instance. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStream/getWriter) - */ - getWriter(): WritableStreamDefaultWriter; -} -/** - * The **`WritableStreamDefaultWriter`** interface of the Streams API is the object returned by WritableStream.getWriter() and once created locks the writer to the `WritableStream` ensuring that no other streams can write to the underlying sink. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultWriter) - */ -declare class WritableStreamDefaultWriter { - constructor(stream: WritableStream); - /** - * The **`closed`** read-only property of the the stream errors or the writer's lock is released. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultWriter/closed) - */ - get closed(): Promise; - /** - * The **`ready`** read-only property of the that resolves when the desired size of the stream's internal queue transitions from non-positive to positive, signaling that it is no longer applying backpressure. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultWriter/ready) - */ - get ready(): Promise; - /** - * The **`desiredSize`** read-only property of the to fill the stream's internal queue. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultWriter/desiredSize) - */ - get desiredSize(): number | null; - /** - * The **`abort()`** method of the the producer can no longer successfully write to the stream and it is to be immediately moved to an error state, with any queued writes discarded. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultWriter/abort) - */ - abort(reason?: any): Promise; - /** - * The **`close()`** method of the stream. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultWriter/close) - */ - close(): Promise; - /** - * The **`write()`** method of the operation. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultWriter/write) - */ - write(chunk?: W): Promise; - /** - * The **`releaseLock()`** method of the corresponding stream. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultWriter/releaseLock) - */ - releaseLock(): void; -} -/** - * The **`TransformStream`** interface of the Streams API represents a concrete implementation of the pipe chain _transform stream_ concept. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TransformStream) - */ -declare class TransformStream { - constructor( - transformer?: Transformer, - writableStrategy?: QueuingStrategy, - readableStrategy?: QueuingStrategy, - ); - /** - * The **`readable`** read-only property of the TransformStream interface returns the ReadableStream instance controlled by this `TransformStream`. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TransformStream/readable) - */ - get readable(): ReadableStream; - /** - * The **`writable`** read-only property of the TransformStream interface returns the WritableStream instance controlled by this `TransformStream`. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TransformStream/writable) - */ - get writable(): WritableStream; -} -declare class FixedLengthStream extends IdentityTransformStream { - constructor( - expectedLength: number | bigint, - queuingStrategy?: IdentityTransformStreamQueuingStrategy, - ); -} -declare class IdentityTransformStream extends TransformStream< - ArrayBuffer | ArrayBufferView, - Uint8Array -> { - constructor(queuingStrategy?: IdentityTransformStreamQueuingStrategy); -} -interface IdentityTransformStreamQueuingStrategy { - highWaterMark?: number | bigint; -} -interface ReadableStreamValuesOptions { - preventCancel?: boolean; -} -/** - * The **`CompressionStream`** interface of the Compression Streams API is an API for compressing a stream of data. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CompressionStream) - */ -declare class CompressionStream extends TransformStream { - constructor(format: 'gzip' | 'deflate' | 'deflate-raw'); -} -/** - * The **`DecompressionStream`** interface of the Compression Streams API is an API for decompressing a stream of data. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/DecompressionStream) - */ -declare class DecompressionStream extends TransformStream< - ArrayBuffer | ArrayBufferView, - Uint8Array -> { - constructor(format: 'gzip' | 'deflate' | 'deflate-raw'); -} -/** - * The **`TextEncoderStream`** interface of the Encoding API converts a stream of strings into bytes in the UTF-8 encoding. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TextEncoderStream) - */ -declare class TextEncoderStream extends TransformStream { - constructor(); - get encoding(): string; -} -/** - * The **`TextDecoderStream`** interface of the Encoding API converts a stream of text in a binary encoding, such as UTF-8 etc., to a stream of strings. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TextDecoderStream) - */ -declare class TextDecoderStream extends TransformStream { - constructor(label?: string, options?: TextDecoderStreamTextDecoderStreamInit); - get encoding(): string; - get fatal(): boolean; - get ignoreBOM(): boolean; -} -interface TextDecoderStreamTextDecoderStreamInit { - fatal?: boolean; - ignoreBOM?: boolean; -} -/** - * The **`ByteLengthQueuingStrategy`** interface of the Streams API provides a built-in byte length queuing strategy that can be used when constructing streams. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ByteLengthQueuingStrategy) - */ -declare class ByteLengthQueuingStrategy implements QueuingStrategy { - constructor(init: QueuingStrategyInit); - /** - * The read-only **`ByteLengthQueuingStrategy.highWaterMark`** property returns the total number of bytes that can be contained in the internal queue before backpressure is applied. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ByteLengthQueuingStrategy/highWaterMark) - */ - get highWaterMark(): number; - /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ByteLengthQueuingStrategy/size) */ - get size(): (chunk?: any) => number; -} -/** - * The **`CountQueuingStrategy`** interface of the Streams API provides a built-in chunk counting queuing strategy that can be used when constructing streams. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CountQueuingStrategy) - */ -declare class CountQueuingStrategy implements QueuingStrategy { - constructor(init: QueuingStrategyInit); - /** - * The read-only **`CountQueuingStrategy.highWaterMark`** property returns the total number of chunks that can be contained in the internal queue before backpressure is applied. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CountQueuingStrategy/highWaterMark) - */ - get highWaterMark(): number; - /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/CountQueuingStrategy/size) */ - get size(): (chunk?: any) => number; -} -interface QueuingStrategyInit { - /** - * Creates a new ByteLengthQueuingStrategy with the provided high water mark. - * - * Note that the provided high water mark will not be validated ahead of time. Instead, if it is negative, NaN, or not a number, the resulting ByteLengthQueuingStrategy will cause the corresponding stream constructor to throw. - */ - highWaterMark: number; -} -interface ScriptVersion { - id?: string; - tag?: string; - message?: string; -} -declare abstract class TailEvent extends ExtendableEvent { - readonly events: TraceItem[]; - readonly traces: TraceItem[]; -} -interface TraceItem { - readonly event: - | ( - | TraceItemFetchEventInfo - | TraceItemJsRpcEventInfo - | TraceItemScheduledEventInfo - | TraceItemAlarmEventInfo - | TraceItemQueueEventInfo - | TraceItemEmailEventInfo - | TraceItemTailEventInfo - | TraceItemCustomEventInfo - | TraceItemHibernatableWebSocketEventInfo - ) - | null; - readonly eventTimestamp: number | null; - readonly logs: TraceLog[]; - readonly exceptions: TraceException[]; - readonly diagnosticsChannelEvents: TraceDiagnosticChannelEvent[]; - readonly scriptName: string | null; - readonly entrypoint?: string; - readonly scriptVersion?: ScriptVersion; - readonly dispatchNamespace?: string; - readonly scriptTags?: string[]; - readonly durableObjectId?: string; - readonly outcome: string; - readonly executionModel: string; - readonly truncated: boolean; - readonly cpuTime: number; - readonly wallTime: number; -} -interface TraceItemAlarmEventInfo { - readonly scheduledTime: Date; -} -interface TraceItemCustomEventInfo {} -interface TraceItemScheduledEventInfo { - readonly scheduledTime: number; - readonly cron: string; -} -interface TraceItemQueueEventInfo { - readonly queue: string; - readonly batchSize: number; -} -interface TraceItemEmailEventInfo { - readonly mailFrom: string; - readonly rcptTo: string; - readonly rawSize: number; -} -interface TraceItemTailEventInfo { - readonly consumedEvents: TraceItemTailEventInfoTailItem[]; -} -interface TraceItemTailEventInfoTailItem { - readonly scriptName: string | null; -} -interface TraceItemFetchEventInfo { - readonly response?: TraceItemFetchEventInfoResponse; - readonly request: TraceItemFetchEventInfoRequest; -} -interface TraceItemFetchEventInfoRequest { - readonly cf?: any; - readonly headers: Record; - readonly method: string; - readonly url: string; - getUnredacted(): TraceItemFetchEventInfoRequest; -} -interface TraceItemFetchEventInfoResponse { - readonly status: number; -} -interface TraceItemJsRpcEventInfo { - readonly rpcMethod: string; -} -interface TraceItemHibernatableWebSocketEventInfo { - readonly getWebSocketEvent: - | TraceItemHibernatableWebSocketEventInfoMessage - | TraceItemHibernatableWebSocketEventInfoClose - | TraceItemHibernatableWebSocketEventInfoError; -} -interface TraceItemHibernatableWebSocketEventInfoMessage { - readonly webSocketEventType: string; -} -interface TraceItemHibernatableWebSocketEventInfoClose { - readonly webSocketEventType: string; - readonly code: number; - readonly wasClean: boolean; -} -interface TraceItemHibernatableWebSocketEventInfoError { - readonly webSocketEventType: string; -} -interface TraceLog { - readonly timestamp: number; - readonly level: string; - readonly message: any; -} -interface TraceException { - readonly timestamp: number; - readonly message: string; - readonly name: string; - readonly stack?: string; -} -interface TraceDiagnosticChannelEvent { - readonly timestamp: number; - readonly channel: string; - readonly message: any; -} -interface TraceMetrics { - readonly cpuTime: number; - readonly wallTime: number; -} -interface UnsafeTraceMetrics { - fromTrace(item: TraceItem): TraceMetrics; -} -/** - * The **`URL`** interface is used to parse, construct, normalize, and encode URL. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL) - */ -declare class URL { - constructor(url: string | URL, base?: string | URL); - /** - * The **`origin`** read-only property of the URL interface returns a string containing the Unicode serialization of the origin of the represented URL. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/origin) - */ - get origin(): string; - /** - * The **`href`** property of the URL interface is a string containing the whole URL. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/href) - */ - get href(): string; - /** - * The **`href`** property of the URL interface is a string containing the whole URL. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/href) - */ - set href(value: string); - /** - * The **`protocol`** property of the URL interface is a string containing the protocol or scheme of the URL, including the final `':'`. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/protocol) - */ - get protocol(): string; - /** - * The **`protocol`** property of the URL interface is a string containing the protocol or scheme of the URL, including the final `':'`. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/protocol) - */ - set protocol(value: string); - /** - * The **`username`** property of the URL interface is a string containing the username component of the URL. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/username) - */ - get username(): string; - /** - * The **`username`** property of the URL interface is a string containing the username component of the URL. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/username) - */ - set username(value: string); - /** - * The **`password`** property of the URL interface is a string containing the password component of the URL. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/password) - */ - get password(): string; - /** - * The **`password`** property of the URL interface is a string containing the password component of the URL. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/password) - */ - set password(value: string); - /** - * The **`host`** property of the URL interface is a string containing the host, which is the URL.hostname, and then, if the port of the URL is nonempty, a `':'`, followed by the URL.port of the URL. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/host) - */ - get host(): string; - /** - * The **`host`** property of the URL interface is a string containing the host, which is the URL.hostname, and then, if the port of the URL is nonempty, a `':'`, followed by the URL.port of the URL. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/host) - */ - set host(value: string); - /** - * The **`hostname`** property of the URL interface is a string containing either the domain name or IP address of the URL. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/hostname) - */ - get hostname(): string; - /** - * The **`hostname`** property of the URL interface is a string containing either the domain name or IP address of the URL. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/hostname) - */ - set hostname(value: string); - /** - * The **`port`** property of the URL interface is a string containing the port number of the URL. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/port) - */ - get port(): string; - /** - * The **`port`** property of the URL interface is a string containing the port number of the URL. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/port) - */ - set port(value: string); - /** - * The **`pathname`** property of the URL interface represents a location in a hierarchical structure. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/pathname) - */ - get pathname(): string; - /** - * The **`pathname`** property of the URL interface represents a location in a hierarchical structure. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/pathname) - */ - set pathname(value: string); - /** - * The **`search`** property of the URL interface is a search string, also called a _query string_, that is a string containing a `'?'` followed by the parameters of the URL. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/search) - */ - get search(): string; - /** - * The **`search`** property of the URL interface is a search string, also called a _query string_, that is a string containing a `'?'` followed by the parameters of the URL. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/search) - */ - set search(value: string); - /** - * The **`hash`** property of the URL interface is a string containing a `'#'` followed by the fragment identifier of the URL. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/hash) - */ - get hash(): string; - /** - * The **`hash`** property of the URL interface is a string containing a `'#'` followed by the fragment identifier of the URL. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/hash) - */ - set hash(value: string); - /** - * The **`searchParams`** read-only property of the access to the [MISSING: httpmethod('GET')] decoded query arguments contained in the URL. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/searchParams) - */ - get searchParams(): URLSearchParams; - /** - * The **`toJSON()`** method of the URL interface returns a string containing a serialized version of the URL, although in practice it seems to have the same effect as ```js-nolint toJSON() ``` None. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/toJSON) - */ - toJSON(): string; - /*function toString() { [native code] }*/ - toString(): string; - /** - * The **`URL.canParse()`** static method of the URL interface returns a boolean indicating whether or not an absolute URL, or a relative URL combined with a base URL, are parsable and valid. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/canParse_static) - */ - static canParse(url: string, base?: string): boolean; - /** - * The **`URL.parse()`** static method of the URL interface returns a newly created URL object representing the URL defined by the parameters. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/parse_static) - */ - static parse(url: string, base?: string): URL | null; - /** - * The **`createObjectURL()`** static method of the URL interface creates a string containing a URL representing the object given in the parameter. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/createObjectURL_static) - */ - static createObjectURL(object: File | Blob): string; - /** - * The **`revokeObjectURL()`** static method of the URL interface releases an existing object URL which was previously created by calling Call this method when you've finished using an object URL to let the browser know not to keep the reference to the file any longer. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/revokeObjectURL_static) - */ - static revokeObjectURL(object_url: string): void; -} -/** - * The **`URLSearchParams`** interface defines utility methods to work with the query string of a URL. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URLSearchParams) - */ -declare class URLSearchParams { - constructor(init?: Iterable> | Record | string); - /** - * The **`size`** read-only property of the URLSearchParams interface indicates the total number of search parameter entries. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URLSearchParams/size) - */ - get size(): number; - /** - * The **`append()`** method of the URLSearchParams interface appends a specified key/value pair as a new search parameter. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URLSearchParams/append) - */ - append(name: string, value: string): void; - /** - * The **`delete()`** method of the URLSearchParams interface deletes specified parameters and their associated value(s) from the list of all search parameters. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URLSearchParams/delete) - */ - delete(name: string, value?: string): void; - /** - * The **`get()`** method of the URLSearchParams interface returns the first value associated to the given search parameter. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URLSearchParams/get) - */ - get(name: string): string | null; - /** - * The **`getAll()`** method of the URLSearchParams interface returns all the values associated with a given search parameter as an array. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URLSearchParams/getAll) - */ - getAll(name: string): string[]; - /** - * The **`has()`** method of the URLSearchParams interface returns a boolean value that indicates whether the specified parameter is in the search parameters. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URLSearchParams/has) - */ - has(name: string, value?: string): boolean; - /** - * The **`set()`** method of the URLSearchParams interface sets the value associated with a given search parameter to the given value. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URLSearchParams/set) - */ - set(name: string, value: string): void; - /** - * The **`URLSearchParams.sort()`** method sorts all key/value pairs contained in this object in place and returns `undefined`. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URLSearchParams/sort) - */ - sort(): void; - /* Returns an array of key, value pairs for every entry in the search params. */ - entries(): IterableIterator<[key: string, value: string]>; - /* Returns a list of keys in the search params. */ - keys(): IterableIterator; - /* Returns a list of values in the search params. */ - values(): IterableIterator; - forEach( - callback: (this: This, value: string, key: string, parent: URLSearchParams) => void, - thisArg?: This, - ): void; - /*function toString() { [native code] }*/ - toString(): string; - [Symbol.iterator](): IterableIterator<[key: string, value: string]>; -} -declare class URLPattern { - constructor( - input?: string | URLPatternInit, - baseURL?: string | URLPatternOptions, - patternOptions?: URLPatternOptions, - ); - get protocol(): string; - get username(): string; - get password(): string; - get hostname(): string; - get port(): string; - get pathname(): string; - get search(): string; - get hash(): string; - get hasRegExpGroups(): boolean; - test(input?: string | URLPatternInit, baseURL?: string): boolean; - exec(input?: string | URLPatternInit, baseURL?: string): URLPatternResult | null; -} -interface URLPatternInit { - protocol?: string; - username?: string; - password?: string; - hostname?: string; - port?: string; - pathname?: string; - search?: string; - hash?: string; - baseURL?: string; -} -interface URLPatternComponentResult { - input: string; - groups: Record; -} -interface URLPatternResult { - inputs: (string | URLPatternInit)[]; - protocol: URLPatternComponentResult; - username: URLPatternComponentResult; - password: URLPatternComponentResult; - hostname: URLPatternComponentResult; - port: URLPatternComponentResult; - pathname: URLPatternComponentResult; - search: URLPatternComponentResult; - hash: URLPatternComponentResult; -} -interface URLPatternOptions { - ignoreCase?: boolean; -} -/** - * A `CloseEvent` is sent to clients using WebSockets when the connection is closed. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CloseEvent) - */ -declare class CloseEvent extends Event { - constructor(type: string, initializer?: CloseEventInit); - /** - * The **`code`** read-only property of the CloseEvent interface returns a WebSocket connection close code indicating the reason the connection was closed. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CloseEvent/code) - */ - readonly code: number; - /** - * The **`reason`** read-only property of the CloseEvent interface returns the WebSocket connection close reason the server gave for closing the connection; that is, a concise human-readable prose explanation for the closure. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CloseEvent/reason) - */ - readonly reason: string; - /** - * The **`wasClean`** read-only property of the CloseEvent interface returns `true` if the connection closed cleanly. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CloseEvent/wasClean) - */ - readonly wasClean: boolean; -} -interface CloseEventInit { - code?: number; - reason?: string; - wasClean?: boolean; -} -type WebSocketEventMap = { - close: CloseEvent; - message: MessageEvent; - open: Event; - error: ErrorEvent; -}; -/** - * The `WebSocket` object provides the API for creating and managing a WebSocket connection to a server, as well as for sending and receiving data on the connection. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebSocket) - */ -declare var WebSocket: { - prototype: WebSocket; - new (url: string, protocols?: string[] | string): WebSocket; - readonly READY_STATE_CONNECTING: number; - readonly CONNECTING: number; - readonly READY_STATE_OPEN: number; - readonly OPEN: number; - readonly READY_STATE_CLOSING: number; - readonly CLOSING: number; - readonly READY_STATE_CLOSED: number; - readonly CLOSED: number; -}; -/** - * The `WebSocket` object provides the API for creating and managing a WebSocket connection to a server, as well as for sending and receiving data on the connection. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebSocket) - */ -interface WebSocket extends EventTarget { - accept(): void; - /** - * The **`WebSocket.send()`** method enqueues the specified data to be transmitted to the server over the WebSocket connection, increasing the value of `bufferedAmount` by the number of bytes needed to contain the data. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebSocket/send) - */ - send(message: (ArrayBuffer | ArrayBufferView) | string): void; - /** - * The **`WebSocket.close()`** method closes the already `CLOSED`, this method does nothing. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebSocket/close) - */ - close(code?: number, reason?: string): void; - serializeAttachment(attachment: any): void; - deserializeAttachment(): any | null; - /** - * The **`WebSocket.readyState`** read-only property returns the current state of the WebSocket connection. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebSocket/readyState) - */ - readyState: number; - /** - * The **`WebSocket.url`** read-only property returns the absolute URL of the WebSocket as resolved by the constructor. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebSocket/url) - */ - url: string | null; - /** - * The **`WebSocket.protocol`** read-only property returns the name of the sub-protocol the server selected; this will be one of the strings specified in the `protocols` parameter when creating the WebSocket object, or the empty string if no connection is established. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebSocket/protocol) - */ - protocol: string | null; - /** - * The **`WebSocket.extensions`** read-only property returns the extensions selected by the server. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebSocket/extensions) - */ - extensions: string | null; -} -declare const WebSocketPair: { - new (): { - 0: WebSocket; - 1: WebSocket; - }; -}; -interface SqlStorage { - exec>( - query: string, - ...bindings: any[] - ): SqlStorageCursor; - get databaseSize(): number; - Cursor: typeof SqlStorageCursor; - Statement: typeof SqlStorageStatement; -} -declare abstract class SqlStorageStatement {} -type SqlStorageValue = ArrayBuffer | string | number | null; -declare abstract class SqlStorageCursor> { - next(): - | { - done?: false; - value: T; - } - | { - done: true; - value?: never; - }; - toArray(): T[]; - one(): T; - raw(): IterableIterator; - columnNames: string[]; - get rowsRead(): number; - get rowsWritten(): number; - [Symbol.iterator](): IterableIterator; -} -interface Socket { - get readable(): ReadableStream; - get writable(): WritableStream; - get closed(): Promise; - get opened(): Promise; - get upgraded(): boolean; - get secureTransport(): 'on' | 'off' | 'starttls'; - close(): Promise; - startTls(options?: TlsOptions): Socket; -} -interface SocketOptions { - secureTransport?: string; - allowHalfOpen: boolean; - highWaterMark?: number | bigint; -} -interface SocketAddress { - hostname: string; - port: number; -} -interface TlsOptions { - expectedServerHostname?: string; -} -interface SocketInfo { - remoteAddress?: string; - localAddress?: string; -} -/** - * The **`EventSource`** interface is web content's interface to server-sent events. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource) - */ -declare class EventSource extends EventTarget { - constructor(url: string, init?: EventSourceEventSourceInit); - /** - * The **`close()`** method of the EventSource interface closes the connection, if one is made, and sets the ```js-nolint close() ``` None. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/close) - */ - close(): void; - /** - * The **`url`** read-only property of the URL of the source. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/url) - */ - get url(): string; - /** - * The **`withCredentials`** read-only property of the the `EventSource` object was instantiated with CORS credentials set. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/withCredentials) - */ - get withCredentials(): boolean; - /** - * The **`readyState`** read-only property of the connection. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/readyState) - */ - get readyState(): number; - /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/open_event) */ - get onopen(): any | null; - /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/open_event) */ - set onopen(value: any | null); - /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/message_event) */ - get onmessage(): any | null; - /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/message_event) */ - set onmessage(value: any | null); - /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/error_event) */ - get onerror(): any | null; - /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/error_event) */ - set onerror(value: any | null); - static readonly CONNECTING: number; - static readonly OPEN: number; - static readonly CLOSED: number; - static from(stream: ReadableStream): EventSource; -} -interface EventSourceEventSourceInit { - withCredentials?: boolean; - fetcher?: Fetcher; -} -interface Container { - get running(): boolean; - start(options?: ContainerStartupOptions): void; - monitor(): Promise; - destroy(error?: any): Promise; - signal(signo: number): void; - getTcpPort(port: number): Fetcher; - setInactivityTimeout(durationMs: number | bigint): Promise; -} -interface ContainerStartupOptions { - entrypoint?: string[]; - enableInternet: boolean; - env?: Record; - hardTimeout?: number | bigint; -} -/** - * The **`MessagePort`** interface of the Channel Messaging API represents one of the two ports of a MessageChannel, allowing messages to be sent from one port and listening out for them arriving at the other. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessagePort) - */ -declare abstract class MessagePort extends EventTarget { - /** - * The **`postMessage()`** method of the transfers ownership of objects to other browsing contexts. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessagePort/postMessage) - */ - postMessage(data?: any, options?: any[] | MessagePortPostMessageOptions): void; - /** - * The **`close()`** method of the MessagePort interface disconnects the port, so it is no longer active. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessagePort/close) - */ - close(): void; - /** - * The **`start()`** method of the MessagePort interface starts the sending of messages queued on the port. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessagePort/start) - */ - start(): void; - get onmessage(): any | null; - set onmessage(value: any | null); -} -/** - * The **`MessageChannel`** interface of the Channel Messaging API allows us to create a new message channel and send data through it via its two MessagePort properties. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessageChannel) - */ -declare class MessageChannel { - constructor(); - /** - * The **`port1`** read-only property of the the port attached to the context that originated the channel. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessageChannel/port1) - */ - readonly port1: MessagePort; - /** - * The **`port2`** read-only property of the the port attached to the context at the other end of the channel, which the message is initially sent to. - * - * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessageChannel/port2) - */ - readonly port2: MessagePort; -} -interface MessagePortPostMessageOptions { - transfer?: any[]; -} -type LoopbackForExport< - T extends - | (new (...args: any[]) => Rpc.EntrypointBranded) - | ExportedHandler - | undefined = undefined, -> = - T extends new (...args: any[]) => Rpc.WorkerEntrypointBranded ? - LoopbackServiceStub> - : T extends new (...args: any[]) => Rpc.DurableObjectBranded ? - LoopbackDurableObjectClass> - : T extends ExportedHandler ? LoopbackServiceStub - : undefined; -type LoopbackServiceStub = - Fetcher & - (T extends CloudflareWorkersModule.WorkerEntrypoint ? - (opts: { props?: Props }) => Fetcher - : (opts: { props?: any }) => Fetcher); -type LoopbackDurableObjectClass = - DurableObjectClass & - (T extends CloudflareWorkersModule.DurableObject ? - (opts: { props?: Props }) => DurableObjectClass - : (opts: { props?: any }) => DurableObjectClass); -interface SyncKvStorage { - get(key: string): T | undefined; - list(options?: SyncKvListOptions): Iterable<[string, T]>; - put(key: string, value: T): void; - delete(key: string): boolean; -} -interface SyncKvListOptions { - start?: string; - startAfter?: string; - end?: string; - prefix?: string; - reverse?: boolean; - limit?: number; -} -interface WorkerStub { - getEntrypoint( - name?: string, - options?: WorkerStubEntrypointOptions, - ): Fetcher; -} -interface WorkerStubEntrypointOptions { - props?: any; -} -interface WorkerLoader { - get( - name: string | null, - getCode: () => WorkerLoaderWorkerCode | Promise, - ): WorkerStub; -} -interface WorkerLoaderModule { - js?: string; - cjs?: string; - text?: string; - data?: ArrayBuffer; - json?: any; - py?: string; - wasm?: ArrayBuffer; -} -interface WorkerLoaderWorkerCode { - compatibilityDate: string; - compatibilityFlags?: string[]; - allowExperimental?: boolean; - mainModule: string; - modules: Record; - env?: any; - globalOutbound?: Fetcher | null; - tails?: Fetcher[]; - streamingTails?: Fetcher[]; -} -/** - * The Workers runtime supports a subset of the Performance API, used to measure timing and performance, - * as well as timing of subrequests and other operations. - * - * [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/performance/) - */ -declare abstract class Performance { - /* [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/performance/#performancetimeorigin) */ - get timeOrigin(): number; - /* [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/performance/#performancenow) */ - now(): number; -} -type AiImageClassificationInput = { - image: number[]; -}; -type AiImageClassificationOutput = { - score?: number; - label?: string; -}[]; -declare abstract class BaseAiImageClassification { - inputs: AiImageClassificationInput; - postProcessedOutputs: AiImageClassificationOutput; -} -type AiImageToTextInput = { - image: number[]; - prompt?: string; - max_tokens?: number; - temperature?: number; - top_p?: number; - top_k?: number; - seed?: number; - repetition_penalty?: number; - frequency_penalty?: number; - presence_penalty?: number; - raw?: boolean; - messages?: RoleScopedChatInput[]; -}; -type AiImageToTextOutput = { - description: string; -}; -declare abstract class BaseAiImageToText { - inputs: AiImageToTextInput; - postProcessedOutputs: AiImageToTextOutput; -} -type AiImageTextToTextInput = { - image: string; - prompt?: string; - max_tokens?: number; - temperature?: number; - ignore_eos?: boolean; - top_p?: number; - top_k?: number; - seed?: number; - repetition_penalty?: number; - frequency_penalty?: number; - presence_penalty?: number; - raw?: boolean; - messages?: RoleScopedChatInput[]; -}; -type AiImageTextToTextOutput = { - description: string; -}; -declare abstract class BaseAiImageTextToText { - inputs: AiImageTextToTextInput; - postProcessedOutputs: AiImageTextToTextOutput; -} -type AiMultimodalEmbeddingsInput = { - image: string; - text: string[]; -}; -type AiIMultimodalEmbeddingsOutput = { - data: number[][]; - shape: number[]; -}; -declare abstract class BaseAiMultimodalEmbeddings { - inputs: AiImageTextToTextInput; - postProcessedOutputs: AiImageTextToTextOutput; -} -type AiObjectDetectionInput = { - image: number[]; -}; -type AiObjectDetectionOutput = { - score?: number; - label?: string; -}[]; -declare abstract class BaseAiObjectDetection { - inputs: AiObjectDetectionInput; - postProcessedOutputs: AiObjectDetectionOutput; -} -type AiSentenceSimilarityInput = { - source: string; - sentences: string[]; -}; -type AiSentenceSimilarityOutput = number[]; -declare abstract class BaseAiSentenceSimilarity { - inputs: AiSentenceSimilarityInput; - postProcessedOutputs: AiSentenceSimilarityOutput; -} -type AiAutomaticSpeechRecognitionInput = { - audio: number[]; -}; -type AiAutomaticSpeechRecognitionOutput = { - text?: string; - words?: { - word: string; - start: number; - end: number; - }[]; - vtt?: string; -}; -declare abstract class BaseAiAutomaticSpeechRecognition { - inputs: AiAutomaticSpeechRecognitionInput; - postProcessedOutputs: AiAutomaticSpeechRecognitionOutput; -} -type AiSummarizationInput = { - input_text: string; - max_length?: number; -}; -type AiSummarizationOutput = { - summary: string; -}; -declare abstract class BaseAiSummarization { - inputs: AiSummarizationInput; - postProcessedOutputs: AiSummarizationOutput; -} -type AiTextClassificationInput = { - text: string; -}; -type AiTextClassificationOutput = { - score?: number; - label?: string; -}[]; -declare abstract class BaseAiTextClassification { - inputs: AiTextClassificationInput; - postProcessedOutputs: AiTextClassificationOutput; -} -type AiTextEmbeddingsInput = { - text: string | string[]; -}; -type AiTextEmbeddingsOutput = { - shape: number[]; - data: number[][]; -}; -declare abstract class BaseAiTextEmbeddings { - inputs: AiTextEmbeddingsInput; - postProcessedOutputs: AiTextEmbeddingsOutput; -} -type RoleScopedChatInput = { - role: 'user' | 'assistant' | 'system' | 'tool' | (string & NonNullable); - content: string; - name?: string; -}; -type AiTextGenerationToolLegacyInput = { - name: string; - description: string; - parameters?: { - type: 'object' | (string & NonNullable); - properties: { - [key: string]: { - type: string; - description?: string; - }; - }; - required: string[]; - }; -}; -type AiTextGenerationToolInput = { - type: 'function' | (string & NonNullable); - function: { - name: string; - description: string; - parameters?: { - type: 'object' | (string & NonNullable); - properties: { - [key: string]: { - type: string; - description?: string; - }; - }; - required: string[]; - }; - }; -}; -type AiTextGenerationFunctionsInput = { - name: string; - code: string; -}; -type AiTextGenerationResponseFormat = { - type: string; - json_schema?: any; -}; -type AiTextGenerationInput = { - prompt?: string; - raw?: boolean; - stream?: boolean; - max_tokens?: number; - temperature?: number; - top_p?: number; - top_k?: number; - seed?: number; - repetition_penalty?: number; - frequency_penalty?: number; - presence_penalty?: number; - messages?: RoleScopedChatInput[]; - response_format?: AiTextGenerationResponseFormat; - tools?: - | AiTextGenerationToolInput[] - | AiTextGenerationToolLegacyInput[] - | (object & NonNullable); - functions?: AiTextGenerationFunctionsInput[]; -}; -type AiTextGenerationToolLegacyOutput = { - name: string; - arguments: unknown; -}; -type AiTextGenerationToolOutput = { - id: string; - type: 'function'; - function: { - name: string; - arguments: string; - }; -}; -type UsageTags = { - prompt_tokens: number; - completion_tokens: number; - total_tokens: number; -}; -type AiTextGenerationOutput = { - response?: string; - tool_calls?: AiTextGenerationToolLegacyOutput[] & AiTextGenerationToolOutput[]; - usage?: UsageTags; -}; -declare abstract class BaseAiTextGeneration { - inputs: AiTextGenerationInput; - postProcessedOutputs: AiTextGenerationOutput; -} -type AiTextToSpeechInput = { - prompt: string; - lang?: string; -}; -type AiTextToSpeechOutput = - | Uint8Array - | { - audio: string; - }; -declare abstract class BaseAiTextToSpeech { - inputs: AiTextToSpeechInput; - postProcessedOutputs: AiTextToSpeechOutput; -} -type AiTextToImageInput = { - prompt: string; - negative_prompt?: string; - height?: number; - width?: number; - image?: number[]; - image_b64?: string; - mask?: number[]; - num_steps?: number; - strength?: number; - guidance?: number; - seed?: number; -}; -type AiTextToImageOutput = ReadableStream; -declare abstract class BaseAiTextToImage { - inputs: AiTextToImageInput; - postProcessedOutputs: AiTextToImageOutput; -} -type AiTranslationInput = { - text: string; - target_lang: string; - source_lang?: string; -}; -type AiTranslationOutput = { - translated_text?: string; -}; -declare abstract class BaseAiTranslation { - inputs: AiTranslationInput; - postProcessedOutputs: AiTranslationOutput; -} -/** - * Workers AI support for OpenAI's Responses API - * Reference: https://github.com/openai/openai-node/blob/master/src/resources/responses/responses.ts - * - * It's a stripped down version from its source. - * It currently supports basic function calling, json mode and accepts images as input. - * - * It does not include types for WebSearch, CodeInterpreter, FileInputs, MCP, CustomTools. - * We plan to add those incrementally as model + platform capabilities evolve. - */ -type ResponsesInput = { - background?: boolean | null; - conversation?: string | ResponseConversationParam | null; - include?: Array | null; - input?: string | ResponseInput; - instructions?: string | null; - max_output_tokens?: number | null; - parallel_tool_calls?: boolean | null; - previous_response_id?: string | null; - prompt_cache_key?: string; - reasoning?: Reasoning | null; - safety_identifier?: string; - service_tier?: 'auto' | 'default' | 'flex' | 'scale' | 'priority' | null; - stream?: boolean | null; - stream_options?: StreamOptions | null; - temperature?: number | null; - text?: ResponseTextConfig; - tool_choice?: ToolChoiceOptions | ToolChoiceFunction; - tools?: Array; - top_p?: number | null; - truncation?: 'auto' | 'disabled' | null; -}; -type ResponsesOutput = { - id?: string; - created_at?: number; - output_text?: string; - error?: ResponseError | null; - incomplete_details?: ResponseIncompleteDetails | null; - instructions?: string | Array | null; - object?: 'response'; - output?: Array; - parallel_tool_calls?: boolean; - temperature?: number | null; - tool_choice?: ToolChoiceOptions | ToolChoiceFunction; - tools?: Array; - top_p?: number | null; - max_output_tokens?: number | null; - previous_response_id?: string | null; - prompt?: ResponsePrompt | null; - reasoning?: Reasoning | null; - safety_identifier?: string; - service_tier?: 'auto' | 'default' | 'flex' | 'scale' | 'priority' | null; - status?: ResponseStatus; - text?: ResponseTextConfig; - truncation?: 'auto' | 'disabled' | null; - usage?: ResponseUsage; -}; -type EasyInputMessage = { - content: string | ResponseInputMessageContentList; - role: 'user' | 'assistant' | 'system' | 'developer'; - type?: 'message'; -}; -type ResponsesFunctionTool = { - name: string; - parameters: { - [key: string]: unknown; - } | null; - strict: boolean | null; - type: 'function'; - description?: string | null; -}; -type ResponseIncompleteDetails = { - reason?: 'max_output_tokens' | 'content_filter'; -}; -type ResponsePrompt = { - id: string; - variables?: { - [key: string]: string | ResponseInputText | ResponseInputImage; - } | null; - version?: string | null; -}; -type Reasoning = { - effort?: ReasoningEffort | null; - generate_summary?: 'auto' | 'concise' | 'detailed' | null; - summary?: 'auto' | 'concise' | 'detailed' | null; -}; -type ResponseContent = - | ResponseInputText - | ResponseInputImage - | ResponseOutputText - | ResponseOutputRefusal - | ResponseContentReasoningText; -type ResponseContentReasoningText = { - text: string; - type: 'reasoning_text'; -}; -type ResponseConversationParam = { - id: string; -}; -type ResponseCreatedEvent = { - response: Response; - sequence_number: number; - type: 'response.created'; -}; -type ResponseCustomToolCallOutput = { - call_id: string; - output: string | Array; - type: 'custom_tool_call_output'; - id?: string; -}; -type ResponseError = { - code: - | 'server_error' - | 'rate_limit_exceeded' - | 'invalid_prompt' - | 'vector_store_timeout' - | 'invalid_image' - | 'invalid_image_format' - | 'invalid_base64_image' - | 'invalid_image_url' - | 'image_too_large' - | 'image_too_small' - | 'image_parse_error' - | 'image_content_policy_violation' - | 'invalid_image_mode' - | 'image_file_too_large' - | 'unsupported_image_media_type' - | 'empty_image_file' - | 'failed_to_download_image' - | 'image_file_not_found'; - message: string; -}; -type ResponseErrorEvent = { - code: string | null; - message: string; - param: string | null; - sequence_number: number; - type: 'error'; -}; -type ResponseFailedEvent = { - response: Response; - sequence_number: number; - type: 'response.failed'; -}; -type ResponseFormatText = { - type: 'text'; -}; -type ResponseFormatJSONObject = { - type: 'json_object'; -}; -type ResponseFormatTextConfig = - | ResponseFormatText - | ResponseFormatTextJSONSchemaConfig - | ResponseFormatJSONObject; -type ResponseFormatTextJSONSchemaConfig = { - name: string; - schema: { - [key: string]: unknown; - }; - type: 'json_schema'; - description?: string; - strict?: boolean | null; -}; -type ResponseFunctionCallArgumentsDeltaEvent = { - delta: string; - item_id: string; - output_index: number; - sequence_number: number; - type: 'response.function_call_arguments.delta'; -}; -type ResponseFunctionCallArgumentsDoneEvent = { - arguments: string; - item_id: string; - name: string; - output_index: number; - sequence_number: number; - type: 'response.function_call_arguments.done'; -}; -type ResponseFunctionCallOutputItem = ResponseInputTextContent | ResponseInputImageContent; -type ResponseFunctionCallOutputItemList = Array; -type ResponseFunctionToolCall = { - arguments: string; - call_id: string; - name: string; - type: 'function_call'; - id?: string; - status?: 'in_progress' | 'completed' | 'incomplete'; -}; -interface ResponseFunctionToolCallItem extends ResponseFunctionToolCall { - id: string; -} -type ResponseFunctionToolCallOutputItem = { - id: string; - call_id: string; - output: string | Array; - type: 'function_call_output'; - status?: 'in_progress' | 'completed' | 'incomplete'; -}; -type ResponseIncludable = 'message.input_image.image_url' | 'message.output_text.logprobs'; -type ResponseIncompleteEvent = { - response: Response; - sequence_number: number; - type: 'response.incomplete'; -}; -type ResponseInput = Array; -type ResponseInputContent = ResponseInputText | ResponseInputImage; -type ResponseInputImage = { - detail: 'low' | 'high' | 'auto'; - type: 'input_image'; - /** - * Base64 encoded image - */ - image_url?: string | null; -}; -type ResponseInputImageContent = { - type: 'input_image'; - detail?: 'low' | 'high' | 'auto' | null; - /** - * Base64 encoded image - */ - image_url?: string | null; -}; -type ResponseInputItem = - | EasyInputMessage - | ResponseInputItemMessage - | ResponseOutputMessage - | ResponseFunctionToolCall - | ResponseInputItemFunctionCallOutput - | ResponseReasoningItem; -type ResponseInputItemFunctionCallOutput = { - call_id: string; - output: string | ResponseFunctionCallOutputItemList; - type: 'function_call_output'; - id?: string | null; - status?: 'in_progress' | 'completed' | 'incomplete' | null; -}; -type ResponseInputItemMessage = { - content: ResponseInputMessageContentList; - role: 'user' | 'system' | 'developer'; - status?: 'in_progress' | 'completed' | 'incomplete'; - type?: 'message'; -}; -type ResponseInputMessageContentList = Array; -type ResponseInputMessageItem = { - id: string; - content: ResponseInputMessageContentList; - role: 'user' | 'system' | 'developer'; - status?: 'in_progress' | 'completed' | 'incomplete'; - type?: 'message'; -}; -type ResponseInputText = { - text: string; - type: 'input_text'; -}; -type ResponseInputTextContent = { - text: string; - type: 'input_text'; -}; -type ResponseItem = - | ResponseInputMessageItem - | ResponseOutputMessage - | ResponseFunctionToolCallItem - | ResponseFunctionToolCallOutputItem; -type ResponseOutputItem = ResponseOutputMessage | ResponseFunctionToolCall | ResponseReasoningItem; -type ResponseOutputItemAddedEvent = { - item: ResponseOutputItem; - output_index: number; - sequence_number: number; - type: 'response.output_item.added'; -}; -type ResponseOutputItemDoneEvent = { - item: ResponseOutputItem; - output_index: number; - sequence_number: number; - type: 'response.output_item.done'; -}; -type ResponseOutputMessage = { - id: string; - content: Array; - role: 'assistant'; - status: 'in_progress' | 'completed' | 'incomplete'; - type: 'message'; -}; -type ResponseOutputRefusal = { - refusal: string; - type: 'refusal'; -}; -type ResponseOutputText = { - text: string; - type: 'output_text'; - logprobs?: Array; -}; -type ResponseReasoningItem = { - id: string; - summary: Array; - type: 'reasoning'; - content?: Array; - encrypted_content?: string | null; - status?: 'in_progress' | 'completed' | 'incomplete'; -}; -type ResponseReasoningSummaryItem = { - text: string; - type: 'summary_text'; -}; -type ResponseReasoningContentItem = { - text: string; - type: 'reasoning_text'; -}; -type ResponseReasoningTextDeltaEvent = { - content_index: number; - delta: string; - item_id: string; - output_index: number; - sequence_number: number; - type: 'response.reasoning_text.delta'; -}; -type ResponseReasoningTextDoneEvent = { - content_index: number; - item_id: string; - output_index: number; - sequence_number: number; - text: string; - type: 'response.reasoning_text.done'; -}; -type ResponseRefusalDeltaEvent = { - content_index: number; - delta: string; - item_id: string; - output_index: number; - sequence_number: number; - type: 'response.refusal.delta'; -}; -type ResponseRefusalDoneEvent = { - content_index: number; - item_id: string; - output_index: number; - refusal: string; - sequence_number: number; - type: 'response.refusal.done'; -}; -type ResponseStatus = - | 'completed' - | 'failed' - | 'in_progress' - | 'cancelled' - | 'queued' - | 'incomplete'; -type ResponseStreamEvent = - | ResponseCompletedEvent - | ResponseCreatedEvent - | ResponseErrorEvent - | ResponseFunctionCallArgumentsDeltaEvent - | ResponseFunctionCallArgumentsDoneEvent - | ResponseFailedEvent - | ResponseIncompleteEvent - | ResponseOutputItemAddedEvent - | ResponseOutputItemDoneEvent - | ResponseReasoningTextDeltaEvent - | ResponseReasoningTextDoneEvent - | ResponseRefusalDeltaEvent - | ResponseRefusalDoneEvent - | ResponseTextDeltaEvent - | ResponseTextDoneEvent; -type ResponseCompletedEvent = { - response: Response; - sequence_number: number; - type: 'response.completed'; -}; -type ResponseTextConfig = { - format?: ResponseFormatTextConfig; - verbosity?: 'low' | 'medium' | 'high' | null; -}; -type ResponseTextDeltaEvent = { - content_index: number; - delta: string; - item_id: string; - logprobs: Array; - output_index: number; - sequence_number: number; - type: 'response.output_text.delta'; -}; -type ResponseTextDoneEvent = { - content_index: number; - item_id: string; - logprobs: Array; - output_index: number; - sequence_number: number; - text: string; - type: 'response.output_text.done'; -}; -type Logprob = { - token: string; - logprob: number; - top_logprobs?: Array; -}; -type TopLogprob = { - token?: string; - logprob?: number; -}; -type ResponseUsage = { - input_tokens: number; - output_tokens: number; - total_tokens: number; -}; -type Tool = ResponsesFunctionTool; -type ToolChoiceFunction = { - name: string; - type: 'function'; -}; -type ToolChoiceOptions = 'none'; -type ReasoningEffort = 'minimal' | 'low' | 'medium' | 'high' | null; -type StreamOptions = { - include_obfuscation?: boolean; -}; -type Ai_Cf_Baai_Bge_Base_En_V1_5_Input = - | { - text: string | string[]; - /** - * The pooling method used in the embedding process. `cls` pooling will generate more accurate embeddings on larger inputs - however, embeddings created with cls pooling are not compatible with embeddings generated with mean pooling. The default pooling method is `mean` in order for this to not be a breaking change, but we highly suggest using the new `cls` pooling for better accuracy. - */ - pooling?: 'mean' | 'cls'; - } - | { - /** - * Batch of the embeddings requests to run using async-queue - */ - requests: { - text: string | string[]; - /** - * The pooling method used in the embedding process. `cls` pooling will generate more accurate embeddings on larger inputs - however, embeddings created with cls pooling are not compatible with embeddings generated with mean pooling. The default pooling method is `mean` in order for this to not be a breaking change, but we highly suggest using the new `cls` pooling for better accuracy. - */ - pooling?: 'mean' | 'cls'; - }[]; - }; -type Ai_Cf_Baai_Bge_Base_En_V1_5_Output = - | { - shape?: number[]; - /** - * Embeddings of the requested text values - */ - data?: number[][]; - /** - * The pooling method used in the embedding process. - */ - pooling?: 'mean' | 'cls'; - } - | Ai_Cf_Baai_Bge_Base_En_V1_5_AsyncResponse; -interface Ai_Cf_Baai_Bge_Base_En_V1_5_AsyncResponse { - /** - * The async request id that can be used to obtain the results. - */ - request_id?: string; -} -declare abstract class Base_Ai_Cf_Baai_Bge_Base_En_V1_5 { - inputs: Ai_Cf_Baai_Bge_Base_En_V1_5_Input; - postProcessedOutputs: Ai_Cf_Baai_Bge_Base_En_V1_5_Output; -} -type Ai_Cf_Openai_Whisper_Input = - | string - | { - /** - * An array of integers that represent the audio data constrained to 8-bit unsigned integer values - */ - audio: number[]; - }; -interface Ai_Cf_Openai_Whisper_Output { - /** - * The transcription - */ - text: string; - word_count?: number; - words?: { - word?: string; - /** - * The second this word begins in the recording - */ - start?: number; - /** - * The ending second when the word completes - */ - end?: number; - }[]; - vtt?: string; -} -declare abstract class Base_Ai_Cf_Openai_Whisper { - inputs: Ai_Cf_Openai_Whisper_Input; - postProcessedOutputs: Ai_Cf_Openai_Whisper_Output; -} -type Ai_Cf_Meta_M2M100_1_2B_Input = - | { - /** - * The text to be translated - */ - text: string; - /** - * The language code of the source text (e.g., 'en' for English). Defaults to 'en' if not specified - */ - source_lang?: string; - /** - * The language code to translate the text into (e.g., 'es' for Spanish) - */ - target_lang: string; - } - | { - /** - * Batch of the embeddings requests to run using async-queue - */ - requests: { - /** - * The text to be translated - */ - text: string; - /** - * The language code of the source text (e.g., 'en' for English). Defaults to 'en' if not specified - */ - source_lang?: string; - /** - * The language code to translate the text into (e.g., 'es' for Spanish) - */ - target_lang: string; - }[]; - }; -type Ai_Cf_Meta_M2M100_1_2B_Output = - | { - /** - * The translated text in the target language - */ - translated_text?: string; - } - | Ai_Cf_Meta_M2M100_1_2B_AsyncResponse; -interface Ai_Cf_Meta_M2M100_1_2B_AsyncResponse { - /** - * The async request id that can be used to obtain the results. - */ - request_id?: string; -} -declare abstract class Base_Ai_Cf_Meta_M2M100_1_2B { - inputs: Ai_Cf_Meta_M2M100_1_2B_Input; - postProcessedOutputs: Ai_Cf_Meta_M2M100_1_2B_Output; -} -type Ai_Cf_Baai_Bge_Small_En_V1_5_Input = - | { - text: string | string[]; - /** - * The pooling method used in the embedding process. `cls` pooling will generate more accurate embeddings on larger inputs - however, embeddings created with cls pooling are not compatible with embeddings generated with mean pooling. The default pooling method is `mean` in order for this to not be a breaking change, but we highly suggest using the new `cls` pooling for better accuracy. - */ - pooling?: 'mean' | 'cls'; - } - | { - /** - * Batch of the embeddings requests to run using async-queue - */ - requests: { - text: string | string[]; - /** - * The pooling method used in the embedding process. `cls` pooling will generate more accurate embeddings on larger inputs - however, embeddings created with cls pooling are not compatible with embeddings generated with mean pooling. The default pooling method is `mean` in order for this to not be a breaking change, but we highly suggest using the new `cls` pooling for better accuracy. - */ - pooling?: 'mean' | 'cls'; - }[]; - }; -type Ai_Cf_Baai_Bge_Small_En_V1_5_Output = - | { - shape?: number[]; - /** - * Embeddings of the requested text values - */ - data?: number[][]; - /** - * The pooling method used in the embedding process. - */ - pooling?: 'mean' | 'cls'; - } - | Ai_Cf_Baai_Bge_Small_En_V1_5_AsyncResponse; -interface Ai_Cf_Baai_Bge_Small_En_V1_5_AsyncResponse { - /** - * The async request id that can be used to obtain the results. - */ - request_id?: string; -} -declare abstract class Base_Ai_Cf_Baai_Bge_Small_En_V1_5 { - inputs: Ai_Cf_Baai_Bge_Small_En_V1_5_Input; - postProcessedOutputs: Ai_Cf_Baai_Bge_Small_En_V1_5_Output; -} -type Ai_Cf_Baai_Bge_Large_En_V1_5_Input = - | { - text: string | string[]; - /** - * The pooling method used in the embedding process. `cls` pooling will generate more accurate embeddings on larger inputs - however, embeddings created with cls pooling are not compatible with embeddings generated with mean pooling. The default pooling method is `mean` in order for this to not be a breaking change, but we highly suggest using the new `cls` pooling for better accuracy. - */ - pooling?: 'mean' | 'cls'; - } - | { - /** - * Batch of the embeddings requests to run using async-queue - */ - requests: { - text: string | string[]; - /** - * The pooling method used in the embedding process. `cls` pooling will generate more accurate embeddings on larger inputs - however, embeddings created with cls pooling are not compatible with embeddings generated with mean pooling. The default pooling method is `mean` in order for this to not be a breaking change, but we highly suggest using the new `cls` pooling for better accuracy. - */ - pooling?: 'mean' | 'cls'; - }[]; - }; -type Ai_Cf_Baai_Bge_Large_En_V1_5_Output = - | { - shape?: number[]; - /** - * Embeddings of the requested text values - */ - data?: number[][]; - /** - * The pooling method used in the embedding process. - */ - pooling?: 'mean' | 'cls'; - } - | Ai_Cf_Baai_Bge_Large_En_V1_5_AsyncResponse; -interface Ai_Cf_Baai_Bge_Large_En_V1_5_AsyncResponse { - /** - * The async request id that can be used to obtain the results. - */ - request_id?: string; -} -declare abstract class Base_Ai_Cf_Baai_Bge_Large_En_V1_5 { - inputs: Ai_Cf_Baai_Bge_Large_En_V1_5_Input; - postProcessedOutputs: Ai_Cf_Baai_Bge_Large_En_V1_5_Output; -} -type Ai_Cf_Unum_Uform_Gen2_Qwen_500M_Input = - | string - | { - /** - * The input text prompt for the model to generate a response. - */ - prompt?: string; - /** - * If true, a chat template is not applied and you must adhere to the specific model's expected formatting. - */ - raw?: boolean; - /** - * Controls the creativity of the AI's responses by adjusting how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses. - */ - top_p?: number; - /** - * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises. - */ - top_k?: number; - /** - * Random seed for reproducibility of the generation. - */ - seed?: number; - /** - * Penalty for repeated tokens; higher values discourage repetition. - */ - repetition_penalty?: number; - /** - * Decreases the likelihood of the model repeating the same lines verbatim. - */ - frequency_penalty?: number; - /** - * Increases the likelihood of the model introducing new topics. - */ - presence_penalty?: number; - image: number[] | (string & NonNullable); - /** - * The maximum number of tokens to generate in the response. - */ - max_tokens?: number; - }; -interface Ai_Cf_Unum_Uform_Gen2_Qwen_500M_Output { - description?: string; -} -declare abstract class Base_Ai_Cf_Unum_Uform_Gen2_Qwen_500M { - inputs: Ai_Cf_Unum_Uform_Gen2_Qwen_500M_Input; - postProcessedOutputs: Ai_Cf_Unum_Uform_Gen2_Qwen_500M_Output; -} -type Ai_Cf_Openai_Whisper_Tiny_En_Input = - | string - | { - /** - * An array of integers that represent the audio data constrained to 8-bit unsigned integer values - */ - audio: number[]; - }; -interface Ai_Cf_Openai_Whisper_Tiny_En_Output { - /** - * The transcription - */ - text: string; - word_count?: number; - words?: { - word?: string; - /** - * The second this word begins in the recording - */ - start?: number; - /** - * The ending second when the word completes - */ - end?: number; - }[]; - vtt?: string; -} -declare abstract class Base_Ai_Cf_Openai_Whisper_Tiny_En { - inputs: Ai_Cf_Openai_Whisper_Tiny_En_Input; - postProcessedOutputs: Ai_Cf_Openai_Whisper_Tiny_En_Output; -} -interface Ai_Cf_Openai_Whisper_Large_V3_Turbo_Input { - /** - * Base64 encoded value of the audio data. - */ - audio: string; - /** - * Supported tasks are 'translate' or 'transcribe'. - */ - task?: string; - /** - * The language of the audio being transcribed or translated. - */ - language?: string; - /** - * Preprocess the audio with a voice activity detection model. - */ - vad_filter?: boolean; - /** - * A text prompt to help provide context to the model on the contents of the audio. - */ - initial_prompt?: string; - /** - * The prefix it appended the the beginning of the output of the transcription and can guide the transcription result. - */ - prefix?: string; -} -interface Ai_Cf_Openai_Whisper_Large_V3_Turbo_Output { - transcription_info?: { - /** - * The language of the audio being transcribed or translated. - */ - language?: string; - /** - * The confidence level or probability of the detected language being accurate, represented as a decimal between 0 and 1. - */ - language_probability?: number; - /** - * The total duration of the original audio file, in seconds. - */ - duration?: number; - /** - * The duration of the audio after applying Voice Activity Detection (VAD) to remove silent or irrelevant sections, in seconds. - */ - duration_after_vad?: number; - }; - /** - * The complete transcription of the audio. - */ - text: string; - /** - * The total number of words in the transcription. - */ - word_count?: number; - segments?: { - /** - * The starting time of the segment within the audio, in seconds. - */ - start?: number; - /** - * The ending time of the segment within the audio, in seconds. - */ - end?: number; - /** - * The transcription of the segment. - */ - text?: string; - /** - * The temperature used in the decoding process, controlling randomness in predictions. Lower values result in more deterministic outputs. - */ - temperature?: number; - /** - * The average log probability of the predictions for the words in this segment, indicating overall confidence. - */ - avg_logprob?: number; - /** - * The compression ratio of the input to the output, measuring how much the text was compressed during the transcription process. - */ - compression_ratio?: number; - /** - * The probability that the segment contains no speech, represented as a decimal between 0 and 1. - */ - no_speech_prob?: number; - words?: { - /** - * The individual word transcribed from the audio. - */ - word?: string; - /** - * The starting time of the word within the audio, in seconds. - */ - start?: number; - /** - * The ending time of the word within the audio, in seconds. - */ - end?: number; - }[]; - }[]; - /** - * The transcription in WebVTT format, which includes timing and text information for use in subtitles. - */ - vtt?: string; -} -declare abstract class Base_Ai_Cf_Openai_Whisper_Large_V3_Turbo { - inputs: Ai_Cf_Openai_Whisper_Large_V3_Turbo_Input; - postProcessedOutputs: Ai_Cf_Openai_Whisper_Large_V3_Turbo_Output; -} -type Ai_Cf_Baai_Bge_M3_Input = - | Ai_Cf_Baai_Bge_M3_Input_QueryAnd_Contexts - | Ai_Cf_Baai_Bge_M3_Input_Embedding - | { - /** - * Batch of the embeddings requests to run using async-queue - */ - requests: ( - | Ai_Cf_Baai_Bge_M3_Input_QueryAnd_Contexts_1 - | Ai_Cf_Baai_Bge_M3_Input_Embedding_1 - )[]; - }; -interface Ai_Cf_Baai_Bge_M3_Input_QueryAnd_Contexts { - /** - * A query you wish to perform against the provided contexts. If no query is provided the model with respond with embeddings for contexts - */ - query?: string; - /** - * List of provided contexts. Note that the index in this array is important, as the response will refer to it. - */ - contexts: { - /** - * One of the provided context content - */ - text?: string; - }[]; - /** - * When provided with too long context should the model error out or truncate the context to fit? - */ - truncate_inputs?: boolean; -} -interface Ai_Cf_Baai_Bge_M3_Input_Embedding { - text: string | string[]; - /** - * When provided with too long context should the model error out or truncate the context to fit? - */ - truncate_inputs?: boolean; -} -interface Ai_Cf_Baai_Bge_M3_Input_QueryAnd_Contexts_1 { - /** - * A query you wish to perform against the provided contexts. If no query is provided the model with respond with embeddings for contexts - */ - query?: string; - /** - * List of provided contexts. Note that the index in this array is important, as the response will refer to it. - */ - contexts: { - /** - * One of the provided context content - */ - text?: string; - }[]; - /** - * When provided with too long context should the model error out or truncate the context to fit? - */ - truncate_inputs?: boolean; -} -interface Ai_Cf_Baai_Bge_M3_Input_Embedding_1 { - text: string | string[]; - /** - * When provided with too long context should the model error out or truncate the context to fit? - */ - truncate_inputs?: boolean; -} -type Ai_Cf_Baai_Bge_M3_Output = - | Ai_Cf_Baai_Bge_M3_Ouput_Query - | Ai_Cf_Baai_Bge_M3_Output_EmbeddingFor_Contexts - | Ai_Cf_Baai_Bge_M3_Ouput_Embedding - | Ai_Cf_Baai_Bge_M3_AsyncResponse; -interface Ai_Cf_Baai_Bge_M3_Ouput_Query { - response?: { - /** - * Index of the context in the request - */ - id?: number; - /** - * Score of the context under the index. - */ - score?: number; - }[]; -} -interface Ai_Cf_Baai_Bge_M3_Output_EmbeddingFor_Contexts { - response?: number[][]; - shape?: number[]; - /** - * The pooling method used in the embedding process. - */ - pooling?: 'mean' | 'cls'; -} -interface Ai_Cf_Baai_Bge_M3_Ouput_Embedding { - shape?: number[]; - /** - * Embeddings of the requested text values - */ - data?: number[][]; - /** - * The pooling method used in the embedding process. - */ - pooling?: 'mean' | 'cls'; -} -interface Ai_Cf_Baai_Bge_M3_AsyncResponse { - /** - * The async request id that can be used to obtain the results. - */ - request_id?: string; -} -declare abstract class Base_Ai_Cf_Baai_Bge_M3 { - inputs: Ai_Cf_Baai_Bge_M3_Input; - postProcessedOutputs: Ai_Cf_Baai_Bge_M3_Output; -} -interface Ai_Cf_Black_Forest_Labs_Flux_1_Schnell_Input { - /** - * A text description of the image you want to generate. - */ - prompt: string; - /** - * The number of diffusion steps; higher values can improve quality but take longer. - */ - steps?: number; -} -interface Ai_Cf_Black_Forest_Labs_Flux_1_Schnell_Output { - /** - * The generated image in Base64 format. - */ - image?: string; -} -declare abstract class Base_Ai_Cf_Black_Forest_Labs_Flux_1_Schnell { - inputs: Ai_Cf_Black_Forest_Labs_Flux_1_Schnell_Input; - postProcessedOutputs: Ai_Cf_Black_Forest_Labs_Flux_1_Schnell_Output; -} -type Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct_Input = - | Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct_Prompt - | Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct_Messages; -interface Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct_Prompt { - /** - * The input text prompt for the model to generate a response. - */ - prompt: string; - image?: number[] | (string & NonNullable); - /** - * If true, a chat template is not applied and you must adhere to the specific model's expected formatting. - */ - raw?: boolean; - /** - * If true, the response will be streamed back incrementally using SSE, Server Sent Events. - */ - stream?: boolean; - /** - * The maximum number of tokens to generate in the response. - */ - max_tokens?: number; - /** - * Controls the randomness of the output; higher values produce more random results. - */ - temperature?: number; - /** - * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses. - */ - top_p?: number; - /** - * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises. - */ - top_k?: number; - /** - * Random seed for reproducibility of the generation. - */ - seed?: number; - /** - * Penalty for repeated tokens; higher values discourage repetition. - */ - repetition_penalty?: number; - /** - * Decreases the likelihood of the model repeating the same lines verbatim. - */ - frequency_penalty?: number; - /** - * Increases the likelihood of the model introducing new topics. - */ - presence_penalty?: number; - /** - * Name of the LoRA (Low-Rank Adaptation) model to fine-tune the base model. - */ - lora?: string; -} -interface Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct_Messages { - /** - * An array of message objects representing the conversation history. - */ - messages: { - /** - * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool'). - */ - role?: string; - /** - * The tool call id. Must be supplied for tool calls for Mistral-3. If you don't know what to put here you can fall back to 000000001 - */ - tool_call_id?: string; - content?: - | string - | { - /** - * Type of the content provided - */ - type?: string; - text?: string; - image_url?: { - /** - * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted - */ - url?: string; - }; - }[] - | { - /** - * Type of the content provided - */ - type?: string; - text?: string; - image_url?: { - /** - * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted - */ - url?: string; - }; - }; - }[]; - image?: number[] | (string & NonNullable); - functions?: { - name: string; - code: string; - }[]; - /** - * A list of tools available for the assistant to use. - */ - tools?: ( - | { - /** - * The name of the tool. More descriptive the better. - */ - name: string; - /** - * A brief description of what the tool does. - */ - description: string; - /** - * Schema defining the parameters accepted by the tool. - */ - parameters: { - /** - * The type of the parameters object (usually 'object'). - */ - type: string; - /** - * List of required parameter names. - */ - required?: string[]; - /** - * Definitions of each parameter. - */ - properties: { - [k: string]: { - /** - * The data type of the parameter. - */ - type: string; - /** - * A description of the expected parameter. - */ - description: string; - }; - }; - }; - } - | { - /** - * Specifies the type of tool (e.g., 'function'). - */ - type: string; - /** - * Details of the function tool. - */ - function: { - /** - * The name of the function. - */ - name: string; - /** - * A brief description of what the function does. - */ - description: string; - /** - * Schema defining the parameters accepted by the function. - */ - parameters: { - /** - * The type of the parameters object (usually 'object'). - */ - type: string; - /** - * List of required parameter names. - */ - required?: string[]; - /** - * Definitions of each parameter. - */ - properties: { - [k: string]: { - /** - * The data type of the parameter. - */ - type: string; - /** - * A description of the expected parameter. - */ - description: string; - }; - }; - }; - }; - } - )[]; - /** - * If true, the response will be streamed back incrementally. - */ - stream?: boolean; - /** - * The maximum number of tokens to generate in the response. - */ - max_tokens?: number; - /** - * Controls the randomness of the output; higher values produce more random results. - */ - temperature?: number; - /** - * Controls the creativity of the AI's responses by adjusting how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses. - */ - top_p?: number; - /** - * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises. - */ - top_k?: number; - /** - * Random seed for reproducibility of the generation. - */ - seed?: number; - /** - * Penalty for repeated tokens; higher values discourage repetition. - */ - repetition_penalty?: number; - /** - * Decreases the likelihood of the model repeating the same lines verbatim. - */ - frequency_penalty?: number; - /** - * Increases the likelihood of the model introducing new topics. - */ - presence_penalty?: number; -} -type Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct_Output = { - /** - * The generated text response from the model - */ - response?: string; - /** - * An array of tool calls requests made during the response generation - */ - tool_calls?: { - /** - * The arguments passed to be passed to the tool call request - */ - arguments?: object; - /** - * The name of the tool to be called - */ - name?: string; - }[]; -}; -declare abstract class Base_Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct { - inputs: Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct_Input; - postProcessedOutputs: Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct_Output; -} -type Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_Input = - | Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_Prompt - | Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_Messages - | Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_Async_Batch; -interface Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_Prompt { - /** - * The input text prompt for the model to generate a response. - */ - prompt: string; - /** - * Name of the LoRA (Low-Rank Adaptation) model to fine-tune the base model. - */ - lora?: string; - response_format?: Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_JSON_Mode; - /** - * If true, a chat template is not applied and you must adhere to the specific model's expected formatting. - */ - raw?: boolean; - /** - * If true, the response will be streamed back incrementally using SSE, Server Sent Events. - */ - stream?: boolean; - /** - * The maximum number of tokens to generate in the response. - */ - max_tokens?: number; - /** - * Controls the randomness of the output; higher values produce more random results. - */ - temperature?: number; - /** - * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses. - */ - top_p?: number; - /** - * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises. - */ - top_k?: number; - /** - * Random seed for reproducibility of the generation. - */ - seed?: number; - /** - * Penalty for repeated tokens; higher values discourage repetition. - */ - repetition_penalty?: number; - /** - * Decreases the likelihood of the model repeating the same lines verbatim. - */ - frequency_penalty?: number; - /** - * Increases the likelihood of the model introducing new topics. - */ - presence_penalty?: number; -} -interface Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_JSON_Mode { - type?: 'json_object' | 'json_schema'; - json_schema?: unknown; -} -interface Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_Messages { - /** - * An array of message objects representing the conversation history. - */ - messages: { - /** - * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool'). - */ - role: string; - /** - * The content of the message as a string. - */ - content: string; - }[]; - functions?: { - name: string; - code: string; - }[]; - /** - * A list of tools available for the assistant to use. - */ - tools?: ( - | { - /** - * The name of the tool. More descriptive the better. - */ - name: string; - /** - * A brief description of what the tool does. - */ - description: string; - /** - * Schema defining the parameters accepted by the tool. - */ - parameters: { - /** - * The type of the parameters object (usually 'object'). - */ - type: string; - /** - * List of required parameter names. - */ - required?: string[]; - /** - * Definitions of each parameter. - */ - properties: { - [k: string]: { - /** - * The data type of the parameter. - */ - type: string; - /** - * A description of the expected parameter. - */ - description: string; - }; - }; - }; - } - | { - /** - * Specifies the type of tool (e.g., 'function'). - */ - type: string; - /** - * Details of the function tool. - */ - function: { - /** - * The name of the function. - */ - name: string; - /** - * A brief description of what the function does. - */ - description: string; - /** - * Schema defining the parameters accepted by the function. - */ - parameters: { - /** - * The type of the parameters object (usually 'object'). - */ - type: string; - /** - * List of required parameter names. - */ - required?: string[]; - /** - * Definitions of each parameter. - */ - properties: { - [k: string]: { - /** - * The data type of the parameter. - */ - type: string; - /** - * A description of the expected parameter. - */ - description: string; - }; - }; - }; - }; - } - )[]; - response_format?: Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_JSON_Mode_1; - /** - * If true, a chat template is not applied and you must adhere to the specific model's expected formatting. - */ - raw?: boolean; - /** - * If true, the response will be streamed back incrementally using SSE, Server Sent Events. - */ - stream?: boolean; - /** - * The maximum number of tokens to generate in the response. - */ - max_tokens?: number; - /** - * Controls the randomness of the output; higher values produce more random results. - */ - temperature?: number; - /** - * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses. - */ - top_p?: number; - /** - * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises. - */ - top_k?: number; - /** - * Random seed for reproducibility of the generation. - */ - seed?: number; - /** - * Penalty for repeated tokens; higher values discourage repetition. - */ - repetition_penalty?: number; - /** - * Decreases the likelihood of the model repeating the same lines verbatim. - */ - frequency_penalty?: number; - /** - * Increases the likelihood of the model introducing new topics. - */ - presence_penalty?: number; -} -interface Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_JSON_Mode_1 { - type?: 'json_object' | 'json_schema'; - json_schema?: unknown; -} -interface Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_Async_Batch { - requests?: { - /** - * User-supplied reference. This field will be present in the response as well it can be used to reference the request and response. It's NOT validated to be unique. - */ - external_reference?: string; - /** - * Prompt for the text generation model - */ - prompt?: string; - /** - * If true, the response will be streamed back incrementally using SSE, Server Sent Events. - */ - stream?: boolean; - /** - * The maximum number of tokens to generate in the response. - */ - max_tokens?: number; - /** - * Controls the randomness of the output; higher values produce more random results. - */ - temperature?: number; - /** - * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses. - */ - top_p?: number; - /** - * Random seed for reproducibility of the generation. - */ - seed?: number; - /** - * Penalty for repeated tokens; higher values discourage repetition. - */ - repetition_penalty?: number; - /** - * Decreases the likelihood of the model repeating the same lines verbatim. - */ - frequency_penalty?: number; - /** - * Increases the likelihood of the model introducing new topics. - */ - presence_penalty?: number; - response_format?: Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_JSON_Mode_2; - }[]; -} -interface Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_JSON_Mode_2 { - type?: 'json_object' | 'json_schema'; - json_schema?: unknown; -} -type Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_Output = - | { - /** - * The generated text response from the model - */ - response: string; - /** - * Usage statistics for the inference request - */ - usage?: { - /** - * Total number of tokens in input - */ - prompt_tokens?: number; - /** - * Total number of tokens in output - */ - completion_tokens?: number; - /** - * Total number of input and output tokens - */ - total_tokens?: number; - }; - /** - * An array of tool calls requests made during the response generation - */ - tool_calls?: { - /** - * The arguments passed to be passed to the tool call request - */ - arguments?: object; - /** - * The name of the tool to be called - */ - name?: string; - }[]; - } - | string - | Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_AsyncResponse; -interface Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_AsyncResponse { - /** - * The async request id that can be used to obtain the results. - */ - request_id?: string; -} -declare abstract class Base_Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast { - inputs: Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_Input; - postProcessedOutputs: Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_Output; -} -interface Ai_Cf_Meta_Llama_Guard_3_8B_Input { - /** - * An array of message objects representing the conversation history. - */ - messages: { - /** - * The role of the message sender must alternate between 'user' and 'assistant'. - */ - role: 'user' | 'assistant'; - /** - * The content of the message as a string. - */ - content: string; - }[]; - /** - * The maximum number of tokens to generate in the response. - */ - max_tokens?: number; - /** - * Controls the randomness of the output; higher values produce more random results. - */ - temperature?: number; - /** - * Dictate the output format of the generated response. - */ - response_format?: { - /** - * Set to json_object to process and output generated text as JSON. - */ - type?: string; - }; -} -interface Ai_Cf_Meta_Llama_Guard_3_8B_Output { - response?: - | string - | { - /** - * Whether the conversation is safe or not. - */ - safe?: boolean; - /** - * A list of what hazard categories predicted for the conversation, if the conversation is deemed unsafe. - */ - categories?: string[]; - }; - /** - * Usage statistics for the inference request - */ - usage?: { - /** - * Total number of tokens in input - */ - prompt_tokens?: number; - /** - * Total number of tokens in output - */ - completion_tokens?: number; - /** - * Total number of input and output tokens - */ - total_tokens?: number; - }; -} -declare abstract class Base_Ai_Cf_Meta_Llama_Guard_3_8B { - inputs: Ai_Cf_Meta_Llama_Guard_3_8B_Input; - postProcessedOutputs: Ai_Cf_Meta_Llama_Guard_3_8B_Output; -} -interface Ai_Cf_Baai_Bge_Reranker_Base_Input { - /** - * A query you wish to perform against the provided contexts. - */ - /** - * Number of returned results starting with the best score. - */ - top_k?: number; - /** - * List of provided contexts. Note that the index in this array is important, as the response will refer to it. - */ - contexts: { - /** - * One of the provided context content - */ - text?: string; - }[]; -} -interface Ai_Cf_Baai_Bge_Reranker_Base_Output { - response?: { - /** - * Index of the context in the request - */ - id?: number; - /** - * Score of the context under the index. - */ - score?: number; - }[]; -} -declare abstract class Base_Ai_Cf_Baai_Bge_Reranker_Base { - inputs: Ai_Cf_Baai_Bge_Reranker_Base_Input; - postProcessedOutputs: Ai_Cf_Baai_Bge_Reranker_Base_Output; -} -type Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct_Input = - | Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct_Prompt - | Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct_Messages; -interface Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct_Prompt { - /** - * The input text prompt for the model to generate a response. - */ - prompt: string; - /** - * Name of the LoRA (Low-Rank Adaptation) model to fine-tune the base model. - */ - lora?: string; - response_format?: Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct_JSON_Mode; - /** - * If true, a chat template is not applied and you must adhere to the specific model's expected formatting. - */ - raw?: boolean; - /** - * If true, the response will be streamed back incrementally using SSE, Server Sent Events. - */ - stream?: boolean; - /** - * The maximum number of tokens to generate in the response. - */ - max_tokens?: number; - /** - * Controls the randomness of the output; higher values produce more random results. - */ - temperature?: number; - /** - * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses. - */ - top_p?: number; - /** - * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises. - */ - top_k?: number; - /** - * Random seed for reproducibility of the generation. - */ - seed?: number; - /** - * Penalty for repeated tokens; higher values discourage repetition. - */ - repetition_penalty?: number; - /** - * Decreases the likelihood of the model repeating the same lines verbatim. - */ - frequency_penalty?: number; - /** - * Increases the likelihood of the model introducing new topics. - */ - presence_penalty?: number; -} -interface Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct_JSON_Mode { - type?: 'json_object' | 'json_schema'; - json_schema?: unknown; -} -interface Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct_Messages { - /** - * An array of message objects representing the conversation history. - */ - messages: { - /** - * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool'). - */ - role: string; - /** - * The content of the message as a string. - */ - content: string; - }[]; - functions?: { - name: string; - code: string; - }[]; - /** - * A list of tools available for the assistant to use. - */ - tools?: ( - | { - /** - * The name of the tool. More descriptive the better. - */ - name: string; - /** - * A brief description of what the tool does. - */ - description: string; - /** - * Schema defining the parameters accepted by the tool. - */ - parameters: { - /** - * The type of the parameters object (usually 'object'). - */ - type: string; - /** - * List of required parameter names. - */ - required?: string[]; - /** - * Definitions of each parameter. - */ - properties: { - [k: string]: { - /** - * The data type of the parameter. - */ - type: string; - /** - * A description of the expected parameter. - */ - description: string; - }; - }; - }; - } - | { - /** - * Specifies the type of tool (e.g., 'function'). - */ - type: string; - /** - * Details of the function tool. - */ - function: { - /** - * The name of the function. - */ - name: string; - /** - * A brief description of what the function does. - */ - description: string; - /** - * Schema defining the parameters accepted by the function. - */ - parameters: { - /** - * The type of the parameters object (usually 'object'). - */ - type: string; - /** - * List of required parameter names. - */ - required?: string[]; - /** - * Definitions of each parameter. - */ - properties: { - [k: string]: { - /** - * The data type of the parameter. - */ - type: string; - /** - * A description of the expected parameter. - */ - description: string; - }; - }; - }; - }; - } - )[]; - response_format?: Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct_JSON_Mode_1; - /** - * If true, a chat template is not applied and you must adhere to the specific model's expected formatting. - */ - raw?: boolean; - /** - * If true, the response will be streamed back incrementally using SSE, Server Sent Events. - */ - stream?: boolean; - /** - * The maximum number of tokens to generate in the response. - */ - max_tokens?: number; - /** - * Controls the randomness of the output; higher values produce more random results. - */ - temperature?: number; - /** - * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses. - */ - top_p?: number; - /** - * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises. - */ - top_k?: number; - /** - * Random seed for reproducibility of the generation. - */ - seed?: number; - /** - * Penalty for repeated tokens; higher values discourage repetition. - */ - repetition_penalty?: number; - /** - * Decreases the likelihood of the model repeating the same lines verbatim. - */ - frequency_penalty?: number; - /** - * Increases the likelihood of the model introducing new topics. - */ - presence_penalty?: number; -} -interface Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct_JSON_Mode_1 { - type?: 'json_object' | 'json_schema'; - json_schema?: unknown; -} -type Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct_Output = { - /** - * The generated text response from the model - */ - response: string; - /** - * Usage statistics for the inference request - */ - usage?: { - /** - * Total number of tokens in input - */ - prompt_tokens?: number; - /** - * Total number of tokens in output - */ - completion_tokens?: number; - /** - * Total number of input and output tokens - */ - total_tokens?: number; - }; - /** - * An array of tool calls requests made during the response generation - */ - tool_calls?: { - /** - * The arguments passed to be passed to the tool call request - */ - arguments?: object; - /** - * The name of the tool to be called - */ - name?: string; - }[]; -}; -declare abstract class Base_Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct { - inputs: Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct_Input; - postProcessedOutputs: Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct_Output; -} -type Ai_Cf_Qwen_Qwq_32B_Input = Ai_Cf_Qwen_Qwq_32B_Prompt | Ai_Cf_Qwen_Qwq_32B_Messages; -interface Ai_Cf_Qwen_Qwq_32B_Prompt { - /** - * The input text prompt for the model to generate a response. - */ - prompt: string; - /** - * JSON schema that should be fulfilled for the response. - */ - guided_json?: object; - /** - * If true, a chat template is not applied and you must adhere to the specific model's expected formatting. - */ - raw?: boolean; - /** - * If true, the response will be streamed back incrementally using SSE, Server Sent Events. - */ - stream?: boolean; - /** - * The maximum number of tokens to generate in the response. - */ - max_tokens?: number; - /** - * Controls the randomness of the output; higher values produce more random results. - */ - temperature?: number; - /** - * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses. - */ - top_p?: number; - /** - * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises. - */ - top_k?: number; - /** - * Random seed for reproducibility of the generation. - */ - seed?: number; - /** - * Penalty for repeated tokens; higher values discourage repetition. - */ - repetition_penalty?: number; - /** - * Decreases the likelihood of the model repeating the same lines verbatim. - */ - frequency_penalty?: number; - /** - * Increases the likelihood of the model introducing new topics. - */ - presence_penalty?: number; -} -interface Ai_Cf_Qwen_Qwq_32B_Messages { - /** - * An array of message objects representing the conversation history. - */ - messages: { - /** - * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool'). - */ - role?: string; - /** - * The tool call id. Must be supplied for tool calls for Mistral-3. If you don't know what to put here you can fall back to 000000001 - */ - tool_call_id?: string; - content?: - | string - | { - /** - * Type of the content provided - */ - type?: string; - text?: string; - image_url?: { - /** - * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted - */ - url?: string; - }; - }[] - | { - /** - * Type of the content provided - */ - type?: string; - text?: string; - image_url?: { - /** - * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted - */ - url?: string; - }; - }; - }[]; - functions?: { - name: string; - code: string; - }[]; - /** - * A list of tools available for the assistant to use. - */ - tools?: ( - | { - /** - * The name of the tool. More descriptive the better. - */ - name: string; - /** - * A brief description of what the tool does. - */ - description: string; - /** - * Schema defining the parameters accepted by the tool. - */ - parameters: { - /** - * The type of the parameters object (usually 'object'). - */ - type: string; - /** - * List of required parameter names. - */ - required?: string[]; - /** - * Definitions of each parameter. - */ - properties: { - [k: string]: { - /** - * The data type of the parameter. - */ - type: string; - /** - * A description of the expected parameter. - */ - description: string; - }; - }; - }; - } - | { - /** - * Specifies the type of tool (e.g., 'function'). - */ - type: string; - /** - * Details of the function tool. - */ - function: { - /** - * The name of the function. - */ - name: string; - /** - * A brief description of what the function does. - */ - description: string; - /** - * Schema defining the parameters accepted by the function. - */ - parameters: { - /** - * The type of the parameters object (usually 'object'). - */ - type: string; - /** - * List of required parameter names. - */ - required?: string[]; - /** - * Definitions of each parameter. - */ - properties: { - [k: string]: { - /** - * The data type of the parameter. - */ - type: string; - /** - * A description of the expected parameter. - */ - description: string; - }; - }; - }; - }; - } - )[]; - /** - * JSON schema that should be fufilled for the response. - */ - guided_json?: object; - /** - * If true, a chat template is not applied and you must adhere to the specific model's expected formatting. - */ - raw?: boolean; - /** - * If true, the response will be streamed back incrementally using SSE, Server Sent Events. - */ - stream?: boolean; - /** - * The maximum number of tokens to generate in the response. - */ - max_tokens?: number; - /** - * Controls the randomness of the output; higher values produce more random results. - */ - temperature?: number; - /** - * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses. - */ - top_p?: number; - /** - * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises. - */ - top_k?: number; - /** - * Random seed for reproducibility of the generation. - */ - seed?: number; - /** - * Penalty for repeated tokens; higher values discourage repetition. - */ - repetition_penalty?: number; - /** - * Decreases the likelihood of the model repeating the same lines verbatim. - */ - frequency_penalty?: number; - /** - * Increases the likelihood of the model introducing new topics. - */ - presence_penalty?: number; -} -type Ai_Cf_Qwen_Qwq_32B_Output = { - /** - * The generated text response from the model - */ - response: string; - /** - * Usage statistics for the inference request - */ - usage?: { - /** - * Total number of tokens in input - */ - prompt_tokens?: number; - /** - * Total number of tokens in output - */ - completion_tokens?: number; - /** - * Total number of input and output tokens - */ - total_tokens?: number; - }; - /** - * An array of tool calls requests made during the response generation - */ - tool_calls?: { - /** - * The arguments passed to be passed to the tool call request - */ - arguments?: object; - /** - * The name of the tool to be called - */ - name?: string; - }[]; -}; -declare abstract class Base_Ai_Cf_Qwen_Qwq_32B { - inputs: Ai_Cf_Qwen_Qwq_32B_Input; - postProcessedOutputs: Ai_Cf_Qwen_Qwq_32B_Output; -} -type Ai_Cf_Mistralai_Mistral_Small_3_1_24B_Instruct_Input = - | Ai_Cf_Mistralai_Mistral_Small_3_1_24B_Instruct_Prompt - | Ai_Cf_Mistralai_Mistral_Small_3_1_24B_Instruct_Messages; -interface Ai_Cf_Mistralai_Mistral_Small_3_1_24B_Instruct_Prompt { - /** - * The input text prompt for the model to generate a response. - */ - prompt: string; - /** - * JSON schema that should be fulfilled for the response. - */ - guided_json?: object; - /** - * If true, a chat template is not applied and you must adhere to the specific model's expected formatting. - */ - raw?: boolean; - /** - * If true, the response will be streamed back incrementally using SSE, Server Sent Events. - */ - stream?: boolean; - /** - * The maximum number of tokens to generate in the response. - */ - max_tokens?: number; - /** - * Controls the randomness of the output; higher values produce more random results. - */ - temperature?: number; - /** - * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses. - */ - top_p?: number; - /** - * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises. - */ - top_k?: number; - /** - * Random seed for reproducibility of the generation. - */ - seed?: number; - /** - * Penalty for repeated tokens; higher values discourage repetition. - */ - repetition_penalty?: number; - /** - * Decreases the likelihood of the model repeating the same lines verbatim. - */ - frequency_penalty?: number; - /** - * Increases the likelihood of the model introducing new topics. - */ - presence_penalty?: number; -} -interface Ai_Cf_Mistralai_Mistral_Small_3_1_24B_Instruct_Messages { - /** - * An array of message objects representing the conversation history. - */ - messages: { - /** - * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool'). - */ - role?: string; - /** - * The tool call id. Must be supplied for tool calls for Mistral-3. If you don't know what to put here you can fall back to 000000001 - */ - tool_call_id?: string; - content?: - | string - | { - /** - * Type of the content provided - */ - type?: string; - text?: string; - image_url?: { - /** - * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted - */ - url?: string; - }; - }[] - | { - /** - * Type of the content provided - */ - type?: string; - text?: string; - image_url?: { - /** - * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted - */ - url?: string; - }; - }; - }[]; - functions?: { - name: string; - code: string; - }[]; - /** - * A list of tools available for the assistant to use. - */ - tools?: ( - | { - /** - * The name of the tool. More descriptive the better. - */ - name: string; - /** - * A brief description of what the tool does. - */ - description: string; - /** - * Schema defining the parameters accepted by the tool. - */ - parameters: { - /** - * The type of the parameters object (usually 'object'). - */ - type: string; - /** - * List of required parameter names. - */ - required?: string[]; - /** - * Definitions of each parameter. - */ - properties: { - [k: string]: { - /** - * The data type of the parameter. - */ - type: string; - /** - * A description of the expected parameter. - */ - description: string; - }; - }; - }; - } - | { - /** - * Specifies the type of tool (e.g., 'function'). - */ - type: string; - /** - * Details of the function tool. - */ - function: { - /** - * The name of the function. - */ - name: string; - /** - * A brief description of what the function does. - */ - description: string; - /** - * Schema defining the parameters accepted by the function. - */ - parameters: { - /** - * The type of the parameters object (usually 'object'). - */ - type: string; - /** - * List of required parameter names. - */ - required?: string[]; - /** - * Definitions of each parameter. - */ - properties: { - [k: string]: { - /** - * The data type of the parameter. - */ - type: string; - /** - * A description of the expected parameter. - */ - description: string; - }; - }; - }; - }; - } - )[]; - /** - * JSON schema that should be fufilled for the response. - */ - guided_json?: object; - /** - * If true, a chat template is not applied and you must adhere to the specific model's expected formatting. - */ - raw?: boolean; - /** - * If true, the response will be streamed back incrementally using SSE, Server Sent Events. - */ - stream?: boolean; - /** - * The maximum number of tokens to generate in the response. - */ - max_tokens?: number; - /** - * Controls the randomness of the output; higher values produce more random results. - */ - temperature?: number; - /** - * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses. - */ - top_p?: number; - /** - * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises. - */ - top_k?: number; - /** - * Random seed for reproducibility of the generation. - */ - seed?: number; - /** - * Penalty for repeated tokens; higher values discourage repetition. - */ - repetition_penalty?: number; - /** - * Decreases the likelihood of the model repeating the same lines verbatim. - */ - frequency_penalty?: number; - /** - * Increases the likelihood of the model introducing new topics. - */ - presence_penalty?: number; -} -type Ai_Cf_Mistralai_Mistral_Small_3_1_24B_Instruct_Output = { - /** - * The generated text response from the model - */ - response: string; - /** - * Usage statistics for the inference request - */ - usage?: { - /** - * Total number of tokens in input - */ - prompt_tokens?: number; - /** - * Total number of tokens in output - */ - completion_tokens?: number; - /** - * Total number of input and output tokens - */ - total_tokens?: number; - }; - /** - * An array of tool calls requests made during the response generation - */ - tool_calls?: { - /** - * The arguments passed to be passed to the tool call request - */ - arguments?: object; - /** - * The name of the tool to be called - */ - name?: string; - }[]; -}; -declare abstract class Base_Ai_Cf_Mistralai_Mistral_Small_3_1_24B_Instruct { - inputs: Ai_Cf_Mistralai_Mistral_Small_3_1_24B_Instruct_Input; - postProcessedOutputs: Ai_Cf_Mistralai_Mistral_Small_3_1_24B_Instruct_Output; -} -type Ai_Cf_Google_Gemma_3_12B_It_Input = - | Ai_Cf_Google_Gemma_3_12B_It_Prompt - | Ai_Cf_Google_Gemma_3_12B_It_Messages; -interface Ai_Cf_Google_Gemma_3_12B_It_Prompt { - /** - * The input text prompt for the model to generate a response. - */ - prompt: string; - /** - * JSON schema that should be fufilled for the response. - */ - guided_json?: object; - /** - * If true, a chat template is not applied and you must adhere to the specific model's expected formatting. - */ - raw?: boolean; - /** - * If true, the response will be streamed back incrementally using SSE, Server Sent Events. - */ - stream?: boolean; - /** - * The maximum number of tokens to generate in the response. - */ - max_tokens?: number; - /** - * Controls the randomness of the output; higher values produce more random results. - */ - temperature?: number; - /** - * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses. - */ - top_p?: number; - /** - * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises. - */ - top_k?: number; - /** - * Random seed for reproducibility of the generation. - */ - seed?: number; - /** - * Penalty for repeated tokens; higher values discourage repetition. - */ - repetition_penalty?: number; - /** - * Decreases the likelihood of the model repeating the same lines verbatim. - */ - frequency_penalty?: number; - /** - * Increases the likelihood of the model introducing new topics. - */ - presence_penalty?: number; -} -interface Ai_Cf_Google_Gemma_3_12B_It_Messages { - /** - * An array of message objects representing the conversation history. - */ - messages: { - /** - * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool'). - */ - role?: string; - content?: - | string - | { - /** - * Type of the content provided - */ - type?: string; - text?: string; - image_url?: { - /** - * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted - */ - url?: string; - }; - }[]; - }[]; - functions?: { - name: string; - code: string; - }[]; - /** - * A list of tools available for the assistant to use. - */ - tools?: ( - | { - /** - * The name of the tool. More descriptive the better. - */ - name: string; - /** - * A brief description of what the tool does. - */ - description: string; - /** - * Schema defining the parameters accepted by the tool. - */ - parameters: { - /** - * The type of the parameters object (usually 'object'). - */ - type: string; - /** - * List of required parameter names. - */ - required?: string[]; - /** - * Definitions of each parameter. - */ - properties: { - [k: string]: { - /** - * The data type of the parameter. - */ - type: string; - /** - * A description of the expected parameter. - */ - description: string; - }; - }; - }; - } - | { - /** - * Specifies the type of tool (e.g., 'function'). - */ - type: string; - /** - * Details of the function tool. - */ - function: { - /** - * The name of the function. - */ - name: string; - /** - * A brief description of what the function does. - */ - description: string; - /** - * Schema defining the parameters accepted by the function. - */ - parameters: { - /** - * The type of the parameters object (usually 'object'). - */ - type: string; - /** - * List of required parameter names. - */ - required?: string[]; - /** - * Definitions of each parameter. - */ - properties: { - [k: string]: { - /** - * The data type of the parameter. - */ - type: string; - /** - * A description of the expected parameter. - */ - description: string; - }; - }; - }; - }; - } - )[]; - /** - * JSON schema that should be fufilled for the response. - */ - guided_json?: object; - /** - * If true, a chat template is not applied and you must adhere to the specific model's expected formatting. - */ - raw?: boolean; - /** - * If true, the response will be streamed back incrementally using SSE, Server Sent Events. - */ - stream?: boolean; - /** - * The maximum number of tokens to generate in the response. - */ - max_tokens?: number; - /** - * Controls the randomness of the output; higher values produce more random results. - */ - temperature?: number; - /** - * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses. - */ - top_p?: number; - /** - * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises. - */ - top_k?: number; - /** - * Random seed for reproducibility of the generation. - */ - seed?: number; - /** - * Penalty for repeated tokens; higher values discourage repetition. - */ - repetition_penalty?: number; - /** - * Decreases the likelihood of the model repeating the same lines verbatim. - */ - frequency_penalty?: number; - /** - * Increases the likelihood of the model introducing new topics. - */ - presence_penalty?: number; -} -type Ai_Cf_Google_Gemma_3_12B_It_Output = { - /** - * The generated text response from the model - */ - response: string; - /** - * Usage statistics for the inference request - */ - usage?: { - /** - * Total number of tokens in input - */ - prompt_tokens?: number; - /** - * Total number of tokens in output - */ - completion_tokens?: number; - /** - * Total number of input and output tokens - */ - total_tokens?: number; - }; - /** - * An array of tool calls requests made during the response generation - */ - tool_calls?: { - /** - * The arguments passed to be passed to the tool call request - */ - arguments?: object; - /** - * The name of the tool to be called - */ - name?: string; - }[]; -}; -declare abstract class Base_Ai_Cf_Google_Gemma_3_12B_It { - inputs: Ai_Cf_Google_Gemma_3_12B_It_Input; - postProcessedOutputs: Ai_Cf_Google_Gemma_3_12B_It_Output; -} -type Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Input = - | Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Prompt - | Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Messages - | Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Async_Batch; -interface Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Prompt { - /** - * The input text prompt for the model to generate a response. - */ - prompt: string; - /** - * JSON schema that should be fulfilled for the response. - */ - guided_json?: object; - response_format?: Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_JSON_Mode; - /** - * If true, a chat template is not applied and you must adhere to the specific model's expected formatting. - */ - raw?: boolean; - /** - * If true, the response will be streamed back incrementally using SSE, Server Sent Events. - */ - stream?: boolean; - /** - * The maximum number of tokens to generate in the response. - */ - max_tokens?: number; - /** - * Controls the randomness of the output; higher values produce more random results. - */ - temperature?: number; - /** - * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses. - */ - top_p?: number; - /** - * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises. - */ - top_k?: number; - /** - * Random seed for reproducibility of the generation. - */ - seed?: number; - /** - * Penalty for repeated tokens; higher values discourage repetition. - */ - repetition_penalty?: number; - /** - * Decreases the likelihood of the model repeating the same lines verbatim. - */ - frequency_penalty?: number; - /** - * Increases the likelihood of the model introducing new topics. - */ - presence_penalty?: number; -} -interface Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_JSON_Mode { - type?: 'json_object' | 'json_schema'; - json_schema?: unknown; -} -interface Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Messages { - /** - * An array of message objects representing the conversation history. - */ - messages: { - /** - * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool'). - */ - role?: string; - /** - * The tool call id. If you don't know what to put here you can fall back to 000000001 - */ - tool_call_id?: string; - content?: - | string - | { - /** - * Type of the content provided - */ - type?: string; - text?: string; - image_url?: { - /** - * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted - */ - url?: string; - }; - }[] - | { - /** - * Type of the content provided - */ - type?: string; - text?: string; - image_url?: { - /** - * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted - */ - url?: string; - }; - }; - }[]; - functions?: { - name: string; - code: string; - }[]; - /** - * A list of tools available for the assistant to use. - */ - tools?: ( - | { - /** - * The name of the tool. More descriptive the better. - */ - name: string; - /** - * A brief description of what the tool does. - */ - description: string; - /** - * Schema defining the parameters accepted by the tool. - */ - parameters: { - /** - * The type of the parameters object (usually 'object'). - */ - type: string; - /** - * List of required parameter names. - */ - required?: string[]; - /** - * Definitions of each parameter. - */ - properties: { - [k: string]: { - /** - * The data type of the parameter. - */ - type: string; - /** - * A description of the expected parameter. - */ - description: string; - }; - }; - }; - } - | { - /** - * Specifies the type of tool (e.g., 'function'). - */ - type: string; - /** - * Details of the function tool. - */ - function: { - /** - * The name of the function. - */ - name: string; - /** - * A brief description of what the function does. - */ - description: string; - /** - * Schema defining the parameters accepted by the function. - */ - parameters: { - /** - * The type of the parameters object (usually 'object'). - */ - type: string; - /** - * List of required parameter names. - */ - required?: string[]; - /** - * Definitions of each parameter. - */ - properties: { - [k: string]: { - /** - * The data type of the parameter. - */ - type: string; - /** - * A description of the expected parameter. - */ - description: string; - }; - }; - }; - }; - } - )[]; - response_format?: Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_JSON_Mode; - /** - * JSON schema that should be fufilled for the response. - */ - guided_json?: object; - /** - * If true, a chat template is not applied and you must adhere to the specific model's expected formatting. - */ - raw?: boolean; - /** - * If true, the response will be streamed back incrementally using SSE, Server Sent Events. - */ - stream?: boolean; - /** - * The maximum number of tokens to generate in the response. - */ - max_tokens?: number; - /** - * Controls the randomness of the output; higher values produce more random results. - */ - temperature?: number; - /** - * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses. - */ - top_p?: number; - /** - * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises. - */ - top_k?: number; - /** - * Random seed for reproducibility of the generation. - */ - seed?: number; - /** - * Penalty for repeated tokens; higher values discourage repetition. - */ - repetition_penalty?: number; - /** - * Decreases the likelihood of the model repeating the same lines verbatim. - */ - frequency_penalty?: number; - /** - * Increases the likelihood of the model introducing new topics. - */ - presence_penalty?: number; -} -interface Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Async_Batch { - requests: ( - | Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Prompt_Inner - | Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Messages_Inner - )[]; -} -interface Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Prompt_Inner { - /** - * The input text prompt for the model to generate a response. - */ - prompt: string; - /** - * JSON schema that should be fulfilled for the response. - */ - guided_json?: object; - response_format?: Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_JSON_Mode; - /** - * If true, a chat template is not applied and you must adhere to the specific model's expected formatting. - */ - raw?: boolean; - /** - * If true, the response will be streamed back incrementally using SSE, Server Sent Events. - */ - stream?: boolean; - /** - * The maximum number of tokens to generate in the response. - */ - max_tokens?: number; - /** - * Controls the randomness of the output; higher values produce more random results. - */ - temperature?: number; - /** - * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses. - */ - top_p?: number; - /** - * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises. - */ - top_k?: number; - /** - * Random seed for reproducibility of the generation. - */ - seed?: number; - /** - * Penalty for repeated tokens; higher values discourage repetition. - */ - repetition_penalty?: number; - /** - * Decreases the likelihood of the model repeating the same lines verbatim. - */ - frequency_penalty?: number; - /** - * Increases the likelihood of the model introducing new topics. - */ - presence_penalty?: number; -} -interface Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Messages_Inner { - /** - * An array of message objects representing the conversation history. - */ - messages: { - /** - * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool'). - */ - role?: string; - /** - * The tool call id. If you don't know what to put here you can fall back to 000000001 - */ - tool_call_id?: string; - content?: - | string - | { - /** - * Type of the content provided - */ - type?: string; - text?: string; - image_url?: { - /** - * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted - */ - url?: string; - }; - }[] - | { - /** - * Type of the content provided - */ - type?: string; - text?: string; - image_url?: { - /** - * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted - */ - url?: string; - }; - }; - }[]; - functions?: { - name: string; - code: string; - }[]; - /** - * A list of tools available for the assistant to use. - */ - tools?: ( - | { - /** - * The name of the tool. More descriptive the better. - */ - name: string; - /** - * A brief description of what the tool does. - */ - description: string; - /** - * Schema defining the parameters accepted by the tool. - */ - parameters: { - /** - * The type of the parameters object (usually 'object'). - */ - type: string; - /** - * List of required parameter names. - */ - required?: string[]; - /** - * Definitions of each parameter. - */ - properties: { - [k: string]: { - /** - * The data type of the parameter. - */ - type: string; - /** - * A description of the expected parameter. - */ - description: string; - }; - }; - }; - } - | { - /** - * Specifies the type of tool (e.g., 'function'). - */ - type: string; - /** - * Details of the function tool. - */ - function: { - /** - * The name of the function. - */ - name: string; - /** - * A brief description of what the function does. - */ - description: string; - /** - * Schema defining the parameters accepted by the function. - */ - parameters: { - /** - * The type of the parameters object (usually 'object'). - */ - type: string; - /** - * List of required parameter names. - */ - required?: string[]; - /** - * Definitions of each parameter. - */ - properties: { - [k: string]: { - /** - * The data type of the parameter. - */ - type: string; - /** - * A description of the expected parameter. - */ - description: string; - }; - }; - }; - }; - } - )[]; - response_format?: Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_JSON_Mode; - /** - * JSON schema that should be fufilled for the response. - */ - guided_json?: object; - /** - * If true, a chat template is not applied and you must adhere to the specific model's expected formatting. - */ - raw?: boolean; - /** - * If true, the response will be streamed back incrementally using SSE, Server Sent Events. - */ - stream?: boolean; - /** - * The maximum number of tokens to generate in the response. - */ - max_tokens?: number; - /** - * Controls the randomness of the output; higher values produce more random results. - */ - temperature?: number; - /** - * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses. - */ - top_p?: number; - /** - * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises. - */ - top_k?: number; - /** - * Random seed for reproducibility of the generation. - */ - seed?: number; - /** - * Penalty for repeated tokens; higher values discourage repetition. - */ - repetition_penalty?: number; - /** - * Decreases the likelihood of the model repeating the same lines verbatim. - */ - frequency_penalty?: number; - /** - * Increases the likelihood of the model introducing new topics. - */ - presence_penalty?: number; -} -type Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Output = { - /** - * The generated text response from the model - */ - response: string; - /** - * Usage statistics for the inference request - */ - usage?: { - /** - * Total number of tokens in input - */ - prompt_tokens?: number; - /** - * Total number of tokens in output - */ - completion_tokens?: number; - /** - * Total number of input and output tokens - */ - total_tokens?: number; - }; - /** - * An array of tool calls requests made during the response generation - */ - tool_calls?: { - /** - * The tool call id. - */ - id?: string; - /** - * Specifies the type of tool (e.g., 'function'). - */ - type?: string; - /** - * Details of the function tool. - */ - function?: { - /** - * The name of the tool to be called - */ - name?: string; - /** - * The arguments passed to be passed to the tool call request - */ - arguments?: object; - }; - }[]; -}; -declare abstract class Base_Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct { - inputs: Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Input; - postProcessedOutputs: Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Output; -} -type Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Input = - | Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Prompt - | Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Messages - | Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Async_Batch; -interface Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Prompt { - /** - * The input text prompt for the model to generate a response. - */ - prompt: string; - /** - * Name of the LoRA (Low-Rank Adaptation) model to fine-tune the base model. - */ - lora?: string; - response_format?: Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_JSON_Mode; - /** - * If true, a chat template is not applied and you must adhere to the specific model's expected formatting. - */ - raw?: boolean; - /** - * If true, the response will be streamed back incrementally using SSE, Server Sent Events. - */ - stream?: boolean; - /** - * The maximum number of tokens to generate in the response. - */ - max_tokens?: number; - /** - * Controls the randomness of the output; higher values produce more random results. - */ - temperature?: number; - /** - * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses. - */ - top_p?: number; - /** - * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises. - */ - top_k?: number; - /** - * Random seed for reproducibility of the generation. - */ - seed?: number; - /** - * Penalty for repeated tokens; higher values discourage repetition. - */ - repetition_penalty?: number; - /** - * Decreases the likelihood of the model repeating the same lines verbatim. - */ - frequency_penalty?: number; - /** - * Increases the likelihood of the model introducing new topics. - */ - presence_penalty?: number; -} -interface Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_JSON_Mode { - type?: 'json_object' | 'json_schema'; - json_schema?: unknown; -} -interface Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Messages { - /** - * An array of message objects representing the conversation history. - */ - messages: { - /** - * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool'). - */ - role: string; - /** - * The content of the message as a string. - */ - content: string; - }[]; - functions?: { - name: string; - code: string; - }[]; - /** - * A list of tools available for the assistant to use. - */ - tools?: ( - | { - /** - * The name of the tool. More descriptive the better. - */ - name: string; - /** - * A brief description of what the tool does. - */ - description: string; - /** - * Schema defining the parameters accepted by the tool. - */ - parameters: { - /** - * The type of the parameters object (usually 'object'). - */ - type: string; - /** - * List of required parameter names. - */ - required?: string[]; - /** - * Definitions of each parameter. - */ - properties: { - [k: string]: { - /** - * The data type of the parameter. - */ - type: string; - /** - * A description of the expected parameter. - */ - description: string; - }; - }; - }; - } - | { - /** - * Specifies the type of tool (e.g., 'function'). - */ - type: string; - /** - * Details of the function tool. - */ - function: { - /** - * The name of the function. - */ - name: string; - /** - * A brief description of what the function does. - */ - description: string; - /** - * Schema defining the parameters accepted by the function. - */ - parameters: { - /** - * The type of the parameters object (usually 'object'). - */ - type: string; - /** - * List of required parameter names. - */ - required?: string[]; - /** - * Definitions of each parameter. - */ - properties: { - [k: string]: { - /** - * The data type of the parameter. - */ - type: string; - /** - * A description of the expected parameter. - */ - description: string; - }; - }; - }; - }; - } - )[]; - response_format?: Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_JSON_Mode_1; - /** - * If true, a chat template is not applied and you must adhere to the specific model's expected formatting. - */ - raw?: boolean; - /** - * If true, the response will be streamed back incrementally using SSE, Server Sent Events. - */ - stream?: boolean; - /** - * The maximum number of tokens to generate in the response. - */ - max_tokens?: number; - /** - * Controls the randomness of the output; higher values produce more random results. - */ - temperature?: number; - /** - * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses. - */ - top_p?: number; - /** - * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises. - */ - top_k?: number; - /** - * Random seed for reproducibility of the generation. - */ - seed?: number; - /** - * Penalty for repeated tokens; higher values discourage repetition. - */ - repetition_penalty?: number; - /** - * Decreases the likelihood of the model repeating the same lines verbatim. - */ - frequency_penalty?: number; - /** - * Increases the likelihood of the model introducing new topics. - */ - presence_penalty?: number; -} -interface Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_JSON_Mode_1 { - type?: 'json_object' | 'json_schema'; - json_schema?: unknown; -} -interface Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Async_Batch { - requests: (Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Prompt_1 | Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Messages_1)[]; -} -interface Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Prompt_1 { - /** - * The input text prompt for the model to generate a response. - */ - prompt: string; - /** - * Name of the LoRA (Low-Rank Adaptation) model to fine-tune the base model. - */ - lora?: string; - response_format?: Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_JSON_Mode_2; - /** - * If true, a chat template is not applied and you must adhere to the specific model's expected formatting. - */ - raw?: boolean; - /** - * If true, the response will be streamed back incrementally using SSE, Server Sent Events. - */ - stream?: boolean; - /** - * The maximum number of tokens to generate in the response. - */ - max_tokens?: number; - /** - * Controls the randomness of the output; higher values produce more random results. - */ - temperature?: number; - /** - * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses. - */ - top_p?: number; - /** - * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises. - */ - top_k?: number; - /** - * Random seed for reproducibility of the generation. - */ - seed?: number; - /** - * Penalty for repeated tokens; higher values discourage repetition. - */ - repetition_penalty?: number; - /** - * Decreases the likelihood of the model repeating the same lines verbatim. - */ - frequency_penalty?: number; - /** - * Increases the likelihood of the model introducing new topics. - */ - presence_penalty?: number; -} -interface Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_JSON_Mode_2 { - type?: 'json_object' | 'json_schema'; - json_schema?: unknown; -} -interface Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Messages_1 { - /** - * An array of message objects representing the conversation history. - */ - messages: { - /** - * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool'). - */ - role: string; - /** - * The content of the message as a string. - */ - content: string; - }[]; - functions?: { - name: string; - code: string; - }[]; - /** - * A list of tools available for the assistant to use. - */ - tools?: ( - | { - /** - * The name of the tool. More descriptive the better. - */ - name: string; - /** - * A brief description of what the tool does. - */ - description: string; - /** - * Schema defining the parameters accepted by the tool. - */ - parameters: { - /** - * The type of the parameters object (usually 'object'). - */ - type: string; - /** - * List of required parameter names. - */ - required?: string[]; - /** - * Definitions of each parameter. - */ - properties: { - [k: string]: { - /** - * The data type of the parameter. - */ - type: string; - /** - * A description of the expected parameter. - */ - description: string; - }; - }; - }; - } - | { - /** - * Specifies the type of tool (e.g., 'function'). - */ - type: string; - /** - * Details of the function tool. - */ - function: { - /** - * The name of the function. - */ - name: string; - /** - * A brief description of what the function does. - */ - description: string; - /** - * Schema defining the parameters accepted by the function. - */ - parameters: { - /** - * The type of the parameters object (usually 'object'). - */ - type: string; - /** - * List of required parameter names. - */ - required?: string[]; - /** - * Definitions of each parameter. - */ - properties: { - [k: string]: { - /** - * The data type of the parameter. - */ - type: string; - /** - * A description of the expected parameter. - */ - description: string; - }; - }; - }; - }; - } - )[]; - response_format?: Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_JSON_Mode_3; - /** - * If true, a chat template is not applied and you must adhere to the specific model's expected formatting. - */ - raw?: boolean; - /** - * If true, the response will be streamed back incrementally using SSE, Server Sent Events. - */ - stream?: boolean; - /** - * The maximum number of tokens to generate in the response. - */ - max_tokens?: number; - /** - * Controls the randomness of the output; higher values produce more random results. - */ - temperature?: number; - /** - * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses. - */ - top_p?: number; - /** - * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises. - */ - top_k?: number; - /** - * Random seed for reproducibility of the generation. - */ - seed?: number; - /** - * Penalty for repeated tokens; higher values discourage repetition. - */ - repetition_penalty?: number; - /** - * Decreases the likelihood of the model repeating the same lines verbatim. - */ - frequency_penalty?: number; - /** - * Increases the likelihood of the model introducing new topics. - */ - presence_penalty?: number; -} -interface Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_JSON_Mode_3 { - type?: 'json_object' | 'json_schema'; - json_schema?: unknown; -} -type Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Output = - | Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Chat_Completion_Response - | Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Text_Completion_Response - | string - | Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_AsyncResponse; -interface Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Chat_Completion_Response { - /** - * Unique identifier for the completion - */ - id?: string; - /** - * Object type identifier - */ - object?: 'chat.completion'; - /** - * Unix timestamp of when the completion was created - */ - created?: number; - /** - * Model used for the completion - */ - model?: string; - /** - * List of completion choices - */ - choices?: { - /** - * Index of the choice in the list - */ - index?: number; - /** - * The message generated by the model - */ - message?: { - /** - * Role of the message author - */ - role: string; - /** - * The content of the message - */ - content: string; - /** - * Internal reasoning content (if available) - */ - reasoning_content?: string; - /** - * Tool calls made by the assistant - */ - tool_calls?: { - /** - * Unique identifier for the tool call - */ - id: string; - /** - * Type of tool call - */ - type: 'function'; - function: { - /** - * Name of the function to call - */ - name: string; - /** - * JSON string of arguments for the function - */ - arguments: string; - }; - }[]; - }; - /** - * Reason why the model stopped generating - */ - finish_reason?: string; - /** - * Stop reason (may be null) - */ - stop_reason?: string | null; - /** - * Log probabilities (if requested) - */ - logprobs?: {} | null; - }[]; - /** - * Usage statistics for the inference request - */ - usage?: { - /** - * Total number of tokens in input - */ - prompt_tokens?: number; - /** - * Total number of tokens in output - */ - completion_tokens?: number; - /** - * Total number of input and output tokens - */ - total_tokens?: number; - }; - /** - * Log probabilities for the prompt (if requested) - */ - prompt_logprobs?: {} | null; -} -interface Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Text_Completion_Response { - /** - * Unique identifier for the completion - */ - id?: string; - /** - * Object type identifier - */ - object?: 'text_completion'; - /** - * Unix timestamp of when the completion was created - */ - created?: number; - /** - * Model used for the completion - */ - model?: string; - /** - * List of completion choices - */ - choices?: { - /** - * Index of the choice in the list - */ - index: number; - /** - * The generated text completion - */ - text: string; - /** - * Reason why the model stopped generating - */ - finish_reason: string; - /** - * Stop reason (may be null) - */ - stop_reason?: string | null; - /** - * Log probabilities (if requested) - */ - logprobs?: {} | null; - /** - * Log probabilities for the prompt (if requested) - */ - prompt_logprobs?: {} | null; - }[]; - /** - * Usage statistics for the inference request - */ - usage?: { - /** - * Total number of tokens in input - */ - prompt_tokens?: number; - /** - * Total number of tokens in output - */ - completion_tokens?: number; - /** - * Total number of input and output tokens - */ - total_tokens?: number; - }; -} -interface Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_AsyncResponse { - /** - * The async request id that can be used to obtain the results. - */ - request_id?: string; -} -declare abstract class Base_Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8 { - inputs: Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Input; - postProcessedOutputs: Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Output; -} -interface Ai_Cf_Deepgram_Nova_3_Input { - audio: { - body: object; - contentType: string; - }; - /** - * Sets how the model will interpret strings submitted to the custom_topic param. When strict, the model will only return topics submitted using the custom_topic param. When extended, the model will return its own detected topics in addition to those submitted using the custom_topic param. - */ - custom_topic_mode?: 'extended' | 'strict'; - /** - * Custom topics you want the model to detect within your input audio or text if present Submit up to 100 - */ - custom_topic?: string; - /** - * Sets how the model will interpret intents submitted to the custom_intent param. When strict, the model will only return intents submitted using the custom_intent param. When extended, the model will return its own detected intents in addition those submitted using the custom_intents param - */ - custom_intent_mode?: 'extended' | 'strict'; - /** - * Custom intents you want the model to detect within your input audio if present - */ - custom_intent?: string; - /** - * Identifies and extracts key entities from content in submitted audio - */ - detect_entities?: boolean; - /** - * Identifies the dominant language spoken in submitted audio - */ - detect_language?: boolean; - /** - * Recognize speaker changes. Each word in the transcript will be assigned a speaker number starting at 0 - */ - diarize?: boolean; - /** - * Identify and extract key entities from content in submitted audio - */ - dictation?: boolean; - /** - * Specify the expected encoding of your submitted audio - */ - encoding?: 'linear16' | 'flac' | 'mulaw' | 'amr-nb' | 'amr-wb' | 'opus' | 'speex' | 'g729'; - /** - * Arbitrary key-value pairs that are attached to the API response for usage in downstream processing - */ - extra?: string; - /** - * Filler Words can help transcribe interruptions in your audio, like 'uh' and 'um' - */ - filler_words?: boolean; - /** - * Key term prompting can boost or suppress specialized terminology and brands. - */ - keyterm?: string; - /** - * Keywords can boost or suppress specialized terminology and brands. - */ - keywords?: string; - /** - * The BCP-47 language tag that hints at the primary spoken language. Depending on the Model and API endpoint you choose only certain languages are available. - */ - language?: string; - /** - * Spoken measurements will be converted to their corresponding abbreviations. - */ - measurements?: boolean; - /** - * Opts out requests from the Deepgram Model Improvement Program. Refer to our Docs for pricing impacts before setting this to true. https://dpgr.am/deepgram-mip. - */ - mip_opt_out?: boolean; - /** - * Mode of operation for the model representing broad area of topic that will be talked about in the supplied audio - */ - mode?: 'general' | 'medical' | 'finance'; - /** - * Transcribe each audio channel independently. - */ - multichannel?: boolean; - /** - * Numerals converts numbers from written format to numerical format. - */ - numerals?: boolean; - /** - * Splits audio into paragraphs to improve transcript readability. - */ - paragraphs?: boolean; - /** - * Profanity Filter looks for recognized profanity and converts it to the nearest recognized non-profane word or removes it from the transcript completely. - */ - profanity_filter?: boolean; - /** - * Add punctuation and capitalization to the transcript. - */ - punctuate?: boolean; - /** - * Redaction removes sensitive information from your transcripts. - */ - redact?: string; - /** - * Search for terms or phrases in submitted audio and replaces them. - */ - replace?: string; - /** - * Search for terms or phrases in submitted audio. - */ - search?: string; - /** - * Recognizes the sentiment throughout a transcript or text. - */ - sentiment?: boolean; - /** - * Apply formatting to transcript output. When set to true, additional formatting will be applied to transcripts to improve readability. - */ - smart_format?: boolean; - /** - * Detect topics throughout a transcript or text. - */ - topics?: boolean; - /** - * Segments speech into meaningful semantic units. - */ - utterances?: boolean; - /** - * Seconds to wait before detecting a pause between words in submitted audio. - */ - utt_split?: number; - /** - * The number of channels in the submitted audio - */ - channels?: number; - /** - * Specifies whether the streaming endpoint should provide ongoing transcription updates as more audio is received. When set to true, the endpoint sends continuous updates, meaning transcription results may evolve over time. Note: Supported only for webosockets. - */ - interim_results?: boolean; - /** - * Indicates how long model will wait to detect whether a speaker has finished speaking or pauses for a significant period of time. When set to a value, the streaming endpoint immediately finalizes the transcription for the processed time range and returns the transcript with a speech_final parameter set to true. Can also be set to false to disable endpointing - */ - endpointing?: string; - /** - * Indicates that speech has started. You'll begin receiving Speech Started messages upon speech starting. Note: Supported only for webosockets. - */ - vad_events?: boolean; - /** - * Indicates how long model will wait to send an UtteranceEnd message after a word has been transcribed. Use with interim_results. Note: Supported only for webosockets. - */ - utterance_end_ms?: boolean; -} -interface Ai_Cf_Deepgram_Nova_3_Output { - results?: { - channels?: { - alternatives?: { - confidence?: number; - transcript?: string; - words?: { - confidence?: number; - end?: number; - start?: number; - word?: string; - }[]; - }[]; - }[]; - summary?: { - result?: string; - short?: string; - }; - sentiments?: { - segments?: { - text?: string; - start_word?: number; - end_word?: number; - sentiment?: string; - sentiment_score?: number; - }[]; - average?: { - sentiment?: string; - sentiment_score?: number; - }; - }; - }; -} -declare abstract class Base_Ai_Cf_Deepgram_Nova_3 { - inputs: Ai_Cf_Deepgram_Nova_3_Input; - postProcessedOutputs: Ai_Cf_Deepgram_Nova_3_Output; -} -interface Ai_Cf_Qwen_Qwen3_Embedding_0_6B_Input { - queries?: string | string[]; - /** - * Optional instruction for the task - */ - instruction?: string; - documents?: string | string[]; - text?: string | string[]; -} -interface Ai_Cf_Qwen_Qwen3_Embedding_0_6B_Output { - data?: number[][]; - shape?: number[]; -} -declare abstract class Base_Ai_Cf_Qwen_Qwen3_Embedding_0_6B { - inputs: Ai_Cf_Qwen_Qwen3_Embedding_0_6B_Input; - postProcessedOutputs: Ai_Cf_Qwen_Qwen3_Embedding_0_6B_Output; -} -type Ai_Cf_Pipecat_Ai_Smart_Turn_V2_Input = - | { - /** - * readable stream with audio data and content-type specified for that data - */ - audio: { - body: object; - contentType: string; - }; - /** - * type of data PCM data that's sent to the inference server as raw array - */ - dtype?: 'uint8' | 'float32' | 'float64'; - } - | { - /** - * base64 encoded audio data - */ - audio: string; - /** - * type of data PCM data that's sent to the inference server as raw array - */ - dtype?: 'uint8' | 'float32' | 'float64'; - }; -interface Ai_Cf_Pipecat_Ai_Smart_Turn_V2_Output { - /** - * if true, end-of-turn was detected - */ - is_complete?: boolean; - /** - * probability of the end-of-turn detection - */ - probability?: number; -} -declare abstract class Base_Ai_Cf_Pipecat_Ai_Smart_Turn_V2 { - inputs: Ai_Cf_Pipecat_Ai_Smart_Turn_V2_Input; - postProcessedOutputs: Ai_Cf_Pipecat_Ai_Smart_Turn_V2_Output; -} -declare abstract class Base_Ai_Cf_Openai_Gpt_Oss_120B { - inputs: ResponsesInput; - postProcessedOutputs: ResponsesOutput; -} -declare abstract class Base_Ai_Cf_Openai_Gpt_Oss_20B { - inputs: ResponsesInput; - postProcessedOutputs: ResponsesOutput; -} -interface Ai_Cf_Leonardo_Phoenix_1_0_Input { - /** - * A text description of the image you want to generate. - */ - prompt: string; - /** - * Controls how closely the generated image should adhere to the prompt; higher values make the image more aligned with the prompt - */ - guidance?: number; - /** - * Random seed for reproducibility of the image generation - */ - seed?: number; - /** - * The height of the generated image in pixels - */ - height?: number; - /** - * The width of the generated image in pixels - */ - width?: number; - /** - * The number of diffusion steps; higher values can improve quality but take longer - */ - num_steps?: number; - /** - * Specify what to exclude from the generated images - */ - negative_prompt?: string; -} -/** - * The generated image in JPEG format - */ -type Ai_Cf_Leonardo_Phoenix_1_0_Output = string; -declare abstract class Base_Ai_Cf_Leonardo_Phoenix_1_0 { - inputs: Ai_Cf_Leonardo_Phoenix_1_0_Input; - postProcessedOutputs: Ai_Cf_Leonardo_Phoenix_1_0_Output; -} -interface Ai_Cf_Leonardo_Lucid_Origin_Input { - /** - * A text description of the image you want to generate. - */ - prompt: string; - /** - * Controls how closely the generated image should adhere to the prompt; higher values make the image more aligned with the prompt - */ - guidance?: number; - /** - * Random seed for reproducibility of the image generation - */ - seed?: number; - /** - * The height of the generated image in pixels - */ - height?: number; - /** - * The width of the generated image in pixels - */ - width?: number; - /** - * The number of diffusion steps; higher values can improve quality but take longer - */ - num_steps?: number; - /** - * The number of diffusion steps; higher values can improve quality but take longer - */ - steps?: number; -} -interface Ai_Cf_Leonardo_Lucid_Origin_Output { - /** - * The generated image in Base64 format. - */ - image?: string; -} -declare abstract class Base_Ai_Cf_Leonardo_Lucid_Origin { - inputs: Ai_Cf_Leonardo_Lucid_Origin_Input; - postProcessedOutputs: Ai_Cf_Leonardo_Lucid_Origin_Output; -} -interface Ai_Cf_Deepgram_Aura_1_Input { - /** - * Speaker used to produce the audio. - */ - speaker?: - | 'angus' - | 'asteria' - | 'arcas' - | 'orion' - | 'orpheus' - | 'athena' - | 'luna' - | 'zeus' - | 'perseus' - | 'helios' - | 'hera' - | 'stella'; - /** - * Encoding of the output audio. - */ - encoding?: 'linear16' | 'flac' | 'mulaw' | 'alaw' | 'mp3' | 'opus' | 'aac'; - /** - * Container specifies the file format wrapper for the output audio. The available options depend on the encoding type.. - */ - container?: 'none' | 'wav' | 'ogg'; - /** - * The text content to be converted to speech - */ - text: string; - /** - * Sample Rate specifies the sample rate for the output audio. Based on the encoding, different sample rates are supported. For some encodings, the sample rate is not configurable - */ - sample_rate?: number; - /** - * The bitrate of the audio in bits per second. Choose from predefined ranges or specific values based on the encoding type. - */ - bit_rate?: number; -} -/** - * The generated audio in MP3 format - */ -type Ai_Cf_Deepgram_Aura_1_Output = string; -declare abstract class Base_Ai_Cf_Deepgram_Aura_1 { - inputs: Ai_Cf_Deepgram_Aura_1_Input; - postProcessedOutputs: Ai_Cf_Deepgram_Aura_1_Output; -} -interface Ai_Cf_Ai4Bharat_Indictrans2_En_Indic_1B_Input { - /** - * Input text to translate. Can be a single string or a list of strings. - */ - text: string | string[]; - /** - * Target langauge to translate to - */ - target_language: - | 'asm_Beng' - | 'awa_Deva' - | 'ben_Beng' - | 'bho_Deva' - | 'brx_Deva' - | 'doi_Deva' - | 'eng_Latn' - | 'gom_Deva' - | 'gon_Deva' - | 'guj_Gujr' - | 'hin_Deva' - | 'hne_Deva' - | 'kan_Knda' - | 'kas_Arab' - | 'kas_Deva' - | 'kha_Latn' - | 'lus_Latn' - | 'mag_Deva' - | 'mai_Deva' - | 'mal_Mlym' - | 'mar_Deva' - | 'mni_Beng' - | 'mni_Mtei' - | 'npi_Deva' - | 'ory_Orya' - | 'pan_Guru' - | 'san_Deva' - | 'sat_Olck' - | 'snd_Arab' - | 'snd_Deva' - | 'tam_Taml' - | 'tel_Telu' - | 'urd_Arab' - | 'unr_Deva'; -} -interface Ai_Cf_Ai4Bharat_Indictrans2_En_Indic_1B_Output { - /** - * Translated texts - */ - translations: string[]; -} -declare abstract class Base_Ai_Cf_Ai4Bharat_Indictrans2_En_Indic_1B { - inputs: Ai_Cf_Ai4Bharat_Indictrans2_En_Indic_1B_Input; - postProcessedOutputs: Ai_Cf_Ai4Bharat_Indictrans2_En_Indic_1B_Output; -} -type Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Input = - | Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Prompt - | Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Messages - | Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Async_Batch; -interface Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Prompt { - /** - * The input text prompt for the model to generate a response. - */ - prompt: string; - /** - * Name of the LoRA (Low-Rank Adaptation) model to fine-tune the base model. - */ - lora?: string; - response_format?: Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_JSON_Mode; - /** - * If true, a chat template is not applied and you must adhere to the specific model's expected formatting. - */ - raw?: boolean; - /** - * If true, the response will be streamed back incrementally using SSE, Server Sent Events. - */ - stream?: boolean; - /** - * The maximum number of tokens to generate in the response. - */ - max_tokens?: number; - /** - * Controls the randomness of the output; higher values produce more random results. - */ - temperature?: number; - /** - * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses. - */ - top_p?: number; - /** - * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises. - */ - top_k?: number; - /** - * Random seed for reproducibility of the generation. - */ - seed?: number; - /** - * Penalty for repeated tokens; higher values discourage repetition. - */ - repetition_penalty?: number; - /** - * Decreases the likelihood of the model repeating the same lines verbatim. - */ - frequency_penalty?: number; - /** - * Increases the likelihood of the model introducing new topics. - */ - presence_penalty?: number; -} -interface Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_JSON_Mode { - type?: 'json_object' | 'json_schema'; - json_schema?: unknown; -} -interface Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Messages { - /** - * An array of message objects representing the conversation history. - */ - messages: { - /** - * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool'). - */ - role: string; - /** - * The content of the message as a string. - */ - content: string; - }[]; - functions?: { - name: string; - code: string; - }[]; - /** - * A list of tools available for the assistant to use. - */ - tools?: ( - | { - /** - * The name of the tool. More descriptive the better. - */ - name: string; - /** - * A brief description of what the tool does. - */ - description: string; - /** - * Schema defining the parameters accepted by the tool. - */ - parameters: { - /** - * The type of the parameters object (usually 'object'). - */ - type: string; - /** - * List of required parameter names. - */ - required?: string[]; - /** - * Definitions of each parameter. - */ - properties: { - [k: string]: { - /** - * The data type of the parameter. - */ - type: string; - /** - * A description of the expected parameter. - */ - description: string; - }; - }; - }; - } - | { - /** - * Specifies the type of tool (e.g., 'function'). - */ - type: string; - /** - * Details of the function tool. - */ - function: { - /** - * The name of the function. - */ - name: string; - /** - * A brief description of what the function does. - */ - description: string; - /** - * Schema defining the parameters accepted by the function. - */ - parameters: { - /** - * The type of the parameters object (usually 'object'). - */ - type: string; - /** - * List of required parameter names. - */ - required?: string[]; - /** - * Definitions of each parameter. - */ - properties: { - [k: string]: { - /** - * The data type of the parameter. - */ - type: string; - /** - * A description of the expected parameter. - */ - description: string; - }; - }; - }; - }; - } - )[]; - response_format?: Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_JSON_Mode_1; - /** - * If true, a chat template is not applied and you must adhere to the specific model's expected formatting. - */ - raw?: boolean; - /** - * If true, the response will be streamed back incrementally using SSE, Server Sent Events. - */ - stream?: boolean; - /** - * The maximum number of tokens to generate in the response. - */ - max_tokens?: number; - /** - * Controls the randomness of the output; higher values produce more random results. - */ - temperature?: number; - /** - * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses. - */ - top_p?: number; - /** - * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises. - */ - top_k?: number; - /** - * Random seed for reproducibility of the generation. - */ - seed?: number; - /** - * Penalty for repeated tokens; higher values discourage repetition. - */ - repetition_penalty?: number; - /** - * Decreases the likelihood of the model repeating the same lines verbatim. - */ - frequency_penalty?: number; - /** - * Increases the likelihood of the model introducing new topics. - */ - presence_penalty?: number; -} -interface Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_JSON_Mode_1 { - type?: 'json_object' | 'json_schema'; - json_schema?: unknown; -} -interface Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Async_Batch { - requests: ( - | Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Prompt_1 - | Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Messages_1 - )[]; -} -interface Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Prompt_1 { - /** - * The input text prompt for the model to generate a response. - */ - prompt: string; - /** - * Name of the LoRA (Low-Rank Adaptation) model to fine-tune the base model. - */ - lora?: string; - response_format?: Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_JSON_Mode_2; - /** - * If true, a chat template is not applied and you must adhere to the specific model's expected formatting. - */ - raw?: boolean; - /** - * If true, the response will be streamed back incrementally using SSE, Server Sent Events. - */ - stream?: boolean; - /** - * The maximum number of tokens to generate in the response. - */ - max_tokens?: number; - /** - * Controls the randomness of the output; higher values produce more random results. - */ - temperature?: number; - /** - * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses. - */ - top_p?: number; - /** - * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises. - */ - top_k?: number; - /** - * Random seed for reproducibility of the generation. - */ - seed?: number; - /** - * Penalty for repeated tokens; higher values discourage repetition. - */ - repetition_penalty?: number; - /** - * Decreases the likelihood of the model repeating the same lines verbatim. - */ - frequency_penalty?: number; - /** - * Increases the likelihood of the model introducing new topics. - */ - presence_penalty?: number; -} -interface Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_JSON_Mode_2 { - type?: 'json_object' | 'json_schema'; - json_schema?: unknown; -} -interface Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Messages_1 { - /** - * An array of message objects representing the conversation history. - */ - messages: { - /** - * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool'). - */ - role: string; - /** - * The content of the message as a string. - */ - content: string; - }[]; - functions?: { - name: string; - code: string; - }[]; - /** - * A list of tools available for the assistant to use. - */ - tools?: ( - | { - /** - * The name of the tool. More descriptive the better. - */ - name: string; - /** - * A brief description of what the tool does. - */ - description: string; - /** - * Schema defining the parameters accepted by the tool. - */ - parameters: { - /** - * The type of the parameters object (usually 'object'). - */ - type: string; - /** - * List of required parameter names. - */ - required?: string[]; - /** - * Definitions of each parameter. - */ - properties: { - [k: string]: { - /** - * The data type of the parameter. - */ - type: string; - /** - * A description of the expected parameter. - */ - description: string; - }; - }; - }; - } - | { - /** - * Specifies the type of tool (e.g., 'function'). - */ - type: string; - /** - * Details of the function tool. - */ - function: { - /** - * The name of the function. - */ - name: string; - /** - * A brief description of what the function does. - */ - description: string; - /** - * Schema defining the parameters accepted by the function. - */ - parameters: { - /** - * The type of the parameters object (usually 'object'). - */ - type: string; - /** - * List of required parameter names. - */ - required?: string[]; - /** - * Definitions of each parameter. - */ - properties: { - [k: string]: { - /** - * The data type of the parameter. - */ - type: string; - /** - * A description of the expected parameter. - */ - description: string; - }; - }; - }; - }; - } - )[]; - response_format?: Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_JSON_Mode_3; - /** - * If true, a chat template is not applied and you must adhere to the specific model's expected formatting. - */ - raw?: boolean; - /** - * If true, the response will be streamed back incrementally using SSE, Server Sent Events. - */ - stream?: boolean; - /** - * The maximum number of tokens to generate in the response. - */ - max_tokens?: number; - /** - * Controls the randomness of the output; higher values produce more random results. - */ - temperature?: number; - /** - * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses. - */ - top_p?: number; - /** - * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises. - */ - top_k?: number; - /** - * Random seed for reproducibility of the generation. - */ - seed?: number; - /** - * Penalty for repeated tokens; higher values discourage repetition. - */ - repetition_penalty?: number; - /** - * Decreases the likelihood of the model repeating the same lines verbatim. - */ - frequency_penalty?: number; - /** - * Increases the likelihood of the model introducing new topics. - */ - presence_penalty?: number; -} -interface Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_JSON_Mode_3 { - type?: 'json_object' | 'json_schema'; - json_schema?: unknown; -} -type Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Output = - | Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Chat_Completion_Response - | Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Text_Completion_Response - | string - | Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_AsyncResponse; -interface Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Chat_Completion_Response { - /** - * Unique identifier for the completion - */ - id?: string; - /** - * Object type identifier - */ - object?: 'chat.completion'; - /** - * Unix timestamp of when the completion was created - */ - created?: number; - /** - * Model used for the completion - */ - model?: string; - /** - * List of completion choices - */ - choices?: { - /** - * Index of the choice in the list - */ - index?: number; - /** - * The message generated by the model - */ - message?: { - /** - * Role of the message author - */ - role: string; - /** - * The content of the message - */ - content: string; - /** - * Internal reasoning content (if available) - */ - reasoning_content?: string; - /** - * Tool calls made by the assistant - */ - tool_calls?: { - /** - * Unique identifier for the tool call - */ - id: string; - /** - * Type of tool call - */ - type: 'function'; - function: { - /** - * Name of the function to call - */ - name: string; - /** - * JSON string of arguments for the function - */ - arguments: string; - }; - }[]; - }; - /** - * Reason why the model stopped generating - */ - finish_reason?: string; - /** - * Stop reason (may be null) - */ - stop_reason?: string | null; - /** - * Log probabilities (if requested) - */ - logprobs?: {} | null; - }[]; - /** - * Usage statistics for the inference request - */ - usage?: { - /** - * Total number of tokens in input - */ - prompt_tokens?: number; - /** - * Total number of tokens in output - */ - completion_tokens?: number; - /** - * Total number of input and output tokens - */ - total_tokens?: number; - }; - /** - * Log probabilities for the prompt (if requested) - */ - prompt_logprobs?: {} | null; -} -interface Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Text_Completion_Response { - /** - * Unique identifier for the completion - */ - id?: string; - /** - * Object type identifier - */ - object?: 'text_completion'; - /** - * Unix timestamp of when the completion was created - */ - created?: number; - /** - * Model used for the completion - */ - model?: string; - /** - * List of completion choices - */ - choices?: { - /** - * Index of the choice in the list - */ - index: number; - /** - * The generated text completion - */ - text: string; - /** - * Reason why the model stopped generating - */ - finish_reason: string; - /** - * Stop reason (may be null) - */ - stop_reason?: string | null; - /** - * Log probabilities (if requested) - */ - logprobs?: {} | null; - /** - * Log probabilities for the prompt (if requested) - */ - prompt_logprobs?: {} | null; - }[]; - /** - * Usage statistics for the inference request - */ - usage?: { - /** - * Total number of tokens in input - */ - prompt_tokens?: number; - /** - * Total number of tokens in output - */ - completion_tokens?: number; - /** - * Total number of input and output tokens - */ - total_tokens?: number; - }; -} -interface Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_AsyncResponse { - /** - * The async request id that can be used to obtain the results. - */ - request_id?: string; -} -declare abstract class Base_Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It { - inputs: Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Input; - postProcessedOutputs: Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Output; -} -interface Ai_Cf_Pfnet_Plamo_Embedding_1B_Input { - /** - * Input text to embed. Can be a single string or a list of strings. - */ - text: string | string[]; -} -interface Ai_Cf_Pfnet_Plamo_Embedding_1B_Output { - /** - * Embedding vectors, where each vector is a list of floats. - */ - data: number[][]; - /** - * Shape of the embedding data as [number_of_embeddings, embedding_dimension]. - * - * @minItems 2 - * @maxItems 2 - */ - shape: [number, number]; -} -declare abstract class Base_Ai_Cf_Pfnet_Plamo_Embedding_1B { - inputs: Ai_Cf_Pfnet_Plamo_Embedding_1B_Input; - postProcessedOutputs: Ai_Cf_Pfnet_Plamo_Embedding_1B_Output; -} -interface Ai_Cf_Deepgram_Flux_Input { - /** - * Encoding of the audio stream. Currently only supports raw signed little-endian 16-bit PCM. - */ - encoding: 'linear16'; - /** - * Sample rate of the audio stream in Hz. - */ - sample_rate: string; - /** - * End-of-turn confidence required to fire an eager end-of-turn event. When set, enables EagerEndOfTurn and TurnResumed events. Valid Values 0.3 - 0.9. - */ - eager_eot_threshold?: string; - /** - * End-of-turn confidence required to finish a turn. Valid Values 0.5 - 0.9. - */ - eot_threshold?: string; - /** - * A turn will be finished when this much time has passed after speech, regardless of EOT confidence. - */ - eot_timeout_ms?: string; - /** - * Keyterm prompting can improve recognition of specialized terminology. Pass multiple keyterm query parameters to boost multiple keyterms. - */ - keyterm?: string; - /** - * Opts out requests from the Deepgram Model Improvement Program. Refer to Deepgram Docs for pricing impacts before setting this to true. https://dpgr.am/deepgram-mip - */ - mip_opt_out?: 'true' | 'false'; - /** - * Label your requests for the purpose of identification during usage reporting - */ - tag?: string; -} -/** - * Output will be returned as websocket messages. - */ -interface Ai_Cf_Deepgram_Flux_Output { - /** - * The unique identifier of the request (uuid) - */ - request_id?: string; - /** - * Starts at 0 and increments for each message the server sends to the client. - */ - sequence_id?: number; - /** - * The type of event being reported. - */ - event?: 'Update' | 'StartOfTurn' | 'EagerEndOfTurn' | 'TurnResumed' | 'EndOfTurn'; - /** - * The index of the current turn - */ - turn_index?: number; - /** - * Start time in seconds of the audio range that was transcribed - */ - audio_window_start?: number; - /** - * End time in seconds of the audio range that was transcribed - */ - audio_window_end?: number; - /** - * Text that was said over the course of the current turn - */ - transcript?: string; - /** - * The words in the transcript - */ - words?: { - /** - * The individual punctuated, properly-cased word from the transcript - */ - word: string; - /** - * Confidence that this word was transcribed correctly - */ - confidence: number; - }[]; - /** - * Confidence that no more speech is coming in this turn - */ - end_of_turn_confidence?: number; -} -declare abstract class Base_Ai_Cf_Deepgram_Flux { - inputs: Ai_Cf_Deepgram_Flux_Input; - postProcessedOutputs: Ai_Cf_Deepgram_Flux_Output; -} -interface Ai_Cf_Deepgram_Aura_2_En_Input { - /** - * Speaker used to produce the audio. - */ - speaker?: - | 'amalthea' - | 'andromeda' - | 'apollo' - | 'arcas' - | 'aries' - | 'asteria' - | 'athena' - | 'atlas' - | 'aurora' - | 'callista' - | 'cora' - | 'cordelia' - | 'delia' - | 'draco' - | 'electra' - | 'harmonia' - | 'helena' - | 'hera' - | 'hermes' - | 'hyperion' - | 'iris' - | 'janus' - | 'juno' - | 'jupiter' - | 'luna' - | 'mars' - | 'minerva' - | 'neptune' - | 'odysseus' - | 'ophelia' - | 'orion' - | 'orpheus' - | 'pandora' - | 'phoebe' - | 'pluto' - | 'saturn' - | 'thalia' - | 'theia' - | 'vesta' - | 'zeus'; - /** - * Encoding of the output audio. - */ - encoding?: 'linear16' | 'flac' | 'mulaw' | 'alaw' | 'mp3' | 'opus' | 'aac'; - /** - * Container specifies the file format wrapper for the output audio. The available options depend on the encoding type.. - */ - container?: 'none' | 'wav' | 'ogg'; - /** - * The text content to be converted to speech - */ - text: string; - /** - * Sample Rate specifies the sample rate for the output audio. Based on the encoding, different sample rates are supported. For some encodings, the sample rate is not configurable - */ - sample_rate?: number; - /** - * The bitrate of the audio in bits per second. Choose from predefined ranges or specific values based on the encoding type. - */ - bit_rate?: number; -} -/** - * The generated audio in MP3 format - */ -type Ai_Cf_Deepgram_Aura_2_En_Output = string; -declare abstract class Base_Ai_Cf_Deepgram_Aura_2_En { - inputs: Ai_Cf_Deepgram_Aura_2_En_Input; - postProcessedOutputs: Ai_Cf_Deepgram_Aura_2_En_Output; -} -interface Ai_Cf_Deepgram_Aura_2_Es_Input { - /** - * Speaker used to produce the audio. - */ - speaker?: - | 'sirio' - | 'nestor' - | 'carina' - | 'celeste' - | 'alvaro' - | 'diana' - | 'aquila' - | 'selena' - | 'estrella' - | 'javier'; - /** - * Encoding of the output audio. - */ - encoding?: 'linear16' | 'flac' | 'mulaw' | 'alaw' | 'mp3' | 'opus' | 'aac'; - /** - * Container specifies the file format wrapper for the output audio. The available options depend on the encoding type.. - */ - container?: 'none' | 'wav' | 'ogg'; - /** - * The text content to be converted to speech - */ - text: string; - /** - * Sample Rate specifies the sample rate for the output audio. Based on the encoding, different sample rates are supported. For some encodings, the sample rate is not configurable - */ - sample_rate?: number; - /** - * The bitrate of the audio in bits per second. Choose from predefined ranges or specific values based on the encoding type. - */ - bit_rate?: number; -} -/** - * The generated audio in MP3 format - */ -type Ai_Cf_Deepgram_Aura_2_Es_Output = string; -declare abstract class Base_Ai_Cf_Deepgram_Aura_2_Es { - inputs: Ai_Cf_Deepgram_Aura_2_Es_Input; - postProcessedOutputs: Ai_Cf_Deepgram_Aura_2_Es_Output; -} -interface AiModels { - '@cf/huggingface/distilbert-sst-2-int8': BaseAiTextClassification; - '@cf/stabilityai/stable-diffusion-xl-base-1.0': BaseAiTextToImage; - '@cf/runwayml/stable-diffusion-v1-5-inpainting': BaseAiTextToImage; - '@cf/runwayml/stable-diffusion-v1-5-img2img': BaseAiTextToImage; - '@cf/lykon/dreamshaper-8-lcm': BaseAiTextToImage; - '@cf/bytedance/stable-diffusion-xl-lightning': BaseAiTextToImage; - '@cf/myshell-ai/melotts': BaseAiTextToSpeech; - '@cf/google/embeddinggemma-300m': BaseAiTextEmbeddings; - '@cf/microsoft/resnet-50': BaseAiImageClassification; - '@cf/meta/llama-2-7b-chat-int8': BaseAiTextGeneration; - '@cf/mistral/mistral-7b-instruct-v0.1': BaseAiTextGeneration; - '@cf/meta/llama-2-7b-chat-fp16': BaseAiTextGeneration; - '@hf/thebloke/llama-2-13b-chat-awq': BaseAiTextGeneration; - '@hf/thebloke/mistral-7b-instruct-v0.1-awq': BaseAiTextGeneration; - '@hf/thebloke/zephyr-7b-beta-awq': BaseAiTextGeneration; - '@hf/thebloke/openhermes-2.5-mistral-7b-awq': BaseAiTextGeneration; - '@hf/thebloke/neural-chat-7b-v3-1-awq': BaseAiTextGeneration; - '@hf/thebloke/llamaguard-7b-awq': BaseAiTextGeneration; - '@hf/thebloke/deepseek-coder-6.7b-base-awq': BaseAiTextGeneration; - '@hf/thebloke/deepseek-coder-6.7b-instruct-awq': BaseAiTextGeneration; - '@cf/deepseek-ai/deepseek-math-7b-instruct': BaseAiTextGeneration; - '@cf/defog/sqlcoder-7b-2': BaseAiTextGeneration; - '@cf/openchat/openchat-3.5-0106': BaseAiTextGeneration; - '@cf/tiiuae/falcon-7b-instruct': BaseAiTextGeneration; - '@cf/thebloke/discolm-german-7b-v1-awq': BaseAiTextGeneration; - '@cf/qwen/qwen1.5-0.5b-chat': BaseAiTextGeneration; - '@cf/qwen/qwen1.5-7b-chat-awq': BaseAiTextGeneration; - '@cf/qwen/qwen1.5-14b-chat-awq': BaseAiTextGeneration; - '@cf/tinyllama/tinyllama-1.1b-chat-v1.0': BaseAiTextGeneration; - '@cf/microsoft/phi-2': BaseAiTextGeneration; - '@cf/qwen/qwen1.5-1.8b-chat': BaseAiTextGeneration; - '@cf/mistral/mistral-7b-instruct-v0.2-lora': BaseAiTextGeneration; - '@hf/nousresearch/hermes-2-pro-mistral-7b': BaseAiTextGeneration; - '@hf/nexusflow/starling-lm-7b-beta': BaseAiTextGeneration; - '@hf/google/gemma-7b-it': BaseAiTextGeneration; - '@cf/meta-llama/llama-2-7b-chat-hf-lora': BaseAiTextGeneration; - '@cf/google/gemma-2b-it-lora': BaseAiTextGeneration; - '@cf/google/gemma-7b-it-lora': BaseAiTextGeneration; - '@hf/mistral/mistral-7b-instruct-v0.2': BaseAiTextGeneration; - '@cf/meta/llama-3-8b-instruct': BaseAiTextGeneration; - '@cf/fblgit/una-cybertron-7b-v2-bf16': BaseAiTextGeneration; - '@cf/meta/llama-3-8b-instruct-awq': BaseAiTextGeneration; - '@cf/meta/llama-3.1-8b-instruct-fp8': BaseAiTextGeneration; - '@cf/meta/llama-3.1-8b-instruct-awq': BaseAiTextGeneration; - '@cf/meta/llama-3.2-3b-instruct': BaseAiTextGeneration; - '@cf/meta/llama-3.2-1b-instruct': BaseAiTextGeneration; - '@cf/deepseek-ai/deepseek-r1-distill-qwen-32b': BaseAiTextGeneration; - '@cf/ibm-granite/granite-4.0-h-micro': BaseAiTextGeneration; - '@cf/facebook/bart-large-cnn': BaseAiSummarization; - '@cf/llava-hf/llava-1.5-7b-hf': BaseAiImageToText; - '@cf/baai/bge-base-en-v1.5': Base_Ai_Cf_Baai_Bge_Base_En_V1_5; - '@cf/openai/whisper': Base_Ai_Cf_Openai_Whisper; - '@cf/meta/m2m100-1.2b': Base_Ai_Cf_Meta_M2M100_1_2B; - '@cf/baai/bge-small-en-v1.5': Base_Ai_Cf_Baai_Bge_Small_En_V1_5; - '@cf/baai/bge-large-en-v1.5': Base_Ai_Cf_Baai_Bge_Large_En_V1_5; - '@cf/unum/uform-gen2-qwen-500m': Base_Ai_Cf_Unum_Uform_Gen2_Qwen_500M; - '@cf/openai/whisper-tiny-en': Base_Ai_Cf_Openai_Whisper_Tiny_En; - '@cf/openai/whisper-large-v3-turbo': Base_Ai_Cf_Openai_Whisper_Large_V3_Turbo; - '@cf/baai/bge-m3': Base_Ai_Cf_Baai_Bge_M3; - '@cf/black-forest-labs/flux-1-schnell': Base_Ai_Cf_Black_Forest_Labs_Flux_1_Schnell; - '@cf/meta/llama-3.2-11b-vision-instruct': Base_Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct; - '@cf/meta/llama-3.3-70b-instruct-fp8-fast': Base_Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast; - '@cf/meta/llama-guard-3-8b': Base_Ai_Cf_Meta_Llama_Guard_3_8B; - '@cf/baai/bge-reranker-base': Base_Ai_Cf_Baai_Bge_Reranker_Base; - '@cf/qwen/qwen2.5-coder-32b-instruct': Base_Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct; - '@cf/qwen/qwq-32b': Base_Ai_Cf_Qwen_Qwq_32B; - '@cf/mistralai/mistral-small-3.1-24b-instruct': Base_Ai_Cf_Mistralai_Mistral_Small_3_1_24B_Instruct; - '@cf/google/gemma-3-12b-it': Base_Ai_Cf_Google_Gemma_3_12B_It; - '@cf/meta/llama-4-scout-17b-16e-instruct': Base_Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct; - '@cf/qwen/qwen3-30b-a3b-fp8': Base_Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8; - '@cf/deepgram/nova-3': Base_Ai_Cf_Deepgram_Nova_3; - '@cf/qwen/qwen3-embedding-0.6b': Base_Ai_Cf_Qwen_Qwen3_Embedding_0_6B; - '@cf/pipecat-ai/smart-turn-v2': Base_Ai_Cf_Pipecat_Ai_Smart_Turn_V2; - '@cf/openai/gpt-oss-120b': Base_Ai_Cf_Openai_Gpt_Oss_120B; - '@cf/openai/gpt-oss-20b': Base_Ai_Cf_Openai_Gpt_Oss_20B; - '@cf/leonardo/phoenix-1.0': Base_Ai_Cf_Leonardo_Phoenix_1_0; - '@cf/leonardo/lucid-origin': Base_Ai_Cf_Leonardo_Lucid_Origin; - '@cf/deepgram/aura-1': Base_Ai_Cf_Deepgram_Aura_1; - '@cf/ai4bharat/indictrans2-en-indic-1B': Base_Ai_Cf_Ai4Bharat_Indictrans2_En_Indic_1B; - '@cf/aisingapore/gemma-sea-lion-v4-27b-it': Base_Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It; - '@cf/pfnet/plamo-embedding-1b': Base_Ai_Cf_Pfnet_Plamo_Embedding_1B; - '@cf/deepgram/flux': Base_Ai_Cf_Deepgram_Flux; - '@cf/deepgram/aura-2-en': Base_Ai_Cf_Deepgram_Aura_2_En; - '@cf/deepgram/aura-2-es': Base_Ai_Cf_Deepgram_Aura_2_Es; -} -type AiOptions = { - /** - * Send requests as an asynchronous batch job, only works for supported models - * https://developers.cloudflare.com/workers-ai/features/batch-api - */ - queueRequest?: boolean; - /** - * Establish websocket connections, only works for supported models - */ - websocket?: boolean; - /** - * Tag your requests to group and view them in Cloudflare dashboard. - * - * Rules: - * Tags must only contain letters, numbers, and the symbols: : - . / @ - * Each tag can have maximum 50 characters. - * Maximum 5 tags are allowed each request. - * Duplicate tags will removed. - */ - tags?: string[]; - gateway?: GatewayOptions; - returnRawResponse?: boolean; - prefix?: string; - extraHeaders?: object; -}; -type AiModelsSearchParams = { - author?: string; - hide_experimental?: boolean; - page?: number; - per_page?: number; - search?: string; - source?: number; - task?: string; -}; -type AiModelsSearchObject = { - id: string; - source: number; - name: string; - description: string; - task: { - id: string; - name: string; - description: string; - }; - tags: string[]; - properties: { - property_id: string; - value: string; - }[]; -}; -interface InferenceUpstreamError extends Error {} -interface AiInternalError extends Error {} -type AiModelListType = Record; -declare abstract class Ai { - aiGatewayLogId: string | null; - gateway(gatewayId: string): AiGateway; - autorag(autoragId: string): AutoRAG; - run< - Name extends keyof AiModelList, - Options extends AiOptions, - InputOptions extends AiModelList[Name]['inputs'], - >( - model: Name, - inputs: InputOptions, - options?: Options, - ): Promise< - Options extends ( - | { - returnRawResponse: true; - } - | { - websocket: true; - } - ) ? - Response - : InputOptions extends ( - { - stream: true; - } - ) ? - ReadableStream - : AiModelList[Name]['postProcessedOutputs'] - >; - models(params?: AiModelsSearchParams): Promise; - toMarkdown(): ToMarkdownService; - toMarkdown( - files: MarkdownDocument[], - options?: ConversionRequestOptions, - ): Promise; - toMarkdown( - files: MarkdownDocument, - options?: ConversionRequestOptions, - ): Promise; -} -type GatewayRetries = { - maxAttempts?: 1 | 2 | 3 | 4 | 5; - retryDelayMs?: number; - backoff?: 'constant' | 'linear' | 'exponential'; -}; -type GatewayOptions = { - id: string; - cacheKey?: string; - cacheTtl?: number; - skipCache?: boolean; - metadata?: Record; - collectLog?: boolean; - eventId?: string; - requestTimeoutMs?: number; - retries?: GatewayRetries; -}; -type UniversalGatewayOptions = Exclude & { - /** - ** @deprecated - */ - id?: string; -}; -type AiGatewayPatchLog = { - score?: number | null; - feedback?: -1 | 1 | null; - metadata?: Record | null; -}; -type AiGatewayLog = { - id: string; - provider: string; - model: string; - model_type?: string; - path: string; - duration: number; - request_type?: string; - request_content_type?: string; - status_code: number; - response_content_type?: string; - success: boolean; - cached: boolean; - tokens_in?: number; - tokens_out?: number; - metadata?: Record; - step?: number; - cost?: number; - custom_cost?: boolean; - request_size: number; - request_head?: string; - request_head_complete: boolean; - response_size: number; - response_head?: string; - response_head_complete: boolean; - created_at: Date; -}; -type AIGatewayProviders = - | 'workers-ai' - | 'anthropic' - | 'aws-bedrock' - | 'azure-openai' - | 'google-vertex-ai' - | 'huggingface' - | 'openai' - | 'perplexity-ai' - | 'replicate' - | 'groq' - | 'cohere' - | 'google-ai-studio' - | 'mistral' - | 'grok' - | 'openrouter' - | 'deepseek' - | 'cerebras' - | 'cartesia' - | 'elevenlabs' - | 'adobe-firefly'; -type AIGatewayHeaders = { - 'cf-aig-metadata': Record | string; - 'cf-aig-custom-cost': - | { - per_token_in?: number; - per_token_out?: number; - } - | { - total_cost?: number; - } - | string; - 'cf-aig-cache-ttl': number | string; - 'cf-aig-skip-cache': boolean | string; - 'cf-aig-cache-key': string; - 'cf-aig-event-id': string; - 'cf-aig-request-timeout': number | string; - 'cf-aig-max-attempts': number | string; - 'cf-aig-retry-delay': number | string; - 'cf-aig-backoff': string; - 'cf-aig-collect-log': boolean | string; - Authorization: string; - 'Content-Type': string; - [key: string]: string | number | boolean | object; -}; -type AIGatewayUniversalRequest = { - provider: AIGatewayProviders | string; - endpoint: string; - headers: Partial; - query: unknown; -}; -interface AiGatewayInternalError extends Error {} -interface AiGatewayLogNotFound extends Error {} -declare abstract class AiGateway { - patchLog(logId: string, data: AiGatewayPatchLog): Promise; - getLog(logId: string): Promise; - run( - data: AIGatewayUniversalRequest | AIGatewayUniversalRequest[], - options?: { - gateway?: UniversalGatewayOptions; - extraHeaders?: object; - }, - ): Promise; - getUrl(provider?: AIGatewayProviders | string): Promise; -} -interface AutoRAGInternalError extends Error {} -interface AutoRAGNotFoundError extends Error {} -interface AutoRAGUnauthorizedError extends Error {} -interface AutoRAGNameNotSetError extends Error {} -type ComparisonFilter = { - key: string; - type: 'eq' | 'ne' | 'gt' | 'gte' | 'lt' | 'lte'; - value: string | number | boolean; -}; -type CompoundFilter = { - type: 'and' | 'or'; - filters: ComparisonFilter[]; -}; -type AutoRagSearchRequest = { - query: string; - filters?: CompoundFilter | ComparisonFilter; - max_num_results?: number; - ranking_options?: { - ranker?: string; - score_threshold?: number; - }; - reranking?: { - enabled?: boolean; - model?: string; - }; - rewrite_query?: boolean; -}; -type AutoRagAiSearchRequest = AutoRagSearchRequest & { - stream?: boolean; - system_prompt?: string; -}; -type AutoRagAiSearchRequestStreaming = Omit & { - stream: true; -}; -type AutoRagSearchResponse = { - object: 'vector_store.search_results.page'; - search_query: string; - data: { - file_id: string; - filename: string; - score: number; - attributes: Record; - content: { - type: 'text'; - text: string; - }[]; - }[]; - has_more: boolean; - next_page: string | null; -}; -type AutoRagListResponse = { - id: string; - enable: boolean; - type: string; - source: string; - vectorize_name: string; - paused: boolean; - status: string; -}[]; -type AutoRagAiSearchResponse = AutoRagSearchResponse & { - response: string; -}; -declare abstract class AutoRAG { - list(): Promise; - search(params: AutoRagSearchRequest): Promise; - aiSearch(params: AutoRagAiSearchRequestStreaming): Promise; - aiSearch(params: AutoRagAiSearchRequest): Promise; - aiSearch(params: AutoRagAiSearchRequest): Promise; -} -interface BasicImageTransformations { - /** - * Maximum width in image pixels. The value must be an integer. - */ - width?: number; - /** - * Maximum height in image pixels. The value must be an integer. - */ - height?: number; - /** - * Resizing mode as a string. It affects interpretation of width and height - * options: - * - scale-down: Similar to contain, but the image is never enlarged. If - * the image is larger than given width or height, it will be resized. - * Otherwise its original size will be kept. - * - contain: Resizes to maximum size that fits within the given width and - * height. If only a single dimension is given (e.g. only width), the - * image will be shrunk or enlarged to exactly match that dimension. - * Aspect ratio is always preserved. - * - cover: Resizes (shrinks or enlarges) to fill the entire area of width - * and height. If the image has an aspect ratio different from the ratio - * of width and height, it will be cropped to fit. - * - crop: The image will be shrunk and cropped to fit within the area - * specified by width and height. The image will not be enlarged. For images - * smaller than the given dimensions it's the same as scale-down. For - * images larger than the given dimensions, it's the same as cover. - * See also trim. - * - pad: Resizes to the maximum size that fits within the given width and - * height, and then fills the remaining area with a background color - * (white by default). Use of this mode is not recommended, as the same - * effect can be more efficiently achieved with the contain mode and the - * CSS object-fit: contain property. - * - squeeze: Stretches and deforms to the width and height given, even if it - * breaks aspect ratio - */ - fit?: 'scale-down' | 'contain' | 'cover' | 'crop' | 'pad' | 'squeeze'; - /** - * Image segmentation using artificial intelligence models. Sets pixels not - * within selected segment area to transparent e.g "foreground" sets every - * background pixel as transparent. - */ - segment?: 'foreground'; - /** - * When cropping with fit: "cover", this defines the side or point that should - * be left uncropped. The value is either a string - * "left", "right", "top", "bottom", "auto", or "center" (the default), - * or an object {x, y} containing focal point coordinates in the original - * image expressed as fractions ranging from 0.0 (top or left) to 1.0 - * (bottom or right), 0.5 being the center. {fit: "cover", gravity: "top"} will - * crop bottom or left and right sides as necessary, but won’t crop anything - * from the top. {fit: "cover", gravity: {x:0.5, y:0.2}} will crop each side to - * preserve as much as possible around a point at 20% of the height of the - * source image. - */ - gravity?: - | 'face' - | 'left' - | 'right' - | 'top' - | 'bottom' - | 'center' - | 'auto' - | 'entropy' - | BasicImageTransformationsGravityCoordinates; - /** - * Background color to add underneath the image. Applies only to images with - * transparency (such as PNG). Accepts any CSS color (#RRGGBB, rgba(…), - * hsl(…), etc.) - */ - background?: string; - /** - * Number of degrees (90, 180, 270) to rotate the image by. width and height - * options refer to axes after rotation. - */ - rotate?: 0 | 90 | 180 | 270 | 360; -} -interface BasicImageTransformationsGravityCoordinates { - x?: number; - y?: number; - mode?: 'remainder' | 'box-center'; -} -/** - * In addition to the properties you can set in the RequestInit dict - * that you pass as an argument to the Request constructor, you can - * set certain properties of a `cf` object to control how Cloudflare - * features are applied to that new Request. - * - * Note: Currently, these properties cannot be tested in the - * playground. - */ -interface RequestInitCfProperties extends Record { - cacheEverything?: boolean; - /** - * A request's cache key is what determines if two requests are - * "the same" for caching purposes. If a request has the same cache key - * as some previous request, then we can serve the same cached response for - * both. (e.g. 'some-key') - * - * Only available for Enterprise customers. - */ - cacheKey?: string; - /** - * This allows you to append additional Cache-Tag response headers - * to the origin response without modifications to the origin server. - * This will allow for greater control over the Purge by Cache Tag feature - * utilizing changes only in the Workers process. - * - * Only available for Enterprise customers. - */ - cacheTags?: string[]; - /** - * Force response to be cached for a given number of seconds. (e.g. 300) - */ - cacheTtl?: number; - /** - * Force response to be cached for a given number of seconds based on the Origin status code. - * (e.g. { '200-299': 86400, '404': 1, '500-599': 0 }) - */ - cacheTtlByStatus?: Record; - scrapeShield?: boolean; - apps?: boolean; - image?: RequestInitCfPropertiesImage; - minify?: RequestInitCfPropertiesImageMinify; - mirage?: boolean; - polish?: 'lossy' | 'lossless' | 'off'; - r2?: RequestInitCfPropertiesR2; - /** - * Redirects the request to an alternate origin server. You can use this, - * for example, to implement load balancing across several origins. - * (e.g.us-east.example.com) - * - * Note - For security reasons, the hostname set in resolveOverride must - * be proxied on the same Cloudflare zone of the incoming request. - * Otherwise, the setting is ignored. CNAME hosts are allowed, so to - * resolve to a host under a different domain or a DNS only domain first - * declare a CNAME record within your own zone’s DNS mapping to the - * external hostname, set proxy on Cloudflare, then set resolveOverride - * to point to that CNAME record. - */ - resolveOverride?: string; -} -interface RequestInitCfPropertiesImageDraw extends BasicImageTransformations { - /** - * Absolute URL of the image file to use for the drawing. It can be any of - * the supported file formats. For drawing of watermarks or non-rectangular - * overlays we recommend using PNG or WebP images. - */ - url: string; - /** - * Floating-point number between 0 (transparent) and 1 (opaque). - * For example, opacity: 0.5 makes overlay semitransparent. - */ - opacity?: number; - /** - * - If set to true, the overlay image will be tiled to cover the entire - * area. This is useful for stock-photo-like watermarks. - * - If set to "x", the overlay image will be tiled horizontally only - * (form a line). - * - If set to "y", the overlay image will be tiled vertically only - * (form a line). - */ - repeat?: true | 'x' | 'y'; - /** - * Position of the overlay image relative to a given edge. Each property is - * an offset in pixels. 0 aligns exactly to the edge. For example, left: 10 - * positions left side of the overlay 10 pixels from the left edge of the - * image it's drawn over. bottom: 0 aligns bottom of the overlay with bottom - * of the background image. - * - * Setting both left & right, or both top & bottom is an error. - * - * If no position is specified, the image will be centered. - */ - top?: number; - left?: number; - bottom?: number; - right?: number; -} -interface RequestInitCfPropertiesImage extends BasicImageTransformations { - /** - * Device Pixel Ratio. Default 1. Multiplier for width/height that makes it - * easier to specify higher-DPI sizes in . - */ - dpr?: number; - /** - * Allows you to trim your image. Takes dpr into account and is performed before - * resizing or rotation. - * - * It can be used as: - * - left, top, right, bottom - it will specify the number of pixels to cut - * off each side - * - width, height - the width/height you'd like to end up with - can be used - * in combination with the properties above - * - border - this will automatically trim the surroundings of an image based on - * it's color. It consists of three properties: - * - color: rgb or hex representation of the color you wish to trim (todo: verify the rgba bit) - * - tolerance: difference from color to treat as color - * - keep: the number of pixels of border to keep - */ - trim?: - | 'border' - | { - top?: number; - bottom?: number; - left?: number; - right?: number; - width?: number; - height?: number; - border?: - | boolean - | { - color?: string; - tolerance?: number; - keep?: number; - }; - }; - /** - * Quality setting from 1-100 (useful values are in 60-90 range). Lower values - * make images look worse, but load faster. The default is 85. It applies only - * to JPEG and WebP images. It doesn’t have any effect on PNG. - */ - quality?: number | 'low' | 'medium-low' | 'medium-high' | 'high'; - /** - * Output format to generate. It can be: - * - avif: generate images in AVIF format. - * - webp: generate images in Google WebP format. Set quality to 100 to get - * the WebP-lossless format. - * - json: instead of generating an image, outputs information about the - * image, in JSON format. The JSON object will contain image size - * (before and after resizing), source image’s MIME type, file size, etc. - * - jpeg: generate images in JPEG format. - * - png: generate images in PNG format. - */ - format?: 'avif' | 'webp' | 'json' | 'jpeg' | 'png' | 'baseline-jpeg' | 'png-force' | 'svg'; - /** - * Whether to preserve animation frames from input files. Default is true. - * Setting it to false reduces animations to still images. This setting is - * recommended when enlarging images or processing arbitrary user content, - * because large GIF animations can weigh tens or even hundreds of megabytes. - * It is also useful to set anim:false when using format:"json" to get the - * response quicker without the number of frames. - */ - anim?: boolean; - /** - * What EXIF data should be preserved in the output image. Note that EXIF - * rotation and embedded color profiles are always applied ("baked in" into - * the image), and aren't affected by this option. Note that if the Polish - * feature is enabled, all metadata may have been removed already and this - * option may have no effect. - * - keep: Preserve most of EXIF metadata, including GPS location if there's - * any. - * - copyright: Only keep the copyright tag, and discard everything else. - * This is the default behavior for JPEG files. - * - none: Discard all invisible EXIF metadata. Currently WebP and PNG - * output formats always discard metadata. - */ - metadata?: 'keep' | 'copyright' | 'none'; - /** - * Strength of sharpening filter to apply to the image. Floating-point - * number between 0 (no sharpening, default) and 10 (maximum). 1.0 is a - * recommended value for downscaled images. - */ - sharpen?: number; - /** - * Radius of a blur filter (approximate gaussian). Maximum supported radius - * is 250. - */ - blur?: number; - /** - * Overlays are drawn in the order they appear in the array (last array - * entry is the topmost layer). - */ - draw?: RequestInitCfPropertiesImageDraw[]; - /** - * Fetching image from authenticated origin. Setting this property will - * pass authentication headers (Authorization, Cookie, etc.) through to - * the origin. - */ - 'origin-auth'?: 'share-publicly'; - /** - * Adds a border around the image. The border is added after resizing. Border - * width takes dpr into account, and can be specified either using a single - * width property, or individually for each side. - */ - border?: - | { - color: string; - width: number; - } - | { - color: string; - top: number; - right: number; - bottom: number; - left: number; - }; - /** - * Increase brightness by a factor. A value of 1.0 equals no change, a value - * of 0.5 equals half brightness, and a value of 2.0 equals twice as bright. - * 0 is ignored. - */ - brightness?: number; - /** - * Increase contrast by a factor. A value of 1.0 equals no change, a value of - * 0.5 equals low contrast, and a value of 2.0 equals high contrast. 0 is - * ignored. - */ - contrast?: number; - /** - * Increase exposure by a factor. A value of 1.0 equals no change, a value of - * 0.5 darkens the image, and a value of 2.0 lightens the image. 0 is ignored. - */ - gamma?: number; - /** - * Increase contrast by a factor. A value of 1.0 equals no change, a value of - * 0.5 equals low contrast, and a value of 2.0 equals high contrast. 0 is - * ignored. - */ - saturation?: number; - /** - * Flips the images horizontally, vertically, or both. Flipping is applied before - * rotation, so if you apply flip=h,rotate=90 then the image will be flipped - * horizontally, then rotated by 90 degrees. - */ - flip?: 'h' | 'v' | 'hv'; - /** - * Slightly reduces latency on a cache miss by selecting a - * quickest-to-compress file format, at a cost of increased file size and - * lower image quality. It will usually override the format option and choose - * JPEG over WebP or AVIF. We do not recommend using this option, except in - * unusual circumstances like resizing uncacheable dynamically-generated - * images. - */ - compression?: 'fast'; -} -interface RequestInitCfPropertiesImageMinify { - javascript?: boolean; - css?: boolean; - html?: boolean; -} -interface RequestInitCfPropertiesR2 { - /** - * Colo id of bucket that an object is stored in - */ - bucketColoId?: number; -} -/** - * Request metadata provided by Cloudflare's edge. - */ -type IncomingRequestCfProperties = IncomingRequestCfPropertiesBase & - IncomingRequestCfPropertiesBotManagementEnterprise & - IncomingRequestCfPropertiesCloudflareForSaaSEnterprise & - IncomingRequestCfPropertiesGeographicInformation & - IncomingRequestCfPropertiesCloudflareAccessOrApiShield; -interface IncomingRequestCfPropertiesBase extends Record { - /** - * [ASN](https://www.iana.org/assignments/as-numbers/as-numbers.xhtml) of the incoming request. - * - * @example 395747 - */ - asn?: number; - /** - * The organization which owns the ASN of the incoming request. - * - * @example "Google Cloud" - */ - asOrganization?: string; - /** - * The original value of the `Accept-Encoding` header if Cloudflare modified it. - * - * @example "gzip, deflate, br" - */ - clientAcceptEncoding?: string; - /** - * The number of milliseconds it took for the request to reach your worker. - * - * @example 22 - */ - clientTcpRtt?: number; - /** - * The three-letter [IATA](https://en.wikipedia.org/wiki/IATA_airport_code) - * airport code of the data center that the request hit. - * - * @example "DFW" - */ - colo: string; - /** - * Represents the upstream's response to a - * [TCP `keepalive` message](https://tldp.org/HOWTO/TCP-Keepalive-HOWTO/overview.html) - * from cloudflare. - * - * For workers with no upstream, this will always be `1`. - * - * @example 3 - */ - edgeRequestKeepAliveStatus: IncomingRequestCfPropertiesEdgeRequestKeepAliveStatus; - /** - * The HTTP Protocol the request used. - * - * @example "HTTP/2" - */ - httpProtocol: string; - /** - * The browser-requested prioritization information in the request object. - * - * If no information was set, defaults to the empty string `""` - * - * @example "weight=192;exclusive=0;group=3;group-weight=127" - * @default "" - */ - requestPriority: string; - /** - * The TLS version of the connection to Cloudflare. - * In requests served over plaintext (without TLS), this property is the empty string `""`. - * - * @example "TLSv1.3" - */ - tlsVersion: string; - /** - * The cipher for the connection to Cloudflare. - * In requests served over plaintext (without TLS), this property is the empty string `""`. - * - * @example "AEAD-AES128-GCM-SHA256" - */ - tlsCipher: string; - /** - * Metadata containing the [`HELLO`](https://www.rfc-editor.org/rfc/rfc5246#section-7.4.1.2) and [`FINISHED`](https://www.rfc-editor.org/rfc/rfc5246#section-7.4.9) messages from this request's TLS handshake. - * - * If the incoming request was served over plaintext (without TLS) this field is undefined. - */ - tlsExportedAuthenticator?: IncomingRequestCfPropertiesExportedAuthenticatorMetadata; -} -interface IncomingRequestCfPropertiesBotManagementBase { - /** - * Cloudflare’s [level of certainty](https://developers.cloudflare.com/bots/concepts/bot-score/) that a request comes from a bot, - * represented as an integer percentage between `1` (almost certainly a bot) and `99` (almost certainly human). - * - * @example 54 - */ - score: number; - /** - * A boolean value that is true if the request comes from a good bot, like Google or Bing. - * Most customers choose to allow this traffic. For more details, see [Traffic from known bots](https://developers.cloudflare.com/firewall/known-issues-and-faq/#how-does-firewall-rules-handle-traffic-from-known-bots). - */ - verifiedBot: boolean; - /** - * A boolean value that is true if the request originates from a - * Cloudflare-verified proxy service. - */ - corporateProxy: boolean; - /** - * A boolean value that's true if the request matches [file extensions](https://developers.cloudflare.com/bots/reference/static-resources/) for many types of static resources. - */ - staticResource: boolean; - /** - * List of IDs that correlate to the Bot Management heuristic detections made on a request (you can have multiple heuristic detections on the same request). - */ - detectionIds: number[]; -} -interface IncomingRequestCfPropertiesBotManagement { - /** - * Results of Cloudflare's Bot Management analysis - */ - botManagement: IncomingRequestCfPropertiesBotManagementBase; - /** - * Duplicate of `botManagement.score`. - * - * @deprecated - */ - clientTrustScore: number; -} -interface IncomingRequestCfPropertiesBotManagementEnterprise extends IncomingRequestCfPropertiesBotManagement { - /** - * Results of Cloudflare's Bot Management analysis - */ - botManagement: IncomingRequestCfPropertiesBotManagementBase & { - /** - * A [JA3 Fingerprint](https://developers.cloudflare.com/bots/concepts/ja3-fingerprint/) to help profile specific SSL/TLS clients - * across different destination IPs, Ports, and X509 certificates. - */ - ja3Hash: string; - }; -} -interface IncomingRequestCfPropertiesCloudflareForSaaSEnterprise { - /** - * Custom metadata set per-host in [Cloudflare for SaaS](https://developers.cloudflare.com/cloudflare-for-platforms/cloudflare-for-saas/). - * - * This field is only present if you have Cloudflare for SaaS enabled on your account - * and you have followed the [required steps to enable it]((https://developers.cloudflare.com/cloudflare-for-platforms/cloudflare-for-saas/domain-support/custom-metadata/)). - */ - hostMetadata?: HostMetadata; -} -interface IncomingRequestCfPropertiesCloudflareAccessOrApiShield { - /** - * Information about the client certificate presented to Cloudflare. - * - * This is populated when the incoming request is served over TLS using - * either Cloudflare Access or API Shield (mTLS) - * and the presented SSL certificate has a valid - * [Certificate Serial Number](https://ldapwiki.com/wiki/Certificate%20Serial%20Number) - * (i.e., not `null` or `""`). - * - * Otherwise, a set of placeholder values are used. - * - * The property `certPresented` will be set to `"1"` when - * the object is populated (i.e. the above conditions were met). - */ - tlsClientAuth: - | IncomingRequestCfPropertiesTLSClientAuth - | IncomingRequestCfPropertiesTLSClientAuthPlaceholder; -} -/** - * Metadata about the request's TLS handshake - */ -interface IncomingRequestCfPropertiesExportedAuthenticatorMetadata { - /** - * The client's [`HELLO` message](https://www.rfc-editor.org/rfc/rfc5246#section-7.4.1.2), encoded in hexadecimal - * - * @example "44372ba35fa1270921d318f34c12f155dc87b682cf36a790cfaa3ba8737a1b5d" - */ - clientHandshake: string; - /** - * The server's [`HELLO` message](https://www.rfc-editor.org/rfc/rfc5246#section-7.4.1.2), encoded in hexadecimal - * - * @example "44372ba35fa1270921d318f34c12f155dc87b682cf36a790cfaa3ba8737a1b5d" - */ - serverHandshake: string; - /** - * The client's [`FINISHED` message](https://www.rfc-editor.org/rfc/rfc5246#section-7.4.9), encoded in hexadecimal - * - * @example "084ee802fe1348f688220e2a6040a05b2199a761f33cf753abb1b006792d3f8b" - */ - clientFinished: string; - /** - * The server's [`FINISHED` message](https://www.rfc-editor.org/rfc/rfc5246#section-7.4.9), encoded in hexadecimal - * - * @example "084ee802fe1348f688220e2a6040a05b2199a761f33cf753abb1b006792d3f8b" - */ - serverFinished: string; -} -/** - * Geographic data about the request's origin. - */ -interface IncomingRequestCfPropertiesGeographicInformation { - /** - * The [ISO 3166-1 Alpha 2](https://www.iso.org/iso-3166-country-codes.html) country code the request originated from. - * - * If your worker is [configured to accept TOR connections](https://support.cloudflare.com/hc/en-us/articles/203306930-Understanding-Cloudflare-Tor-support-and-Onion-Routing), this may also be `"T1"`, indicating a request that originated over TOR. - * - * If Cloudflare is unable to determine where the request originated this property is omitted. - * - * The country code `"T1"` is used for requests originating on TOR. - * - * @example "GB" - */ - country?: Iso3166Alpha2Code | 'T1'; - /** - * If present, this property indicates that the request originated in the EU - * - * @example "1" - */ - isEUCountry?: '1'; - /** - * A two-letter code indicating the continent the request originated from. - * - * @example "AN" - */ - continent?: ContinentCode; - /** - * The city the request originated from - * - * @example "Austin" - */ - city?: string; - /** - * Postal code of the incoming request - * - * @example "78701" - */ - postalCode?: string; - /** - * Latitude of the incoming request - * - * @example "30.27130" - */ - latitude?: string; - /** - * Longitude of the incoming request - * - * @example "-97.74260" - */ - longitude?: string; - /** - * Timezone of the incoming request - * - * @example "America/Chicago" - */ - timezone?: string; - /** - * If known, the ISO 3166-2 name for the first level region associated with - * the IP address of the incoming request - * - * @example "Texas" - */ - region?: string; - /** - * If known, the ISO 3166-2 code for the first-level region associated with - * the IP address of the incoming request - * - * @example "TX" - */ - regionCode?: string; - /** - * Metro code (DMA) of the incoming request - * - * @example "635" - */ - metroCode?: string; -} -/** Data about the incoming request's TLS certificate */ -interface IncomingRequestCfPropertiesTLSClientAuth { - /** Always `"1"`, indicating that the certificate was presented */ - certPresented: '1'; - /** - * Result of certificate verification. - * - * @example "FAILED:self signed certificate" - */ - certVerified: Exclude; - /** The presented certificate's revokation status. - * - * - A value of `"1"` indicates the certificate has been revoked - * - A value of `"0"` indicates the certificate has not been revoked - */ - certRevoked: '1' | '0'; - /** - * The certificate issuer's [distinguished name](https://knowledge.digicert.com/generalinformation/INFO1745.html) - * - * @example "CN=cloudflareaccess.com, C=US, ST=Texas, L=Austin, O=Cloudflare" - */ - certIssuerDN: string; - /** - * The certificate subject's [distinguished name](https://knowledge.digicert.com/generalinformation/INFO1745.html) - * - * @example "CN=*.cloudflareaccess.com, C=US, ST=Texas, L=Austin, O=Cloudflare" - */ - certSubjectDN: string; - /** - * The certificate issuer's [distinguished name](https://knowledge.digicert.com/generalinformation/INFO1745.html) ([RFC 2253](https://www.rfc-editor.org/rfc/rfc2253.html) formatted) - * - * @example "CN=cloudflareaccess.com, C=US, ST=Texas, L=Austin, O=Cloudflare" - */ - certIssuerDNRFC2253: string; - /** - * The certificate subject's [distinguished name](https://knowledge.digicert.com/generalinformation/INFO1745.html) ([RFC 2253](https://www.rfc-editor.org/rfc/rfc2253.html) formatted) - * - * @example "CN=*.cloudflareaccess.com, C=US, ST=Texas, L=Austin, O=Cloudflare" - */ - certSubjectDNRFC2253: string; - /** The certificate issuer's distinguished name (legacy policies) */ - certIssuerDNLegacy: string; - /** The certificate subject's distinguished name (legacy policies) */ - certSubjectDNLegacy: string; - /** - * The certificate's serial number - * - * @example "00936EACBE07F201DF" - */ - certSerial: string; - /** - * The certificate issuer's serial number - * - * @example "2489002934BDFEA34" - */ - certIssuerSerial: string; - /** - * The certificate's Subject Key Identifier - * - * @example "BB:AF:7E:02:3D:FA:A6:F1:3C:84:8E:AD:EE:38:98:EC:D9:32:32:D4" - */ - certSKI: string; - /** - * The certificate issuer's Subject Key Identifier - * - * @example "BB:AF:7E:02:3D:FA:A6:F1:3C:84:8E:AD:EE:38:98:EC:D9:32:32:D4" - */ - certIssuerSKI: string; - /** - * The certificate's SHA-1 fingerprint - * - * @example "6b9109f323999e52259cda7373ff0b4d26bd232e" - */ - certFingerprintSHA1: string; - /** - * The certificate's SHA-256 fingerprint - * - * @example "acf77cf37b4156a2708e34c4eb755f9b5dbbe5ebb55adfec8f11493438d19e6ad3f157f81fa3b98278453d5652b0c1fd1d71e5695ae4d709803a4d3f39de9dea" - */ - certFingerprintSHA256: string; - /** - * The effective starting date of the certificate - * - * @example "Dec 22 19:39:00 2018 GMT" - */ - certNotBefore: string; - /** - * The effective expiration date of the certificate - * - * @example "Dec 22 19:39:00 2018 GMT" - */ - certNotAfter: string; -} -/** Placeholder values for TLS Client Authorization */ -interface IncomingRequestCfPropertiesTLSClientAuthPlaceholder { - certPresented: '0'; - certVerified: 'NONE'; - certRevoked: '0'; - certIssuerDN: ''; - certSubjectDN: ''; - certIssuerDNRFC2253: ''; - certSubjectDNRFC2253: ''; - certIssuerDNLegacy: ''; - certSubjectDNLegacy: ''; - certSerial: ''; - certIssuerSerial: ''; - certSKI: ''; - certIssuerSKI: ''; - certFingerprintSHA1: ''; - certFingerprintSHA256: ''; - certNotBefore: ''; - certNotAfter: ''; -} -/** Possible outcomes of TLS verification */ -declare type CertVerificationStatus = - /** Authentication succeeded */ - | 'SUCCESS' - /** No certificate was presented */ - | 'NONE' - /** Failed because the certificate was self-signed */ - | 'FAILED:self signed certificate' - /** Failed because the certificate failed a trust chain check */ - | 'FAILED:unable to verify the first certificate' - /** Failed because the certificate not yet valid */ - | 'FAILED:certificate is not yet valid' - /** Failed because the certificate is expired */ - | 'FAILED:certificate has expired' - /** Failed for another unspecified reason */ - | 'FAILED'; -/** - * An upstream endpoint's response to a TCP `keepalive` message from Cloudflare. - */ -declare type IncomingRequestCfPropertiesEdgeRequestKeepAliveStatus = - | 0 /** Unknown */ - | 1 /** no keepalives (not found) */ - | 2 /** no connection re-use, opening keepalive connection failed */ - | 3 /** no connection re-use, keepalive accepted and saved */ - | 4 /** connection re-use, refused by the origin server (`TCP FIN`) */ - | 5; /** connection re-use, accepted by the origin server */ -/** ISO 3166-1 Alpha-2 codes */ -declare type Iso3166Alpha2Code = - | 'AD' - | 'AE' - | 'AF' - | 'AG' - | 'AI' - | 'AL' - | 'AM' - | 'AO' - | 'AQ' - | 'AR' - | 'AS' - | 'AT' - | 'AU' - | 'AW' - | 'AX' - | 'AZ' - | 'BA' - | 'BB' - | 'BD' - | 'BE' - | 'BF' - | 'BG' - | 'BH' - | 'BI' - | 'BJ' - | 'BL' - | 'BM' - | 'BN' - | 'BO' - | 'BQ' - | 'BR' - | 'BS' - | 'BT' - | 'BV' - | 'BW' - | 'BY' - | 'BZ' - | 'CA' - | 'CC' - | 'CD' - | 'CF' - | 'CG' - | 'CH' - | 'CI' - | 'CK' - | 'CL' - | 'CM' - | 'CN' - | 'CO' - | 'CR' - | 'CU' - | 'CV' - | 'CW' - | 'CX' - | 'CY' - | 'CZ' - | 'DE' - | 'DJ' - | 'DK' - | 'DM' - | 'DO' - | 'DZ' - | 'EC' - | 'EE' - | 'EG' - | 'EH' - | 'ER' - | 'ES' - | 'ET' - | 'FI' - | 'FJ' - | 'FK' - | 'FM' - | 'FO' - | 'FR' - | 'GA' - | 'GB' - | 'GD' - | 'GE' - | 'GF' - | 'GG' - | 'GH' - | 'GI' - | 'GL' - | 'GM' - | 'GN' - | 'GP' - | 'GQ' - | 'GR' - | 'GS' - | 'GT' - | 'GU' - | 'GW' - | 'GY' - | 'HK' - | 'HM' - | 'HN' - | 'HR' - | 'HT' - | 'HU' - | 'ID' - | 'IE' - | 'IL' - | 'IM' - | 'IN' - | 'IO' - | 'IQ' - | 'IR' - | 'IS' - | 'IT' - | 'JE' - | 'JM' - | 'JO' - | 'JP' - | 'KE' - | 'KG' - | 'KH' - | 'KI' - | 'KM' - | 'KN' - | 'KP' - | 'KR' - | 'KW' - | 'KY' - | 'KZ' - | 'LA' - | 'LB' - | 'LC' - | 'LI' - | 'LK' - | 'LR' - | 'LS' - | 'LT' - | 'LU' - | 'LV' - | 'LY' - | 'MA' - | 'MC' - | 'MD' - | 'ME' - | 'MF' - | 'MG' - | 'MH' - | 'MK' - | 'ML' - | 'MM' - | 'MN' - | 'MO' - | 'MP' - | 'MQ' - | 'MR' - | 'MS' - | 'MT' - | 'MU' - | 'MV' - | 'MW' - | 'MX' - | 'MY' - | 'MZ' - | 'NA' - | 'NC' - | 'NE' - | 'NF' - | 'NG' - | 'NI' - | 'NL' - | 'NO' - | 'NP' - | 'NR' - | 'NU' - | 'NZ' - | 'OM' - | 'PA' - | 'PE' - | 'PF' - | 'PG' - | 'PH' - | 'PK' - | 'PL' - | 'PM' - | 'PN' - | 'PR' - | 'PS' - | 'PT' - | 'PW' - | 'PY' - | 'QA' - | 'RE' - | 'RO' - | 'RS' - | 'RU' - | 'RW' - | 'SA' - | 'SB' - | 'SC' - | 'SD' - | 'SE' - | 'SG' - | 'SH' - | 'SI' - | 'SJ' - | 'SK' - | 'SL' - | 'SM' - | 'SN' - | 'SO' - | 'SR' - | 'SS' - | 'ST' - | 'SV' - | 'SX' - | 'SY' - | 'SZ' - | 'TC' - | 'TD' - | 'TF' - | 'TG' - | 'TH' - | 'TJ' - | 'TK' - | 'TL' - | 'TM' - | 'TN' - | 'TO' - | 'TR' - | 'TT' - | 'TV' - | 'TW' - | 'TZ' - | 'UA' - | 'UG' - | 'UM' - | 'US' - | 'UY' - | 'UZ' - | 'VA' - | 'VC' - | 'VE' - | 'VG' - | 'VI' - | 'VN' - | 'VU' - | 'WF' - | 'WS' - | 'YE' - | 'YT' - | 'ZA' - | 'ZM' - | 'ZW'; -/** The 2-letter continent codes Cloudflare uses */ -declare type ContinentCode = 'AF' | 'AN' | 'AS' | 'EU' | 'NA' | 'OC' | 'SA'; -type CfProperties = - | IncomingRequestCfProperties - | RequestInitCfProperties; -interface D1Meta { - duration: number; - size_after: number; - rows_read: number; - rows_written: number; - last_row_id: number; - changed_db: boolean; - changes: number; - /** - * The region of the database instance that executed the query. - */ - served_by_region?: string; - /** - * True if-and-only-if the database instance that executed the query was the primary. - */ - served_by_primary?: boolean; - timings?: { - /** - * The duration of the SQL query execution by the database instance. It doesn't include any network time. - */ - sql_duration_ms: number; - }; - /** - * Number of total attempts to execute the query, due to automatic retries. - * Note: All other fields in the response like `timings` only apply to the last attempt. - */ - total_attempts?: number; -} -interface D1Response { - success: true; - meta: D1Meta & Record; - error?: never; -} -type D1Result = D1Response & { - results: T[]; -}; -interface D1ExecResult { - count: number; - duration: number; -} -type D1SessionConstraint = - // Indicates that the first query should go to the primary, and the rest queries - // using the same D1DatabaseSession will go to any replica that is consistent with - // the bookmark maintained by the session (returned by the first query). - | 'first-primary' - // Indicates that the first query can go anywhere (primary or replica), and the rest queries - // using the same D1DatabaseSession will go to any replica that is consistent with - // the bookmark maintained by the session (returned by the first query). - | 'first-unconstrained'; -type D1SessionBookmark = string; -declare abstract class D1Database { - prepare(query: string): D1PreparedStatement; - batch(statements: D1PreparedStatement[]): Promise[]>; - exec(query: string): Promise; - /** - * Creates a new D1 Session anchored at the given constraint or the bookmark. - * All queries executed using the created session will have sequential consistency, - * meaning that all writes done through the session will be visible in subsequent reads. - * - * @param constraintOrBookmark Either the session constraint or the explicit bookmark to anchor the created session. - */ - withSession(constraintOrBookmark?: D1SessionBookmark | D1SessionConstraint): D1DatabaseSession; - /** - * @deprecated dump() will be removed soon, only applies to deprecated alpha v1 databases. - */ - dump(): Promise; -} -declare abstract class D1DatabaseSession { - prepare(query: string): D1PreparedStatement; - batch(statements: D1PreparedStatement[]): Promise[]>; - /** - * @returns The latest session bookmark across all executed queries on the session. - * If no query has been executed yet, `null` is returned. - */ - getBookmark(): D1SessionBookmark | null; -} -declare abstract class D1PreparedStatement { - bind(...values: unknown[]): D1PreparedStatement; - first(colName: string): Promise; - first>(): Promise; - run>(): Promise>; - all>(): Promise>; - raw(options: { columnNames: true }): Promise<[string[], ...T[]]>; - raw(options?: { columnNames?: false }): Promise; -} -// `Disposable` was added to TypeScript's standard lib types in version 5.2. -// To support older TypeScript versions, define an empty `Disposable` interface. -// Users won't be able to use `using`/`Symbol.dispose` without upgrading to 5.2, -// but this will ensure type checking on older versions still passes. -// TypeScript's interface merging will ensure our empty interface is effectively -// ignored when `Disposable` is included in the standard lib. -interface Disposable {} -/** - * An email message that can be sent from a Worker. - */ -interface EmailMessage { - /** - * Envelope From attribute of the email message. - */ - readonly from: string; - /** - * Envelope To attribute of the email message. - */ - readonly to: string; -} -/** - * An email message that is sent to a consumer Worker and can be rejected/forwarded. - */ -interface ForwardableEmailMessage extends EmailMessage { - /** - * Stream of the email message content. - */ - readonly raw: ReadableStream; - /** - * An [Headers object](https://developer.mozilla.org/en-US/docs/Web/API/Headers). - */ - readonly headers: Headers; - /** - * Size of the email message content. - */ - readonly rawSize: number; - /** - * Reject this email message by returning a permanent SMTP error back to the connecting client including the given reason. - * @param reason The reject reason. - * @returns void - */ - setReject(reason: string): void; - /** - * Forward this email message to a verified destination address of the account. - * @param rcptTo Verified destination address. - * @param headers A [Headers object](https://developer.mozilla.org/en-US/docs/Web/API/Headers). - * @returns A promise that resolves when the email message is forwarded. - */ - forward(rcptTo: string, headers?: Headers): Promise; - /** - * Reply to the sender of this email message with a new EmailMessage object. - * @param message The reply message. - * @returns A promise that resolves when the email message is replied. - */ - reply(message: EmailMessage): Promise; -} -/** - * A binding that allows a Worker to send email messages. - */ -interface SendEmail { - send(message: EmailMessage): Promise; -} -declare abstract class EmailEvent extends ExtendableEvent { - readonly message: ForwardableEmailMessage; -} -declare type EmailExportedHandler = ( - message: ForwardableEmailMessage, - env: Env, - ctx: ExecutionContext, -) => void | Promise; -declare module 'cloudflare:email' { - let _EmailMessage: { - prototype: EmailMessage; - new (from: string, to: string, raw: ReadableStream | string): EmailMessage; - }; - export { _EmailMessage as EmailMessage }; -} -/** - * Hello World binding to serve as an explanatory example. DO NOT USE - */ -interface HelloWorldBinding { - /** - * Retrieve the current stored value - */ - get(): Promise<{ - value: string; - ms?: number; - }>; - /** - * Set a new stored value - */ - set(value: string): Promise; -} -interface Hyperdrive { - /** - * Connect directly to Hyperdrive as if it's your database, returning a TCP socket. - * - * Calling this method returns an idential socket to if you call - * `connect("host:port")` using the `host` and `port` fields from this object. - * Pick whichever approach works better with your preferred DB client library. - * - * Note that this socket is not yet authenticated -- it's expected that your - * code (or preferably, the client library of your choice) will authenticate - * using the information in this class's readonly fields. - */ - connect(): Socket; - /** - * A valid DB connection string that can be passed straight into the typical - * client library/driver/ORM. This will typically be the easiest way to use - * Hyperdrive. - */ - readonly connectionString: string; - /* - * A randomly generated hostname that is only valid within the context of the - * currently running Worker which, when passed into `connect()` function from - * the "cloudflare:sockets" module, will connect to the Hyperdrive instance - * for your database. - */ - readonly host: string; - /* - * The port that must be paired the the host field when connecting. - */ - readonly port: number; - /* - * The username to use when authenticating to your database via Hyperdrive. - * Unlike the host and password, this will be the same every time - */ - readonly user: string; - /* - * The randomly generated password to use when authenticating to your - * database via Hyperdrive. Like the host field, this password is only valid - * within the context of the currently running Worker instance from which - * it's read. - */ - readonly password: string; - /* - * The name of the database to connect to. - */ - readonly database: string; -} -// Copyright (c) 2024 Cloudflare, Inc. -// Licensed under the Apache 2.0 license found in the LICENSE file or at: -// https://opensource.org/licenses/Apache-2.0 -type ImageInfoResponse = - | { - format: 'image/svg+xml'; - } - | { - format: string; - fileSize: number; - width: number; - height: number; - }; -type ImageTransform = { - width?: number; - height?: number; - background?: string; - blur?: number; - border?: - | { - color?: string; - width?: number; - } - | { - top?: number; - bottom?: number; - left?: number; - right?: number; - }; - brightness?: number; - contrast?: number; - fit?: 'scale-down' | 'contain' | 'pad' | 'squeeze' | 'cover' | 'crop'; - flip?: 'h' | 'v' | 'hv'; - gamma?: number; - segment?: 'foreground'; - gravity?: - | 'face' - | 'left' - | 'right' - | 'top' - | 'bottom' - | 'center' - | 'auto' - | 'entropy' - | { - x?: number; - y?: number; - mode: 'remainder' | 'box-center'; - }; - rotate?: 0 | 90 | 180 | 270; - saturation?: number; - sharpen?: number; - trim?: - | 'border' - | { - top?: number; - bottom?: number; - left?: number; - right?: number; - width?: number; - height?: number; - border?: - | boolean - | { - color?: string; - tolerance?: number; - keep?: number; - }; - }; -}; -type ImageDrawOptions = { - opacity?: number; - repeat?: boolean | string; - top?: number; - left?: number; - bottom?: number; - right?: number; -}; -type ImageInputOptions = { - encoding?: 'base64'; -}; -type ImageOutputOptions = { - format: 'image/jpeg' | 'image/png' | 'image/gif' | 'image/webp' | 'image/avif' | 'rgb' | 'rgba'; - quality?: number; - background?: string; - anim?: boolean; -}; -interface ImagesBinding { - /** - * Get image metadata (type, width and height) - * @throws {@link ImagesError} with code 9412 if input is not an image - * @param stream The image bytes - */ - info(stream: ReadableStream, options?: ImageInputOptions): Promise; - /** - * Begin applying a series of transformations to an image - * @param stream The image bytes - * @returns A transform handle - */ - input(stream: ReadableStream, options?: ImageInputOptions): ImageTransformer; -} -interface ImageTransformer { - /** - * Apply transform next, returning a transform handle. - * You can then apply more transformations, draw, or retrieve the output. - * @param transform - */ - transform(transform: ImageTransform): ImageTransformer; - /** - * Draw an image on this transformer, returning a transform handle. - * You can then apply more transformations, draw, or retrieve the output. - * @param image The image (or transformer that will give the image) to draw - * @param options The options configuring how to draw the image - */ - draw( - image: ReadableStream | ImageTransformer, - options?: ImageDrawOptions, - ): ImageTransformer; - /** - * Retrieve the image that results from applying the transforms to the - * provided input - * @param options Options that apply to the output e.g. output format - */ - output(options: ImageOutputOptions): Promise; -} -type ImageTransformationOutputOptions = { - encoding?: 'base64'; -}; -interface ImageTransformationResult { - /** - * The image as a response, ready to store in cache or return to users - */ - response(): Response; - /** - * The content type of the returned image - */ - contentType(): string; - /** - * The bytes of the response - */ - image(options?: ImageTransformationOutputOptions): ReadableStream; -} -interface ImagesError extends Error { - readonly code: number; - readonly message: string; - readonly stack?: string; -} -/** - * Media binding for transforming media streams. - * Provides the entry point for media transformation operations. - */ -interface MediaBinding { - /** - * Creates a media transformer from an input stream. - * @param media - The input media bytes - * @returns A MediaTransformer instance for applying transformations - */ - input(media: ReadableStream): MediaTransformer; -} -/** - * Media transformer for applying transformation operations to media content. - * Handles sizing, fitting, and other input transformation parameters. - */ -interface MediaTransformer { - /** - * Applies transformation options to the media content. - * @param transform - Configuration for how the media should be transformed - * @returns A generator for producing the transformed media output - */ - transform(transform: MediaTransformationInputOptions): MediaTransformationGenerator; -} -/** - * Generator for producing media transformation results. - * Configures the output format and parameters for the transformed media. - */ -interface MediaTransformationGenerator { - /** - * Generates the final media output with specified options. - * @param output - Configuration for the output format and parameters - * @returns The final transformation result containing the transformed media - */ - output(output: MediaTransformationOutputOptions): MediaTransformationResult; -} -/** - * Result of a media transformation operation. - * Provides multiple ways to access the transformed media content. - */ -interface MediaTransformationResult { - /** - * Returns the transformed media as a readable stream of bytes. - * @returns A stream containing the transformed media data - */ - media(): ReadableStream; - /** - * Returns the transformed media as an HTTP response object. - * @returns The transformed media as a Response, ready to store in cache or return to users - */ - response(): Response; - /** - * Returns the MIME type of the transformed media. - * @returns The content type string (e.g., 'image/jpeg', 'video/mp4') - */ - contentType(): string; -} -/** - * Configuration options for transforming media input. - * Controls how the media should be resized and fitted. - */ -type MediaTransformationInputOptions = { - /** How the media should be resized to fit the specified dimensions */ - fit?: 'contain' | 'cover' | 'scale-down'; - /** Target width in pixels */ - width?: number; - /** Target height in pixels */ - height?: number; -}; -/** - * Configuration options for Media Transformations output. - * Controls the format, timing, and type of the generated output. - */ -type MediaTransformationOutputOptions = { - /** - * Output mode determining the type of media to generate - */ - mode?: 'video' | 'spritesheet' | 'frame' | 'audio'; - /** Whether to include audio in the output */ - audio?: boolean; - /** - * Starting timestamp for frame extraction or start time for clips. (e.g. '2s'). - */ - time?: string; - /** - * Duration for video clips, audio extraction, and spritesheet generation (e.g. '5s'). - */ - duration?: string; - /** - * Number of frames in the spritesheet. - */ - imageCount?: number; - /** - * Output format for the generated media. - */ - format?: 'jpg' | 'png' | 'm4a'; -}; -/** - * Error object for media transformation operations. - * Extends the standard Error interface with additional media-specific information. - */ -interface MediaError extends Error { - readonly code: number; - readonly message: string; - readonly stack?: string; -} -declare module 'cloudflare:node' { - interface NodeStyleServer { - listen(...args: unknown[]): this; - address(): { - port?: number | null | undefined; - }; - } - export function httpServerHandler(port: number): ExportedHandler; - export function httpServerHandler(options: { port: number }): ExportedHandler; - export function httpServerHandler(server: NodeStyleServer): ExportedHandler; -} -type Params

= Record; -type EventContext = { - request: Request>; - functionPath: string; - waitUntil: (promise: Promise) => void; - passThroughOnException: () => void; - next: (input?: Request | string, init?: RequestInit) => Promise; - env: Env & { - ASSETS: { - fetch: typeof fetch; - }; - }; - params: Params

; - data: Data; -}; -type PagesFunction< - Env = unknown, - Params extends string = any, - Data extends Record = Record, -> = (context: EventContext) => Response | Promise; -type EventPluginContext = { - request: Request>; - functionPath: string; - waitUntil: (promise: Promise) => void; - passThroughOnException: () => void; - next: (input?: Request | string, init?: RequestInit) => Promise; - env: Env & { - ASSETS: { - fetch: typeof fetch; - }; - }; - params: Params

; - data: Data; - pluginArgs: PluginArgs; -}; -type PagesPluginFunction< - Env = unknown, - Params extends string = any, - Data extends Record = Record, - PluginArgs = unknown, -> = (context: EventPluginContext) => Response | Promise; -declare module 'assets:*' { - export const onRequest: PagesFunction; -} -// Copyright (c) 2022-2023 Cloudflare, Inc. -// Licensed under the Apache 2.0 license found in the LICENSE file or at: -// https://opensource.org/licenses/Apache-2.0 -declare module 'cloudflare:pipelines' { - export abstract class PipelineTransformationEntrypoint< - Env = unknown, - I extends PipelineRecord = PipelineRecord, - O extends PipelineRecord = PipelineRecord, - > { - protected env: Env; - protected ctx: ExecutionContext; - constructor(ctx: ExecutionContext, env: Env); - /** - * run recieves an array of PipelineRecord which can be - * transformed and returned to the pipeline - * @param records Incoming records from the pipeline to be transformed - * @param metadata Information about the specific pipeline calling the transformation entrypoint - * @returns A promise containing the transformed PipelineRecord array - */ - public run(records: I[], metadata: PipelineBatchMetadata): Promise; - } - export type PipelineRecord = Record; - export type PipelineBatchMetadata = { - pipelineId: string; - pipelineName: string; - }; - export interface Pipeline { - /** - * The Pipeline interface represents the type of a binding to a Pipeline - * - * @param records The records to send to the pipeline - */ - send(records: T[]): Promise; - } -} -// PubSubMessage represents an incoming PubSub message. -// The message includes metadata about the broker, the client, and the payload -// itself. -// https://developers.cloudflare.com/pub-sub/ -interface PubSubMessage { - // Message ID - readonly mid: number; - // MQTT broker FQDN in the form mqtts://BROKER.NAMESPACE.cloudflarepubsub.com:PORT - readonly broker: string; - // The MQTT topic the message was sent on. - readonly topic: string; - // The client ID of the client that published this message. - readonly clientId: string; - // The unique identifier (JWT ID) used by the client to authenticate, if token - // auth was used. - readonly jti?: string; - // A Unix timestamp (seconds from Jan 1, 1970), set when the Pub/Sub Broker - // received the message from the client. - readonly receivedAt: number; - // An (optional) string with the MIME type of the payload, if set by the - // client. - readonly contentType: string; - // Set to 1 when the payload is a UTF-8 string - // https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901063 - readonly payloadFormatIndicator: number; - // Pub/Sub (MQTT) payloads can be UTF-8 strings, or byte arrays. - // You can use payloadFormatIndicator to inspect this before decoding. - payload: string | Uint8Array; -} -// JsonWebKey extended by kid parameter -interface JsonWebKeyWithKid extends JsonWebKey { - // Key Identifier of the JWK - readonly kid: string; -} -interface RateLimitOptions { - key: string; -} -interface RateLimitOutcome { - success: boolean; -} -interface RateLimit { - /** - * Rate limit a request based on the provided options. - * @see https://developers.cloudflare.com/workers/runtime-apis/bindings/rate-limit/ - * @returns A promise that resolves with the outcome of the rate limit. - */ - limit(options: RateLimitOptions): Promise; -} -// Namespace for RPC utility types. Unfortunately, we can't use a `module` here as these types need -// to referenced by `Fetcher`. This is included in the "importable" version of the types which -// strips all `module` blocks. -declare namespace Rpc { - // Branded types for identifying `WorkerEntrypoint`/`DurableObject`/`Target`s. - // TypeScript uses *structural* typing meaning anything with the same shape as type `T` is a `T`. - // For the classes exported by `cloudflare:workers` we want *nominal* typing (i.e. we only want to - // accept `WorkerEntrypoint` from `cloudflare:workers`, not any other class with the same shape) - export const __RPC_STUB_BRAND: '__RPC_STUB_BRAND'; - export const __RPC_TARGET_BRAND: '__RPC_TARGET_BRAND'; - export const __WORKER_ENTRYPOINT_BRAND: '__WORKER_ENTRYPOINT_BRAND'; - export const __DURABLE_OBJECT_BRAND: '__DURABLE_OBJECT_BRAND'; - export const __WORKFLOW_ENTRYPOINT_BRAND: '__WORKFLOW_ENTRYPOINT_BRAND'; - export interface RpcTargetBranded { - [__RPC_TARGET_BRAND]: never; - } - export interface WorkerEntrypointBranded { - [__WORKER_ENTRYPOINT_BRAND]: never; - } - export interface DurableObjectBranded { - [__DURABLE_OBJECT_BRAND]: never; - } - export interface WorkflowEntrypointBranded { - [__WORKFLOW_ENTRYPOINT_BRAND]: never; - } - export type EntrypointBranded = - | WorkerEntrypointBranded - | DurableObjectBranded - | WorkflowEntrypointBranded; - // Types that can be used through `Stub`s - export type Stubable = RpcTargetBranded | ((...args: any[]) => any); - // Types that can be passed over RPC - // The reason for using a generic type here is to build a serializable subset of structured - // cloneable composite types. This allows types defined with the "interface" keyword to pass the - // serializable check as well. Otherwise, only types defined with the "type" keyword would pass. - type Serializable = - // Structured cloneables - | BaseType - // Structured cloneable composites - | Map< - T extends Map ? Serializable : never, - T extends Map ? Serializable : never - > - | Set ? Serializable : never> - | ReadonlyArray ? Serializable : never> - | { - [K in keyof T]: K extends number | string ? Serializable : never; - } - // Special types - | Stub - // Serialized as stubs, see `Stubify` - | Stubable; - // Base type for all RPC stubs, including common memory management methods. - // `T` is used as a marker type for unwrapping `Stub`s later. - interface StubBase extends Disposable { - [__RPC_STUB_BRAND]: T; - dup(): this; - } - export type Stub = Provider & StubBase; - // This represents all the types that can be sent as-is over an RPC boundary - type BaseType = - | void - | undefined - | null - | boolean - | number - | bigint - | string - | TypedArray - | ArrayBuffer - | DataView - | Date - | Error - | RegExp - | ReadableStream - | WritableStream - | Request - | Response - | Headers; - // Recursively rewrite all `Stubable` types with `Stub`s - // prettier-ignore - type Stubify = T extends Stubable ? Stub : T extends Map ? Map, Stubify> : T extends Set ? Set> : T extends Array ? Array> : T extends ReadonlyArray ? ReadonlyArray> : T extends BaseType ? T : T extends { - [key: string | number]: any; - } ? { - [K in keyof T]: Stubify; - } : T; - // Recursively rewrite all `Stub`s with the corresponding `T`s. - // Note we use `StubBase` instead of `Stub` here to avoid circular dependencies: - // `Stub` depends on `Provider`, which depends on `Unstubify`, which would depend on `Stub`. - // prettier-ignore - type Unstubify = T extends StubBase ? V : T extends Map ? Map, Unstubify> : T extends Set ? Set> : T extends Array ? Array> : T extends ReadonlyArray ? ReadonlyArray> : T extends BaseType ? T : T extends { - [key: string | number]: unknown; - } ? { - [K in keyof T]: Unstubify; - } : T; - type UnstubifyAll = { - [I in keyof A]: Unstubify; - }; - // Utility type for adding `Provider`/`Disposable`s to `object` types only. - // Note `unknown & T` is equivalent to `T`. - type MaybeProvider = T extends object ? Provider : unknown; - type MaybeDisposable = T extends object ? Disposable : unknown; - // Type for method return or property on an RPC interface. - // - Stubable types are replaced by stubs. - // - Serializable types are passed by value, with stubable types replaced by stubs - // and a top-level `Disposer`. - // Everything else can't be passed over PRC. - // Technically, we use custom thenables here, but they quack like `Promise`s. - // Intersecting with `(Maybe)Provider` allows pipelining. - // prettier-ignore - type Result = R extends Stubable ? Promise> & Provider : R extends Serializable ? Promise & MaybeDisposable> & MaybeProvider : never; - // Type for method or property on an RPC interface. - // For methods, unwrap `Stub`s in parameters, and rewrite returns to be `Result`s. - // Unwrapping `Stub`s allows calling with `Stubable` arguments. - // For properties, rewrite types to be `Result`s. - // In each case, unwrap `Promise`s. - type MethodOrProperty = - V extends (...args: infer P) => infer R ? (...args: UnstubifyAll

) => Result> - : Result>; - // Type for the callable part of an `Provider` if `T` is callable. - // This is intersected with methods/properties. - type MaybeCallableProvider = T extends (...args: any[]) => any ? MethodOrProperty : unknown; - // Base type for all other types providing RPC-like interfaces. - // Rewrites all methods/properties to be `MethodOrProperty`s, while preserving callable types. - // `Reserved` names (e.g. stub method names like `dup()`) and symbols can't be accessed over RPC. - export type Provider< - T extends object, - Reserved extends string = never, - > = MaybeCallableProvider & - Pick< - { - [K in keyof T]: MethodOrProperty; - }, - Exclude> - >; -} -declare namespace Cloudflare { - // Type of `env`. - // - // The specific project can extend `Env` by redeclaring it in project-specific files. Typescript - // will merge all declarations. - // - // You can use `wrangler types` to generate the `Env` type automatically. - interface Env {} - // Project-specific parameters used to inform types. - // - // This interface is, again, intended to be declared in project-specific files, and then that - // declaration will be merged with this one. - // - // A project should have a declaration like this: - // - // interface GlobalProps { - // // Declares the main module's exports. Used to populate Cloudflare.Exports aka the type - // // of `ctx.exports`. - // mainModule: typeof import("my-main-module"); - // - // // Declares which of the main module's exports are configured with durable storage, and - // // thus should behave as Durable Object namsepace bindings. - // durableNamespaces: "MyDurableObject" | "AnotherDurableObject"; - // } - // - // You can use `wrangler types` to generate `GlobalProps` automatically. - interface GlobalProps {} - // Evaluates to the type of a property in GlobalProps, defaulting to `Default` if it is not - // present. - type GlobalProp = - K extends keyof GlobalProps ? GlobalProps[K] : Default; - // The type of the program's main module exports, if known. Requires `GlobalProps` to declare the - // `mainModule` property. - type MainModule = GlobalProp<'mainModule', {}>; - // The type of ctx.exports, which contains loopback bindings for all top-level exports. - type Exports = { - [K in keyof MainModule]: LoopbackForExport & - // If the export is listed in `durableNamespaces`, then it is also a - // DurableObjectNamespace. - (K extends GlobalProp<'durableNamespaces', never> ? - MainModule[K] extends new (...args: any[]) => infer DoInstance ? - DoInstance extends Rpc.DurableObjectBranded ? - DurableObjectNamespace - : DurableObjectNamespace - : DurableObjectNamespace - : {}); - }; -} -declare namespace CloudflareWorkersModule { - export type RpcStub = Rpc.Stub; - export const RpcStub: { - new (value: T): Rpc.Stub; - }; - export abstract class RpcTarget implements Rpc.RpcTargetBranded { - [Rpc.__RPC_TARGET_BRAND]: never; - } - // `protected` fields don't appear in `keyof`s, so can't be accessed over RPC - export abstract class WorkerEntrypoint - implements Rpc.WorkerEntrypointBranded - { - [Rpc.__WORKER_ENTRYPOINT_BRAND]: never; - protected ctx: ExecutionContext; - protected env: Env; - constructor(ctx: ExecutionContext, env: Env); - email?(message: ForwardableEmailMessage): void | Promise; - fetch?(request: Request): Response | Promise; - queue?(batch: MessageBatch): void | Promise; - scheduled?(controller: ScheduledController): void | Promise; - tail?(events: TraceItem[]): void | Promise; - tailStream?( - event: TailStream.TailEvent, - ): TailStream.TailEventHandlerType | Promise; - test?(controller: TestController): void | Promise; - trace?(traces: TraceItem[]): void | Promise; - } - export abstract class DurableObject - implements Rpc.DurableObjectBranded - { - [Rpc.__DURABLE_OBJECT_BRAND]: never; - protected ctx: DurableObjectState; - protected env: Env; - constructor(ctx: DurableObjectState, env: Env); - alarm?(alarmInfo?: AlarmInvocationInfo): void | Promise; - fetch?(request: Request): Response | Promise; - webSocketMessage?(ws: WebSocket, message: string | ArrayBuffer): void | Promise; - webSocketClose?( - ws: WebSocket, - code: number, - reason: string, - wasClean: boolean, - ): void | Promise; - webSocketError?(ws: WebSocket, error: unknown): void | Promise; - } - export type WorkflowDurationLabel = - | 'second' - | 'minute' - | 'hour' - | 'day' - | 'week' - | 'month' - | 'year'; - export type WorkflowSleepDuration = `${number} ${WorkflowDurationLabel}${'s' | ''}` | number; - export type WorkflowDelayDuration = WorkflowSleepDuration; - export type WorkflowTimeoutDuration = WorkflowSleepDuration; - export type WorkflowRetentionDuration = WorkflowSleepDuration; - export type WorkflowBackoff = 'constant' | 'linear' | 'exponential'; - export type WorkflowStepConfig = { - retries?: { - limit: number; - delay: WorkflowDelayDuration | number; - backoff?: WorkflowBackoff; - }; - timeout?: WorkflowTimeoutDuration | number; - }; - export type WorkflowEvent = { - payload: Readonly; - timestamp: Date; - instanceId: string; - }; - export type WorkflowStepEvent = { - payload: Readonly; - timestamp: Date; - type: string; - }; - export abstract class WorkflowStep { - do>(name: string, callback: () => Promise): Promise; - do>( - name: string, - config: WorkflowStepConfig, - callback: () => Promise, - ): Promise; - sleep: (name: string, duration: WorkflowSleepDuration) => Promise; - sleepUntil: (name: string, timestamp: Date | number) => Promise; - waitForEvent>( - name: string, - options: { - type: string; - timeout?: WorkflowTimeoutDuration | number; - }, - ): Promise>; - } - export abstract class WorkflowEntrypoint< - Env = unknown, - T extends Rpc.Serializable | unknown = unknown, - > - implements Rpc.WorkflowEntrypointBranded - { - [Rpc.__WORKFLOW_ENTRYPOINT_BRAND]: never; - protected ctx: ExecutionContext; - protected env: Env; - constructor(ctx: ExecutionContext, env: Env); - run(event: Readonly>, step: WorkflowStep): Promise; - } - export function waitUntil(promise: Promise): void; - export function withEnv(newEnv: unknown, fn: () => unknown): unknown; - export function withExports(newExports: unknown, fn: () => unknown): unknown; - export function withEnvAndExports( - newEnv: unknown, - newExports: unknown, - fn: () => unknown, - ): unknown; - export const env: Cloudflare.Env; - export const exports: Cloudflare.Exports; -} -declare module 'cloudflare:workers' { - export = CloudflareWorkersModule; -} -interface SecretsStoreSecret { - /** - * Get a secret from the Secrets Store, returning a string of the secret value - * if it exists, or throws an error if it does not exist - */ - get(): Promise; -} -declare module 'cloudflare:sockets' { - function _connect(address: string | SocketAddress, options?: SocketOptions): Socket; - export { _connect as connect }; -} -type MarkdownDocument = { - name: string; - blob: Blob; -}; -type ConversionResponse = - | { - name: string; - mimeType: string; - format: 'markdown'; - tokens: number; - data: string; - } - | { - name: string; - mimeType: string; - format: 'error'; - error: string; - }; -type ImageConversionOptions = { - descriptionLanguage?: 'en' | 'es' | 'fr' | 'it' | 'pt' | 'de'; -}; -type EmbeddedImageConversionOptions = ImageConversionOptions & { - convert?: boolean; - maxConvertedImages?: number; -}; -type ConversionOptions = { - html?: { - images?: EmbeddedImageConversionOptions & { - convertOGImage?: boolean; - }; - }; - docx?: { - images?: EmbeddedImageConversionOptions; - }; - image?: ImageConversionOptions; - pdf?: { - images?: EmbeddedImageConversionOptions; - metadata?: boolean; - }; -}; -type ConversionRequestOptions = { - gateway?: GatewayOptions; - extraHeaders?: object; - conversionOptions?: ConversionOptions; -}; -type SupportedFileFormat = { - mimeType: string; - extension: string; -}; -declare abstract class ToMarkdownService { - transform( - files: MarkdownDocument[], - options?: ConversionRequestOptions, - ): Promise; - transform( - files: MarkdownDocument, - options?: ConversionRequestOptions, - ): Promise; - supported(): Promise; -} -declare namespace TailStream { - interface Header { - readonly name: string; - readonly value: string; - } - interface FetchEventInfo { - readonly type: 'fetch'; - readonly method: string; - readonly url: string; - readonly cfJson?: object; - readonly headers: Header[]; - } - interface JsRpcEventInfo { - readonly type: 'jsrpc'; - } - interface ScheduledEventInfo { - readonly type: 'scheduled'; - readonly scheduledTime: Date; - readonly cron: string; - } - interface AlarmEventInfo { - readonly type: 'alarm'; - readonly scheduledTime: Date; - } - interface QueueEventInfo { - readonly type: 'queue'; - readonly queueName: string; - readonly batchSize: number; - } - interface EmailEventInfo { - readonly type: 'email'; - readonly mailFrom: string; - readonly rcptTo: string; - readonly rawSize: number; - } - interface TraceEventInfo { - readonly type: 'trace'; - readonly traces: (string | null)[]; - } - interface HibernatableWebSocketEventInfoMessage { - readonly type: 'message'; - } - interface HibernatableWebSocketEventInfoError { - readonly type: 'error'; - } - interface HibernatableWebSocketEventInfoClose { - readonly type: 'close'; - readonly code: number; - readonly wasClean: boolean; - } - interface HibernatableWebSocketEventInfo { - readonly type: 'hibernatableWebSocket'; - readonly info: - | HibernatableWebSocketEventInfoClose - | HibernatableWebSocketEventInfoError - | HibernatableWebSocketEventInfoMessage; - } - interface CustomEventInfo { - readonly type: 'custom'; - } - interface FetchResponseInfo { - readonly type: 'fetch'; - readonly statusCode: number; - } - type EventOutcome = - | 'ok' - | 'canceled' - | 'exception' - | 'unknown' - | 'killSwitch' - | 'daemonDown' - | 'exceededCpu' - | 'exceededMemory' - | 'loadShed' - | 'responseStreamDisconnected' - | 'scriptNotFound'; - interface ScriptVersion { - readonly id: string; - readonly tag?: string; - readonly message?: string; - } - interface Onset { - readonly type: 'onset'; - readonly attributes: Attribute[]; - // id for the span being opened by this Onset event. - readonly spanId: string; - readonly dispatchNamespace?: string; - readonly entrypoint?: string; - readonly executionModel: string; - readonly scriptName?: string; - readonly scriptTags?: string[]; - readonly scriptVersion?: ScriptVersion; - readonly info: - | FetchEventInfo - | JsRpcEventInfo - | ScheduledEventInfo - | AlarmEventInfo - | QueueEventInfo - | EmailEventInfo - | TraceEventInfo - | HibernatableWebSocketEventInfo - | CustomEventInfo; - } - interface Outcome { - readonly type: 'outcome'; - readonly outcome: EventOutcome; - readonly cpuTime: number; - readonly wallTime: number; - } - interface SpanOpen { - readonly type: 'spanOpen'; - readonly name: string; - // id for the span being opened by this SpanOpen event. - readonly spanId: string; - readonly info?: FetchEventInfo | JsRpcEventInfo | Attributes; - } - interface SpanClose { - readonly type: 'spanClose'; - readonly outcome: EventOutcome; - } - interface DiagnosticChannelEvent { - readonly type: 'diagnosticChannel'; - readonly channel: string; - readonly message: any; - } - interface Exception { - readonly type: 'exception'; - readonly name: string; - readonly message: string; - readonly stack?: string; - } - interface Log { - readonly type: 'log'; - readonly level: 'debug' | 'error' | 'info' | 'log' | 'warn'; - readonly message: object; - } - // This marks the worker handler return information. - // This is separate from Outcome because the worker invocation can live for a long time after - // returning. For example - Websockets that return an http upgrade response but then continue - // streaming information or SSE http connections. - interface Return { - readonly type: 'return'; - readonly info?: FetchResponseInfo; - } - interface Attribute { - readonly name: string; - readonly value: string | string[] | boolean | boolean[] | number | number[] | bigint | bigint[]; - } - interface Attributes { - readonly type: 'attributes'; - readonly info: Attribute[]; - } - type EventType = - | Onset - | Outcome - | SpanOpen - | SpanClose - | DiagnosticChannelEvent - | Exception - | Log - | Return - | Attributes; - // Context in which this trace event lives. - interface SpanContext { - // Single id for the entire top-level invocation - // This should be a new traceId for the first worker stage invoked in the eyeball request and then - // same-account service-bindings should reuse the same traceId but cross-account service-bindings - // should use a new traceId. - readonly traceId: string; - // spanId in which this event is handled - // for Onset and SpanOpen events this would be the parent span id - // for Outcome and SpanClose these this would be the span id of the opening Onset and SpanOpen events - // For Hibernate and Mark this would be the span under which they were emitted. - // spanId is not set ONLY if: - // 1. This is an Onset event - // 2. We are not inherting any SpanContext. (e.g. this is a cross-account service binding or a new top-level invocation) - readonly spanId?: string; - } - interface TailEvent { - // invocation id of the currently invoked worker stage. - // invocation id will always be unique to every Onset event and will be the same until the Outcome event. - readonly invocationId: string; - // Inherited spanContext for this event. - readonly spanContext: SpanContext; - readonly timestamp: Date; - readonly sequence: number; - readonly event: Event; - } - type TailEventHandler = ( - event: TailEvent, - ) => void | Promise; - type TailEventHandlerObject = { - outcome?: TailEventHandler; - spanOpen?: TailEventHandler; - spanClose?: TailEventHandler; - diagnosticChannel?: TailEventHandler; - exception?: TailEventHandler; - log?: TailEventHandler; - return?: TailEventHandler; - attributes?: TailEventHandler; - }; - type TailEventHandlerType = TailEventHandler | TailEventHandlerObject; -} -// Copyright (c) 2022-2023 Cloudflare, Inc. -// Licensed under the Apache 2.0 license found in the LICENSE file or at: -// https://opensource.org/licenses/Apache-2.0 -/** - * Data types supported for holding vector metadata. - */ -type VectorizeVectorMetadataValue = string | number | boolean | string[]; -/** - * Additional information to associate with a vector. - */ -type VectorizeVectorMetadata = - | VectorizeVectorMetadataValue - | Record; -type VectorFloatArray = Float32Array | Float64Array; -interface VectorizeError { - code?: number; - error: string; -} -/** - * Comparison logic/operation to use for metadata filtering. - * - * This list is expected to grow as support for more operations are released. - */ -type VectorizeVectorMetadataFilterOp = '$eq' | '$ne' | '$lt' | '$lte' | '$gt' | '$gte'; -type VectorizeVectorMetadataFilterCollectionOp = '$in' | '$nin'; -/** - * Filter criteria for vector metadata used to limit the retrieved query result set. - */ -type VectorizeVectorMetadataFilter = { - [field: string]: - | Exclude - | null - | { - [Op in VectorizeVectorMetadataFilterOp]?: Exclude< - VectorizeVectorMetadataValue, - string[] - > | null; - } - | { - [Op in VectorizeVectorMetadataFilterCollectionOp]?: Exclude< - VectorizeVectorMetadataValue, - string[] - >[]; - }; -}; -/** - * Supported distance metrics for an index. - * Distance metrics determine how other "similar" vectors are determined. - */ -type VectorizeDistanceMetric = 'euclidean' | 'cosine' | 'dot-product'; -/** - * Metadata return levels for a Vectorize query. - * - * Default to "none". - * - * @property all Full metadata for the vector return set, including all fields (including those un-indexed) without truncation. This is a more expensive retrieval, as it requires additional fetching & reading of un-indexed data. - * @property indexed Return all metadata fields configured for indexing in the vector return set. This level of retrieval is "free" in that no additional overhead is incurred returning this data. However, note that indexed metadata is subject to truncation (especially for larger strings). - * @property none No indexed metadata will be returned. - */ -type VectorizeMetadataRetrievalLevel = 'all' | 'indexed' | 'none'; -interface VectorizeQueryOptions { - topK?: number; - namespace?: string; - returnValues?: boolean; - returnMetadata?: boolean | VectorizeMetadataRetrievalLevel; - filter?: VectorizeVectorMetadataFilter; -} -/** - * Information about the configuration of an index. - */ -type VectorizeIndexConfig = - | { - dimensions: number; - metric: VectorizeDistanceMetric; - } - | { - preset: string; // keep this generic, as we'll be adding more presets in the future and this is only in a read capacity - }; -/** - * Metadata about an existing index. - * - * This type is exclusively for the Vectorize **beta** and will be deprecated once Vectorize RC is released. - * See {@link VectorizeIndexInfo} for its post-beta equivalent. - */ -interface VectorizeIndexDetails { - /** The unique ID of the index */ - readonly id: string; - /** The name of the index. */ - name: string; - /** (optional) A human readable description for the index. */ - description?: string; - /** The index configuration, including the dimension size and distance metric. */ - config: VectorizeIndexConfig; - /** The number of records containing vectors within the index. */ - vectorsCount: number; -} -/** - * Metadata about an existing index. - */ -interface VectorizeIndexInfo { - /** The number of records containing vectors within the index. */ - vectorCount: number; - /** Number of dimensions the index has been configured for. */ - dimensions: number; - /** ISO 8601 datetime of the last processed mutation on in the index. All changes before this mutation will be reflected in the index state. */ - processedUpToDatetime: number; - /** UUIDv4 of the last mutation processed by the index. All changes before this mutation will be reflected in the index state. */ - processedUpToMutation: number; -} -/** - * Represents a single vector value set along with its associated metadata. - */ -interface VectorizeVector { - /** The ID for the vector. This can be user-defined, and must be unique. It should uniquely identify the object, and is best set based on the ID of what the vector represents. */ - id: string; - /** The vector values */ - values: VectorFloatArray | number[]; - /** The namespace this vector belongs to. */ - namespace?: string; - /** Metadata associated with the vector. Includes the values of other fields and potentially additional details. */ - metadata?: Record; -} -/** - * Represents a matched vector for a query along with its score and (if specified) the matching vector information. - */ -type VectorizeMatch = Pick, 'values'> & - Omit & { - /** The score or rank for similarity, when returned as a result */ - score: number; - }; -/** - * A set of matching {@link VectorizeMatch} for a particular query. - */ -interface VectorizeMatches { - matches: VectorizeMatch[]; - count: number; -} -/** - * Results of an operation that performed a mutation on a set of vectors. - * Here, `ids` is a list of vectors that were successfully processed. - * - * This type is exclusively for the Vectorize **beta** and will be deprecated once Vectorize RC is released. - * See {@link VectorizeAsyncMutation} for its post-beta equivalent. - */ -interface VectorizeVectorMutation { - /* List of ids of vectors that were successfully processed. */ - ids: string[]; - /* Total count of the number of processed vectors. */ - count: number; -} -/** - * Result type indicating a mutation on the Vectorize Index. - * Actual mutations are processed async where the `mutationId` is the unique identifier for the operation. - */ -interface VectorizeAsyncMutation { - /** The unique identifier for the async mutation operation containing the changeset. */ - mutationId: string; -} -/** - * A Vectorize Vector Search Index for querying vectors/embeddings. - * - * This type is exclusively for the Vectorize **beta** and will be deprecated once Vectorize RC is released. - * See {@link Vectorize} for its new implementation. - */ -declare abstract class VectorizeIndex { - /** - * Get information about the currently bound index. - * @returns A promise that resolves with information about the current index. - */ - public describe(): Promise; - /** - * Use the provided vector to perform a similarity search across the index. - * @param vector Input vector that will be used to drive the similarity search. - * @param options Configuration options to massage the returned data. - * @returns A promise that resolves with matched and scored vectors. - */ - public query( - vector: VectorFloatArray | number[], - options?: VectorizeQueryOptions, - ): Promise; - /** - * Insert a list of vectors into the index dataset. If a provided id exists, an error will be thrown. - * @param vectors List of vectors that will be inserted. - * @returns A promise that resolves with the ids & count of records that were successfully processed. - */ - public insert(vectors: VectorizeVector[]): Promise; - /** - * Upsert a list of vectors into the index dataset. If a provided id exists, it will be replaced with the new values. - * @param vectors List of vectors that will be upserted. - * @returns A promise that resolves with the ids & count of records that were successfully processed. - */ - public upsert(vectors: VectorizeVector[]): Promise; - /** - * Delete a list of vectors with a matching id. - * @param ids List of vector ids that should be deleted. - * @returns A promise that resolves with the ids & count of records that were successfully processed (and thus deleted). - */ - public deleteByIds(ids: string[]): Promise; - /** - * Get a list of vectors with a matching id. - * @param ids List of vector ids that should be returned. - * @returns A promise that resolves with the raw unscored vectors matching the id set. - */ - public getByIds(ids: string[]): Promise; -} -/** - * A Vectorize Vector Search Index for querying vectors/embeddings. - * - * Mutations in this version are async, returning a mutation id. - */ -declare abstract class Vectorize { - /** - * Get information about the currently bound index. - * @returns A promise that resolves with information about the current index. - */ - public describe(): Promise; - /** - * Use the provided vector to perform a similarity search across the index. - * @param vector Input vector that will be used to drive the similarity search. - * @param options Configuration options to massage the returned data. - * @returns A promise that resolves with matched and scored vectors. - */ - public query( - vector: VectorFloatArray | number[], - options?: VectorizeQueryOptions, - ): Promise; - /** - * Use the provided vector-id to perform a similarity search across the index. - * @param vectorId Id for a vector in the index against which the index should be queried. - * @param options Configuration options to massage the returned data. - * @returns A promise that resolves with matched and scored vectors. - */ - public queryById(vectorId: string, options?: VectorizeQueryOptions): Promise; - /** - * Insert a list of vectors into the index dataset. If a provided id exists, an error will be thrown. - * @param vectors List of vectors that will be inserted. - * @returns A promise that resolves with a unique identifier of a mutation containing the insert changeset. - */ - public insert(vectors: VectorizeVector[]): Promise; - /** - * Upsert a list of vectors into the index dataset. If a provided id exists, it will be replaced with the new values. - * @param vectors List of vectors that will be upserted. - * @returns A promise that resolves with a unique identifier of a mutation containing the upsert changeset. - */ - public upsert(vectors: VectorizeVector[]): Promise; - /** - * Delete a list of vectors with a matching id. - * @param ids List of vector ids that should be deleted. - * @returns A promise that resolves with a unique identifier of a mutation containing the delete changeset. - */ - public deleteByIds(ids: string[]): Promise; - /** - * Get a list of vectors with a matching id. - * @param ids List of vector ids that should be returned. - * @returns A promise that resolves with the raw unscored vectors matching the id set. - */ - public getByIds(ids: string[]): Promise; -} -/** - * The interface for "version_metadata" binding - * providing metadata about the Worker Version using this binding. - */ -type WorkerVersionMetadata = { - /** The ID of the Worker Version using this binding */ - id: string; - /** The tag of the Worker Version using this binding */ - tag: string; - /** The timestamp of when the Worker Version was uploaded */ - timestamp: string; -}; -interface DynamicDispatchLimits { - /** - * Limit CPU time in milliseconds. - */ - cpuMs?: number; - /** - * Limit number of subrequests. - */ - subRequests?: number; -} -interface DynamicDispatchOptions { - /** - * Limit resources of invoked Worker script. - */ - limits?: DynamicDispatchLimits; - /** - * Arguments for outbound Worker script, if configured. - */ - outbound?: { - [key: string]: any; - }; -} -interface DispatchNamespace { - /** - * @param name Name of the Worker script. - * @param args Arguments to Worker script. - * @param options Options for Dynamic Dispatch invocation. - * @returns A Fetcher object that allows you to send requests to the Worker script. - * @throws If the Worker script does not exist in this dispatch namespace, an error will be thrown. - */ - get( - name: string, - args?: { - [key: string]: any; - }, - options?: DynamicDispatchOptions, - ): Fetcher; -} -declare module 'cloudflare:workflows' { - /** - * NonRetryableError allows for a user to throw a fatal error - * that makes a Workflow instance fail immediately without triggering a retry - */ - export class NonRetryableError extends Error { - public constructor(message: string, name?: string); - } -} -declare abstract class Workflow { - /** - * Get a handle to an existing instance of the Workflow. - * @param id Id for the instance of this Workflow - * @returns A promise that resolves with a handle for the Instance - */ - public get(id: string): Promise; - /** - * Create a new instance and return a handle to it. If a provided id exists, an error will be thrown. - * @param options Options when creating an instance including id and params - * @returns A promise that resolves with a handle for the Instance - */ - public create(options?: WorkflowInstanceCreateOptions): Promise; - /** - * Create a batch of instances and return handle for all of them. If a provided id exists, an error will be thrown. - * `createBatch` is limited at 100 instances at a time or when the RPC limit for the batch (1MiB) is reached. - * @param batch List of Options when creating an instance including name and params - * @returns A promise that resolves with a list of handles for the created instances. - */ - public createBatch(batch: WorkflowInstanceCreateOptions[]): Promise; -} -type WorkflowDurationLabel = 'second' | 'minute' | 'hour' | 'day' | 'week' | 'month' | 'year'; -type WorkflowSleepDuration = `${number} ${WorkflowDurationLabel}${'s' | ''}` | number; -type WorkflowRetentionDuration = WorkflowSleepDuration; -interface WorkflowInstanceCreateOptions { - /** - * An id for your Workflow instance. Must be unique within the Workflow. - */ - id?: string; - /** - * The event payload the Workflow instance is triggered with - */ - params?: PARAMS; - /** - * The retention policy for Workflow instance. - * Defaults to the maximum retention period available for the owner's account. - */ - retention?: { - successRetention?: WorkflowRetentionDuration; - errorRetention?: WorkflowRetentionDuration; - }; -} -type InstanceStatus = { - status: - | 'queued' // means that instance is waiting to be started (see concurrency limits) - | 'running' - | 'paused' - | 'errored' - | 'terminated' // user terminated the instance while it was running - | 'complete' - | 'waiting' // instance is hibernating and waiting for sleep or event to finish - | 'waitingForPause' // instance is finishing the current work to pause - | 'unknown'; - error?: { - name: string; - message: string; - }; - output?: unknown; -}; -interface WorkflowError { - code?: number; - message: string; -} -declare abstract class WorkflowInstance { - public id: string; - /** - * Pause the instance. - */ - public pause(): Promise; - /** - * Resume the instance. If it is already running, an error will be thrown. - */ - public resume(): Promise; - /** - * Terminate the instance. If it is errored, terminated or complete, an error will be thrown. - */ - public terminate(): Promise; - /** - * Restart the instance. - */ - public restart(): Promise; - /** - * Returns the current status of the instance. - */ - public status(): Promise; - /** - * Send an event to this instance. - */ - public sendEvent({ type, payload }: { type: string; payload: unknown }): Promise; -} diff --git a/packages/workers/wrangler.jsonc b/packages/workers/wrangler.jsonc index f9f9e8dd5..7d82a4931 100644 --- a/packages/workers/wrangler.jsonc +++ b/packages/workers/wrangler.jsonc @@ -4,6 +4,11 @@ "compatibility_date": "2025-12-01", "compatibility_flags": ["nodejs_compat"], + // Version metadata for Sentry release tracking + "version_metadata": { + "binding": "CF_VERSION_METADATA" + }, + // Durable Objects Migrations "migrations": [ { @@ -56,7 +61,8 @@ "ENVIRONMENT": "development", "DEV_MODE": true, "AUTH_BASE_URL": "http://localhost:8787", - "APP_URL": "http://localhost:5173" + "APP_URL": "http://localhost:5173", + "SENTRY_DSN": "https://3ddf61abb06680bffc1ebc7f540f05de@o4510738063818752.ingest.us.sentry.io/4510738126274560" }, "observability": { @@ -118,6 +124,7 @@ // wrangler secret put EMAIL_FROM --env production // wrangler secret put GOOGLE_CLIENT_ID --env production // wrangler secret put GOOGLE_CLIENT_SECRET --env production + // wrangler secret put SENTRY_DSN --env production "d1_databases": [ { "binding": "DB", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8f9190b1e..76221cb20 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -73,7 +73,7 @@ importers: version: 0.15.4(solid-js@1.9.10) '@solidjs/start': specifier: ^1.2.1 - version: 1.2.1(@testing-library/jest-dom@6.9.1)(solid-js@1.9.10)(vinxi@0.5.10(@types/node@25.0.9)(better-sqlite3@12.6.2)(db0@0.3.4(better-sqlite3@12.6.2)(drizzle-orm@0.45.1(@cloudflare/workers-types@4.20260118.0)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.6.2)(gel@2.2.0)(kysely@0.28.10)))(drizzle-orm@0.45.1(@cloudflare/workers-types@4.20260118.0)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.6.2)(gel@2.2.0)(kysely@0.28.10))(ioredis@5.9.2)(jiti@2.6.1)(lightningcss@1.30.2)(rolldown@1.0.0-beta.51)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) + version: 1.2.1(@testing-library/jest-dom@6.9.1)(solid-js@1.9.10)(vinxi@0.5.10(@types/node@25.0.9)(better-sqlite3@12.6.2)(db0@0.3.4(better-sqlite3@12.6.2)(drizzle-orm@0.45.1(@cloudflare/workers-types@4.20260118.0)(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.6.2)(gel@2.2.0)(kysely@0.28.10)))(drizzle-orm@0.45.1(@cloudflare/workers-types@4.20260118.0)(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.6.2)(gel@2.2.0)(kysely@0.28.10))(ioredis@5.9.2)(jiti@2.6.1)(lightningcss@1.30.2)(rolldown@1.0.0-beta.51)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) solid-icons: specifier: ^1.1.0 version: 1.1.0(solid-js@1.9.10) @@ -82,7 +82,7 @@ importers: version: 1.9.10 vinxi: specifier: ^0.5.10 - version: 0.5.10(@types/node@25.0.9)(better-sqlite3@12.6.2)(db0@0.3.4(better-sqlite3@12.6.2)(drizzle-orm@0.45.1(@cloudflare/workers-types@4.20260118.0)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.6.2)(gel@2.2.0)(kysely@0.28.10)))(drizzle-orm@0.45.1(@cloudflare/workers-types@4.20260118.0)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.6.2)(gel@2.2.0)(kysely@0.28.10))(ioredis@5.9.2)(jiti@2.6.1)(lightningcss@1.30.2)(rolldown@1.0.0-beta.51)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) + version: 0.5.10(@types/node@25.0.9)(better-sqlite3@12.6.2)(db0@0.3.4(better-sqlite3@12.6.2)(drizzle-orm@0.45.1(@cloudflare/workers-types@4.20260118.0)(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.6.2)(gel@2.2.0)(kysely@0.28.10)))(drizzle-orm@0.45.1(@cloudflare/workers-types@4.20260118.0)(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.6.2)(gel@2.2.0)(kysely@0.28.10))(ioredis@5.9.2)(jiti@2.6.1)(lightningcss@1.30.2)(rolldown@1.0.0-beta.51)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) devDependencies: '@tailwindcss/vite': specifier: ^4.1.18 @@ -111,7 +111,7 @@ importers: version: 5.9.3 vitest: specifier: ^4.0.17 - version: 4.0.17(@types/node@25.0.9)(@vitest/ui@4.0.17)(jiti@2.6.1)(jsdom@27.4.0(@noble/hashes@2.0.1))(lightningcss@1.30.2)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) + version: 4.0.17(@opentelemetry/api@1.9.0)(@types/node@25.0.9)(@vitest/ui@4.0.17)(jiti@2.6.1)(jsdom@27.4.0(@noble/hashes@2.0.1))(lightningcss@1.30.2)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) packages/mcp-memory: dependencies: @@ -142,7 +142,7 @@ importers: version: 5.9.3 vitest: specifier: ^4.0.17 - version: 4.0.17(@types/node@25.0.9)(@vitest/ui@4.0.17)(jiti@2.6.1)(jsdom@27.4.0(@noble/hashes@2.0.1))(lightningcss@1.30.2)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) + version: 4.0.17(@opentelemetry/api@1.9.0)(@types/node@25.0.9)(@vitest/ui@4.0.17)(jiti@2.6.1)(jsdom@27.4.0(@noble/hashes@2.0.1))(lightningcss@1.30.2)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) packages/shared: devDependencies: @@ -151,7 +151,7 @@ importers: version: 5.9.3 vitest: specifier: ^4.0.17 - version: 4.0.17(@types/node@25.0.9)(@vitest/ui@4.0.17)(jiti@2.6.1)(jsdom@27.4.0(@noble/hashes@2.0.1))(lightningcss@1.30.2)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) + version: 4.0.17(@opentelemetry/api@1.9.0)(@types/node@25.0.9)(@vitest/ui@4.0.17)(jiti@2.6.1)(jsdom@27.4.0(@noble/hashes@2.0.1))(lightningcss@1.30.2)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) packages/stripe-dev: devDependencies: @@ -251,6 +251,9 @@ importers: '@embedpdf/plugin-zoom': specifier: ^2.2.0 version: 2.2.0(6bd7b58b8c00f9d90d9b5543c10d606a) + '@sentry/solid': + specifier: ^10.35.0 + version: 10.35.0(@solidjs/router@0.15.4(solid-js@1.9.10))(solid-js@1.9.10) '@solid-primitives/scheduled': specifier: ^1.5.2 version: 1.5.2(solid-js@1.9.10) @@ -265,7 +268,7 @@ importers: version: 8.21.3(solid-js@1.9.10) better-auth: specifier: ^1.4.15 - version: 1.4.15(better-sqlite3@12.6.2)(drizzle-kit@0.31.8)(drizzle-orm@0.45.1(@cloudflare/workers-types@4.20260118.0)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.6.2)(gel@2.2.0)(kysely@0.28.10))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(solid-js@1.9.10)(svelte@5.46.1)(vitest@4.0.17)(vue@3.5.26(typescript@5.9.3)) + version: 1.4.15(better-sqlite3@12.6.2)(drizzle-kit@0.31.8)(drizzle-orm@0.45.1(@cloudflare/workers-types@4.20260118.0)(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.6.2)(gel@2.2.0)(kysely@0.28.10))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(solid-js@1.9.10)(svelte@5.46.1)(vitest@4.0.17)(vue@3.5.26(typescript@5.9.3)) chart.js: specifier: ^4.5.1 version: 4.5.1 @@ -356,13 +359,13 @@ importers: version: 2.11.10(@testing-library/jest-dom@6.9.1)(solid-js@1.9.10)(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) vitest: specifier: ^4.0.17 - version: 4.0.17(@types/node@25.0.9)(@vitest/ui@4.0.17)(jiti@2.6.1)(jsdom@27.4.0(@noble/hashes@2.0.1))(lightningcss@1.30.2)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) + version: 4.0.17(@opentelemetry/api@1.9.0)(@types/node@25.0.9)(@vitest/ui@4.0.17)(jiti@2.6.1)(jsdom@27.4.0(@noble/hashes@2.0.1))(lightningcss@1.30.2)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) packages/workers: dependencies: '@better-auth/stripe': specifier: ^1.4.15 - version: 1.4.15(@better-auth/core@1.4.15(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@4.3.5))(jose@6.1.3)(kysely@0.28.10)(nanostores@1.1.0))(better-auth@1.4.15(better-sqlite3@12.6.2)(drizzle-kit@0.31.8)(drizzle-orm@0.45.1(@cloudflare/workers-types@4.20260118.0)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.6.2)(gel@2.2.0)(kysely@0.28.10))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(solid-js@1.9.10)(svelte@5.46.1)(vitest@3.2.0(@types/node@25.0.9)(jiti@2.6.1)(jsdom@27.4.0(@noble/hashes@2.0.1))(lightningcss@1.30.2)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(vue@3.5.26(typescript@5.9.3)))(stripe@20.2.0(@types/node@25.0.9)) + version: 1.4.15(@better-auth/core@1.4.15(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@4.3.5))(jose@6.1.3)(kysely@0.28.10)(nanostores@1.1.0))(better-auth@1.4.15(better-sqlite3@12.6.2)(drizzle-kit@0.31.8)(drizzle-orm@0.45.1(@cloudflare/workers-types@4.20260118.0)(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.6.2)(gel@2.2.0)(kysely@0.28.10))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(solid-js@1.9.10)(svelte@5.46.1)(vitest@3.2.0(@types/node@25.0.9)(jiti@2.6.1)(jsdom@27.4.0(@noble/hashes@2.0.1))(lightningcss@1.30.2)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(vue@3.5.26(typescript@5.9.3)))(stripe@20.2.0(@types/node@25.0.9)) '@cloudflare/workers-types': specifier: ^4.20260118.0 version: 4.20260118.0 @@ -372,12 +375,15 @@ importers: '@hono/zod-openapi': specifier: ^1.2.0 version: 1.2.0(hono@4.11.4)(zod@4.3.5) + '@sentry/cloudflare': + specifier: ^10.35.0 + version: 10.35.0(@cloudflare/workers-types@4.20260118.0) better-auth: specifier: ^1.4.15 - version: 1.4.15(better-sqlite3@12.6.2)(drizzle-kit@0.31.8)(drizzle-orm@0.45.1(@cloudflare/workers-types@4.20260118.0)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.6.2)(gel@2.2.0)(kysely@0.28.10))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(solid-js@1.9.10)(svelte@5.46.1)(vitest@3.2.0(@types/node@25.0.9)(jiti@2.6.1)(jsdom@27.4.0(@noble/hashes@2.0.1))(lightningcss@1.30.2)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(vue@3.5.26(typescript@5.9.3)) + version: 1.4.15(better-sqlite3@12.6.2)(drizzle-kit@0.31.8)(drizzle-orm@0.45.1(@cloudflare/workers-types@4.20260118.0)(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.6.2)(gel@2.2.0)(kysely@0.28.10))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(solid-js@1.9.10)(svelte@5.46.1)(vitest@3.2.0(@types/node@25.0.9)(jiti@2.6.1)(jsdom@27.4.0(@noble/hashes@2.0.1))(lightningcss@1.30.2)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(vue@3.5.26(typescript@5.9.3)) drizzle-orm: specifier: ^0.45.1 - version: 0.45.1(@cloudflare/workers-types@4.20260118.0)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.6.2)(gel@2.2.0)(kysely@0.28.10) + version: 0.45.1(@cloudflare/workers-types@4.20260118.0)(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.6.2)(gel@2.2.0)(kysely@0.28.10) hono: specifier: ^4.11.4 version: 4.11.4 @@ -2372,6 +2378,10 @@ packages: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} + '@opentelemetry/api@1.9.0': + resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==} + engines: {node: '>=8.0.0'} + '@oxc-project/types@0.98.0': resolution: {integrity: sha512-Vzmd6FsqVuz5HQVcRC/hrx7Ujo3WEVeQP7C2UNP5uy1hUY4SQvMB+93jxkI1KRHz9a/6cni3glPOtvteN+zpsw==} @@ -2833,6 +2843,52 @@ packages: cpu: [x64] os: [win32] + '@sentry-internal/browser-utils@10.35.0': + resolution: {integrity: sha512-YjVbyqpJu6E6U/BCdOgIUuUQPUDZ7XdFiBYXtGy59xqQB1qSqNfei163hkfnXxIN90csDubxWNrnit+W5Wo/uQ==} + engines: {node: '>=18'} + + '@sentry-internal/feedback@10.35.0': + resolution: {integrity: sha512-h/rtGcgvGtZIY9njxnzHHMzMwFYAYG/UwDaNtpf8jN63JD6cTQDQ8wNWp0arD9gmUr96YjER55BNRRF8oSg6Fw==} + engines: {node: '>=18'} + + '@sentry-internal/replay-canvas@10.35.0': + resolution: {integrity: sha512-efaz8ETDLd0rSpoqX4m8fMnq7abzUJAdqeChz9Jdq6OgvHeBgM6tTfqWSes6sFnSCvFUVkdFngZQfgmBxWGuEA==} + engines: {node: '>=18'} + + '@sentry-internal/replay@10.35.0': + resolution: {integrity: sha512-9hGP3lD+7o/4ovGTdwv3T9K2t9LxSlR/CAcRQeFApW2c0AGsjTdcglOxsgxYei4YmaISx0CBJ/YqJfQVYxaxWw==} + engines: {node: '>=18'} + + '@sentry/browser@10.35.0': + resolution: {integrity: sha512-3wCdmKOTqg6Fvmb9HLHzCVIpSSYCPhXFQ95VaYsb1rESIgL7BMS9nyqhecPcPR3oJppU2a/TqZk4YH3nFrPXmA==} + engines: {node: '>=18'} + + '@sentry/cloudflare@10.35.0': + resolution: {integrity: sha512-Ob+ahHtB7QG4XSXNY0hXG8Y2R9scO1ipzZO7ked7OMqHq2pk/ycReJjPu358a19g1HSPa1FXFQioYclA/FL3Vw==} + engines: {node: '>=18'} + peerDependencies: + '@cloudflare/workers-types': ^4.x + peerDependenciesMeta: + '@cloudflare/workers-types': + optional: true + + '@sentry/core@10.35.0': + resolution: {integrity: sha512-lEK1WFqt6oHtMq5dDLVE/FDzHDGs1PlYT5cZH4aBirYtJVyUiTf0NknKFob4a2zTywczlq7SbLv6Ba8UMU9dYg==} + engines: {node: '>=18'} + + '@sentry/solid@10.35.0': + resolution: {integrity: sha512-v/rtrlXCma+D0CymSgV8bI4uMhn9wD6sPIJMm71jk6Mi7tH22+It5pEAlDCdGOEuuUe560YVT+8riwbTnaNhcQ==} + engines: {node: '>=18'} + peerDependencies: + '@solidjs/router': ^0.13.4 || ^0.14.0 || ^0.15.0 + '@tanstack/solid-router': ^1.132.27 + solid-js: ^1.8.4 + peerDependenciesMeta: + '@solidjs/router': + optional: true + '@tanstack/solid-router': + optional: true + '@shikijs/core@1.29.2': resolution: {integrity: sha512-vju0lY9r27jJfOY4Z7+Rt/nIOjzJpZ3y+nYpqtUZInVoXQ/TJZcfGnNOGnKjFdVZb8qexiCuSlZRKcGfhhTTZQ==} @@ -8358,10 +8414,10 @@ snapshots: nanostores: 1.1.0 zod: 4.3.5 - '@better-auth/stripe@1.4.15(@better-auth/core@1.4.15(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@4.3.5))(jose@6.1.3)(kysely@0.28.10)(nanostores@1.1.0))(better-auth@1.4.15(better-sqlite3@12.6.2)(drizzle-kit@0.31.8)(drizzle-orm@0.45.1(@cloudflare/workers-types@4.20260118.0)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.6.2)(gel@2.2.0)(kysely@0.28.10))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(solid-js@1.9.10)(svelte@5.46.1)(vitest@3.2.0(@types/node@25.0.9)(jiti@2.6.1)(jsdom@27.4.0(@noble/hashes@2.0.1))(lightningcss@1.30.2)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(vue@3.5.26(typescript@5.9.3)))(stripe@20.2.0(@types/node@25.0.9))': + '@better-auth/stripe@1.4.15(@better-auth/core@1.4.15(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@4.3.5))(jose@6.1.3)(kysely@0.28.10)(nanostores@1.1.0))(better-auth@1.4.15(better-sqlite3@12.6.2)(drizzle-kit@0.31.8)(drizzle-orm@0.45.1(@cloudflare/workers-types@4.20260118.0)(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.6.2)(gel@2.2.0)(kysely@0.28.10))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(solid-js@1.9.10)(svelte@5.46.1)(vitest@3.2.0(@types/node@25.0.9)(jiti@2.6.1)(jsdom@27.4.0(@noble/hashes@2.0.1))(lightningcss@1.30.2)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(vue@3.5.26(typescript@5.9.3)))(stripe@20.2.0(@types/node@25.0.9))': dependencies: '@better-auth/core': 1.4.15(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@4.3.5))(jose@6.1.3)(kysely@0.28.10)(nanostores@1.1.0) - better-auth: 1.4.15(better-sqlite3@12.6.2)(drizzle-kit@0.31.8)(drizzle-orm@0.45.1(@cloudflare/workers-types@4.20260118.0)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.6.2)(gel@2.2.0)(kysely@0.28.10))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(solid-js@1.9.10)(svelte@5.46.1)(vitest@3.2.0(@types/node@25.0.9)(jiti@2.6.1)(jsdom@27.4.0(@noble/hashes@2.0.1))(lightningcss@1.30.2)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(vue@3.5.26(typescript@5.9.3)) + better-auth: 1.4.15(better-sqlite3@12.6.2)(drizzle-kit@0.31.8)(drizzle-orm@0.45.1(@cloudflare/workers-types@4.20260118.0)(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.6.2)(gel@2.2.0)(kysely@0.28.10))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(solid-js@1.9.10)(svelte@5.46.1)(vitest@3.2.0(@types/node@25.0.9)(jiti@2.6.1)(jsdom@27.4.0(@noble/hashes@2.0.1))(lightningcss@1.30.2)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(vue@3.5.26(typescript@5.9.3)) defu: 6.1.4 stripe: 20.2.0(@types/node@25.0.9) zod: 4.3.5 @@ -9645,6 +9701,8 @@ snapshots: '@nodelib/fs.scandir': 2.1.5 fastq: 1.20.1 + '@opentelemetry/api@1.9.0': {} + '@oxc-project/types@0.98.0': optional: true @@ -10002,6 +10060,49 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.55.1': optional: true + '@sentry-internal/browser-utils@10.35.0': + dependencies: + '@sentry/core': 10.35.0 + + '@sentry-internal/feedback@10.35.0': + dependencies: + '@sentry/core': 10.35.0 + + '@sentry-internal/replay-canvas@10.35.0': + dependencies: + '@sentry-internal/replay': 10.35.0 + '@sentry/core': 10.35.0 + + '@sentry-internal/replay@10.35.0': + dependencies: + '@sentry-internal/browser-utils': 10.35.0 + '@sentry/core': 10.35.0 + + '@sentry/browser@10.35.0': + dependencies: + '@sentry-internal/browser-utils': 10.35.0 + '@sentry-internal/feedback': 10.35.0 + '@sentry-internal/replay': 10.35.0 + '@sentry-internal/replay-canvas': 10.35.0 + '@sentry/core': 10.35.0 + + '@sentry/cloudflare@10.35.0(@cloudflare/workers-types@4.20260118.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@sentry/core': 10.35.0 + optionalDependencies: + '@cloudflare/workers-types': 4.20260118.0 + + '@sentry/core@10.35.0': {} + + '@sentry/solid@10.35.0(@solidjs/router@0.15.4(solid-js@1.9.10))(solid-js@1.9.10)': + dependencies: + '@sentry/browser': 10.35.0 + '@sentry/core': 10.35.0 + solid-js: 1.9.10 + optionalDependencies: + '@solidjs/router': 0.15.4(solid-js@1.9.10) + '@shikijs/core@1.29.2': dependencies: '@shikijs/engine-javascript': 1.29.2 @@ -10095,11 +10196,11 @@ snapshots: dependencies: solid-js: 1.9.10 - '@solidjs/start@1.2.1(@testing-library/jest-dom@6.9.1)(solid-js@1.9.10)(vinxi@0.5.10(@types/node@25.0.9)(better-sqlite3@12.6.2)(db0@0.3.4(better-sqlite3@12.6.2)(drizzle-orm@0.45.1(@cloudflare/workers-types@4.20260118.0)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.6.2)(gel@2.2.0)(kysely@0.28.10)))(drizzle-orm@0.45.1(@cloudflare/workers-types@4.20260118.0)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.6.2)(gel@2.2.0)(kysely@0.28.10))(ioredis@5.9.2)(jiti@2.6.1)(lightningcss@1.30.2)(rolldown@1.0.0-beta.51)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))': + '@solidjs/start@1.2.1(@testing-library/jest-dom@6.9.1)(solid-js@1.9.10)(vinxi@0.5.10(@types/node@25.0.9)(better-sqlite3@12.6.2)(db0@0.3.4(better-sqlite3@12.6.2)(drizzle-orm@0.45.1(@cloudflare/workers-types@4.20260118.0)(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.6.2)(gel@2.2.0)(kysely@0.28.10)))(drizzle-orm@0.45.1(@cloudflare/workers-types@4.20260118.0)(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.6.2)(gel@2.2.0)(kysely@0.28.10))(ioredis@5.9.2)(jiti@2.6.1)(lightningcss@1.30.2)(rolldown@1.0.0-beta.51)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))': dependencies: '@tanstack/server-functions-plugin': 1.121.21(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) - '@vinxi/plugin-directives': 0.5.1(vinxi@0.5.10(@types/node@25.0.9)(better-sqlite3@12.6.2)(db0@0.3.4(better-sqlite3@12.6.2)(drizzle-orm@0.45.1(@cloudflare/workers-types@4.20260118.0)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.6.2)(gel@2.2.0)(kysely@0.28.10)))(drizzle-orm@0.45.1(@cloudflare/workers-types@4.20260118.0)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.6.2)(gel@2.2.0)(kysely@0.28.10))(ioredis@5.9.2)(jiti@2.6.1)(lightningcss@1.30.2)(rolldown@1.0.0-beta.51)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) - '@vinxi/server-components': 0.5.1(vinxi@0.5.10(@types/node@25.0.9)(better-sqlite3@12.6.2)(db0@0.3.4(better-sqlite3@12.6.2)(drizzle-orm@0.45.1(@cloudflare/workers-types@4.20260118.0)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.6.2)(gel@2.2.0)(kysely@0.28.10)))(drizzle-orm@0.45.1(@cloudflare/workers-types@4.20260118.0)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.6.2)(gel@2.2.0)(kysely@0.28.10))(ioredis@5.9.2)(jiti@2.6.1)(lightningcss@1.30.2)(rolldown@1.0.0-beta.51)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) + '@vinxi/plugin-directives': 0.5.1(vinxi@0.5.10(@types/node@25.0.9)(better-sqlite3@12.6.2)(db0@0.3.4(better-sqlite3@12.6.2)(drizzle-orm@0.45.1(@cloudflare/workers-types@4.20260118.0)(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.6.2)(gel@2.2.0)(kysely@0.28.10)))(drizzle-orm@0.45.1(@cloudflare/workers-types@4.20260118.0)(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.6.2)(gel@2.2.0)(kysely@0.28.10))(ioredis@5.9.2)(jiti@2.6.1)(lightningcss@1.30.2)(rolldown@1.0.0-beta.51)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) + '@vinxi/server-components': 0.5.1(vinxi@0.5.10(@types/node@25.0.9)(better-sqlite3@12.6.2)(db0@0.3.4(better-sqlite3@12.6.2)(drizzle-orm@0.45.1(@cloudflare/workers-types@4.20260118.0)(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.6.2)(gel@2.2.0)(kysely@0.28.10)))(drizzle-orm@0.45.1(@cloudflare/workers-types@4.20260118.0)(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.6.2)(gel@2.2.0)(kysely@0.28.10))(ioredis@5.9.2)(jiti@2.6.1)(lightningcss@1.30.2)(rolldown@1.0.0-beta.51)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) cookie-es: 2.0.0 defu: 6.1.4 error-stack-parser: 2.1.4 @@ -10111,7 +10212,7 @@ snapshots: source-map-js: 1.2.1 terracotta: 1.1.0(solid-js@1.9.10) tinyglobby: 0.2.15 - vinxi: 0.5.10(@types/node@25.0.9)(better-sqlite3@12.6.2)(db0@0.3.4(better-sqlite3@12.6.2)(drizzle-orm@0.45.1(@cloudflare/workers-types@4.20260118.0)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.6.2)(gel@2.2.0)(kysely@0.28.10)))(drizzle-orm@0.45.1(@cloudflare/workers-types@4.20260118.0)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.6.2)(gel@2.2.0)(kysely@0.28.10))(ioredis@5.9.2)(jiti@2.6.1)(lightningcss@1.30.2)(rolldown@1.0.0-beta.51)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) + vinxi: 0.5.10(@types/node@25.0.9)(better-sqlite3@12.6.2)(db0@0.3.4(better-sqlite3@12.6.2)(drizzle-orm@0.45.1(@cloudflare/workers-types@4.20260118.0)(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.6.2)(gel@2.2.0)(kysely@0.28.10)))(drizzle-orm@0.45.1(@cloudflare/workers-types@4.20260118.0)(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.6.2)(gel@2.2.0)(kysely@0.28.10))(ioredis@5.9.2)(jiti@2.6.1)(lightningcss@1.30.2)(rolldown@1.0.0-beta.51)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) vite-plugin-solid: 2.11.10(@testing-library/jest-dom@6.9.1)(solid-js@1.9.10)(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) transitivePeerDependencies: - '@testing-library/jest-dom' @@ -10592,7 +10693,7 @@ snapshots: untun: 0.1.3 uqr: 0.1.2 - '@vinxi/plugin-directives@0.5.1(vinxi@0.5.10(@types/node@25.0.9)(better-sqlite3@12.6.2)(db0@0.3.4(better-sqlite3@12.6.2)(drizzle-orm@0.45.1(@cloudflare/workers-types@4.20260118.0)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.6.2)(gel@2.2.0)(kysely@0.28.10)))(drizzle-orm@0.45.1(@cloudflare/workers-types@4.20260118.0)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.6.2)(gel@2.2.0)(kysely@0.28.10))(ioredis@5.9.2)(jiti@2.6.1)(lightningcss@1.30.2)(rolldown@1.0.0-beta.51)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))': + '@vinxi/plugin-directives@0.5.1(vinxi@0.5.10(@types/node@25.0.9)(better-sqlite3@12.6.2)(db0@0.3.4(better-sqlite3@12.6.2)(drizzle-orm@0.45.1(@cloudflare/workers-types@4.20260118.0)(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.6.2)(gel@2.2.0)(kysely@0.28.10)))(drizzle-orm@0.45.1(@cloudflare/workers-types@4.20260118.0)(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.6.2)(gel@2.2.0)(kysely@0.28.10))(ioredis@5.9.2)(jiti@2.6.1)(lightningcss@1.30.2)(rolldown@1.0.0-beta.51)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))': dependencies: '@babel/parser': 7.28.6 acorn: 8.15.0 @@ -10603,18 +10704,18 @@ snapshots: magicast: 0.2.11 recast: 0.23.11 tslib: 2.8.1 - vinxi: 0.5.10(@types/node@25.0.9)(better-sqlite3@12.6.2)(db0@0.3.4(better-sqlite3@12.6.2)(drizzle-orm@0.45.1(@cloudflare/workers-types@4.20260118.0)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.6.2)(gel@2.2.0)(kysely@0.28.10)))(drizzle-orm@0.45.1(@cloudflare/workers-types@4.20260118.0)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.6.2)(gel@2.2.0)(kysely@0.28.10))(ioredis@5.9.2)(jiti@2.6.1)(lightningcss@1.30.2)(rolldown@1.0.0-beta.51)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) + vinxi: 0.5.10(@types/node@25.0.9)(better-sqlite3@12.6.2)(db0@0.3.4(better-sqlite3@12.6.2)(drizzle-orm@0.45.1(@cloudflare/workers-types@4.20260118.0)(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.6.2)(gel@2.2.0)(kysely@0.28.10)))(drizzle-orm@0.45.1(@cloudflare/workers-types@4.20260118.0)(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.6.2)(gel@2.2.0)(kysely@0.28.10))(ioredis@5.9.2)(jiti@2.6.1)(lightningcss@1.30.2)(rolldown@1.0.0-beta.51)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) - '@vinxi/server-components@0.5.1(vinxi@0.5.10(@types/node@25.0.9)(better-sqlite3@12.6.2)(db0@0.3.4(better-sqlite3@12.6.2)(drizzle-orm@0.45.1(@cloudflare/workers-types@4.20260118.0)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.6.2)(gel@2.2.0)(kysely@0.28.10)))(drizzle-orm@0.45.1(@cloudflare/workers-types@4.20260118.0)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.6.2)(gel@2.2.0)(kysely@0.28.10))(ioredis@5.9.2)(jiti@2.6.1)(lightningcss@1.30.2)(rolldown@1.0.0-beta.51)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))': + '@vinxi/server-components@0.5.1(vinxi@0.5.10(@types/node@25.0.9)(better-sqlite3@12.6.2)(db0@0.3.4(better-sqlite3@12.6.2)(drizzle-orm@0.45.1(@cloudflare/workers-types@4.20260118.0)(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.6.2)(gel@2.2.0)(kysely@0.28.10)))(drizzle-orm@0.45.1(@cloudflare/workers-types@4.20260118.0)(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.6.2)(gel@2.2.0)(kysely@0.28.10))(ioredis@5.9.2)(jiti@2.6.1)(lightningcss@1.30.2)(rolldown@1.0.0-beta.51)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))': dependencies: - '@vinxi/plugin-directives': 0.5.1(vinxi@0.5.10(@types/node@25.0.9)(better-sqlite3@12.6.2)(db0@0.3.4(better-sqlite3@12.6.2)(drizzle-orm@0.45.1(@cloudflare/workers-types@4.20260118.0)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.6.2)(gel@2.2.0)(kysely@0.28.10)))(drizzle-orm@0.45.1(@cloudflare/workers-types@4.20260118.0)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.6.2)(gel@2.2.0)(kysely@0.28.10))(ioredis@5.9.2)(jiti@2.6.1)(lightningcss@1.30.2)(rolldown@1.0.0-beta.51)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) + '@vinxi/plugin-directives': 0.5.1(vinxi@0.5.10(@types/node@25.0.9)(better-sqlite3@12.6.2)(db0@0.3.4(better-sqlite3@12.6.2)(drizzle-orm@0.45.1(@cloudflare/workers-types@4.20260118.0)(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.6.2)(gel@2.2.0)(kysely@0.28.10)))(drizzle-orm@0.45.1(@cloudflare/workers-types@4.20260118.0)(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.6.2)(gel@2.2.0)(kysely@0.28.10))(ioredis@5.9.2)(jiti@2.6.1)(lightningcss@1.30.2)(rolldown@1.0.0-beta.51)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) acorn: 8.15.0 acorn-loose: 8.5.2 acorn-typescript: 1.4.13(acorn@8.15.0) astring: 1.9.0 magicast: 0.2.11 recast: 0.23.11 - vinxi: 0.5.10(@types/node@25.0.9)(better-sqlite3@12.6.2)(db0@0.3.4(better-sqlite3@12.6.2)(drizzle-orm@0.45.1(@cloudflare/workers-types@4.20260118.0)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.6.2)(gel@2.2.0)(kysely@0.28.10)))(drizzle-orm@0.45.1(@cloudflare/workers-types@4.20260118.0)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.6.2)(gel@2.2.0)(kysely@0.28.10))(ioredis@5.9.2)(jiti@2.6.1)(lightningcss@1.30.2)(rolldown@1.0.0-beta.51)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) + vinxi: 0.5.10(@types/node@25.0.9)(better-sqlite3@12.6.2)(db0@0.3.4(better-sqlite3@12.6.2)(drizzle-orm@0.45.1(@cloudflare/workers-types@4.20260118.0)(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.6.2)(gel@2.2.0)(kysely@0.28.10)))(drizzle-orm@0.45.1(@cloudflare/workers-types@4.20260118.0)(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.6.2)(gel@2.2.0)(kysely@0.28.10))(ioredis@5.9.2)(jiti@2.6.1)(lightningcss@1.30.2)(rolldown@1.0.0-beta.51)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) '@vitejs/plugin-vue@5.2.4(vite@5.4.21(@types/node@25.0.9)(lightningcss@1.30.2)(terser@5.46.0))(vue@3.5.26(typescript@5.9.3))': dependencies: @@ -10703,7 +10804,7 @@ snapshots: sirv: 3.0.2 tinyglobby: 0.2.15 tinyrainbow: 3.0.3 - vitest: 4.0.17(@types/node@25.0.9)(@vitest/ui@4.0.17)(jiti@2.6.1)(jsdom@27.4.0(@noble/hashes@2.0.1))(lightningcss@1.30.2)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) + vitest: 4.0.17(@opentelemetry/api@1.9.0)(@types/node@25.0.9)(@vitest/ui@4.0.17)(jiti@2.6.1)(jsdom@27.4.0(@noble/hashes@2.0.1))(lightningcss@1.30.2)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) '@vitest/utils@3.2.0': dependencies: @@ -11608,7 +11709,7 @@ snapshots: basic-ftp@5.1.0: {} - better-auth@1.4.15(better-sqlite3@12.6.2)(drizzle-kit@0.31.8)(drizzle-orm@0.45.1(@cloudflare/workers-types@4.20260118.0)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.6.2)(gel@2.2.0)(kysely@0.28.10))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(solid-js@1.9.10)(svelte@5.46.1)(vitest@3.2.0(@types/node@25.0.9)(jiti@2.6.1)(jsdom@27.4.0(@noble/hashes@2.0.1))(lightningcss@1.30.2)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(vue@3.5.26(typescript@5.9.3)): + better-auth@1.4.15(better-sqlite3@12.6.2)(drizzle-kit@0.31.8)(drizzle-orm@0.45.1(@cloudflare/workers-types@4.20260118.0)(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.6.2)(gel@2.2.0)(kysely@0.28.10))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(solid-js@1.9.10)(svelte@5.46.1)(vitest@3.2.0(@types/node@25.0.9)(jiti@2.6.1)(jsdom@27.4.0(@noble/hashes@2.0.1))(lightningcss@1.30.2)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(vue@3.5.26(typescript@5.9.3)): dependencies: '@better-auth/core': 1.4.15(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@4.3.5))(jose@6.1.3)(kysely@0.28.10)(nanostores@1.1.0) '@better-auth/telemetry': 1.4.15(@better-auth/core@1.4.15(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@4.3.5))(jose@6.1.3)(kysely@0.28.10)(nanostores@1.1.0)) @@ -11625,7 +11726,7 @@ snapshots: optionalDependencies: better-sqlite3: 12.6.2 drizzle-kit: 0.31.8 - drizzle-orm: 0.45.1(@cloudflare/workers-types@4.20260118.0)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.6.2)(gel@2.2.0)(kysely@0.28.10) + drizzle-orm: 0.45.1(@cloudflare/workers-types@4.20260118.0)(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.6.2)(gel@2.2.0)(kysely@0.28.10) react: 19.2.3 react-dom: 19.2.3(react@19.2.3) solid-js: 1.9.10 @@ -11633,7 +11734,7 @@ snapshots: vitest: 3.2.0(@types/node@25.0.9)(jiti@2.6.1)(jsdom@27.4.0(@noble/hashes@2.0.1))(lightningcss@1.30.2)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) vue: 3.5.26(typescript@5.9.3) - better-auth@1.4.15(better-sqlite3@12.6.2)(drizzle-kit@0.31.8)(drizzle-orm@0.45.1(@cloudflare/workers-types@4.20260118.0)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.6.2)(gel@2.2.0)(kysely@0.28.10))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(solid-js@1.9.10)(svelte@5.46.1)(vitest@4.0.17)(vue@3.5.26(typescript@5.9.3)): + better-auth@1.4.15(better-sqlite3@12.6.2)(drizzle-kit@0.31.8)(drizzle-orm@0.45.1(@cloudflare/workers-types@4.20260118.0)(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.6.2)(gel@2.2.0)(kysely@0.28.10))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(solid-js@1.9.10)(svelte@5.46.1)(vitest@4.0.17)(vue@3.5.26(typescript@5.9.3)): dependencies: '@better-auth/core': 1.4.15(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@4.3.5))(jose@6.1.3)(kysely@0.28.10)(nanostores@1.1.0) '@better-auth/telemetry': 1.4.15(@better-auth/core@1.4.15(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@4.3.5))(jose@6.1.3)(kysely@0.28.10)(nanostores@1.1.0)) @@ -11650,12 +11751,12 @@ snapshots: optionalDependencies: better-sqlite3: 12.6.2 drizzle-kit: 0.31.8 - drizzle-orm: 0.45.1(@cloudflare/workers-types@4.20260118.0)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.6.2)(gel@2.2.0)(kysely@0.28.10) + drizzle-orm: 0.45.1(@cloudflare/workers-types@4.20260118.0)(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.6.2)(gel@2.2.0)(kysely@0.28.10) react: 19.2.3 react-dom: 19.2.3(react@19.2.3) solid-js: 1.9.10 svelte: 5.46.1 - vitest: 4.0.17(@types/node@25.0.9)(@vitest/ui@4.0.17)(jiti@2.6.1)(jsdom@27.4.0(@noble/hashes@2.0.1))(lightningcss@1.30.2)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) + vitest: 4.0.17(@opentelemetry/api@1.9.0)(@types/node@25.0.9)(@vitest/ui@4.0.17)(jiti@2.6.1)(jsdom@27.4.0(@noble/hashes@2.0.1))(lightningcss@1.30.2)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) vue: 3.5.26(typescript@5.9.3) better-call@1.1.8(zod@4.3.5): @@ -12278,10 +12379,10 @@ snapshots: dayjs@1.11.19: {} - db0@0.3.4(better-sqlite3@12.6.2)(drizzle-orm@0.45.1(@cloudflare/workers-types@4.20260118.0)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.6.2)(gel@2.2.0)(kysely@0.28.10)): + db0@0.3.4(better-sqlite3@12.6.2)(drizzle-orm@0.45.1(@cloudflare/workers-types@4.20260118.0)(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.6.2)(gel@2.2.0)(kysely@0.28.10)): optionalDependencies: better-sqlite3: 12.6.2 - drizzle-orm: 0.45.1(@cloudflare/workers-types@4.20260118.0)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.6.2)(gel@2.2.0)(kysely@0.28.10) + drizzle-orm: 0.45.1(@cloudflare/workers-types@4.20260118.0)(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.6.2)(gel@2.2.0)(kysely@0.28.10) debug@2.6.9: dependencies: @@ -12397,9 +12498,10 @@ snapshots: transitivePeerDependencies: - supports-color - drizzle-orm@0.45.1(@cloudflare/workers-types@4.20260118.0)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.6.2)(gel@2.2.0)(kysely@0.28.10): + drizzle-orm@0.45.1(@cloudflare/workers-types@4.20260118.0)(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.6.2)(gel@2.2.0)(kysely@0.28.10): optionalDependencies: '@cloudflare/workers-types': 4.20260118.0 + '@opentelemetry/api': 1.9.0 '@types/better-sqlite3': 7.6.13 better-sqlite3: 12.6.2 gel: 2.2.0 @@ -13869,7 +13971,7 @@ snapshots: netmask@2.0.2: {} - nitropack@2.13.1(better-sqlite3@12.6.2)(drizzle-orm@0.45.1(@cloudflare/workers-types@4.20260118.0)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.6.2)(gel@2.2.0)(kysely@0.28.10))(rolldown@1.0.0-beta.51): + nitropack@2.13.1(better-sqlite3@12.6.2)(drizzle-orm@0.45.1(@cloudflare/workers-types@4.20260118.0)(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.6.2)(gel@2.2.0)(kysely@0.28.10))(rolldown@1.0.0-beta.51): dependencies: '@cloudflare/kv-asset-handler': 0.4.2 '@rollup/plugin-alias': 6.0.0(rollup@4.55.1) @@ -13890,7 +13992,7 @@ snapshots: cookie-es: 2.0.0 croner: 9.1.0 crossws: 0.3.5 - db0: 0.3.4(better-sqlite3@12.6.2)(drizzle-orm@0.45.1(@cloudflare/workers-types@4.20260118.0)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.6.2)(gel@2.2.0)(kysely@0.28.10)) + db0: 0.3.4(better-sqlite3@12.6.2)(drizzle-orm@0.45.1(@cloudflare/workers-types@4.20260118.0)(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.6.2)(gel@2.2.0)(kysely@0.28.10)) defu: 6.1.4 destr: 2.0.5 dot-prop: 10.1.0 @@ -13936,7 +14038,7 @@ snapshots: unenv: 2.0.0-rc.24 unimport: 5.6.0 unplugin-utils: 0.3.1 - unstorage: 1.17.4(db0@0.3.4(better-sqlite3@12.6.2)(drizzle-orm@0.45.1(@cloudflare/workers-types@4.20260118.0)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.6.2)(gel@2.2.0)(kysely@0.28.10)))(ioredis@5.9.2) + unstorage: 1.17.4(db0@0.3.4(better-sqlite3@12.6.2)(drizzle-orm@0.45.1(@cloudflare/workers-types@4.20260118.0)(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.6.2)(gel@2.2.0)(kysely@0.28.10)))(ioredis@5.9.2) untyped: 2.0.0 unwasm: 0.5.3 youch: 4.1.0-beta.13 @@ -15402,7 +15504,7 @@ snapshots: picomatch: 4.0.3 webpack-virtual-modules: 0.6.2 - unstorage@1.17.4(db0@0.3.4(better-sqlite3@12.6.2)(drizzle-orm@0.45.1(@cloudflare/workers-types@4.20260118.0)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.6.2)(gel@2.2.0)(kysely@0.28.10)))(ioredis@5.9.2): + unstorage@1.17.4(db0@0.3.4(better-sqlite3@12.6.2)(drizzle-orm@0.45.1(@cloudflare/workers-types@4.20260118.0)(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.6.2)(gel@2.2.0)(kysely@0.28.10)))(ioredis@5.9.2): dependencies: anymatch: 3.1.3 chokidar: 5.0.0 @@ -15413,7 +15515,7 @@ snapshots: ofetch: 1.5.1 ufo: 1.6.3 optionalDependencies: - db0: 0.3.4(better-sqlite3@12.6.2)(drizzle-orm@0.45.1(@cloudflare/workers-types@4.20260118.0)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.6.2)(gel@2.2.0)(kysely@0.28.10)) + db0: 0.3.4(better-sqlite3@12.6.2)(drizzle-orm@0.45.1(@cloudflare/workers-types@4.20260118.0)(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.6.2)(gel@2.2.0)(kysely@0.28.10)) ioredis: 5.9.2 untun@0.1.3: @@ -15467,7 +15569,7 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.3 - vinxi@0.5.10(@types/node@25.0.9)(better-sqlite3@12.6.2)(db0@0.3.4(better-sqlite3@12.6.2)(drizzle-orm@0.45.1(@cloudflare/workers-types@4.20260118.0)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.6.2)(gel@2.2.0)(kysely@0.28.10)))(drizzle-orm@0.45.1(@cloudflare/workers-types@4.20260118.0)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.6.2)(gel@2.2.0)(kysely@0.28.10))(ioredis@5.9.2)(jiti@2.6.1)(lightningcss@1.30.2)(rolldown@1.0.0-beta.51)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2): + vinxi@0.5.10(@types/node@25.0.9)(better-sqlite3@12.6.2)(db0@0.3.4(better-sqlite3@12.6.2)(drizzle-orm@0.45.1(@cloudflare/workers-types@4.20260118.0)(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.6.2)(gel@2.2.0)(kysely@0.28.10)))(drizzle-orm@0.45.1(@cloudflare/workers-types@4.20260118.0)(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.6.2)(gel@2.2.0)(kysely@0.28.10))(ioredis@5.9.2)(jiti@2.6.1)(lightningcss@1.30.2)(rolldown@1.0.0-beta.51)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2): dependencies: '@babel/core': 7.28.6 '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.28.6) @@ -15488,7 +15590,7 @@ snapshots: hookable: 5.5.3 http-proxy: 1.18.1 micromatch: 4.0.8 - nitropack: 2.13.1(better-sqlite3@12.6.2)(drizzle-orm@0.45.1(@cloudflare/workers-types@4.20260118.0)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.6.2)(gel@2.2.0)(kysely@0.28.10))(rolldown@1.0.0-beta.51) + nitropack: 2.13.1(better-sqlite3@12.6.2)(drizzle-orm@0.45.1(@cloudflare/workers-types@4.20260118.0)(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.6.2)(gel@2.2.0)(kysely@0.28.10))(rolldown@1.0.0-beta.51) node-fetch-native: 1.6.7 path-to-regexp: 6.3.0 pathe: 1.1.2 @@ -15500,7 +15602,7 @@ snapshots: ufo: 1.6.3 unctx: 2.5.0 unenv: 1.10.0 - unstorage: 1.17.4(db0@0.3.4(better-sqlite3@12.6.2)(drizzle-orm@0.45.1(@cloudflare/workers-types@4.20260118.0)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.6.2)(gel@2.2.0)(kysely@0.28.10)))(ioredis@5.9.2) + unstorage: 1.17.4(db0@0.3.4(better-sqlite3@12.6.2)(drizzle-orm@0.45.1(@cloudflare/workers-types@4.20260118.0)(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.6.2)(gel@2.2.0)(kysely@0.28.10)))(ioredis@5.9.2) vite: 6.4.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) zod: 4.3.5 transitivePeerDependencies: @@ -15741,7 +15843,7 @@ snapshots: - tsx - yaml - vitest@4.0.17(@types/node@25.0.9)(@vitest/ui@4.0.17)(jiti@2.6.1)(jsdom@27.4.0(@noble/hashes@2.0.1))(lightningcss@1.30.2)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2): + vitest@4.0.17(@opentelemetry/api@1.9.0)(@types/node@25.0.9)(@vitest/ui@4.0.17)(jiti@2.6.1)(jsdom@27.4.0(@noble/hashes@2.0.1))(lightningcss@1.30.2)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2): dependencies: '@vitest/expect': 4.0.17 '@vitest/mocker': 4.0.17(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) @@ -15764,6 +15866,7 @@ snapshots: vite: 7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) why-is-node-running: 2.3.0 optionalDependencies: + '@opentelemetry/api': 1.9.0 '@types/node': 25.0.9 '@vitest/ui': 4.0.17(vitest@4.0.17) jsdom: 27.4.0(@noble/hashes@2.0.1)