Skip to content

Make the server embeddable: ServerContext for multi-instance hosting#7

Open
yail259 wants to merge 3 commits into
mainfrom
cloud-embed
Open

Make the server embeddable: ServerContext for multi-instance hosting#7
yail259 wants to merge 3 commits into
mainfrom
cloud-embed

Conversation

@yail259

@yail259 yail259 commented Jun 12, 2026

Copy link
Copy Markdown
Member

What

Introduces ServerContext — the pool plus per-instance config (apiKey, webhookSecret, publicUrl, leaseMs) that previously lived in module-level env reads — and threads it through createApp(), reconcileTick(), dispatchRecovery(), and listEvents(). Adds src/lib.ts + package exports so @tidebase/server is consumable as a library.

Why

One process can now host many isolated Tidebase instances, one per database. This is the embedding contract the hosted tier (cloud gateway) builds on, and it's an OSS story on its own: embed the Tidebase server in your own Node app.

Compatibility

  • Default boot path (index.ts) and createApp() / createApp({ apiKey }) are behavior-identical for self-hosters; all 82 existing invariants pass unchanged.
  • migrate/pendingMigrations/tx/listEvents gained optional pool/target params with the previous singletons as defaults.

New invariants (embed.test.ts)

  • Runs in one context are invisible to another context in the same process.
  • API keys apply per context.
  • A reconciler tick acts only on its own context's database (tick lock is database-scoped).

Publish prep (not flipped in this PR)

files + prepack ship a package-local copy of migrations/; db.ts resolves it as a fallback. "private": true stays until we decide to publish.

🤖 Generated with Claude Code

… config

createContext() bundles the Postgres pool with the config that previously
lived in module-level env reads (apiKey, webhookSecret, publicUrl, leaseMs)
and is threaded through createApp(), reconcileTick(), dispatchRecovery(),
and listEvents(). One process can now host many isolated Tidebase instances
— one per database — which is the contract multi-tenant hosting builds on.

- src/lib.ts + package exports: consume @tidebase/server as a library
- default boot path (index.ts) and createApp()/createApp({apiKey}) are
  behavior-identical for self-hosters; all 82 existing invariants green
- new embed.test.ts invariants: cross-context run invisibility, per-context
  API keys, reconciler ticks scoped to their own database
- migrations dir resolves package-local as a fallback; prepack ships a copy
  so the package is publishable later

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3d61fa76c2

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/server/src/app.ts
const after = Number(c.req.query('after') ?? 0)
return streamSSE(c, async (stream) => {
for (const event of await listEvents(runId, after)) {
for (const event of await listEvents(runId, after, ctx.pool)) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Scope live SSE listeners to the server context

When hosting multiple contexts in one process, this only scopes the initial SSE replay to ctx.pool; the live subscribe(runId, ...) immediately below still uses the module-global listener map keyed only by runId. If two tenant databases contain the same run id (for example after restoring/seeded databases or direct inserts), an event appended in one context is broadcast to the other context's /runs/:runId/events stream, violating the isolation this change is adding. Key the subscription/notification path by context (or database) as well as run id.

Useful? React with 👍 / 👎.

yail259 and others added 2 commits June 13, 2026 19:50
Reality is ahead of the claim — the suite grew with session runs and the
embed contract; the number should not undersell it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`npx @tidebase/sdk signup` provisions Tidebase Cloud and writes
TIDEBASE_URL + TIDEBASE_API_KEY + TIDEBASE_WEBHOOK_SECRET into .env —
zero Docker, zero Postgres. GitHub device flow by default (agent-
friendly: relays a code, human approves once), `--email` for magic link.
Polls the gateway's start→poll endpoints (v1/signup/{github,email}/*),
degrades to self-host guidance if the cloud endpoint isn't reachable yet
(so it's honest in the pre-launch window, not a stack trace).

`init` now offers both paths — hosted via signup, or self-host — in the
injected AGENTS.md snippet and the next-steps output, closing the
zero-infra adoption loop the D12 experiment proved converts.

upsertEnv is unit-tested (idempotent merge, preserves other vars, no
prefix-clobber); end-to-end signup verified against a mock gateway.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant