diff --git a/package-lock.json b/package-lock.json index 5cea1a7..0178ba0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,6 +7,9 @@ "": { "name": "aibtcdev-api-cache", "version": "0.0.1", + "dependencies": { + "@supabase/supabase-js": "^2.46.1" + }, "devDependencies": { "@cloudflare/workers-types": "^4.20241106.0", "typescript": "^5.5.2", @@ -178,16 +181,104 @@ "dev": true, "license": "MIT" }, + "node_modules/@supabase/auth-js": { + "version": "2.65.1", + "resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.65.1.tgz", + "integrity": "sha512-IA7i2Xq2SWNCNMKxwmPlHafBQda0qtnFr8QnyyBr+KaSxoXXqEzFCnQ1dGTy6bsZjVBgXu++o3qrDypTspaAPw==", + "license": "MIT", + "dependencies": { + "@supabase/node-fetch": "^2.6.14" + } + }, + "node_modules/@supabase/functions-js": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.4.3.tgz", + "integrity": "sha512-sOLXy+mWRyu4LLv1onYydq+10mNRQ4rzqQxNhbrKLTLTcdcmS9hbWif0bGz/NavmiQfPs4ZcmQJp4WqOXlR4AQ==", + "license": "MIT", + "dependencies": { + "@supabase/node-fetch": "^2.6.14" + } + }, + "node_modules/@supabase/node-fetch": { + "version": "2.6.15", + "resolved": "https://registry.npmjs.org/@supabase/node-fetch/-/node-fetch-2.6.15.tgz", + "integrity": "sha512-1ibVeYUacxWYi9i0cf5efil6adJ9WRyZBLivgjs+AUpewx1F3xPi7gLgaASI2SmIQxPoCEjAsLAzKPgMJVgOUQ==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + } + }, + "node_modules/@supabase/postgrest-js": { + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-1.16.3.tgz", + "integrity": "sha512-HI6dsbW68AKlOPofUjDTaosiDBCtW4XAm0D18pPwxoW3zKOE2Ru13Z69Wuys9fd6iTpfDViNco5sgrtnP0666A==", + "license": "MIT", + "dependencies": { + "@supabase/node-fetch": "^2.6.14" + } + }, + "node_modules/@supabase/realtime-js": { + "version": "2.10.7", + "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.10.7.tgz", + "integrity": "sha512-OLI0hiSAqQSqRpGMTUwoIWo51eUivSYlaNBgxsXZE7PSoWh12wPRdVt0psUMaUzEonSB85K21wGc7W5jHnT6uA==", + "license": "MIT", + "dependencies": { + "@supabase/node-fetch": "^2.6.14", + "@types/phoenix": "^1.5.4", + "@types/ws": "^8.5.10", + "ws": "^8.14.2" + } + }, + "node_modules/@supabase/storage-js": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.7.1.tgz", + "integrity": "sha512-asYHcyDR1fKqrMpytAS1zjyEfvxuOIp1CIXX7ji4lHHcJKqyk+sLl/Vxgm4sN6u8zvuUtae9e4kDxQP2qrwWBA==", + "license": "MIT", + "dependencies": { + "@supabase/node-fetch": "^2.6.14" + } + }, + "node_modules/@supabase/supabase-js": { + "version": "2.46.1", + "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.46.1.tgz", + "integrity": "sha512-HiBpd8stf7M6+tlr+/82L8b2QmCjAD8ex9YdSAKU+whB/SHXXJdus1dGlqiH9Umy9ePUuxaYmVkGd9BcvBnNvg==", + "license": "MIT", + "dependencies": { + "@supabase/auth-js": "2.65.1", + "@supabase/functions-js": "2.4.3", + "@supabase/node-fetch": "2.6.15", + "@supabase/postgrest-js": "1.16.3", + "@supabase/realtime-js": "2.10.7", + "@supabase/storage-js": "2.7.1" + } + }, "node_modules/@types/node": { "version": "22.9.0", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.9.0.tgz", "integrity": "sha512-vuyHg81vvWA1Z1ELfvLko2c8f34gyA0zaic0+Rllc5lbCnbSyuvb2Oxpm6TAUAC/2xZN3QGqxBNggD1nNR2AfQ==", - "dev": true, "license": "MIT", "dependencies": { "undici-types": "~6.19.8" } }, + "node_modules/@types/phoenix": { + "version": "1.6.5", + "resolved": "https://registry.npmjs.org/@types/phoenix/-/phoenix-1.6.5.tgz", + "integrity": "sha512-xegpDuR+z0UqG9fwHqNoy3rI7JDlvaPh2TY47Fl80oq6g+hXT+c/LEuE43X48clZ6lOfANl5WrPur9fYO1RJ/w==", + "license": "MIT" + }, + "node_modules/@types/ws": { + "version": "8.5.13", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.13.tgz", + "integrity": "sha512-osM/gWBTPKgHV8XkTunnegTRIsvF6owmf5w+JtAfOw472dptdm0dlGv4xCt6GwQRcC2XVOvvRE/0bAoQcL2QkA==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/blake3-wasm": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/blake3-wasm/-/blake3-wasm-2.1.5.tgz", @@ -1083,28 +1174,6 @@ "node": ">=14.0" } }, - "node_modules/miniflare/node_modules/ws": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", - "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, "node_modules/miniflare/node_modules/youch": { "version": "3.3.4", "resolved": "https://registry.npmjs.org/youch/-/youch-3.3.4.tgz", @@ -1284,6 +1353,12 @@ "node": ">=0.10.0" } }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, "node_modules/typescript": { "version": "5.6.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", @@ -1302,7 +1377,6 @@ "version": "6.19.8", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", - "dev": true, "license": "MIT" }, "node_modules/unenv": { @@ -1347,6 +1421,22 @@ "dev": true, "license": "MIT" }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/workerd": { "version": "1.20241106.1", "resolved": "https://registry.npmjs.org/workerd/-/workerd-1.20241106.1.tgz", @@ -1499,6 +1589,27 @@ } } }, + "node_modules/ws": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/xxhash-wasm": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/xxhash-wasm/-/xxhash-wasm-1.0.2.tgz", diff --git a/package.json b/package.json index bc10095..53c6df7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,7 @@ { "name": "aibtcdev-api-cache", "version": "0.0.1", + "type": "module", "private": true, "scripts": { "deploy": "wrangler deploy", @@ -12,5 +13,8 @@ "@cloudflare/workers-types": "^4.20241106.0", "typescript": "^5.5.2", "wrangler": "^3.60.3" + }, + "dependencies": { + "@supabase/supabase-js": "^2.46.1" } } diff --git a/src/config.ts b/src/config.ts index 200808c..6c1b03e 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,15 +1,41 @@ -export const APP_CONFIG = { - // supported services for API caching - // each entry is a durable object that handles requests - SUPPORTED_SERVICES: ['/hiro-api'], - // VALUES BELOW CAN BE OVERRIDDEN BY DURABLE OBJECTS - // default cache TTL used for KV - CACHE_TTL: 900, // 15 minutes - // default rate limiting settings - MAX_REQUESTS_PER_INTERVAL: 30, // no more than 30 requests - INTERVAL_MS: 15000, // in a span of 15 seconds - MAX_RETRIES: 3, // max retries for failed fetches - RETRY_DELAY: 1000, // multiplied by retry attempt number - // how often to warm the cache, should be shorter than the cache TTL - ALARM_INTERVAL_MS: 300000, // 5 minutes -}; +import { Env } from '../worker-configuration'; + +export class AppConfig { + private static instance: AppConfig; + private env: Env; + + private constructor(env: Env) { + this.env = env; + } + + public static getInstance(env?: Env): AppConfig { + if (!AppConfig.instance && env) { + AppConfig.instance = new AppConfig(env); + } else if (!AppConfig.instance) { + throw new Error('AppConfig must be initialized with environment variables first'); + } + return AppConfig.instance; + } + + public getConfig() { + + return { + // supported services for API caching + // each entry is a durable object that handles requests + SUPPORTED_SERVICES: ['/hiro-api', '/supabase'], + // VALUES BELOW CAN BE OVERRIDDEN BY DURABLE OBJECTS + // default cache TTL used for KV + CACHE_TTL: 900, // 15 minutes + // default rate limiting settings + MAX_REQUESTS_PER_INTERVAL: 30, // no more than 30 requests + INTERVAL_MS: 15000, // in a span of 15 seconds + MAX_RETRIES: 3, // max retries for failed fetches + RETRY_DELAY: 1000, // multiplied by retry attempt number + // how often to warm the cache, should be shorter than the cache TTL + ALARM_INTERVAL_MS: 300000, // 5 minutes + // environment variables + SUPABASE_URL: this.env.SUPABASE_URL, + SUPABASE_SERVICE_KEY: this.env.SUPABASE_SERVICE_KEY, + }; + } +} diff --git a/src/durable-objects/hiro-api-do.ts b/src/durable-objects/hiro-api-do.ts index a25fbd2..112df75 100644 --- a/src/durable-objects/hiro-api-do.ts +++ b/src/durable-objects/hiro-api-do.ts @@ -1,6 +1,6 @@ import { DurableObject } from 'cloudflare:workers'; import { Env } from '../../worker-configuration'; -import { APP_CONFIG } from '../config'; +import { AppConfig } from '../config'; import { RateLimitedFetcher } from '../rate-limiter'; /** @@ -8,12 +8,12 @@ import { RateLimitedFetcher } from '../rate-limiter'; */ export class HiroApiDO extends DurableObject { // can override values here for all endpoints - private readonly CACHE_TTL: number = APP_CONFIG.CACHE_TTL; - private readonly MAX_REQUESTS_PER_MINUTE = APP_CONFIG.MAX_REQUESTS_PER_INTERVAL; - private readonly INTERVAL_MS = APP_CONFIG.INTERVAL_MS; - private readonly MAX_RETRIES = APP_CONFIG.MAX_RETRIES; - private readonly RETRY_DELAY = APP_CONFIG.RETRY_DELAY; - private readonly ALARM_INTERVAL_MS = APP_CONFIG.ALARM_INTERVAL_MS; + private readonly CACHE_TTL: number; + private readonly MAX_REQUESTS_PER_MINUTE: number; + private readonly INTERVAL_MS: number; + private readonly MAX_RETRIES: number; + private readonly RETRY_DELAY: number; + private readonly ALARM_INTERVAL_MS: number; // settings specific to this Durable Object private readonly BASE_API_URL: string = 'https://api.hiro.so'; private readonly BASE_PATH: string = '/hiro-api'; @@ -49,6 +49,18 @@ export class HiroApiDO extends DurableObject { super(ctx, env); this.ctx = ctx; this.env = env; + + // Initialize AppConfig with environment + const config = AppConfig.getInstance(env).getConfig(); + + // Set configuration values + this.CACHE_TTL = config.CACHE_TTL; + this.MAX_REQUESTS_PER_MINUTE = config.MAX_REQUESTS_PER_INTERVAL; + this.INTERVAL_MS = config.INTERVAL_MS; + this.MAX_RETRIES = config.MAX_RETRIES; + this.RETRY_DELAY = config.RETRY_DELAY; + this.ALARM_INTERVAL_MS = config.ALARM_INTERVAL_MS; + this.fetcher = new RateLimitedFetcher( this.env, this.BASE_API_URL, diff --git a/src/durable-objects/supabase-do.ts b/src/durable-objects/supabase-do.ts new file mode 100644 index 0000000..e51b83d --- /dev/null +++ b/src/durable-objects/supabase-do.ts @@ -0,0 +1,191 @@ +import { DurableObject } from 'cloudflare:workers'; +import { createClient, SupabaseClient } from '@supabase/supabase-js'; +import { Env } from '../../worker-configuration'; +import { AppConfig } from '../config'; + +interface StatsResponse { + total_jobs: number; + main_chat_jobs: number; + individual_crew_jobs: number; + top_profile_stacks_addresses: string[]; + top_crew_names: string[]; +} + +/** + * Durable Object class for Supabase queries + */ +export class SupabaseDO extends DurableObject { + private readonly CACHE_TTL: number; + private readonly ALARM_INTERVAL_MS = 60000; // 1 minute + private readonly BASE_PATH: string = '/supabase'; + private readonly CACHE_PREFIX: string = this.BASE_PATH.replaceAll('/', ''); + private readonly SUPPORTED_PATHS: string[] = ['/stats']; + private supabase: SupabaseClient; + + constructor(ctx: DurableObjectState, env: Env) { + super(ctx, env); + this.ctx = ctx; + this.env = env; + + // Initialize AppConfig with environment + const config = AppConfig.getInstance(env).getConfig(); + + // Set configuration values + this.CACHE_TTL = config.CACHE_TTL; + + // Initialize Supabase client with config values + this.supabase = createClient(env.SUPABASE_URL, env.SUPABASE_SERVICE_KEY, { + auth: { + persistSession: false + } + }); + + // Set up alarm to run at configured interval + ctx.storage.setAlarm(Date.now() + this.ALARM_INTERVAL_MS); + } + + private async fetchStats(): Promise { + try { + const { data, error } = await this.supabase + .rpc('get_stats', undefined, { + count: 'exact' + }) + .select('*') + .maybeSingle(); + + if (error) { + console.error('Error fetching stats:', error); + return undefined; + } + + if (!data) { + console.error('No stats data returned from database'); + return undefined; + } + + return data; + } catch (err) { + console.error('Exception in fetchStats:', err); + return undefined; + } + } + + async alarm(): Promise { + const startTime = Date.now(); + try { + console.log('Updating Supabase stats cache...'); + + const stats = await this.fetchStats(); + if (!stats) { + console.error('Failed to fetch stats from Supabase'); + return; + } + const data = JSON.stringify({ + timestamp: new Date().toISOString(), + ...stats, + }); + + const cacheKey = `${this.CACHE_PREFIX}_stats`; + await this.env.AIBTCDEV_CACHE_KV.put(cacheKey, data, { + expirationTtl: this.CACHE_TTL, + }); + + const endTime = Date.now(); + console.log(`supabase-do: alarm executed in ${endTime - startTime}ms`); + } catch (error) { + console.error(`Alarm execution failed: ${error instanceof Error ? error.message : String(error)}`); + } finally { + // Schedule next alarm + this.ctx.storage.setAlarm(Date.now() + this.ALARM_INTERVAL_MS); + } + } + + async fetch(request: Request): Promise { + const url = new URL(request.url); + const path = url.pathname; + + // Schedule next alarm if one isn't set + const currentAlarm = await this.ctx.storage.getAlarm(); + if (currentAlarm === null) { + this.ctx.storage.setAlarm(Date.now() + this.ALARM_INTERVAL_MS); + } + + // Handle requests that don't match the base path + if (!path.startsWith(this.BASE_PATH)) { + return new Response( + JSON.stringify({ + error: `Unrecognized path passed to SupabaseDO: ${path}`, + }), + { + status: 404, + headers: { 'Content-Type': 'application/json' }, + } + ); + } + + // Parse requested endpoint from base path + const endpoint = path.replace(this.BASE_PATH, ''); + + // Handle root route + if (endpoint === '' || endpoint === '/') { + return new Response( + JSON.stringify({ + message: `Welcome to the Supabase cache! Supported endpoints: ${this.SUPPORTED_PATHS.join(', ')}`, + }), + { + headers: { 'Content-Type': 'application/json' }, + } + ); + } + + // Handle /stats endpoint + if (endpoint === '/stats') { + const cacheKey = `${this.CACHE_PREFIX}_stats`; + const cached = await this.env.AIBTCDEV_CACHE_KV.get(cacheKey); + + if (cached) { + return new Response(cached, { + headers: { 'Content-Type': 'application/json' }, + }); + } + + const stats = await this.fetchStats(); + // verify that stats were fetched + if (!stats) { + return new Response( + JSON.stringify({ + error: 'Failed to fetch stats from Supabase', + }), + { + status: 500, + headers: { 'Content-Type': 'application/json' }, + } + ); + } + + // format the data, store it, and return it + const data = JSON.stringify({ + timestamp: new Date().toISOString(), + ...stats, + }); + await this.env.AIBTCDEV_CACHE_KV.put(cacheKey, data, { + expirationTtl: this.CACHE_TTL, + }); + + return new Response(data, { + headers: { 'Content-Type': 'application/json' }, + }); + } + + // Return 404 for any other endpoint + return new Response( + JSON.stringify({ + error: `Unrecognized endpoint: ${endpoint}. Supported endpoints: ${this.SUPPORTED_PATHS.join(', ')}`, + }), + { + status: 404, + headers: { 'Content-Type': 'application/json' }, + } + ); + } +} diff --git a/src/index.ts b/src/index.ts index 3e7ded1..fd3957b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,9 +1,10 @@ import { Env } from '../worker-configuration'; -import { APP_CONFIG } from './config'; +import { AppConfig } from './config'; import { HiroApiDO } from './durable-objects/hiro-api-do'; -export { HiroApiDO }; +import { SupabaseDO } from './durable-objects/supabase-do'; -const supportedServices = APP_CONFIG.SUPPORTED_SERVICES; +// export the Durable Object classes we're using +export { HiroApiDO, SupabaseDO }; export default { /** @@ -14,14 +15,16 @@ export default { * @param ctx - The execution context of the Worker * @returns The response to be sent back to the client */ - async fetch(request, env, ctx): Promise { + async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise { + // Initialize config with environment + const config = AppConfig.getInstance(env).getConfig(); const url = new URL(request.url); const path = url.pathname; if (path === '/') { return new Response( JSON.stringify({ - message: `Welcome to the aibtcdev-api-cache! Supported services: ${supportedServices.join(', ')}`, + message: `Welcome to the aibtcdev-api-cache! Supported services: ${config.SUPPORTED_SERVICES.join(', ')}`, }), { headers: { 'Content-Type': 'application/json' }, @@ -30,20 +33,21 @@ export default { } if (path.startsWith('/hiro-api')) { - // Create a DurableObjectId for our instance - let id: DurableObjectId = env.HIRO_API_DO.idFromName('hiro-api-do'); - - // Get the stub for communication - let stub = env.HIRO_API_DO.get(id); + const id: DurableObjectId = env.HIRO_API_DO.idFromName('hiro-api-do'); // create the instance + const stub = env.HIRO_API_DO.get(id); // get the stub for communication + return await stub.fetch(request); // forward the request to the Durable Object + } - // Forward the request to the Durable Object - return await stub.fetch(request); + if (path.startsWith('/supabase')) { + let id: DurableObjectId = env.SUPABASE_DO.idFromName('supabase-do'); // create the instance + let stub = env.SUPABASE_DO.get(id); // get the stub for communication + return await stub.fetch(request); // forward the request to the Durable Object } // Return 404 for any other path return new Response( JSON.stringify({ - error: `Invalid path: ${path}. Supported services: ${supportedServices.join(', ')}`, + error: `Invalid path: ${path}. Supported services: ${config.SUPPORTED_SERVICES.join(', ')}`, }), { status: 404, diff --git a/worker-configuration.d.ts b/worker-configuration.d.ts index fcea45e..c9c0556 100644 --- a/worker-configuration.d.ts +++ b/worker-configuration.d.ts @@ -2,6 +2,9 @@ export interface Env { AIBTCDEV_CACHE_KV: KVNamespace; - HIRO_API_DO: DurableObjectNamespace /* HiroApiDO */; HIRO_API_KEY: string; + SUPABASE_URL: string; + SUPABASE_SERVICE_KEY: string; + HIRO_API_DO: DurableObjectNamespace; + SUPABASE_DO: DurableObjectNamespace; } diff --git a/wrangler.toml b/wrangler.toml index 058d665..6b088a8 100644 --- a/wrangler.toml +++ b/wrangler.toml @@ -11,6 +11,11 @@ routes = [{ pattern = "cache.aibtc.dev", custom_domain = true }] [observability] enabled = true +[vars] +HIRO_API_KEY = "available on the Hiro platform https://platform.hiro.so" +SUPABASE_URL = "from Supabase project dashboard" +SUPABASE_SERVICE_KEY = "from Supabase project dashboard" + # Bind a Durable Object. Durable objects are a scale-to-zero compute primitive based on the actor model. # Durable Objects can live for as long as needed. Use these when you need a long-running "server", such as in realtime apps. # Docs: https://developers.cloudflare.com/workers/wrangler/configuration/#durable-objects @@ -18,11 +23,15 @@ enabled = true name = "HIRO_API_DO" class_name = "HiroApiDO" +[[durable_objects.bindings]] +name = "SUPABASE_DO" +class_name = "SupabaseDO" + # Durable Object migrations. # Docs: https://developers.cloudflare.com/workers/wrangler/configuration/#migrations [[migrations]] tag = "v1" -new_classes = ["HiroApiDO"] +new_classes = ["HiroApiDO", "SupabaseDO"] # Bind a KV Namespace. Use KV as persistent storage for small key-value pairs. # Docs: https://developers.cloudflare.com/workers/wrangler/configuration/#kv-namespaces