Repository navigation
Run Hazel on Cloudflare with Alchemy v2 (cluster stays on Railway) #326
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
04f8ea6
feat(infra): add alchemy v2 stack for existing Cloudflare apps
Makisuo a402ed3
refactor(backend): make the app layer runtime-agnostic
Makisuo 0d36886
feat(backend): run the api on a Cloudflare Worker
Makisuo 4b0e092
feat(cluster): guard the workflow API with a shared secret
Makisuo d5778a0
ci: deploy with alchemy (prd after CI, label-gated PR previews)
Makisuo 4742275
feat(infra): add HAZEL_DB Hyperdrive declaration
Makisuo 039db1c
feat(electric-proxy): run as a Cloudflare Worker with self-hosted Ele…
Makisuo f47022f
merge: electric-proxy Worker and self-hosted Electric container
Makisuo 9a25698
feat(bot-gateway): BotGateway Durable Object with a Worker relay and …
Makisuo 5eded15
merge: BotGateway Durable Object
Makisuo e418986
fix: address review findings on the Cloudflare migration
Makisuo f0a1011
ci: never cancel in-progress PR preview runs
Makisuo 0dca2ce
ci: keep pending PR preview runs queued
Makisuo File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| name: deploy setup | ||
| description: >- | ||
| Toolchain and dependencies for an alchemy deploy job. Secrets come from the job's GitHub | ||
| Environment (`production` or `pr-preview`) as env vars; alchemy reads them through Config | ||
| when it plans the stack (see apps/*/alchemy.run.ts and apps/backend/src/worker/env.ts). | ||
|
|
||
| runs: | ||
| using: composite | ||
| steps: | ||
| - name: Setup Bun | ||
| uses: oven-sh/setup-bun@v1 | ||
| with: | ||
| bun-version: 1.3.14 | ||
|
|
||
| - name: Cache dependencies | ||
| uses: actions/cache@v4 | ||
| with: | ||
| path: | | ||
| ~/.bun/install/cache | ||
| node_modules | ||
| */node_modules | ||
| key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lock') }} | ||
| restore-keys: | | ||
| ${{ runner.os }}-bun- | ||
|
|
||
| - name: Install dependencies | ||
| shell: bash | ||
| run: bun install --frozen-lockfile |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,83 @@ | ||
| name: Deploy PR Preview (Cloudflare via Alchemy) | ||
|
|
||
| # Label-gated: a PR gets its own stage (`pr-<n>`) while it carries `preview`, torn down when the | ||
| # label comes off or the PR closes. Previews share one preview database (`HAZEL_PG_URL`). | ||
| on: | ||
| pull_request: | ||
| types: [opened, reopened, synchronize, labeled, unlabeled, closed] | ||
|
|
||
| concurrency: | ||
| group: pr-preview-${{ github.event.pull_request.number }} | ||
| # Queue instead of cancelling: whether to cancel is decided by the *incoming* run, so any | ||
| # cancellation could cut short an in-progress teardown and leak the preview stage. `queue: max` | ||
| # keeps every pending run too (the default replaces a pending teardown with the next event). | ||
| cancel-in-progress: false | ||
| queue: max | ||
|
|
||
| permissions: | ||
| contents: read | ||
| pull-requests: write | ||
|
|
||
| jobs: | ||
| preview: | ||
| if: >- | ||
| ${{ github.event.pull_request.head.repo.full_name == github.repository | ||
| && ((github.event.action == 'unlabeled' && github.event.label.name == 'preview') | ||
| || (github.event.action != 'unlabeled' | ||
| && contains(github.event.pull_request.labels.*.name, 'preview'))) }} | ||
| runs-on: ubuntu-latest | ||
| timeout-minutes: 45 | ||
| environment: | ||
| name: pr-preview | ||
| url: ${{ steps.deploy.outputs.web_url }} | ||
| env: | ||
| # `unlabeled` only reaches here for the `preview` label, so both mean teardown. | ||
| TEARDOWN: ${{ (github.event.action == 'closed' || github.event.action == 'unlabeled') && 'true' || 'false' }} | ||
| PR_NUMBER: ${{ github.event.pull_request.number }} | ||
| COMMIT_SHA: ${{ github.event.pull_request.head.sha }} | ||
| CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} | ||
| CLOUDFLARE_ACCOUNT_ID: ${{ vars.CLOUDFLARE_ACCOUNT_ID }} | ||
| HAZEL_PG_URL: ${{ secrets.HAZEL_PG_URL }} | ||
| VITE_CLERK_PUBLISHABLE_KEY: ${{ vars.VITE_CLERK_PUBLISHABLE_KEY }} | ||
| CLERK_PUBLISHABLE_KEY: ${{ vars.CLERK_PUBLISHABLE_KEY }} | ||
| CLERK_SECRET_KEY: ${{ secrets.CLERK_SECRET_KEY }} | ||
| S3_BUCKET: ${{ vars.S3_BUCKET }} | ||
| S3_ENDPOINT: ${{ vars.S3_ENDPOINT }} | ||
| S3_PUBLIC_URL: ${{ vars.S3_PUBLIC_URL }} | ||
| S3_ACCESS_KEY_ID: ${{ secrets.S3_ACCESS_KEY_ID }} | ||
| S3_SECRET_ACCESS_KEY: ${{ secrets.S3_SECRET_ACCESS_KEY }} | ||
| CLUSTER_URL: ${{ vars.CLUSTER_URL }} | ||
| CLUSTER_API_SECRET: ${{ secrets.CLUSTER_API_SECRET }} | ||
| INTEGRATION_ENCRYPTION_KEY: ${{ secrets.INTEGRATION_ENCRYPTION_KEY }} | ||
| steps: | ||
| - name: Checkout | ||
| uses: actions/checkout@v4 | ||
|
|
||
| - name: Deploy setup | ||
| uses: ./.github/actions/deploy-env | ||
|
|
||
| - name: Deploy preview stage | ||
| id: deploy | ||
| if: ${{ env.TEARDOWN != 'true' }} | ||
| run: bun run alchemy:deploy:pr | ||
|
|
||
| - name: Destroy preview stage | ||
| if: ${{ env.TEARDOWN == 'true' }} | ||
| run: bun run alchemy:destroy:pr | ||
|
|
||
| - name: Comment the preview URLs | ||
| if: ${{ env.TEARDOWN != 'true' && steps.deploy.outcome == 'success' }} | ||
| env: | ||
| GH_TOKEN: ${{ github.token }} | ||
| WEB_URL: ${{ steps.deploy.outputs.web_url }} | ||
| API_URL: ${{ steps.deploy.outputs.api_url }} | ||
| run: | | ||
| marker="<!-- hazel-preview-comment -->" | ||
| body="$marker | ||
| **Preview** for \`${COMMIT_SHA:0:7}\`: app $WEB_URL · api $API_URL" | ||
| existing=$(gh api "repos/$GITHUB_REPOSITORY/issues/$PR_NUMBER/comments" --jq ".[] | select(.body | startswith(\"$marker\")) | .id" | head -1) | ||
| if [ -n "$existing" ]; then | ||
| gh api -X PATCH "repos/$GITHUB_REPOSITORY/issues/comments/$existing" -f body="$body" >/dev/null | ||
| else | ||
| gh pr comment "$PR_NUMBER" --body "$body" | ||
| fi | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,94 @@ | ||
| name: Deploy PRD (Cloudflare via Alchemy) | ||
|
|
||
| # Gated on CI passing on main (not `push: main`); `workflow_dispatch` skips the gate. | ||
| on: | ||
| workflow_run: | ||
| workflows: ["Test"] | ||
| types: [completed] | ||
| branches: [main] | ||
| workflow_dispatch: | ||
|
|
||
| concurrency: | ||
| group: deploy-prd | ||
| cancel-in-progress: false | ||
|
|
||
| permissions: | ||
| contents: read | ||
|
|
||
| jobs: | ||
| deploy: | ||
| # Only CI runs for pushes to this repo's main deploy; a PR's Test run must never reach prd. | ||
| if: >- | ||
| ${{ github.event_name == 'workflow_dispatch' | ||
| || (github.event.workflow_run.conclusion == 'success' | ||
| && github.event.workflow_run.event == 'push' | ||
| && github.event.workflow_run.head_repository.full_name == github.repository) }} | ||
| runs-on: ubuntu-latest | ||
| timeout-minutes: 45 | ||
| environment: | ||
| name: production | ||
| url: https://app.hazel.sh | ||
| env: | ||
| # On workflow_run, `github.sha` is the branch head, not the commit CI tested. | ||
| COMMIT_SHA: ${{ github.event.workflow_run.head_sha || github.sha }} | ||
| CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} | ||
| CLOUDFLARE_ACCOUNT_ID: ${{ vars.CLOUDFLARE_ACCOUNT_ID }} | ||
| PLANETSCALE_API_TOKEN_ID: ${{ secrets.PLANETSCALE_API_TOKEN_ID }} | ||
| PLANETSCALE_API_TOKEN: ${{ secrets.PLANETSCALE_API_TOKEN }} | ||
| PLANETSCALE_ORGANIZATION: ${{ vars.PLANETSCALE_ORGANIZATION }} | ||
| # Build inputs (web) | ||
| VITE_CLERK_PUBLISHABLE_KEY: ${{ vars.VITE_CLERK_PUBLISHABLE_KEY }} | ||
| VITE_PUBLIC_POSTHOG_KEY: ${{ vars.VITE_PUBLIC_POSTHOG_KEY }} | ||
| VITE_MAPLE_PUBLIC_KEY: ${{ vars.VITE_MAPLE_PUBLIC_KEY }} | ||
| # api Worker (apps/backend/src/worker/env.ts) | ||
| CLERK_PUBLISHABLE_KEY: ${{ vars.CLERK_PUBLISHABLE_KEY }} | ||
| CLERK_SECRET_KEY: ${{ secrets.CLERK_SECRET_KEY }} | ||
| CLERK_WEBHOOK_SECRET: ${{ secrets.CLERK_WEBHOOK_SECRET }} | ||
| COOKIE_DOMAIN: ${{ vars.COOKIE_DOMAIN }} | ||
| S3_BUCKET: ${{ vars.S3_BUCKET }} | ||
| S3_ENDPOINT: ${{ vars.S3_ENDPOINT }} | ||
| S3_PUBLIC_URL: ${{ vars.S3_PUBLIC_URL }} | ||
| S3_ACCESS_KEY_ID: ${{ secrets.S3_ACCESS_KEY_ID }} | ||
| S3_SECRET_ACCESS_KEY: ${{ secrets.S3_SECRET_ACCESS_KEY }} | ||
| CLUSTER_URL: ${{ vars.CLUSTER_URL }} | ||
| CLUSTER_API_SECRET: ${{ secrets.CLUSTER_API_SECRET }} | ||
| INTEGRATION_ENCRYPTION_KEY: ${{ secrets.INTEGRATION_ENCRYPTION_KEY }} | ||
| INTEGRATION_ENCRYPTION_KEY_VERSION: ${{ vars.INTEGRATION_ENCRYPTION_KEY_VERSION }} | ||
| INTEGRATION_ENCRYPTION_KEY_PREV: ${{ secrets.INTEGRATION_ENCRYPTION_KEY_PREV }} | ||
| INTEGRATION_ENCRYPTION_KEY_VERSION_PREV: ${{ vars.INTEGRATION_ENCRYPTION_KEY_VERSION_PREV }} | ||
| LINEAR_CLIENT_ID: ${{ vars.LINEAR_CLIENT_ID }} | ||
| LINEAR_CLIENT_SECRET: ${{ secrets.LINEAR_CLIENT_SECRET }} | ||
| DISCORD_CLIENT_ID: ${{ vars.DISCORD_CLIENT_ID }} | ||
| DISCORD_CLIENT_SECRET: ${{ secrets.DISCORD_CLIENT_SECRET }} | ||
| DISCORD_BOT_TOKEN: ${{ secrets.DISCORD_BOT_TOKEN }} | ||
| DISCORD_GATEWAY_ENABLED: ${{ vars.DISCORD_GATEWAY_ENABLED }} | ||
| GITHUB_APP_ID: ${{ vars.HAZEL_GITHUB_APP_ID }} | ||
| GITHUB_APP_SLUG: ${{ vars.HAZEL_GITHUB_APP_SLUG }} | ||
| GITHUB_APP_PRIVATE_KEY: ${{ secrets.HAZEL_GITHUB_APP_PRIVATE_KEY }} | ||
| GITHUB_WEBHOOK_SECRET: ${{ secrets.HAZEL_GITHUB_WEBHOOK_SECRET }} | ||
| INTERNAL_SECRET: ${{ secrets.INTERNAL_SECRET }} | ||
| KLIPY_API_KEY: ${{ secrets.KLIPY_API_KEY }} | ||
| # electric-proxy + self-hosted Electric (DATABASE_URL comes from the stack's replication role) | ||
| ELECTRIC_SECRET: ${{ secrets.ELECTRIC_SECRET }} | ||
| OTEL_BASE_URL: ${{ vars.OTEL_BASE_URL }} | ||
| MAPLE_INGEST_KEY: ${{ secrets.MAPLE_INGEST_KEY }} | ||
| steps: | ||
| - name: Checkout | ||
| uses: actions/checkout@v4 | ||
| with: | ||
| ref: ${{ env.COMMIT_SHA }} | ||
|
|
||
| - name: Deploy setup | ||
| uses: ./.github/actions/deploy-env | ||
|
|
||
| - name: Deploy with Alchemy | ||
| run: bun run alchemy:deploy:prd | ||
|
|
||
| - name: Check the api serves this revision | ||
| run: | | ||
| for attempt in 1 2 3 4 5 6; do | ||
| if curl -fsS https://api.hazel.sh/health >/dev/null; then exit 0; fi | ||
| sleep 10 | ||
| done | ||
| echo "::error::api.hazel.sh/health did not answer after the deploy" | ||
| exit 1 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -26,3 +26,4 @@ opensrc/ | |
|
|
||
| # Migration exports (contain PII — user emails, IDs, avatars) | ||
| apps/backend/exports/ | ||
| .alchemy/ | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,133 @@ | ||
| // The Hazel stack. Each app declares its resources in `apps/<app>/alchemy.run.ts`; this file | ||
| // builds the deploy context (`HazelStack`) and yields them in dependency order. | ||
| // Plan: infra/cloudflare-migration-plan.md | ||
| import { appendFileSync } from "node:fs" | ||
| import * as Alchemy from "alchemy" | ||
| import * as Cloudflare from "alchemy/Cloudflare" | ||
| import * as Planetscale from "alchemy/Planetscale" | ||
| import { ConfigError } from "effect/Config" | ||
| import { SourceError } from "effect/ConfigProvider" | ||
| import * as Effect from "effect/Effect" | ||
| import * as Layer from "effect/Layer" | ||
| import { | ||
| declareHazelDb, | ||
| formatHazelStage, | ||
| HazelStack, | ||
| type HazelStackContext, | ||
| parseHazelStageEffect, | ||
| resolveHazelDomains, | ||
| } from "@hazel/infra/cloudflare" | ||
| import { plainWithDefault } from "@hazel/infra/env" | ||
| import Actors from "./apps/actors/alchemy.run.ts" | ||
| import ApiLive, { Api } from "./apps/backend/src/worker.ts" | ||
| import BotGateway from "./apps/bot-gateway/alchemy.run.ts" | ||
| import Docs from "./apps/docs/alchemy.run.ts" | ||
| import ElectricProxy from "./apps/electric-proxy/alchemy.run.ts" | ||
| import Landing from "./apps/landing/alchemy.run.ts" | ||
| import LinkPreview from "./apps/link-preview-worker/alchemy.run.ts" | ||
| import Web from "./apps/web/alchemy.run.ts" | ||
|
|
||
| // Some secret stores define CLOUDFLARE_DEFAULT_ACCOUNT_ID; alchemy reads CLOUDFLARE_ACCOUNT_ID. | ||
| if (!process.env.CLOUDFLARE_ACCOUNT_ID && process.env.CLOUDFLARE_DEFAULT_ACCOUNT_ID) { | ||
| process.env.CLOUDFLARE_ACCOUNT_ID = process.env.CLOUDFLARE_DEFAULT_ACCOUNT_ID | ||
| } | ||
|
|
||
| /** `alchemy dev` sets ALCHEMY_DEV on its exec child. Not stage-derived: a dev stage can be deployed. */ | ||
| const isDevServer = process.env.ALCHEMY_DEV === "true" | ||
|
|
||
| // Inter-app URLs must be plain strings at plan time (`worker.url` is a lazy Output), so | ||
| // deployed stages use custom domains and dev stages fall back to env-supplied URLs. | ||
| const resolveUrl = (domain: string | undefined, envKey: string, fallback: string) => | ||
| domain | ||
| ? Effect.succeed(`https://${domain}`) | ||
| : Effect.map(plainWithDefault(envKey, fallback), (record) => record[envKey] ?? fallback) | ||
|
|
||
| const asConfigError = (error: { readonly message: string }) => | ||
| Effect.fail(new ConfigError(new SourceError({ message: error.message, cause: error }))) | ||
|
|
||
| /** Append `key=value` lines to the GitHub Actions step-output file, if any. */ | ||
| const appendStepOutputs = (lines: string[]): void => { | ||
| const file = process.env.GITHUB_OUTPUT | ||
| if (file) appendFileSync(file, `${lines.join("\n")}\n`) | ||
| } | ||
|
|
||
| const HazelStackLive = Layer.effect( | ||
| HazelStack, | ||
| Effect.gen(function* () { | ||
| const stage = yield* parseHazelStageEffect(yield* Alchemy.Stage) | ||
| const domains = resolveHazelDomains(stage) | ||
| const context: HazelStackContext = { | ||
| stage, | ||
| domains, | ||
| isDevServer, | ||
| db: yield* declareHazelDb(stage), | ||
| urls: { | ||
| web: yield* resolveUrl(domains.web, "HAZEL_WEB_URL", "http://localhost:3000"), | ||
| api: yield* resolveUrl(domains.api, "HAZEL_API_URL", "http://localhost:3003"), | ||
| electric: yield* resolveUrl(domains.electric, "HAZEL_ELECTRIC_URL", "http://localhost:8184"), | ||
| rivet: yield* resolveUrl(domains.rivet, "HAZEL_RIVET_URL", "http://localhost:6420"), | ||
| linkPreview: yield* resolveUrl( | ||
| domains.linkPreview, | ||
| "HAZEL_LINK_PREVIEW_URL", | ||
| "http://localhost:5471", | ||
| ), | ||
| }, | ||
| } | ||
| return context | ||
| }), | ||
| ) | ||
|
|
||
| export default Alchemy.Stack( | ||
| "hazel", | ||
| { | ||
| // PlanetScale's credential lookup runs when the layer is built; `alchemy dev` never needs it. | ||
| providers: isDevServer | ||
| ? Cloudflare.providers() | ||
| : Cloudflare.providers().pipe(Layer.provideMerge(Planetscale.providers())), | ||
| // ALCHEMY_LOCAL_STATE=1 uses .alchemy/ file state instead of the account-wide store. | ||
| state: process.env.ALCHEMY_LOCAL_STATE ? Alchemy.localState() : Cloudflare.state(), | ||
| }, | ||
| Effect.gen(function* () { | ||
| const { stage, domains, urls } = yield* HazelStack | ||
|
|
||
| // Before api, which binds its BotGateway Durable Objects cross-script. | ||
| const botGateway = yield* BotGateway | ||
| // The Live layer registers the api Worker's Durable Object classes in its bundle. | ||
| const api = yield* Effect.provide(Api, ApiLive) | ||
| // Also yields the `electric` Worker (self-hosted Electric in a Container) on deployed stages. | ||
| const electricProxy = yield* ElectricProxy | ||
| const linkPreview = yield* LinkPreview | ||
| const actors = yield* Actors | ||
| const web = yield* Web | ||
| // Shared marketing/docs sites: prd only (previews and dev run their own dev servers). | ||
| const landing = stage.kind === "prd" ? yield* Landing : undefined | ||
| const docs = stage.kind === "prd" ? yield* Docs : undefined | ||
|
|
||
| const summary = { | ||
| stage: formatHazelStage(stage), | ||
| webUrl: domains.web ? `https://${domains.web}` : "", | ||
| apiUrl: urls.api, | ||
| electricUrl: urls.electric, | ||
| } | ||
| yield* Effect.sync(() => | ||
| appendStepOutputs([`web_url=${summary.webUrl}`, `api_url=${summary.apiUrl}`]), | ||
| ) | ||
|
|
||
| return { | ||
| ...summary, | ||
| apiWorker: api.workerName, | ||
| botGatewayWorker: botGateway.workerName, | ||
| electricProxyWorker: electricProxy.workerName, | ||
| webWorker: web.workerName, | ||
| linkPreviewWorker: linkPreview.workerName, | ||
| actorsWorker: actors.workerName, | ||
| landingWorker: landing?.workerName, | ||
| docsWorker: docs?.workerName, | ||
| } | ||
| }).pipe( | ||
| // The stack IS the entry point: the one place `HazelStack` is provided. | ||
| Effect.provide(HazelStackLive), | ||
| // `Alchemy.Stack` admits only `ConfigError`. | ||
| Effect.catchTags({ "@hazel/infra/HazelStageError": asConfigError }), | ||
| ), | ||
| ) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| /** | ||
| * RivetKit actors: an async Worker whose `ActorHandler` Durable Object class comes from | ||
| * `@rivetkit/cloudflare-workers`. On the adopting deploy the binding must keep the existing | ||
| * `ACTOR_DO` -> `ActorHandler` mapping (alchemy matches the live class by binding name). | ||
| */ | ||
| import { HazelStack, hazelWorkerProps, stageProps } from "@hazel/infra/cloudflare" | ||
| import * as Cloudflare from "alchemy/Cloudflare" | ||
| import { Effect } from "effect" | ||
|
|
||
| export const ActorKv = Cloudflare.KV.Namespace( | ||
| "actor-kv", | ||
| stageProps("actor-kv", (name, stage) => ({ title: stage.kind === "prd" ? "hazel-actor-kv" : name })), | ||
| ) | ||
|
|
||
| export default Effect.gen(function* () { | ||
| const stack = yield* HazelStack | ||
| const actorKv = yield* ActorKv | ||
| return yield* Cloudflare.Worker("actors", { | ||
| ...hazelWorkerProps("actors", stack), | ||
| main: new URL("./src/index.ts", import.meta.url).pathname, | ||
| workersDev: stack.stage.kind !== "prd", | ||
| domain: stack.domains.rivet, | ||
| env: { | ||
| ACTOR_DO: Cloudflare.DurableObject("ACTOR_DO", { className: "ActorHandler" }), | ||
| ACTOR_KV: actorKv, | ||
| NODE_ENV: stack.stage.kind === "dev" ? "development" : "production", | ||
| RIVET_PUBLIC_ENDPOINT: stack.urls.rivet, | ||
| }, | ||
| observability: { | ||
| enabled: true, | ||
| headSamplingRate: 1, | ||
| logs: { | ||
| enabled: true, | ||
| headSamplingRate: 1, | ||
| persist: true, | ||
| invocationLogs: true, | ||
| destinations: ["maple-logs"], | ||
| }, | ||
| traces: { enabled: true, persist: true, headSamplingRate: 1, destinations: ["maple-traces"] }, | ||
| }, | ||
| }) | ||
| }) |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.