diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml deleted file mode 100644 index bd234a747..000000000 --- a/.github/workflows/deploy.yml +++ /dev/null @@ -1,160 +0,0 @@ -name: Deploy ObjectOS to Cloudflare Containers - -# ───────────────────────────────────────────────────────────────────────── -# Build + push Docker image for the objectos runtime (single-project -# per-env containers fronting *.objectos.app), then `wrangler deploy` the -# matching Worker so the new image is pinned. -# -# NOTE: Cloud (cloud.objectos.app) deployment lives in the split private -# repo objectstack-ai/cloud and is NOT triggered from this workflow. -# -# Triggers: -# 1. Manual: Actions → "Deploy ObjectOS to Cloudflare Containers" → Run -# (optionally provide commit SHA; defaults to workflow ref). -# 2. Tag: push a tag matching `deploy-objectos-*`. The tag suffix is -# informational; the image tag = the resolved commit SHA -# (short, 8 chars) so a redeploy of the same SHA is idempotent. -# -# Image naming: -# registry.cloudflare.com//objectos: -# -# `apps/objectos/wrangler.toml` is rewritten in-place to pin -# `image = ":"` and committed back to `main` so the repo -# always tracks what is actually deployed. -# -# Required secrets (Repository → Settings → Secrets and variables → Actions): -# CLOUDFLARE_API_TOKEN — token with Containers:Edit + Workers:Edit perms -# CLOUDFLARE_ACCOUNT_ID — 2846eb40a60f4738e292b90dcd8cce10 -# ───────────────────────────────────────────────────────────────────────── - -on: - workflow_dispatch: - inputs: - ref: - description: 'Commit SHA or branch to deploy (default: workflow ref)' - required: false - default: '' - push: - tags: - - 'deploy-objectos-*' - -concurrency: - group: deploy-objectos-${{ github.ref_name }} - cancel-in-progress: false - -jobs: - plan: - runs-on: ubuntu-latest - outputs: - sha: ${{ steps.plan.outputs.sha }} - sha8: ${{ steps.plan.outputs.sha8 }} - steps: - - name: Checkout (resolve ref) - uses: actions/checkout@v5 - with: - ref: ${{ github.event.inputs.ref || github.ref }} - fetch-depth: 1 - - - id: plan - name: Resolve SHA - run: | - set -euo pipefail - SHA="$(git rev-parse HEAD)" - SHA8="$(git rev-parse --short=8 HEAD)" - echo "sha=$SHA" >> "$GITHUB_OUTPUT" - echo "sha8=$SHA8" >> "$GITHUB_OUTPUT" - echo "─────────────────────────────────" - echo "Trigger: ${{ github.event_name }}" - echo "SHA: $SHA" - echo "Image tag: $SHA8" - - objectos: - needs: plan - runs-on: ubuntu-latest - permissions: - contents: write - env: - ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} - IMAGE_NAME: objectos - SHA8: ${{ needs.plan.outputs.sha8 }} - steps: - - uses: actions/checkout@v5 - with: - ref: ${{ needs.plan.outputs.sha }} - fetch-depth: 0 - token: ${{ secrets.GITHUB_TOKEN }} - - - name: Set up pnpm - uses: pnpm/action-setup@v4 - - - name: Set up Node - uses: actions/setup-node@v4 - with: - node-version: 22 - cache: pnpm - - - name: Install workspace deps (for wrangler bundle) - run: pnpm install --frozen-lockfile --prefer-offline - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Build objectos image (local) - uses: docker/build-push-action@v6 - with: - context: . - file: apps/objectos/Dockerfile - platforms: linux/amd64 - load: true - tags: registry.cloudflare.com/${{ env.ACCOUNT_ID }}/${{ env.IMAGE_NAME }}:${{ env.SHA8 }} - cache-from: type=gha,scope=objectos - cache-to: type=gha,scope=objectos,mode=max - - - name: Push image via wrangler (handles CF registry auth) - env: - CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} - CLOUDFLARE_ACCOUNT_ID: ${{ env.ACCOUNT_ID }} - run: | - npx --yes wrangler@4 containers push \ - "registry.cloudflare.com/${ACCOUNT_ID}/${IMAGE_NAME}:${SHA8}" - - - name: Pin image tag in wrangler.toml - run: | - set -euo pipefail - NEW="registry.cloudflare.com/${ACCOUNT_ID}/${IMAGE_NAME}:${SHA8}" - sed -i -E "s#^image = .*${IMAGE_NAME}:.*#image = \"${NEW}\"#" apps/objectos/wrangler.toml - echo "Pinned to: $NEW" - grep '^image' apps/objectos/wrangler.toml - - - name: Wrangler deploy (objectos) - working-directory: apps/objectos - env: - CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} - CLOUDFLARE_ACCOUNT_ID: ${{ env.ACCOUNT_ID }} - run: npx --yes wrangler@4 deploy --config wrangler.toml - - - name: Commit pinned tag back to main - if: github.event_name == 'workflow_dispatch' || startsWith(github.ref, 'refs/tags/') - run: | - set -euo pipefail - if git diff --quiet apps/objectos/wrangler.toml; then - echo "No tag change to commit." - exit 0 - fi - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - git checkout -B deploy/objectos-${SHA8} - git add apps/objectos/wrangler.toml - git commit -m "chore(deploy): pin objectos image tag to ${SHA8} - - Auto-bumped by .github/workflows/deploy.yml after successful Cloudflare - Containers deploy of registry.cloudflare.com/${ACCOUNT_ID}/${IMAGE_NAME}:${SHA8}. - - Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>" - git fetch origin main - if git merge-base --is-ancestor origin/main HEAD; then - git push origin HEAD:main - else - git push origin HEAD - echo "::warning::Could not fast-forward main; pushed branch deploy/objectos-${SHA8} instead. Open a PR." - fi diff --git a/CHANGELOG.md b/CHANGELOG.md index c01ff8098..bf6bc3e4a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,12 @@ What this means for framework consumers: 56 files. Lives in `objectstack-ai/cloud` going forward. - **`apps/cloud`** — deleted from this repo. The reference cloud host now lives in `objectstack-ai/cloud/apps/cloud`. +- **`apps/objectos`** — deleted from this repo. The tenant runtime + (serving `*.objectos.app`) now lives in `objectstack-ai/cloud/apps/objectos`. + Production traffic continues uninterrupted on the same Cloudflare Worker + (atomic flip by overwriting the worker named `objectos` from the cloud + repo, which uses the same CF account). The framework `deploy.yml` + GitHub Actions workflow has been deleted along with it. - **`@objectstack/cli`** — no longer hard-depends on `@objectstack/service-cloud`. The `serve --mode=cloud` boot path keeps the existing optional dynamic `import('@objectstack/service-cloud')` @@ -29,10 +35,11 @@ What this means for framework consumers: distribution" hint when the package is absent. The ambient TypeScript stub (`packages/cli/src/types/service-cloud.d.ts`) is retained so the optional path still typechecks. -- **`apps/objectos`** — unchanged. The reference *tenant* runtime stays - in this repo and reaches the control plane over HTTP via - `OS_CLOUD_URL`. Defaults: `OS_CLOUD_URL=https://cloud.objectos.app` - in production, or `local` to disable cloud routing. +- **Root `pnpm dev` / `pnpm start` / `pnpm doctor` scripts removed** — + these were thin aliases for `pnpm --filter @objectstack/objectos …` + which no longer exists in this repo. Use the cloud repo for objectos + development, or `pnpm --filter @example/app-crm dev` for a local + reference runtime. - **Structural couplings remain `any`-typed.** `packages/runtime/`, `packages/rest/`, and `packages/adapters/hono/` previously documented service-cloud as the source of `KernelManager` / `EnvironmentDriverRegistry` diff --git a/CLAUDE.md b/CLAUDE.md index 88e837450..dd501e87e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -24,9 +24,6 @@ pnpm build # turbo run build --filter=!@objectstack/docs # Run all tests pnpm test # turbo run test -# Dev server -pnpm dev # runs @objectstack/objectos in dev mode - # Studio UI dev pnpm studio:dev # runs @objectstack/studio in dev mode @@ -93,8 +90,7 @@ objectstack-ai/framework/ │ ├── apps/ │ ├── studio/ # 🎨 Studio UI (React + Hono, web-based) -│ ├── docs/ # 📖 Documentation site (Fumadocs + Next.js) -│ └── objectos/ # 🚀 ObjectOS Runtime (multi-app orchestration) +│ └── docs/ # 📖 Documentation site (Fumadocs + Next.js) │ ├── examples/ # 📚 Reference implementations ├── skills/ # 🤖 AI skill definitions (for Claude Code, Copilot, Cursor) diff --git a/README.md b/README.md index e3102205e..140850dec 100644 --- a/README.md +++ b/README.md @@ -112,12 +112,9 @@ pnpm docs:dev | Script | Description | | :--- | :--- | | `pnpm build` | Build all packages (excludes docs) | -| `pnpm dev` | Run the reference `@objectstack/objectos` host in dev mode | -| `pnpm dev:cloud` | Run `@objectstack/cloud` (multi-project, control-plane mode) | | `pnpm dev:crm` | Run the CRM example end-to-end (`@example/app-crm`) | | `pnpm studio:start` | Start the prebuilt Studio IDE | | `pnpm test` | Run all tests (Turborepo) | -| `pnpm doctor` | Check environment health | | `pnpm setup` | Install dependencies and build the spec package | | `pnpm docs:dev` | Start the documentation site locally | | `pnpm docs:build` | Build documentation for production | @@ -235,7 +232,6 @@ Cloud, package registry, and project management subcommands (`os projects`, `os | [`@objectstack/cli`](packages/cli) | CLI binary (`os` / `objectstack`) — `init`, `dev`, `serve`, `studio`, `compile`, `validate`, `generate`, `lint`, `doctor` | | [`create-objectstack`](packages/create-objectstack) | Project scaffolder (`npx create-objectstack`) | | [`objectstack-vscode`](packages/vscode-objectstack) | VS Code extension — autocomplete, validation, diagnostics | -| [`@objectstack/objectos`](apps/objectos) | Reference host — local and self-contained ObjectOS runtime | | [`@objectstack/studio`](apps/studio) | Studio IDE — metadata explorer, schema inspector, AI assistant | | [`@objectstack/console`](apps/console) | Cloud console — org / project / branch management UI | | [`@objectstack/account`](apps/account) | Account & identity portal — sign in, organizations, connected apps | diff --git a/apps/objectos/.dockerignore b/apps/objectos/.dockerignore deleted file mode 100644 index f9cc25445..000000000 --- a/apps/objectos/.dockerignore +++ /dev/null @@ -1,48 +0,0 @@ -# Paths here are evaluated relative to the build context (repo root). -# Keep image slim by excluding every workspace member that apps/objectos -# does not depend on + anything that gets regenerated during build. - -# ─── Node / pnpm caches ────────────────────────────────────────────── -**/node_modules -**/.pnpm-store -**/.pnpm - -# ─── Build output that Docker should not carry from the host ──────── -**/dist -**/.turbo -**/.next -**/.vercel -**/.cache -**/build - -# ─── Version control / editor ──────────────────────────────────────── -.git -.gitignore -.github -.vscode -.idea - -# ─── Local env / secrets ───────────────────────────────────────────── -**/.env -**/.env.* -!**/.env.example - -# ─── Tests / scratch data ──────────────────────────────────────────── -**/coverage -**/*.log -**/.DS_Store -**/tmp -**/.objectstack -**/data - -# ─── Workspace members not needed by apps/objectos at runtime ───────── -apps/studio -apps/docs -content/docs -skills -docs - -# ─── Misc ──────────────────────────────────────────────────────────── -README.md -CHANGELOG.md -LICENSE diff --git a/apps/objectos/.env.cloudflare.example b/apps/objectos/.env.cloudflare.example deleted file mode 100644 index 591ea2cfb..000000000 --- a/apps/objectos/.env.cloudflare.example +++ /dev/null @@ -1,13 +0,0 @@ -# Cloudflare Containers deployment config — non-secret defaults. -# -# Copy to `.env.cloudflare` (gitignored) and fill in your account id. -# Secrets go in `.env.cloudflare.secrets` instead. - -# Required: Cloudflare account id (run `npx wrangler whoami` to find it) -CF_ACCOUNT_ID= - -# Optional overrides (defaults shown) -# CF_IMAGE_REGISTRY=registry.cloudflare.com/$CF_ACCOUNT_ID -# CF_IMAGE_NAME=objectos -# CF_IMAGE_TAG=$(git rev-parse --short HEAD) -# CF_PLATFORM=linux/amd64 diff --git a/apps/objectos/.env.cloudflare.secrets.example b/apps/objectos/.env.cloudflare.secrets.example deleted file mode 100644 index ee39720e9..000000000 --- a/apps/objectos/.env.cloudflare.secrets.example +++ /dev/null @@ -1,26 +0,0 @@ -# Cloudflare Worker secrets — copy to `.env.cloudflare.secrets` (gitignored) -# and fill in. `cf:secrets` will skip any unset key, so it is safe to share -# this file across both apps. - -# ── Required for both apps ───────────────────────────────────────────────── -# Remote libSQL/Turso URL — Cloudflare Containers' filesystem is ephemeral, -# do NOT use file:/data/... -OS_DATABASE_URL= -OS_DATABASE_AUTH_TOKEN= -# Cookie/session signing secret. Generate with: openssl rand -hex 32 -AUTH_SECRET= - -# ── Cloud-only (apps/cloud) ──────────────────────────────────────────────── -# Used by the project provisioning workflow to create per-project Turso DBs. -TURSO_API_TOKEN= -TURSO_ORG_NAME= - -# ── ObjectOS multi-project mode (apps/objectos) ──────────────────────────── -# Point at your apps/cloud Worker for multi-project mode. Leave unset for -# single-project local mode (you'll then need OS_DATABASE_URL above). -OS_CLOUD_URL= -OS_CLOUD_API_KEY= - -# ── Optional (both apps) ─────────────────────────────────────────────────── -# Set when binding a custom domain so cookies span subdomains. -# OS_COOKIE_DOMAIN=.your-domain.com diff --git a/apps/objectos/.env.example b/apps/objectos/.env.example deleted file mode 100644 index 029ba04cf..000000000 --- a/apps/objectos/.env.example +++ /dev/null @@ -1,23 +0,0 @@ -# Deployment mode (default: standalone) -# -# standalone → single project, no control plane (apps/objectos/.objectstack/data/app.db) -# cloud → multi-project + control plane (apps/objectos/.objectstack/data/control.db) -# -# Legacy alias: OS_MULTI_PROJECT=true (deprecated, removes in next major) -# OS_MODE=standalone - -# Control-plane / business database -# -# unset → file:./.objectstack/data/control.db (cloud) or app.db (standalone) -# file: → SQLite at that path -# libsql://host → libSQL / Turso -# http(s)://host → libSQL / sqld over HTTP -# -# OS_DATABASE_URL=libsql://your-database.turso.io -# OS_DATABASE_AUTH_TOKEN=your-auth-token-here - -# Turso Platform API (cloud mode only — per-project database provisioning) -# When set, new projects are provisioned as real Turso cloud databases. -# When unset, each project gets a local SQLite file under .objectstack/data/environments/. -TURSO_ORG_NAME=your-org-slug -TURSO_API_TOKEN=your-platform-api-token diff --git a/apps/objectos/.gitignore b/apps/objectos/.gitignore deleted file mode 100644 index caf526db9..000000000 --- a/apps/objectos/.gitignore +++ /dev/null @@ -1,27 +0,0 @@ -# Build artifacts -dist/ -.turbo/ -public/ - -# Bundled API handler (generated during Vercel build) -api/_handler.js -api/_handler.js.map -api/node_modules/ - -# Node modules -node_modules/ - -# Environment files -.env -.env.local -.env.*.local - -# OS files -.DS_Store -Thumbs.db -.vercel -.env*.local - -# Cloudflare Containers deploy config (real values, not examples) -.env.cloudflare -.env.cloudflare.secrets diff --git a/apps/objectos/.vercelignore b/apps/objectos/.vercelignore deleted file mode 100644 index b6b512ed9..000000000 --- a/apps/objectos/.vercelignore +++ /dev/null @@ -1,16 +0,0 @@ -# Ignore build artifacts -dist/ -.turbo/ - -# Ignore test files -test/ -*.test.ts -*.spec.ts - -# Ignore development-only files that are not required by the Vercel build -debug-registry.ts - -# Keep only the bundled API handler -!api/_handler.js -!api/_handler.js.map -!api/[[...route]].js diff --git a/apps/objectos/CHANGELOG.md b/apps/objectos/CHANGELOG.md deleted file mode 100644 index 10810dea3..000000000 --- a/apps/objectos/CHANGELOG.md +++ /dev/null @@ -1,1067 +0,0 @@ -# @objectstack/example-host - -## 4.0.5 - -### Patch Changes - -- Updated dependencies [15e0df6] - - @objectstack/spec@4.0.5 - - @objectstack/metadata@4.0.5 - - @objectstack/objectql@4.0.5 - - @objectstack/runtime@4.0.5 - - @objectstack/driver-memory@4.0.5 - - @objectstack/driver-sql@4.0.5 - - @objectstack/driver-turso@4.0.5 - - @objectstack/driver-mongodb@4.0.5 - - @objectstack/plugin-audit@4.0.5 - - @objectstack/plugin-auth@4.0.5 - - @objectstack/plugin-hono-server@4.0.5 - - @objectstack/plugin-security@4.0.5 - - @objectstack/hono@4.0.5 - - @objectstack/service-automation@4.0.5 - - @objectstack/service-analytics@4.0.5 - - @objectstack/service-feed@4.0.5 - - @objectstack/service-ai@4.0.5 - - @objectstack/service-cloud@4.0.5 - - @objectstack/service-package@4.0.5 - - @objectstack/service-tenant@4.0.5 - - @objectstack/example-crm@4.0.5 - - @example/app-todo@4.0.5 - -## Unreleased - -### Patch Changes - -- **Fixed missing `Access-Control-Allow-Origin` header on Vercel deployment.** Two code paths in `server/index.ts` previously returned raw `Response` objects that bypassed the Hono app (and therefore the CORS middleware registered inside `createHonoApp`): - - `OPTIONS` preflight requests went through full kernel bootstrap, which is slow and can fail on cold start — causing the browser to see a preflight with no CORS headers and block every subsequent `/api/v1/*` GET. - - Bootstrap-failure `503` responses had no CORS headers at all, surfacing in the browser as a generic "missing Access-Control-Allow-Origin" error instead of the real status code. - - Fix: `OPTIONS` is now short-circuited **before** `ensureApp()` with a proper CORS preflight response; the `503` bootstrap-failure response is now wrapped with the same CORS headers via `withCorsHeaders()`. Both helpers honour the same `CORS_ENABLED` / `CORS_ORIGIN` / `CORS_CREDENTIALS` / `CORS_MAX_AGE` env vars as the Hono adapter, so behaviour is identical whether or not the kernel has finished booting. -- **Unified Studio mount path to `/_studio/` for all deployments** (CLI embedded, Vercel, self-host). - - `vercel.json`: studio SPA now serves under `/_studio/:path*` with a dedicated rewrite to `/_studio/index.html`. Root `/` and bare `/_studio` redirect to `/_studio/`. Asset caching headers scoped to `/_studio/assets/*`. `VITE_BASE=/_studio/` is set in `build.env`. - - `scripts/build-vercel.sh`: studio dist is copied to `public/_studio/` (previously `public/`), so Vercel serves it under the same sub-path the CLI uses. This resolves the deep-link / sidebar-click routing failures that occurred when the Studio was mounted at the public root. - -## 4.0.4 - -### Patch Changes - -- Updated dependencies [326b66b] - - @objectstack/spec@4.0.4 - - @example/app-crm@4.0.4 - - @example/app-todo@4.0.4 - - @example/plugin-bi@4.0.4 - - @objectstack/metadata@4.0.4 - - @objectstack/objectql@4.0.4 - - @objectstack/driver-memory@4.0.4 - - @objectstack/driver-turso@4.0.4 - - @objectstack/plugin-audit@4.0.4 - - @objectstack/plugin-auth@4.0.4 - - @objectstack/plugin-hono-server@4.0.4 - - @objectstack/plugin-security@4.0.4 - - @objectstack/plugin-setup@4.0.4 - - @objectstack/runtime@4.0.4 - - @objectstack/service-ai@4.0.4 - - @objectstack/service-analytics@4.0.4 - - @objectstack/service-automation@4.0.4 - - @objectstack/service-feed@4.0.4 - - @objectstack/hono@4.0.4 - -## 4.0.3 - -### Patch Changes - -- @example/app-crm@4.0.3 -- @example/app-todo@4.0.3 -- @objectstack/plugin-auth@4.0.3 -- @objectstack/spec@4.0.3 -- @objectstack/metadata@4.0.3 -- @objectstack/objectql@4.0.3 -- @objectstack/runtime@4.0.3 -- @objectstack/driver-memory@4.0.3 -- @objectstack/plugin-hono-server@4.0.3 -- @example/plugin-bi@4.0.3 - -## 4.0.2 - -### Patch Changes - -- Updated dependencies [5f659e9] - - @objectstack/plugin-hono-server@4.0.2 - - @objectstack/driver-memory@4.0.2 - - @objectstack/spec@4.0.2 - - @example/app-todo@4.0.2 - - @example/app-crm@4.0.2 - - @example/plugin-bi@4.0.2 - - @objectstack/metadata@4.0.2 - - @objectstack/objectql@4.0.2 - - @objectstack/plugin-auth@4.0.2 - - @objectstack/runtime@4.0.2 - -## 3.0.26 - -### Patch Changes - -- Updated dependencies [f08ffc3] -- Updated dependencies [e0b0a78] - - @objectstack/spec@4.0.0 - - @objectstack/runtime@4.0.0 - - @objectstack/objectql@4.0.0 - - @objectstack/plugin-auth@4.0.0 - - @example/app-crm@3.0.26 - - @example/app-todo@3.0.26 - - @example/plugin-bi@3.0.26 - - @objectstack/metadata@4.0.0 - - @objectstack/driver-memory@4.0.0 - - @objectstack/plugin-hono-server@4.0.0 - -## 3.0.25 - -### Patch Changes - -- @objectstack/spec@3.3.1 -- @objectstack/metadata@3.3.1 -- @objectstack/objectql@3.3.1 -- @objectstack/runtime@3.3.1 -- @objectstack/driver-memory@3.3.1 -- @objectstack/plugin-auth@3.3.1 -- @objectstack/plugin-hono-server@3.3.1 -- @example/app-crm@3.0.25 -- @example/app-todo@3.0.25 -- @example/plugin-bi@3.0.25 - -## 3.0.24 - -### Patch Changes - -- Updated dependencies [814a6c4] - - @objectstack/plugin-auth@3.3.0 - - @objectstack/spec@3.3.0 - - @objectstack/metadata@3.3.0 - - @objectstack/objectql@3.3.0 - - @objectstack/runtime@3.3.0 - - @objectstack/driver-memory@3.3.0 - - @objectstack/plugin-hono-server@3.3.0 - - @example/app-crm@3.0.24 - - @example/app-todo@3.0.24 - - @example/plugin-bi@3.0.24 - -## 3.0.23 - -### Patch Changes - -- Updated dependencies [0bc7b0c] -- Updated dependencies [c3065dd] - - @objectstack/plugin-hono-server@3.2.9 - - @objectstack/objectql@3.2.9 - - @example/app-crm@3.0.23 - - @example/app-todo@3.0.23 - - @objectstack/plugin-auth@3.2.9 - - @objectstack/spec@3.2.9 - - @objectstack/metadata@3.2.9 - - @objectstack/runtime@3.2.9 - - @objectstack/driver-memory@3.2.9 - - @example/plugin-bi@3.0.23 - -## 3.0.22 - -### Patch Changes - -- Updated dependencies [1fe5612] - - @objectstack/plugin-auth@3.2.8 - - @objectstack/spec@3.2.8 - - @objectstack/metadata@3.2.8 - - @objectstack/objectql@3.2.8 - - @objectstack/runtime@3.2.8 - - @objectstack/driver-memory@3.2.8 - - @objectstack/plugin-hono-server@3.2.8 - - @example/app-crm@3.0.22 - - @example/app-todo@3.0.22 - - @example/plugin-bi@3.0.22 - -## 3.0.21 - -### Patch Changes - -- Updated dependencies [35a1ebb] - - @objectstack/plugin-auth@3.2.7 - - @objectstack/spec@3.2.7 - - @objectstack/metadata@3.2.7 - - @objectstack/objectql@3.2.7 - - @objectstack/runtime@3.2.7 - - @objectstack/driver-memory@3.2.7 - - @objectstack/plugin-hono-server@3.2.7 - - @example/app-crm@3.0.21 - - @example/app-todo@3.0.21 - - @example/plugin-bi@3.0.21 - -## 3.0.20 - -### Patch Changes - -- @objectstack/spec@3.2.6 -- @objectstack/metadata@3.2.6 -- @objectstack/objectql@3.2.6 -- @objectstack/runtime@3.2.6 -- @objectstack/driver-memory@3.2.6 -- @objectstack/plugin-hono-server@3.2.6 -- @example/app-crm@3.0.20 -- @example/app-todo@3.0.20 -- @example/plugin-bi@3.0.20 - -## 3.0.19 - -### Patch Changes - -- @objectstack/spec@3.2.5 -- @objectstack/metadata@3.2.5 -- @objectstack/objectql@3.2.5 -- @objectstack/runtime@3.2.5 -- @objectstack/driver-memory@3.2.5 -- @objectstack/plugin-hono-server@3.2.5 -- @example/app-crm@3.0.19 -- @example/app-todo@3.0.19 -- @example/plugin-bi@3.0.19 - -## 3.0.18 - -### Patch Changes - -- @objectstack/spec@3.2.4 -- @objectstack/metadata@3.2.4 -- @objectstack/objectql@3.2.4 -- @objectstack/runtime@3.2.4 -- @objectstack/driver-memory@3.2.4 -- @objectstack/plugin-hono-server@3.2.4 -- @example/app-crm@3.0.18 -- @example/app-todo@3.0.18 -- @example/plugin-bi@3.0.18 - -## 3.0.17 - -### Patch Changes - -- @objectstack/spec@3.2.3 -- @objectstack/metadata@3.2.3 -- @objectstack/objectql@3.2.3 -- @objectstack/runtime@3.2.3 -- @objectstack/driver-memory@3.2.3 -- @objectstack/plugin-hono-server@3.2.3 -- @example/app-crm@3.0.17 -- @example/app-todo@3.0.17 -- @example/plugin-bi@3.0.17 - -## 3.0.16 - -### Patch Changes - -- Updated dependencies [46defbb] - - @objectstack/spec@3.2.2 - - @objectstack/driver-memory@3.2.2 - - @example/app-crm@3.0.16 - - @example/app-todo@3.0.16 - - @example/plugin-bi@3.0.16 - - @objectstack/metadata@3.2.2 - - @objectstack/objectql@3.2.2 - - @objectstack/plugin-hono-server@3.2.2 - - @objectstack/runtime@3.2.2 - -## 3.0.15 - -### Patch Changes - -- Updated dependencies [850b546] - - @objectstack/spec@3.2.1 - - @example/app-crm@3.0.15 - - @example/app-todo@3.0.15 - - @example/plugin-bi@3.0.15 - - @objectstack/metadata@3.2.1 - - @objectstack/objectql@3.2.1 - - @objectstack/driver-memory@3.2.1 - - @objectstack/plugin-hono-server@3.2.1 - - @objectstack/runtime@3.2.1 - -## 3.0.14 - -### Patch Changes - -- Updated dependencies [5901c29] - - @objectstack/spec@3.2.0 - - @example/app-crm@3.0.14 - - @example/app-todo@3.0.14 - - @example/plugin-bi@3.0.14 - - @objectstack/metadata@3.2.0 - - @objectstack/objectql@3.2.0 - - @objectstack/driver-memory@3.2.0 - - @objectstack/plugin-hono-server@3.2.0 - - @objectstack/runtime@3.2.0 - -## 3.0.13 - -### Patch Changes - -- Updated dependencies [953d667] - - @objectstack/spec@3.1.1 - - @example/app-crm@3.0.13 - - @example/app-todo@3.0.13 - - @example/plugin-bi@3.0.13 - - @objectstack/metadata@3.1.1 - - @objectstack/objectql@3.1.1 - - @objectstack/driver-memory@3.1.1 - - @objectstack/plugin-hono-server@3.1.1 - - @objectstack/runtime@3.1.1 - -## 3.0.12 - -### Patch Changes - -- Updated dependencies [0088830] - - @objectstack/spec@3.1.0 - - @example/app-crm@3.0.12 - - @example/app-todo@3.0.12 - - @example/plugin-bi@3.0.12 - - @objectstack/metadata@3.1.0 - - @objectstack/objectql@3.1.0 - - @objectstack/driver-memory@3.1.0 - - @objectstack/plugin-hono-server@3.1.0 - - @objectstack/runtime@3.1.0 - -## 3.0.11 - -### Patch Changes - -- Updated dependencies [92d9d99] - - @objectstack/spec@3.0.11 - - @example/app-crm@3.0.11 - - @example/app-todo@3.0.11 - - @example/plugin-bi@3.0.11 - - @objectstack/metadata@3.0.11 - - @objectstack/objectql@3.0.11 - - @objectstack/driver-memory@3.0.11 - - @objectstack/plugin-hono-server@3.0.11 - - @objectstack/runtime@3.0.11 - -## 3.0.10 - -### Patch Changes - -- Updated dependencies [d1e5d31] - - @objectstack/spec@3.0.10 - - @example/app-crm@3.0.10 - - @example/app-todo@3.0.10 - - @example/plugin-bi@3.0.10 - - @objectstack/metadata@3.0.10 - - @objectstack/objectql@3.0.10 - - @objectstack/driver-memory@3.0.10 - - @objectstack/plugin-hono-server@3.0.10 - - @objectstack/runtime@3.0.10 - -## 3.0.9 - -### Patch Changes - -- Updated dependencies [15e0df6] - - @objectstack/spec@3.0.9 - - @example/app-crm@3.0.9 - - @example/app-todo@3.0.9 - - @example/plugin-bi@3.0.9 - - @objectstack/metadata@3.0.9 - - @objectstack/objectql@3.0.9 - - @objectstack/driver-memory@3.0.9 - - @objectstack/plugin-hono-server@3.0.9 - - @objectstack/runtime@3.0.9 - -## 3.0.8 - -### Patch Changes - -- Updated dependencies [5a968a2] - - @objectstack/spec@3.0.8 - - @example/app-crm@3.0.8 - - @example/app-todo@3.0.8 - - @example/plugin-bi@3.0.8 - - @objectstack/metadata@3.0.8 - - @objectstack/objectql@3.0.8 - - @objectstack/driver-memory@3.0.8 - - @objectstack/plugin-hono-server@3.0.8 - - @objectstack/runtime@3.0.8 - -## 1.2.16 - -### Patch Changes - -- Updated dependencies [0119bd7] -- Updated dependencies [5426bdf] - - @objectstack/spec@3.0.7 - - @example/app-crm@1.2.16 - - @example/app-todo@1.2.16 - - @example/plugin-bi@1.2.16 - - @objectstack/metadata@3.0.7 - - @objectstack/objectql@3.0.7 - - @objectstack/driver-memory@3.0.7 - - @objectstack/plugin-hono-server@3.0.7 - - @objectstack/runtime@3.0.7 - -## 1.2.15 - -### Patch Changes - -- Updated dependencies [5df254c] - - @objectstack/spec@3.0.6 - - @example/app-crm@1.2.15 - - @example/app-todo@1.2.15 - - @example/plugin-bi@1.2.15 - - @objectstack/metadata@3.0.6 - - @objectstack/objectql@3.0.6 - - @objectstack/driver-memory@3.0.6 - - @objectstack/plugin-hono-server@3.0.6 - - @objectstack/runtime@3.0.6 - -## 1.2.14 - -### Patch Changes - -- Updated dependencies [23a4a68] - - @objectstack/spec@3.0.5 - - @example/app-crm@1.2.14 - - @example/app-todo@1.2.14 - - @example/plugin-bi@1.2.14 - - @objectstack/metadata@3.0.5 - - @objectstack/objectql@3.0.5 - - @objectstack/driver-memory@3.0.5 - - @objectstack/plugin-hono-server@3.0.5 - - @objectstack/runtime@3.0.5 - -## 1.2.13 - -### Patch Changes - -- Updated dependencies [d738987] -- Updated dependencies [437b0b8] - - @objectstack/spec@3.0.4 - - @objectstack/objectql@3.0.4 - - @example/app-crm@1.2.13 - - @example/app-todo@1.2.13 - - @example/plugin-bi@1.2.13 - - @objectstack/metadata@3.0.4 - - @objectstack/driver-memory@3.0.4 - - @objectstack/plugin-hono-server@3.0.4 - - @objectstack/runtime@3.0.4 - -## 1.2.12 - -### Patch Changes - -- Updated dependencies [c7267f6] - - @objectstack/spec@3.0.3 - - @objectstack/metadata@3.0.3 - - @objectstack/objectql@3.0.3 - - @objectstack/runtime@3.0.3 - - @objectstack/driver-memory@3.0.3 - - @objectstack/plugin-hono-server@3.0.3 - - @example/app-crm@1.2.12 - - @example/app-todo@1.2.12 - - @example/plugin-bi@1.2.12 - -## 1.2.11 - -### Patch Changes - -- Updated dependencies [28985f5] - - @objectstack/spec@3.0.2 - - @example/app-crm@1.2.11 - - @example/app-todo@1.2.11 - - @example/plugin-bi@1.2.11 - - @objectstack/metadata@3.0.2 - - @objectstack/objectql@3.0.2 - - @objectstack/driver-memory@3.0.2 - - @objectstack/plugin-hono-server@3.0.2 - - @objectstack/runtime@3.0.2 - -## 1.2.10 - -### Patch Changes - -- Updated dependencies [389725a] - - @objectstack/spec@3.0.1 - - @example/app-crm@1.2.10 - - @example/app-todo@1.2.10 - - @example/plugin-bi@1.2.10 - - @objectstack/metadata@3.0.1 - - @objectstack/objectql@3.0.1 - - @objectstack/driver-memory@3.0.1 - - @objectstack/plugin-hono-server@3.0.1 - - @objectstack/runtime@3.0.1 - -## 1.2.9 - -### Patch Changes - -- Updated dependencies - - @objectstack/spec@3.0.0 - - @objectstack/metadata@3.0.0 - - @objectstack/objectql@3.0.0 - - @objectstack/runtime@3.0.0 - - @objectstack/driver-memory@3.0.0 - - @objectstack/plugin-hono-server@3.0.0 - - @example/app-crm@1.2.9 - - @example/app-todo@1.2.9 - - @example/plugin-bi@1.2.9 - -## 1.2.8 - -### Patch Changes - -- Updated dependencies - - @objectstack/spec@2.0.7 - - @example/app-crm@1.2.8 - - @example/app-todo@1.2.8 - - @example/plugin-bi@1.2.8 - - @objectstack/metadata@2.0.7 - - @objectstack/objectql@2.0.7 - - @objectstack/driver-memory@2.0.7 - - @objectstack/plugin-hono-server@2.0.7 - - @objectstack/runtime@2.0.7 - -## 1.2.7 - -### Patch Changes - -- Updated dependencies - - @objectstack/spec@2.0.6 - - @objectstack/metadata@2.0.6 - - @objectstack/objectql@2.0.6 - - @objectstack/runtime@2.0.6 - - @objectstack/driver-memory@2.0.6 - - @objectstack/plugin-hono-server@2.0.6 - - @example/app-crm@1.2.7 - - @example/app-todo@1.2.7 - - @example/plugin-bi@1.2.7 - -## 1.2.6 - -### Patch Changes - -- Updated dependencies - - @objectstack/spec@2.0.5 - - @example/app-crm@1.2.6 - - @example/app-todo@1.2.6 - - @example/plugin-bi@1.2.6 - - @objectstack/metadata@2.0.5 - - @objectstack/objectql@2.0.5 - - @objectstack/driver-memory@2.0.5 - - @objectstack/plugin-hono-server@2.0.5 - - @objectstack/runtime@2.0.5 - -## 1.2.5 - -### Patch Changes - -- Updated dependencies - - @objectstack/spec@2.0.4 - - @objectstack/metadata@2.0.4 - - @objectstack/objectql@2.0.4 - - @objectstack/runtime@2.0.4 - - @objectstack/driver-memory@2.0.4 - - @objectstack/plugin-hono-server@2.0.4 - - @example/app-crm@1.2.5 - - @example/app-todo@1.2.5 - - @example/plugin-bi@1.2.5 - -## 1.2.4 - -### Patch Changes - -- Updated dependencies - - @objectstack/spec@2.0.3 - - @objectstack/metadata@2.0.3 - - @objectstack/objectql@2.0.3 - - @objectstack/runtime@2.0.3 - - @objectstack/driver-memory@2.0.3 - - @objectstack/plugin-hono-server@2.0.3 - - @example/app-crm@1.2.4 - - @example/app-todo@1.2.4 - - @example/plugin-bi@1.2.4 - -## 1.2.3 - -### Patch Changes - -- Updated dependencies [1db8559] - - @objectstack/spec@2.0.2 - - @example/app-crm@1.2.3 - - @example/app-todo@1.2.3 - - @example/plugin-bi@1.2.3 - - @objectstack/metadata@2.0.2 - - @objectstack/objectql@2.0.2 - - @objectstack/driver-memory@2.0.2 - - @objectstack/plugin-hono-server@2.0.2 - - @objectstack/runtime@2.0.2 - -## 1.2.2 - -### Patch Changes - -- Updated dependencies - - @objectstack/spec@2.0.1 - - @objectstack/metadata@2.0.1 - - @objectstack/objectql@2.0.1 - - @objectstack/runtime@2.0.1 - - @objectstack/driver-memory@2.0.1 - - @objectstack/plugin-hono-server@2.0.1 - - @example/app-crm@1.2.2 - - @example/app-todo@1.2.2 - - @example/plugin-bi@1.2.2 - -## 1.2.1 - -### Patch Changes - -- Updated dependencies [38e5dd5] -- Updated dependencies [38e5dd5] - - @objectstack/spec@2.0.0 - - @example/app-crm@1.2.1 - - @example/app-todo@1.2.1 - - @example/plugin-bi@1.2.1 - - @objectstack/metadata@2.0.0 - - @objectstack/objectql@2.0.0 - - @objectstack/driver-memory@2.0.0 - - @objectstack/plugin-hono-server@2.0.0 - - @objectstack/runtime@2.0.0 - -## 0.9.15 - -### Patch Changes - -- Updated dependencies - - @objectstack/spec@1.0.12 - - @objectstack/runtime@1.0.12 - - @example/app-crm@0.9.15 - - @example/app-todo@0.9.15 - - @example/plugin-bi@1.0.1 - - @objectstack/metadata@1.0.12 - - @objectstack/objectql@1.0.12 - - @objectstack/driver-memory@1.0.12 - - @objectstack/plugin-hono-server@1.0.12 - -## 0.9.14 - -### Patch Changes - -- @objectstack/spec@1.0.11 -- @objectstack/metadata@1.0.11 -- @objectstack/objectql@1.0.11 -- @objectstack/runtime@1.0.11 -- @objectstack/driver-memory@1.0.11 -- @objectstack/plugin-hono-server@1.0.11 -- @example/app-todo@0.9.14 -- @example/app-crm@0.9.14 -- @example/plugin-bi@0.9.14 - -## 0.9.13 - -### Patch Changes - -- @objectstack/metadata@1.0.10 -- @objectstack/objectql@1.0.10 -- @objectstack/driver-memory@1.0.10 -- @objectstack/plugin-hono-server@1.0.10 -- @objectstack/runtime@1.0.10 -- @example/app-crm@0.9.13 -- @example/app-todo@0.9.13 -- @example/plugin-bi@0.9.13 -- @objectstack/spec@1.0.10 - -## 0.9.12 - -### Patch Changes - -- Updated dependencies [b9f8c68] - - @objectstack/objectql@1.0.9 - - @objectstack/spec@1.0.9 - - @objectstack/metadata@1.0.9 - - @objectstack/runtime@1.0.9 - - @objectstack/driver-memory@1.0.9 - - @objectstack/plugin-hono-server@1.0.9 - - @example/app-todo@0.9.12 - - @example/app-crm@0.9.12 - - @example/plugin-bi@0.9.12 - -## 0.9.11 - -### Patch Changes - -- Updated dependencies [8f2a3a2] - - @objectstack/plugin-hono-server@1.0.8 - - @example/app-crm@0.9.11 - - @example/app-todo@0.9.11 - - @example/plugin-bi@0.9.11 - - @objectstack/spec@1.0.8 - - @objectstack/metadata@1.0.8 - - @objectstack/objectql@1.0.8 - - @objectstack/runtime@1.0.8 - - @objectstack/driver-memory@1.0.8 - -## 0.9.10 - -### Patch Changes - -- Updated dependencies [ebdf787] - - @objectstack/runtime@1.0.7 - - @objectstack/plugin-hono-server@1.0.7 - - @example/app-todo@0.9.10 - - @example/app-crm@0.9.10 - - @example/plugin-bi@0.9.10 - - @objectstack/spec@1.0.7 - - @objectstack/metadata@1.0.7 - - @objectstack/objectql@1.0.7 - - @objectstack/driver-memory@1.0.7 - -## 0.9.9 - -### Patch Changes - -- Updated dependencies [a7f7b9d] - - @objectstack/spec@1.0.6 - - @example/app-crm@0.9.9 - - @example/app-todo@0.9.9 - - @example/plugin-bi@0.9.9 - - @objectstack/metadata@1.0.6 - - @objectstack/objectql@1.0.6 - - @objectstack/driver-memory@1.0.6 - - @objectstack/plugin-hono-server@1.0.6 - - @objectstack/runtime@1.0.6 - -## 0.9.8 - -### Patch Changes - -- Updated dependencies [b1d24bd] -- Updated dependencies [877b864] - - @objectstack/objectql@1.0.5 - - @objectstack/runtime@1.0.5 - - @objectstack/plugin-hono-server@1.0.5 - - @objectstack/driver-memory@1.0.5 - - @objectstack/metadata@1.0.5 - - @objectstack/spec@1.0.5 - - @example/app-todo@0.9.8 - - @example/app-crm@0.9.8 - - @example/plugin-bi@0.9.8 - -## 0.9.7 - -### Patch Changes - -- Updated dependencies [5d13533] - - @objectstack/plugin-hono-server@1.0.4 - - @objectstack/objectql@1.0.4 - - @example/app-crm@0.9.7 - - @example/app-todo@0.9.7 - - @example/plugin-bi@0.9.7 - - @objectstack/spec@1.0.4 - - @objectstack/metadata@1.0.4 - - @objectstack/runtime@1.0.4 - - @objectstack/driver-memory@1.0.4 - -## 0.9.6 - -### Patch Changes - -- Updated dependencies [fb2eabd] -- Updated dependencies [22a48f0] - - @objectstack/runtime@1.0.3 - - @objectstack/plugin-hono-server@1.0.3 - - @objectstack/objectql@1.0.3 - - @objectstack/metadata@1.0.3 - - @objectstack/driver-memory@1.0.3 - - @example/app-crm@0.9.6 - - @example/app-todo@0.9.6 - - @example/plugin-bi@0.9.6 - - @objectstack/spec@1.0.3 - -## 0.9.5 - -### Patch Changes - -- Updated dependencies [a0a6c85] -- Updated dependencies [109fc5b] - - @objectstack/spec@1.0.2 - - @objectstack/metadata@1.0.2 - - @objectstack/objectql@1.0.2 - - @objectstack/runtime@1.0.2 - - @objectstack/driver-memory@1.0.2 - - @objectstack/plugin-hono-server@1.0.2 - - @example/app-crm@0.9.5 - - @example/app-todo@0.9.5 - - @example/plugin-bi@0.9.5 - -## 0.9.4 - -### Patch Changes - -- Updated dependencies - - @objectstack/runtime@1.0.1 - - @objectstack/spec@1.0.1 - - @objectstack/metadata@1.0.1 - - @objectstack/objectql@1.0.1 - - @objectstack/driver-memory@1.0.1 - - @objectstack/plugin-hono-server@1.0.1 - - @example/app-crm@0.9.4 - - @example/app-todo@0.9.4 - - @example/plugin-bi@0.9.4 - -## 0.9.3 - -### Patch Changes - -- Updated dependencies - - @objectstack/spec@1.0.0 - - @objectstack/runtime@1.0.0 - - @objectstack/metadata@1.0.0 - - @objectstack/objectql@1.0.0 - - @objectstack/driver-memory@1.0.0 - - @objectstack/plugin-hono-server@1.0.0 - - @example/app-crm@0.9.3 - - @example/app-todo@0.9.3 - - @example/plugin-bi@0.9.3 - -## 0.9.2 - -### Patch Changes - -- @example/app-crm@0.9.2 -- @example/app-todo@0.9.2 -- @example/plugin-bi@0.9.2 -- @objectstack/metadata@0.9.2 -- @objectstack/objectql@0.9.2 -- @objectstack/driver-memory@0.9.2 -- @objectstack/plugin-hono-server@0.9.2 -- @objectstack/runtime@0.9.2 - -## 0.9.1 - -### Patch Changes - -- Updated dependencies - - @objectstack/metadata@0.9.1 - - @objectstack/objectql@0.9.1 - - @objectstack/runtime@0.9.1 - - @objectstack/driver-memory@0.9.1 - - @objectstack/plugin-hono-server@0.9.1 - - @example/app-crm@0.9.1 - - @example/app-todo@0.9.1 - - @example/plugin-bi@0.9.1 - -## 0.7.5 - -### Patch Changes - -- Updated dependencies [555e6a7] - - @objectstack/objectql@0.8.2 - - @objectstack/example-crm@0.7.5 - - @objectstack/plugin-bi@0.7.5 - - @objectstack/example-todo@0.7.5 - - @objectstack/metadata@0.8.2 - - @objectstack/driver-memory@0.8.2 - - @objectstack/plugin-hono-server@0.8.2 - - @objectstack/runtime@0.8.2 - -## 0.7.4 - -### Patch Changes - -- @objectstack/example-crm@0.7.4 -- @objectstack/example-todo@0.7.4 -- @objectstack/objectql@0.8.1 -- @objectstack/runtime@0.8.1 -- @objectstack/driver-memory@0.8.1 -- @objectstack/plugin-hono-server@0.8.1 -- @objectstack/plugin-bi@0.7.4 - -## 0.7.3 - -### Patch Changes - -- Updated dependencies - - @objectstack/objectql@1.0.0 - - @objectstack/runtime@1.0.0 - - @objectstack/driver-memory@1.0.0 - - @objectstack/plugin-hono-server@1.0.0 - - @objectstack/example-crm@0.7.3 - - @objectstack/plugin-bi@0.7.3 - - @objectstack/example-todo@0.7.3 - -## 0.7.2 - -### Patch Changes - -- Updated dependencies [fb41cc0] - - @objectstack/objectql@0.7.2 - - @objectstack/runtime@0.7.2 - - @objectstack/driver-memory@0.7.2 - - @objectstack/plugin-hono-server@0.7.2 - - @objectstack/example-crm@0.7.2 - - @objectstack/plugin-bi@0.7.2 - - @objectstack/example-todo@0.7.2 - -## 0.7.1 - -### Patch Changes - -- Updated dependencies - - @objectstack/driver-memory@0.7.1 - - @objectstack/objectql@0.7.1 - - @objectstack/plugin-hono-server@0.7.1 - - @objectstack/runtime@0.7.1 - - @objectstack/example-crm@0.7.1 - - @objectstack/plugin-bi@0.7.1 - - @objectstack/example-todo@0.7.1 - -## 0.6.1 - -### Patch Changes - -- Updated dependencies - - @objectstack/driver-memory@0.6.1 - - @objectstack/objectql@0.6.1 - - @objectstack/plugin-hono-server@0.6.1 - - @objectstack/runtime@0.6.1 - - @objectstack/example-crm@0.6.1 - - @objectstack/plugin-bi@0.6.1 - - @objectstack/example-todo@0.6.1 - -## 0.6.0 - -### Minor Changes - -- b2df5f7: Unified version bump to 0.5.0 - - - Standardized all package versions to 0.5.0 across the monorepo - - Fixed driver-memory package.json paths for proper module resolution - - Ensured all packages are in sync for the 0.5.0 release - -### Patch Changes - -- Updated dependencies [b2df5f7] - - @objectstack/driver-memory@0.6.0 - - @objectstack/objectql@0.6.0 - - @objectstack/plugin-hono-server@0.6.0 - - @objectstack/runtime@0.6.0 - - @objectstack/example-todo@0.6.0 - - @objectstack/example-crm@0.6.0 - - @objectstack/plugin-bi@0.6.0 - -## 0.1.9 - -### Patch Changes - -- Updated dependencies - - @objectstack/driver-memory@0.4.2 - - @objectstack/objectql@0.4.2 - - @objectstack/plugin-hono-server@0.4.2 - - @objectstack/runtime@0.4.2 - - @objectstack/example-crm@1.0.9 - - @objectstack/plugin-bi@1.0.9 - - @objectstack/example-todo@1.0.9 - -## 0.1.8 - -### Patch Changes - -- Updated dependencies - - @objectstack/driver-memory@0.4.1 - - @objectstack/objectql@0.4.1 - - @objectstack/plugin-hono-server@0.4.1 - - @objectstack/runtime@0.4.1 - - @objectstack/example-crm@1.0.8 - - @objectstack/plugin-bi@1.0.8 - - @objectstack/example-todo@1.0.8 - -## 0.1.7 - -### Patch Changes - -- Updated dependencies - - @objectstack/driver-memory@0.3.3 - - @objectstack/objectql@0.3.3 - - @objectstack/plugin-hono-server@0.3.3 - - @objectstack/runtime@0.3.3 - - @objectstack/example-crm@1.0.7 - - @objectstack/plugin-bi@1.0.7 - - @objectstack/example-todo@1.0.7 - -## 0.1.6 - -### Patch Changes - -- Patch release for maintenance and stability improvements -- Updated dependencies - - @objectstack/example-crm@1.0.6 - - @objectstack/plugin-bi@1.0.6 - - @objectstack/example-todo@1.0.6 - - @objectstack/driver-memory@0.3.2 - - @objectstack/objectql@0.3.2 - - @objectstack/plugin-hono-server@0.3.2 - - @objectstack/runtime@0.3.2 - -## 0.1.5 - -### Patch Changes - -- Updated dependencies - - @objectstack/runtime@0.3.1 - - @objectstack/driver-memory@0.3.1 - - @objectstack/objectql@0.3.1 - - @objectstack/plugin-hono-server@0.3.1 - - @objectstack/example-crm@1.0.5 - - @objectstack/plugin-bi@1.0.5 - - @objectstack/example-todo@1.0.5 - -## 0.1.4 - -### Patch Changes - -- @objectstack/example-crm@1.0.4 -- @objectstack/plugin-bi@1.0.4 -- @objectstack/example-todo@1.0.4 -- @objectstack/driver-memory@1.0.0 -- @objectstack/objectql@1.0.0 -- @objectstack/plugin-hono-server@1.0.0 -- @objectstack/runtime@1.0.0 - -## 0.1.3 - -### Patch Changes - -- Updated dependencies - - @objectstack/driver-memory@0.2.1 - - @objectstack/objectql@0.2.1 - - @objectstack/plugin-hono-server@0.2.1 - - @objectstack/runtime@0.2.1 - - @objectstack/example-crm@1.0.3 - - @objectstack/plugin-bi@1.0.3 - - @objectstack/example-todo@1.0.3 - -## 0.1.2 - -### Patch Changes - -- Updated dependencies - - @objectstack/objectql@0.2.0 - - @objectstack/runtime@0.2.0 - - @objectstack/driver-memory@0.2.0 - - @objectstack/plugin-hono-server@1.0.0 - - @objectstack/example-crm@1.0.2 - - @objectstack/plugin-bi@1.0.2 - - @objectstack/example-todo@1.0.2 - -## 0.1.1 - -### Patch Changes - -- Updated dependencies - - @objectstack/driver-memory@0.1.1 - - @objectstack/objectql@0.1.1 - - @objectstack/plugin-hono-server@0.1.1 - - @objectstack/runtime@0.1.1 - - @objectstack/example-crm@1.0.1 - - @objectstack/plugin-bi@1.0.1 - - @objectstack/example-todo@1.0.1 diff --git a/apps/objectos/Dockerfile b/apps/objectos/Dockerfile deleted file mode 100644 index a042f93c4..000000000 --- a/apps/objectos/Dockerfile +++ /dev/null @@ -1,152 +0,0 @@ -# -# ObjectOS — production container image -# -------------------------------------------------- -# Build context = repo root (Dockerfile expects the full pnpm workspace): -# -# docker buildx build --platform linux/amd64 \ -# -f apps/objectos/Dockerfile \ -# -t objectos:latest . -# -# Stage 1 (`builder`) installs the full dev tree and builds every workspace -# package apps/objectos transitively depends on. -# -# Stage 2 (`pruner`) uses `pnpm deploy --prod` to extract a self-contained -# tree under /deploy that contains only production deps (workspace packages -# are flattened to plain `node_modules/@objectstack/*` entries). -# -# Stage 3 (`runner`) starts from a clean slim base and only COPYs /deploy, -# yielding an image that is ~3× smaller than `COPY --from=builder /repo /app`. - -# ────────────────────────────────────────────────────────────────────────── -# Stage 1 — install workspace deps + build required packages -# ────────────────────────────────────────────────────────────────────────── -FROM node:22-slim AS builder - -# native bindings for better-sqlite3 and friends -RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ - --mount=type=cache,target=/var/lib/apt,sharing=locked \ - apt-get update \ - && apt-get install -y --no-install-recommends \ - python3 make g++ ca-certificates - -WORKDIR /repo - -# CI=true so pnpm skips interactive prompts (e.g. node_modules cleanup -# confirmation that fails with ERR_PNPM_ABORTED_REMOVE_MODULES_DIR_NO_TTY). -# Route both corepack's pnpm-tarball download and pnpm's own package -# downloads through a fast public mirror — the default registry.npmjs.org -# is unreliable from many networks (CN). Override at build time with -# `--build-arg COREPACK_NPM_REGISTRY=…` if you have your own mirror. -ENV CI=true \ - COREPACK_NPM_REGISTRY=https://registry.npmmirror.com \ - npm_config_registry=https://registry.npmmirror.com - -# ── Layer 0: bake pnpm into the image (one-shot network fetch) ────────────── -# Without this, every subsequent `pnpm …` invocation triggers corepack to -# re-fetch the pinned pnpm version on demand. If the mirror has a transient -# DNS or network blip, the build fails. Pinning to the version declared in -# package.json downloads it ONCE per Dockerfile change, caches the layer, -# and frees later steps from needing the corepack registry at all. -# Fall back to the official npm registry if the mirror is unreachable. -ARG PNPM_VERSION=10.31.0 -RUN corepack enable \ - && (corepack prepare pnpm@${PNPM_VERSION} --activate \ - || (echo "→ npmmirror unreachable, falling back to npmjs.org" \ - && COREPACK_NPM_REGISTRY=https://registry.npmjs.org \ - corepack prepare pnpm@${PNPM_VERSION} --activate)) - -# ── Layer 1: pre-fetch deps from lockfile only ────────────────────────────── -# `pnpm fetch` populates the virtual store from `pnpm-lock.yaml` alone (no -# package.json files needed), so this layer is reused across builds until -# the lockfile changes. The cache mount makes the global store survive -# across `docker buildx build` invocations. -ENV PNPM_HOME=/pnpm -ENV PATH=$PNPM_HOME:$PATH -COPY pnpm-lock.yaml ./ -RUN --mount=type=cache,id=pnpm-store,target=/pnpm/store \ - pnpm config set store-dir /pnpm/store \ - && pnpm fetch --prod=false - -# ── Layer 2: copy workspace manifests + sources, then install offline ─────── -# `--offline` forces resolution from the warm store; on a code-only change -# (lockfile unchanged) this finishes in seconds. -COPY package.json pnpm-workspace.yaml turbo.json tsconfig.json tsup.config.ts ./ -COPY packages ./packages -COPY examples ./examples -COPY apps/objectos ./apps/objectos -COPY apps/console ./apps/console -COPY apps/account ./apps/account - -RUN --mount=type=cache,id=pnpm-store,target=/pnpm/store \ - pnpm install --frozen-lockfile --prefer-offline --prod=false - -# Build apps/objectos + all its workspace transitive deps. -# Use turbo (not raw `pnpm --filter ... build`) so its content-addressed -# cache at /repo/.turbo can short-circuit unchanged packages on rebuilds. -RUN --mount=type=cache,id=turbo-objectos,target=/repo/.turbo,sharing=locked \ - --mount=type=cache,id=node-cache-objectos,target=/repo/node_modules/.cache,sharing=locked \ - pnpm exec turbo run build --filter='@objectstack/objectos...' - -# ────────────────────────────────────────────────────────────────────────── -# Stage 2 — production prune (pnpm deploy) -# ────────────────────────────────────────────────────────────────────────── -# Creates a /deploy tree containing apps/objectos with its full prod -# dependency graph, devDependencies stripped, workspace packages -# materialised as real folders under node_modules/. Source `src/`, tests, -# and `.map` files inside the workspace package dists are kept (they are -# tiny next to node_modules); strip more aggressively if you need to. -FROM builder AS pruner -WORKDIR /repo -RUN pnpm --filter @objectstack/objectos deploy --prod --legacy /deploy \ - && find /deploy -type f \( -name "*.map" -o -name "*.test.*" -o -name "*.spec.*" -o -name "*.md" -o -name "*.markdown" \) -delete \ - && find /deploy -type d \( -name "__tests__" -o -name "test" -o -name "tests" -o -name "docs" -o -name "example" -o -name "examples" \) -prune -exec rm -rf {} + \ - && rm -rf /deploy/.turbo /deploy/.cache \ - # Surgical removal of dev-only deps that get pulled in by package peers - # but are NOT used by the ObjectOS runtime path (verified by smoke test). - && rm -rf \ - /deploy/node_modules/.pnpm/next@* \ - /deploy/node_modules/.pnpm/@next+* \ - /deploy/node_modules/.pnpm/playwright-core@* \ - /deploy/node_modules/.pnpm/@playwright+* \ - /deploy/node_modules/.pnpm/typescript@* \ - /deploy/node_modules/.pnpm/happy-dom@* \ - /deploy/node_modules/.pnpm/@rolldown+* \ - /deploy/node_modules/.pnpm/@img+sharp-libvips-* \ - /deploy/node_modules/.pnpm/@cloudflare+workers-types@* \ - /deploy/node_modules/.pnpm/@esbuild+* \ - /deploy/node_modules/.pnpm/lightningcss-* \ - /deploy/node_modules/.pnpm/caniuse-lite@* - -# ────────────────────────────────────────────────────────────────────────── -# Stage 3 — slim runtime image -# ────────────────────────────────────────────────────────────────────────── -FROM node:22-slim AS runner - -RUN apt-get update \ - && apt-get install -y --no-install-recommends ca-certificates wget \ - && rm -rf /var/lib/apt/lists/* - -WORKDIR /app - -ENV NODE_ENV=production \ - PORT=3000 \ - HOST=0.0.0.0 - -COPY --from=pruner /deploy /app -COPY --from=builder /repo/apps/objectos/dist /app/dist - -EXPOSE 3000 -VOLUME ["/data"] - -# NB: do NOT bake `ENV OS_DATABASE_URL=…` here. service-cloud's -# resolveControlDriver() / single-app driver factory already handles the -# full fallback chain (OS_DATABASE_URL → TURSO_DATABASE_URL → -# file:/.db) and will throw a clear error on serverless -# when none is configured. A baked image default would silently win over -# an unset Worker secret and quietly write to ephemeral container disk. - -HEALTHCHECK --interval=30s --timeout=5s --start-period=30s --retries=3 \ - CMD wget -qO- "http://127.0.0.1:${PORT}/api/v1/health" >/dev/null 2>&1 || exit 1 - -# Invoke the CLI binary directly — no pnpm process needed at runtime. -CMD ["node", "node_modules/@objectstack/cli/bin/run.js", "serve", "dist/objectstack.config.js", "--prebuilt"] diff --git a/apps/objectos/README.md b/apps/objectos/README.md deleted file mode 100644 index 09d306424..000000000 --- a/apps/objectos/README.md +++ /dev/null @@ -1,485 +0,0 @@ -# ObjectOS - -ObjectOS is the ObjectStack runtime — a metadata-driven backend that loads object definitions from app bundles and auto-generates REST APIs. This directory is the reference host configuration for running ObjectOS. - -## Run Modes - -ObjectOS supports four run modes, controlled by the `OS_CLOUD_URL` environment variable. - -### 1. Local (Default) - -Single-project, fully self-contained. No control plane, no network dependency. Data is stored in a local SQLite file at `.objectstack/data/app.db`. - -```bash -pnpm dev -# Server → http://localhost:3000 -# DB → .objectstack/data/app.db (auto-created) -``` - -Best for: local development, quick prototyping, CI. - -#### Serving a compiled app bundle locally - -Point ObjectOS at a third-party app bundle compiled by `objectstack build` (e.g. `examples/app-crm`). The bundle is a JSON file plus a sibling `objectstack-runtime..mjs` that carries the compiled hook handlers; both are loaded automatically. - -> **Tip:** for a pure single-bundle host you do **not** need `apps/objectos` at all -> — just run `objectstack start` from any directory that contains a -> `dist/objectstack.json` (or set `OS_ARTIFACT_PATH` to a file path or -> `https://` URL). The framework now ships the standalone host as -> `createDefaultHostConfig()` in `@objectstack/runtime`. Use `apps/objectos` -> when you need cloud / multi-project / control-plane features on top. - -```bash -# Build the example app once -pnpm --filter @objectstack/app-crm build - -# Boot ObjectOS pointing at the bundle -cd apps/objectos -OS_ARTIFACT_PATH=$PWD/../../examples/app-crm/dist/objectstack.json \ - PORT=3000 pnpm start - -# All three URL shapes resolve to the same project kernel: -curl -X POST http://localhost:3000/api/v1/data/account \ - -H 'Content-Type: application/json' \ - -d '{"name":"Acme","website":"bogus"}' -# → 400 Website must start with http:// or https:// (CRM hook fired) - -curl -X POST http://localhost:3000/api/v1/projects/proj_local/data/account \ - -H 'Content-Type: application/json' \ - -d '{"name":"Acme","website":"https://acme.com","account_number":"abc-9"}' -# → 200 with record.account_number === "ABC-9" (uppercase hook fired) -``` - -The bare `/api/v1/data/...` URL is routed to the default project (`proj_local`) by `createSingleProjectPlugin`. Tables are auto-created by the SQL driver on first access; the bundle's seed data (e.g. `Acme Corporation`) is upserted on boot. - -#### Hosting multiple compiled bundles - -Two bundles can share a single ObjectOS host. Each bundle gets its own -project kernel; isolation is enforced at the kernel boundary (separate -SQLite file per project, separate object registry, separate hooks). - -There are three binding mechanisms, evaluated in this order at request -time (first hit wins): - -| Priority | Source | Scope | Best for | -|:---|:---|:---|:---| -| 1 | `OS_PROJECT_ARTIFACTS` env | per-project, ephemeral | Local dev, CI | -| 2 | `sys_project.metadata.artifact_path` (DB row) | per-project, persisted | Production, control-plane managed | -| 3 | `OS_ARTIFACT_PATH` env | shared default for unbound projects | Single-bundle hosts | - -**Mode 1 — env-driven (recommended for local multi-bundle):** - -```bash -# Build both bundles once -pnpm --filter @objectstack/app-crm build -pnpm --filter @example/app-todo build - -cd apps/objectos -OS_PROJECT_ARTIFACTS="proj_crm:$PWD/../../examples/app-crm/dist/objectstack.json,proj_todo:$PWD/../../examples/app-todo/dist/objectstack.json" \ - PORT=3000 pnpm start - -# Address each project explicitly via scoped URL -curl -X POST http://localhost:3000/api/v1/projects/proj_crm/data/account \ - -H 'Content-Type: application/json' \ - -d '{"name":"Acme","website":"https://acme.com","account_number":"abc-9"}' - -curl -X POST http://localhost:3000/api/v1/projects/proj_todo/data/todo_task \ - -H 'Content-Type: application/json' \ - -d '{"title":"Buy Milk","priority":"high"}' - -# Or use the X-Project-Id header on a bare URL — equivalent -curl -X POST http://localhost:3000/api/v1/data/account \ - -H 'X-Project-Id: proj_crm' \ - -H 'Content-Type: application/json' \ - -d '{"name":"Beta","website":"https://beta.io"}' -``` - -**Mode 2 — DB-persisted (recommended for production):** - -```bash -# Bind once via CLI; the path is stored in sys_project.metadata.artifact_path -pnpm exec objectstack projects bind proj_crm \ - $PWD/examples/app-crm/dist/objectstack.json - -# Subsequent boots load the binding from the control plane DB -pnpm --filter @objectstack/objectos start -``` - -**Routing rules:** - -- A scoped URL `/api/v1/projects//...` always targets the named - project (assuming it's bound). -- A bare URL `/api/v1/data/...` resolves a project via this chain: - hostname → `X-Project-Id` header → `defaultProjectId` (set by - `createSingleProjectPlugin` in single-project mode). Multi-bundle - hosts should not rely on the default fallback — always specify the - project via URL or header. -- `OS_ARTIFACT_PATH` is **only** the default fallback for projects with - no other binding. In multi-bundle mode, leave it unset so each - project picks up its own bundle from `OS_PROJECT_ARTIFACTS` or DB. - ---- - -### 2. Local + External Control Plane - -ObjectOS runtime connects to a locally-running `apps/cloud` instance as the control plane. Studio shows the full org / project / branch picker. - -```bash -# Terminal 1 — start the control plane -pnpm --filter @objectstack/cloud dev - -# Terminal 2 — start ObjectOS, pointing at local cloud -OS_CLOUD_URL=http://localhost:4000 pnpm dev -``` - -Best for: end-to-end multi-project development. - ---- - -### 3. Cloud (Hosted Control Plane) - -ObjectOS runtime connects to the hosted ObjectStack Cloud control plane. Projects, credentials, and artifact resolution are all managed remotely. - -```bash -OS_CLOUD_URL=https://cloud.objectstack.ai \ -OS_CLOUD_API_KEY=osk_... \ -pnpm dev -``` - -Best for: production deployments, staging environments. - ---- - -### 4. Preview / Demo Mode - -Bypass login entirely — the runtime auto-simulates an admin session. Designed for demos and marketplace previews. **Never use in production.** - -```bash -OS_MODE=preview pnpm dev -``` - -Behavior: -- Login / registration pages are hidden -- Admin session is created automatically -- A preview banner is shown in the UI - ---- - -## Environment Variables - -### Runtime - -| Variable | Default | Description | -|:---|:---|:---| -| `OS_CLOUD_URL` | `local` | `local` = standalone; URL = connect to that control plane | -| `OS_CLOUD_API_KEY` | — | API key when connecting to a remote control plane | -| `OS_ARTIFACT_PATH` | `dist/objectstack.json` | Path to the compiled app artifact (single-bundle default) | -| `OS_PROJECT_ARTIFACTS` | — | Comma list of `:` pairs for multi-bundle hosting | -| `OS_MODE` | — | `standalone` (default), `runtime`, `cloud`, or `preview` | - -### Database (Local / Standalone mode) - -ObjectOS in single-project local mode uses **two** databases: - -| DB | Purpose | Env var | Default | -|:---|:---|:---|:---| -| **Project DB** | Your business data (records served at `/api/v1/data/*`) | `OS_DATABASE_URL` / `OS_DATABASE_DRIVER` | local SQLite at `.objectstack/data/proj_local.db` | -| **Control DB** | Framework bookkeeping (`sys_organization`, `sys_project`, `sys_user`, …) | `OS_CONTROL_DATABASE_URL` | local SQLite at `.objectstack/data/control.db` | - -Other variables: - -| Variable | Default | Description | -|:---|:---|:---| -| `OS_DATABASE_AUTH_TOKEN` | — | Auth token for libSQL / Turso | -| `OS_DATABASE_DRIVER` | inferred from URL scheme | Explicit override: `mongodb`, `postgres`, `mysql`, `sqlite`, `turso`, `memory` | - -The driver is **inferred from the URL scheme** of `OS_DATABASE_URL` -(and `OS_CONTROL_DATABASE_URL`). You almost never need to set -`OS_DATABASE_DRIVER` explicitly. - -| `OS_DATABASE_URL` value | Driver | -|:---|:---| -| unset | SQLite — `.objectstack/data/proj_local.db` | -| `file:` / `.db` / `.sqlite` | SQLite at that path | -| `:memory:` | SQLite in-memory | -| `mongodb://…` / `mongodb+srv://…` | **MongoDB** (`@objectstack/driver-mongodb`) | -| `postgres://…` / `postgresql://…` | PostgreSQL (`@objectstack/driver-sql`) | -| `mysql://…` / `mysql2://…` | MySQL (`@objectstack/driver-sql`) | -| `libsql://…` / `https://*.turso.…` | libSQL / Turso (`@objectstack/driver-turso`) | - -Examples: - -```bash -# Project DB on MongoDB (control DB stays on local SQLite) -OS_DATABASE_URL=mongodb://localhost:27017/objectos pnpm dev - -# Project DB on Postgres -OS_DATABASE_URL=postgres://user:pass@host:5432/myapp pnpm dev - -# Pin both DBs explicitly (Postgres for project, Turso for control plane) -OS_DATABASE_URL=postgres://user:pass@host/myapp \ -OS_CONTROL_DATABASE_URL=libsql://control.turso.io \ -OS_DATABASE_AUTH_TOKEN=$TURSO_TOKEN \ -pnpm dev -``` - -For backward compatibility, `OS_DATABASE_URL` is also accepted as a -fallback for the control DB when neither `OS_CONTROL_DATABASE_URL` nor -an explicit programmatic value is set. - -### Kernel Tuning - -| Variable | Default | Description | -|:---|:---|:---| -| `OS_KERNEL_CACHE_SIZE` | `32` | LRU size for per-project kernel instances | -| `OS_KERNEL_TTL_MS` | `900000` | Idle eviction TTL (ms) | -| `AUTH_SECRET` | — | Better Auth session secret (≥ 32 chars, required in production) | - ---- - -## Quick Start - -```bash -# Install workspace dependencies (run once from repo root) -corepack enable && pnpm install - -# Start in local mode (default) -pnpm dev - -# Start with Turso as the control-plane database -OS_DATABASE_URL=libsql://your-db.turso.io \ -OS_DATABASE_AUTH_TOKEN=your-token \ -AUTH_SECRET=$(openssl rand -hex 32) \ -pnpm dev -``` - ---- - -## Build & Deploy - -```bash -# Build the compiled artifact -pnpm build - -# Serve the pre-built artifact (production) -pnpm start -``` - -### Deploy to Vercel - -[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https://github.com/objectstack-ai/framework/tree/main/apps/objectos&project-name=objectos&repository-name=objectos) - -The `api/` directory contains the Vercel serverless handler. Set the environment variables above in the Vercel project settings. - ---- - -## API - -### Metadata - -```bash -# List all loaded objects -curl http://localhost:3000/api/v1/meta/objects -``` - -### Data (CRUD) - -```bash -# Create -curl -X POST http://localhost:3000/api/v1/data/todo_task \ - -H "Content-Type: application/json" \ - -d '{"title": "Buy Milk", "priority": "high"}' - -# List -curl http://localhost:3000/api/v1/data/todo_task - -# Get one -curl http://localhost:3000/api/v1/data/todo_task/ - -# Update -curl -X PATCH http://localhost:3000/api/v1/data/todo_task/ \ - -H "Content-Type: application/json" \ - -d '{"priority": "low"}' - -# Delete -curl -X DELETE http://localhost:3000/api/v1/data/todo_task/ -``` - ---- - -## Production-shape verification (cloud + hostname routing) - -The full production deployment shape — `apps/cloud` as the control plane, -`apps/objectos` as a runtime node, vanity hostnames routed by -`EnvironmentRegistry.resolveByHostname` to per-project kernels — is -covered end-to-end by an in-process test that exercises the same code -paths a 2-process deployment would (the only difference is HTTP vs -in-memory transport between the registry and the control-plane SQL -driver). - -```bash -# from repo root -pnpm --filter @objectstack/cloud build -pnpm --filter @objectstack/cloud test:production-flow -``` - -What it verifies (6 steps, all in one process): - -1. Boot `apps/cloud` in `OS_MODE=cloud` (control plane + runtime node). -2. Seed an organization via the control-plane `objectql` engine. -3. `GET /api/v1/cloud/templates` returns `crm` in the catalog. -4. `POST /api/v1/cloud/projects` with `template_id=crm` + `hostname=` - provisions a project. The provisioning workflow: - 1. Create the project SQLite file (or Turso DB). - 2. Persist `database_url` so kernel-factory can resolve the DB. - 3. Run the template seeder — registers metadata, binds hooks, - `initObjects` to create physical tables, loads seed data. - 4. Flip `status` to `active` (so `waitForActive` clients only see - the project as ready *after* schema + seed data are queryable). -5. `POST /api/v1/data/account` with `Host: ` and `website: bogus` - → routed to the project kernel by hostname → CRM hook returns - `400 Website must start with http:// or https://`. -6. `POST /api/v1/data/account` with a valid payload → 2xx, and the - `account_number` is uppercased by the `account_protection` hook; - subsequent `GET /api/v1/data/account` returns the row through the - same hostname-routed kernel. - -For a true 2-process verification, run `apps/cloud` and `apps/objectos` -on separate ports with `OS_CLOUD_URL=http://:` on the -runtime node. Browser users add `127.0.0.1 crm.localhost` to `/etc/hosts` -and visit `http://crm.localhost:/`. The framework code -paths exercised are identical to the in-process test above. - -## Cloudflare Containers deployment - -ObjectOS runs as a long-lived Node.js process and is **not** Workers-compatible -(better-sqlite3 native bindings, `node:fs`, `node:child_process`). It can, -however, run on **Cloudflare Containers** (GA 2025) using the existing -`Dockerfile`. - -Files: - -- `Dockerfile` — production image (Node 22, port 3000). -- `wrangler.toml` — Worker + Container binding. -- `cloudflare/worker.ts` — fetch handler that proxies HTTP into the - `ObjectOSContainer` Durable Object. -- `scripts/deploy-cloudflare.sh` — `build → push → deploy` pipeline. -- `scripts/setup-cloudflare-secrets.sh` — bulk `wrangler secret put` from - a local env file. - -### Quickstart (automated) - -```bash -# One-time setup -npx wrangler login -cp apps/objectos/.env.cloudflare.example apps/objectos/.env.cloudflare -cp apps/objectos/.env.cloudflare.secrets.example apps/objectos/.env.cloudflare.secrets -# Fill in CF_ACCOUNT_ID + secrets (see comments inside each file) - -# Push secrets (once, or any time they change) -pnpm --filter @objectstack/objectos cf:secrets - -# Build → push → deploy (run as often as you ship) -pnpm --filter @objectstack/objectos cf:deploy - -# Live tail -pnpm --filter @objectstack/objectos cf:tail -``` - -Useful flags on `cf:deploy`: - -| Flag | Effect | -|---|---| -| `--tag v2` | Override image tag (default = current git short SHA). | -| `--skip-build` | Reuse the last-built image (just push + deploy). | -| `--skip-push` | Already pushed; just deploy current `wrangler.toml`. | -| `--dry-run` | Print every step without executing it. | - -### Manual (if you don't want the script) - -```bash -# Build from repo root (Dockerfile expects the full pnpm workspace) -docker buildx build --platform linux/amd64 \ - -f apps/objectos/Dockerfile \ - -t registry.cloudflare.com//objectos:latest . - -wrangler containers push registry.cloudflare.com//objectos:latest - -# Push secrets — control plane MUST point at remote libSQL/Turso, the -# container filesystem is wiped on cold-start. -wrangler secret put OS_DATABASE_URL --config apps/objectos/wrangler.toml -wrangler secret put OS_DATABASE_AUTH_TOKEN --config apps/objectos/wrangler.toml -wrangler secret put AUTH_SECRET --config apps/objectos/wrangler.toml - -wrangler deploy --config apps/objectos/wrangler.toml -``` - -Required runtime env vars (set as Cloudflare secrets, not in `wrangler.toml`): - -| Var | Purpose | -|---|---| -| `OS_DATABASE_URL` | `libsql://.turso.io` — control DB. Do **not** use `file:/data/...` on Containers; the local disk is wiped on cold-start. | -| `OS_DATABASE_AUTH_TOKEN` | Turso auth token. | -| `AUTH_SECRET` | Cookie/session signing secret. | -| `OS_CLOUD_URL` *(optional)* | Point at an `apps/cloud` deployment for multi-project mode. Omit for single-project local mode. | - -## Default per-project plugin slate - -ObjectOS keeps the **host kernel intentionally bare** (stateless -routing shell). All tenant-data plugins live on **per-project kernels**, -mounted on first-hit and cached by `ArtifactKernelFactory` / -`DefaultProjectKernelFactory`. - -Every per-project kernel automatically gets the following default -plugins (in this order) via `mountDefaultProjectPlugins()`: - -1. `QueueServicePlugin` (in-memory) -2. `JobServicePlugin` -3. `CacheServicePlugin` (in-memory) -4. `SettingsServicePlugin` — `sys_setting` / `sys_secret` / - `sys_setting_audit` rows live in the **project's own driver**, so - tenants are fully isolated -5. `EmailServicePlugin` — each tenant configures its own provider / - api_key via Settings; api_key is stored encrypted in `sys_secret` -6. `StorageServicePlugin` — see below. Adapter + credentials are - live-configurable per tenant via the `storage` Settings namespace; - the plugin swaps the inner adapter on every change. - -### Storage adapter selection - -- `OS_STORAGE_ADAPTER=s3` + `OS_S3_*` env → shared S3 bucket with - `pathStylePrefix: projects/` so tenant prefixes never - collide. Per-project Settings can still override the bucket / - credentials at runtime. -- otherwise → local driver under - `/projects//uploads/`; a single boot warning - fires in non-dev mode. - -Tenant admins can switch adapter from the Settings hub -(`namespace=storage`) — the `SwappableStorageService` proxy rebuilds -the inner adapter without restarting the kernel. ⚠ Existing files are -**not** migrated; a warning is logged on every swap. The -`storage/test` action uploads → reads → deletes a probe blob to -validate the new configuration before users start uploading. - -### Ops overrides - -The factory accepts `basePluginsExtra({ projectId, kernel })` which can -return `{ caps?: {…: false}, extraPlugins?: [...] }` to: - -- inject a **shared Redis-backed queue** so retries survive kernel - eviction (set `caps.queue=false` and push your custom plugin in - `extraPlugins`) -- skip storage when the host worker mounts a shared S3 instance - out-of-band -- mount tenant-specific audit/automation/analytics plugins - -### Caveats - -- **In-memory queue eviction** — when a per-project kernel is LRU- - evicted, queued retries are dropped. Use the `basePluginsExtra` - hook above to inject a Redis-backed queue for SLA-bound workloads. -- **InMemoryCryptoProvider is per-process** — the default AES key is - regenerated on every restart, so encrypted `sys_secret` rows cannot - be decrypted after a redeploy. Ship `KmsCryptoProvider` (AWS KMS - envelope) before enabling encrypted settings on hosted objectos. diff --git a/apps/objectos/api/[[...route]].js b/apps/objectos/api/[[...route]].js deleted file mode 100644 index 1bbcc8746..000000000 --- a/apps/objectos/api/[[...route]].js +++ /dev/null @@ -1,16 +0,0 @@ -// Vercel Serverless Function — Catch-all API route. -// -// This file MUST be committed to the repository so Vercel can detect it -// as a serverless function during the pre-build phase. -// -// It delegates to the esbuild bundle (`_handler.js`) generated by -// `scripts/bundle-api.mjs` during the Vercel build step. A separate -// bundle file is used (rather than overwriting this file) so that: -// 1. Vercel always finds this committed entry point (no "File not found"). -// 2. Vercel does not TypeScript-compile a .ts stub that references -// source files absent at runtime (no ERR_MODULE_NOT_FOUND). -// -// @see ../server/index.ts — the actual server entrypoint -// @see ../scripts/bundle-api.mjs — the esbuild bundler - -export { default, config } from './_handler.js'; diff --git a/apps/objectos/cloudflare/worker.ts b/apps/objectos/cloudflare/worker.ts deleted file mode 100644 index d4e585e34..000000000 --- a/apps/objectos/cloudflare/worker.ts +++ /dev/null @@ -1,248 +0,0 @@ -// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. - -// Build marker: bump this comment to force a Durable Object reset -// when shipping a hotfix that lives inside the Container image. The -// DO only reloads when worker code changes, so a no-op edit here -// guarantees the container restarts with the freshly-pushed image. -// build: 2026-05-20T16:45Z add-restart-admin-endpoint - -/** - * Cloudflare Containers entrypoint for ObjectOS. - * - * The Worker fronts a Container-class Durable Object that runs the - * Node.js image built from `apps/objectos/Dockerfile`. All HTTP traffic - * is forwarded 1:1 to the container on port 3000. - * - * Deploy with: - * wrangler deploy --config apps/objectos/wrangler.toml - * - * See `apps/objectos/wrangler.toml` for the full deployment workflow - * (build + push image, then deploy Worker). - */ - -import { Container, getContainer } from '@cloudflare/containers'; - -export interface Env { - OBJECTOS: DurableObjectNamespace; - // ── Secrets / vars forwarded to the Container process ──────────────── - // Set via `wrangler secret put X` or `[vars]` in wrangler.toml. - // Keep in sync with FORWARDED_ENV_KEYS below. - - // — Database (tenant) — - OS_DATABASE_URL?: string; - OS_DATABASE_AUTH_TOKEN?: string; - OS_DATABASE_DRIVER?: string; - TURSO_DATABASE_URL?: string; - TURSO_AUTH_TOKEN?: string; - - // — Auth — - AUTH_SECRET?: string; - OS_AUTH_SECRET?: string; - AUTH_BASE_URL?: string; - OS_BASE_URL?: string; - OS_TRUSTED_ORIGINS?: string; - OS_COOKIE_DOMAIN?: string; - OS_ROOT_DOMAIN?: string; - GOOGLE_CLIENT_ID?: string; - GOOGLE_CLIENT_SECRET?: string; - GITHUB_CLIENT_ID?: string; - GITHUB_CLIENT_SECRET?: string; - - // — Cloud client (point this objectos at a control plane) — - OS_CLOUD_URL?: string; - OS_CLOUD_API_KEY?: string; - OS_PROJECT_ID?: string; - OS_ORG_ID?: string; - - // — Artifact / project — - OS_PROJECT_ARTIFACT_ROOT?: string; - OS_ARTIFACT_PATH?: string; - OS_ARTIFACT_CACHE_TTL_MS?: string; - OS_ARTIFACT_FETCH_TIMEOUT_MS?: string; - OS_DATA_DIR?: string; - OS_PROVISION_SYNC?: string; - OS_EAGER_SCHEMAS?: string; - - // — Storage — - OS_STORAGE_ADAPTER?: string; - OS_STORAGE_LOCAL_DIR?: string; - OS_S3_BUCKET?: string; - OS_S3_REGION?: string; - OS_S3_ENDPOINT?: string; - OS_S3_ACCESS_KEY_ID?: string; - OS_S3_SECRET_ACCESS_KEY?: string; - OS_S3_FORCE_PATH_STYLE?: string; - - // — Performance — - OS_KERNEL_CACHE_SIZE?: string; - OS_KERNEL_TTL_MS?: string; - OS_ENV_CACHE_TTL_MS?: string; - - // — AI — - OPENAI_API_KEY?: string; - ANTHROPIC_API_KEY?: string; - GOOGLE_GENERATIVE_AI_API_KEY?: string; - AI_MODEL?: string; - AI_GATEWAY_MODEL?: string; - - // — MCP — - MCP_SERVER_ENABLED?: string; - MCP_SERVER_NAME?: string; - MCP_SERVER_TRANSPORT?: string; - - // — Misc — - WEBHOOK_SECRET?: string; -} - -const FORWARDED_ENV_KEYS: readonly (keyof Env)[] = [ - // database - 'OS_DATABASE_URL', 'OS_DATABASE_AUTH_TOKEN', 'OS_DATABASE_DRIVER', - 'TURSO_DATABASE_URL', 'TURSO_AUTH_TOKEN', - // auth - 'AUTH_SECRET', 'OS_AUTH_SECRET', - 'AUTH_BASE_URL', 'OS_BASE_URL', - 'OS_TRUSTED_ORIGINS', 'OS_ROOT_DOMAIN', - 'GOOGLE_CLIENT_ID', 'GOOGLE_CLIENT_SECRET', - 'GITHUB_CLIENT_ID', 'GITHUB_CLIENT_SECRET', - // cloud client - 'OS_CLOUD_URL', 'OS_CLOUD_API_KEY', - 'OS_PROJECT_ID', 'OS_ORG_ID', - // artifact - 'OS_PROJECT_ARTIFACT_ROOT', 'OS_ARTIFACT_PATH', - 'OS_ARTIFACT_CACHE_TTL_MS', 'OS_ARTIFACT_FETCH_TIMEOUT_MS', - 'OS_DATA_DIR', 'OS_PROVISION_SYNC', 'OS_EAGER_SCHEMAS', - // storage - 'OS_STORAGE_ADAPTER', 'OS_STORAGE_LOCAL_DIR', - 'OS_S3_BUCKET', 'OS_S3_REGION', 'OS_S3_ENDPOINT', - 'OS_S3_ACCESS_KEY_ID', 'OS_S3_SECRET_ACCESS_KEY', 'OS_S3_FORCE_PATH_STYLE', - // performance - 'OS_KERNEL_CACHE_SIZE', 'OS_KERNEL_TTL_MS', 'OS_ENV_CACHE_TTL_MS', - // AI - 'OPENAI_API_KEY', 'ANTHROPIC_API_KEY', 'GOOGLE_GENERATIVE_AI_API_KEY', - 'AI_MODEL', 'AI_GATEWAY_MODEL', - // MCP - 'MCP_SERVER_ENABLED', 'MCP_SERVER_NAME', 'MCP_SERVER_TRANSPORT', - // misc - 'WEBHOOK_SECRET', -]; - -/** - * Durable Object class that owns a single ObjectOS container instance. - * Cloudflare routes traffic to a specific instance by Durable Object id; - * we use a single shared id so all requests hit the same long-lived - * Node.js process (control-plane state lives in Turso, not the - * container's filesystem, so this is safe and cheap). - */ -export class ObjectOSContainer extends Container { - defaultPort = 3000; - sleepAfter = '15m'; - enableInternet = true; - requiredPorts = [3000]; - - /** - * Cold start budget for the Node app to bind port 3000. The default - * in `@cloudflare/containers` is 20s which is not enough for a fresh - * boot that has to open a remote DB connection + run schema sync for - * every registered `sys_*` object. See apps/cloud/cloudflare/worker.ts - * for the full rationale — this is the same override. - */ - private readonly PORT_READY_TIMEOUT_MS = 120_000; - - override async startAndWaitForPorts( - portsOrArgs?: any, - cancellationOptions?: any, - startOptions?: any, - ): Promise { - const TIMEOUT = this.PORT_READY_TIMEOUT_MS; - if ( - portsOrArgs !== null && - typeof portsOrArgs === 'object' && - !Array.isArray(portsOrArgs) && - ('ports' in portsOrArgs || 'cancellationOptions' in portsOrArgs || 'startOptions' in portsOrArgs) - ) { - const inner = { ...(portsOrArgs.cancellationOptions ?? {}) }; - delete inner.abort; - const merged = { - ...portsOrArgs, - cancellationOptions: { - portReadyTimeoutMS: TIMEOUT, - instanceGetTimeoutMS: TIMEOUT, - ...inner, - }, - }; - return super.startAndWaitForPorts(merged); - } - const inner = { ...(cancellationOptions ?? {}) }; - delete inner.abort; - const merged = { - portReadyTimeoutMS: TIMEOUT, - instanceGetTimeoutMS: TIMEOUT, - ...inner, - }; - return super.startAndWaitForPorts(portsOrArgs, merged, startOptions); - } - - envVars: Record = { - NODE_ENV: 'production', - PORT: '3000', - HOST: '0.0.0.0', - OS_KERNEL_CACHE_SIZE: '50', - OS_KERNEL_TTL_MS: '1800000', - OS_ENV_CACHE_TTL_MS: '300000', - // Schema sync against a cold remote DB easily exceeds Workers' - // ~30s inbound budget; run migrations out of band before deploy. - OS_SKIP_SCHEMA_SYNC: '1', - // Trust the entire platform subdomain space for better-auth's - // CSRF/callbackURL check. Without this, renaming a project's - // hostname leaves the cached per-project AuthPlugin rejecting - // SSO callbacks at the new host with INVALID_CALLBACK_URL. - // Mirrors apps/cloud's posture; override via secret if needed. - OS_TRUSTED_ORIGINS: 'https://*.objectos.app,https://*.objectstack.workers.dev', - OS_ROOT_DOMAIN: 'objectos.app', - }; - - constructor(state: DurableObjectState, env: Env) { - super(state, env); - // Forward Worker-level secrets / vars into the Container process. - // Without this, `wrangler secret put X` only reaches the Worker - // (V8 isolate), NOT the Node.js container. - for (const key of FORWARDED_ENV_KEYS) { - const value = env[key]; - if (typeof value === 'string' && value.length > 0) { - this.envVars[key as string] = value; - } - } - } -} - -export default { - async fetch(request: Request, env: Env): Promise { - const container = getContainer(env.OBJECTOS, 'singleton'); - // Admin: restart container on demand (used by deploy workflow to - // force a fresh image roll-over without waiting for sleepAfter - // eviction). Requires a shared secret to avoid public abuse. - const url = new URL(request.url); - if (url.pathname === '/_admin/restart-container') { - const provided = request.headers.get('x-admin-secret') ?? ''; - const expected = env.OS_CLOUD_API_KEY ?? ''; - if (!expected || provided !== expected) { - return new Response('forbidden', { status: 403 }); - } - try { - // destroy() = SIGKILL the container; next request cold-starts - // with whatever image tag is currently bound in wrangler.toml. - await container.destroy(); - return new Response(JSON.stringify({ ok: true, action: 'destroyed' }), { - status: 200, - headers: { 'content-type': 'application/json' }, - }); - } catch (err) { - return new Response( - JSON.stringify({ ok: false, error: String((err as Error)?.message ?? err) }), - { status: 500, headers: { 'content-type': 'application/json' } }, - ); - } - } - return container.fetch(request); - }, -}; diff --git a/apps/objectos/fly.toml b/apps/objectos/fly.toml deleted file mode 100644 index fdae798f3..000000000 --- a/apps/objectos/fly.toml +++ /dev/null @@ -1,67 +0,0 @@ -# fly.toml — ObjectStack Server -# -# Deploy from the repo root so the monorepo Dockerfile can see the full -# workspace: -# -# fly deploy --config apps/objectos/fly.toml \ -# --dockerfile apps/objectos/Dockerfile -# -# Wildcard subdomain TLS (project-per-subdomain routing): -# -# fly certs add "*.objectstack.app" --app objectstack-server -# # follow the _acme-challenge DNS instructions in the output -# -# Secrets (never commit — use `fly secrets set …`): -# AUTH_SECRET, OS_COOKIE_DOMAIN, -# TURSO_ORG_NAME, TURSO_API_TOKEN, -# (optional) OS_DATABASE_URL + OS_DATABASE_AUTH_TOKEN -# — override the default local-SQLite control DB with a remote libSQL. - -app = "objectstack-server" -primary_region = "iad" -kill_signal = "SIGTERM" -kill_timeout = "30s" - -[build] - dockerfile = "Dockerfile" - -[http_service] - internal_port = 3000 - force_https = true - auto_stop_machines = true - auto_start_machines = true - min_machines_running = 1 - processes = ["app"] - - [http_service.concurrency] - type = "requests" - hard_limit = 250 - soft_limit = 200 - - [[http_service.checks]] - method = "GET" - path = "/api/v1/health" - interval = "30s" - timeout = "5s" - grace_period = "30s" - -[[vm]] - size = "shared-cpu-2x" - memory = "1gb" - -# Persistent volume for the local-SQLite control DB (multi-project-local). -# Attach with `fly volumes create objectstack_data --size 3 --region iad`. -[mounts] - source = "objectstack_data" - destination = "/data" - -[env] - # Control plane lives in the SQLite file on the attached volume. Override - # with a remote libSQL/Turso by setting `OS_DATABASE_URL` (and - # optionally `OS_DATABASE_AUTH_TOKEN`) via `fly secrets set`. - OS_DATABASE_URL = "file:/data/control.db" - OS_KERNEL_CACHE_SIZE = "50" - OS_KERNEL_TTL_MS = "1800000" - OS_ENV_CACHE_TTL_MS = "300000" - PORT = "3000" - HOST = "0.0.0.0" diff --git a/apps/objectos/objectstack.config.ts b/apps/objectos/objectstack.config.ts deleted file mode 100644 index 86f22eeb9..000000000 --- a/apps/objectos/objectstack.config.ts +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. - -/** - * ObjectStack ObjectOS — Host Configuration (runtime node) - * - * Booted by `objectstack dev` / `objectstack serve` (see `package.json`). - * - * ObjectOS is a **stateless multi-tenant runtime**. The host kernel here is - * a routing shell: every incoming request is resolved by hostname to a - * project, the project's compiled artifact is fetched (either from a - * control plane over HTTP or from a local file), and a per-project - * ObjectKernel is built on demand and cached. - * - * ## Boot modes - * - * Default (no env): connects to a locally-running `apps/cloud` on - * `http://localhost:4000`. Spin both up side-by-side for the natural - * dev loop (`apps/cloud` for control plane + Studio, `apps/objectos` - * for the actual runtime that serves project data). - * - * Override via env: - * - `OS_CLOUD_URL=https://cloud.objectstack.ai` — point at the - * hosted ObjectStack Cloud (or any other control plane). - * - `OS_CLOUD_URL=file` — file-backed single-project mode. Reads one - * compiled artifact from `dist/objectstack.json` (or - * `OS_ARTIFACT_PATH`) and serves it on every hostname. Use this - * for smoke tests / one-shot demos where bringing up a separate - * control plane is overkill. - * - * No `@objectstack/service-cloud` dependency — `apps/objectos` only - * needs the runtime + the artifact loader, both of which live in - * `@objectstack/runtime`. - */ - -import { createObjectOSStack } from '@objectstack/runtime'; - -const cloudUrl = process.env.OS_CLOUD_URL?.trim() || 'http://localhost:4000'; - -const config = await createObjectOSStack({ - controlPlaneUrl: cloudUrl, - controlPlaneApiKey: process.env.OS_CLOUD_API_KEY, - fileConfig: cloudUrl === 'file' - ? { - artifactPath: process.env.OS_ARTIFACT_PATH, - projectId: process.env.OS_PROJECT_ID, - } - : undefined, -}); - -export default config; diff --git a/apps/objectos/package.json b/apps/objectos/package.json deleted file mode 100644 index cd0f2ee8e..000000000 --- a/apps/objectos/package.json +++ /dev/null @@ -1,66 +0,0 @@ -{ - "name": "@objectstack/objectos", - "version": "4.0.5", - "license": "Apache-2.0", - "type": "module", - "private": true, - "scripts": { - "dev": "objectstack dev", - "build": "tsup", - "start": "objectstack serve dist/objectstack.config.js --prebuilt", - "doctor": "objectstack doctor", - "typecheck": "tsc --noEmit", - "test": "objectstack test", - "test:e2e": "tsx test/multi-project-e2e.test.ts", - "test:provisioning": "tsx test/provisioning.test.ts", - "test:crm-bundle": "tsx test/single-project-crm-bundle.test.ts", - "test:multi-bundles": "tsx test/multi-project-bundles.test.ts", - "cf:build": "bash scripts/deploy-cloudflare.sh --skip-push --skip-deploy", - "cf:push": "bash scripts/deploy-cloudflare.sh --skip-build --skip-deploy", - "cf:deploy": "bash scripts/deploy-cloudflare.sh", - "cf:deploy:dry": "bash scripts/deploy-cloudflare.sh --dry-run", - "cf:secrets": "bash scripts/setup-cloudflare-secrets.sh", - "cf:tail": "wrangler tail --config wrangler.toml", - "clean": "rm -rf dist node_modules" - }, - "dependencies": { - "@example/app-todo": "workspace:*", - "@hono/node-server": "^2.0.3", - "@libsql/client": "^0.17.3", - "@objectstack/account": "workspace:*", - "@objectstack/cli": "workspace:*", - "@objectstack/console": "workspace:*", - "@objectstack/driver-memory": "workspace:*", - "@objectstack/driver-mongodb": "workspace:*", - "@objectstack/driver-sql": "workspace:*", - "@objectstack/driver-turso": "workspace:*", - "@objectstack/example-crm": "workspace:*", - "@objectstack/hono": "workspace:*", - "@objectstack/metadata": "workspace:*", - "@objectstack/objectql": "workspace:*", - "@objectstack/plugin-audit": "workspace:*", - "@objectstack/plugin-auth": "workspace:*", - "@objectstack/plugin-hono-server": "workspace:*", - "@objectstack/plugin-security": "workspace:*", - "@objectstack/runtime": "workspace:*", - "@objectstack/service-ai": "workspace:*", - "@objectstack/service-analytics": "workspace:*", - "@objectstack/service-automation": "workspace:*", - "@objectstack/service-feed": "workspace:*", - "@objectstack/service-i18n": "workspace:*", - "@objectstack/service-package": "workspace:*", - "@objectstack/service-tenant": "workspace:*", - "@objectstack/spec": "workspace:*", - "hono": "^4.12.21" - }, - "devDependencies": { - "@cloudflare/containers": "^0.3.4", - "@cloudflare/workers-types": "^4.20260520.1", - "esbuild": "^0.28.0", - "ts-node": "^10.9.2", - "tsup": "^8.5.1", - "tsx": "^4.22.3", - "typescript": "^6.0.3", - "wrangler": "^4.93.0" - } -} diff --git a/apps/objectos/scripts/build-vercel.sh b/apps/objectos/scripts/build-vercel.sh deleted file mode 100755 index be65c5e88..000000000 --- a/apps/objectos/scripts/build-vercel.sh +++ /dev/null @@ -1,104 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -# Build script for Vercel deployment of @objectstack/objectos. -# -# Follows the Vercel deployment pattern: -# - api/[[...route]].js is committed to the repo (Vercel detects it pre-build) -# - esbuild bundles server/index.ts → api/_handler.js (self-contained bundle) -# - The committed .js wrapper re-exports from _handler.js at runtime -# - Studio SPA is built and copied to public/ for serving the UI -# - External dependencies installed in api/node_modules/ (no symlinks) -# -# Steps: -# 1. Build the project with turbo (includes studio) -# 2. Bundle the API serverless function (→ api/_handler.js) -# 3. Copy studio dist files to public/ for UI serving -# 4. Install external deps in api/node_modules/ (resolve pnpm symlinks) - -echo "[build-vercel] Starting server build..." - -# 1. Build the project with turbo (from monorepo root) -# This builds server, studio, account, and the runtime console portal. -cd ../.. -pnpm turbo run build --filter=@objectstack/objectos --filter=@objectstack/studio --filter=@objectstack/account --filter=@objectstack/console -cd apps/objectos - -# 1b. Compile objectstack.config.ts → dist/objectstack.json (the metadata artifact). -# MetadataPlugin reads this file at startup in local mode. Without it the kernel -# cannot boot ("Cannot read artifact file … ENOENT"). -echo "[build-vercel] Compiling objectstack artifact..." -pnpm objectstack build -echo "[build-vercel] ✓ dist/objectstack.json generated" - -# 2. Bundle API serverless function -node scripts/bundle-api.mjs - -# 2b. Copy the artifact into api/dist/ so Vercel includes it in the function -# deployment package. The function runs with CWD=/var/task and resolves the -# artifact relative to its own directory (api/), so api/dist/objectstack.json -# maps to /var/task/apps/objectos/api/dist/objectstack.json at runtime. -echo "[build-vercel] Copying artifact into api/dist/..." -mkdir -p api/dist -cp dist/objectstack.json api/dist/objectstack.json -echo "[build-vercel] ✓ api/dist/objectstack.json ready" - -# 3. Copy studio dist files to public/_studio/ for UI serving. -# Studio is always mounted under /_studio/ (same convention as the CLI -# static plugin). Vite builds with base: '/_studio/' so its asset URLs -# and router basepath are already correct for this mount point. -echo "[build-vercel] Copying studio dist to public/_studio/..." -rm -rf public -mkdir -p public/_studio -if [ -d "../studio/dist" ]; then - cp -r ../studio/dist/. public/_studio/ - echo "[build-vercel] ✓ Copied studio dist to public/_studio/" -else - echo "[build-vercel] ⚠ Studio dist not found (skipped)" -fi - -# 3b. Copy the account portal dist to public/_account/ — same pattern as -# studio. The account SPA is always built with base: '/_account/'. -echo "[build-vercel] Copying account dist to public/_account/..." -mkdir -p public/_account -if [ -d "../account/dist" ]; then - cp -r ../account/dist/. public/_account/ - echo "[build-vercel] ✓ Copied account dist to public/_account/" -else - echo "[build-vercel] ⚠ Account dist not found (skipped)" -fi - -# 3c. Copy the runtime Console SPA to public/_console/ — same pattern as -# studio. The Console is built with base: '/_console/'. -echo "[build-vercel] Copying console dist to public/_console/..." -mkdir -p public/_console -if [ -d "../console/dist" ]; then - cp -r ../console/dist/. public/_console/ - echo "[build-vercel] ✓ Copied console dist to public/_console/" -else - echo "[build-vercel] ⚠ Console dist not found (skipped)" -fi - -# 4. Install external dependencies in api/node_modules/ (no symlinks) -# pnpm uses symlinks in node_modules/, which Vercel's serverless function -# packaging cannot handle ("invalid deployment package" error). -# We use npm to install external packages as real files next to the handler. -echo "[build-vercel] Installing external dependencies for serverless function..." -cat > api/_package.json << 'DEPS' -{ - "private": true, - "dependencies": { - "@libsql/client": "0.14.0", - "pino": "10.3.1", - "pino-pretty": "13.1.3" - } -} -DEPS -cd api -mv _package.json package.json -npm install --production --no-package-lock --ignore-scripts --loglevel error -rm package.json -cd .. -echo "[build-vercel] ✓ External dependencies installed in api/node_modules/" - -echo "[build-vercel] Done. Static files in public/, serverless function in api/[[...route]].js → api/_handler.js" diff --git a/apps/objectos/scripts/bundle-api.mjs b/apps/objectos/scripts/bundle-api.mjs deleted file mode 100644 index 8c908d12f..000000000 --- a/apps/objectos/scripts/bundle-api.mjs +++ /dev/null @@ -1,66 +0,0 @@ -/** - * Pre-bundles the Vercel serverless API function. - * - * Vercel's @vercel/node builder resolves pnpm workspace packages via symlinks, - * which can cause esbuild to resolve to TypeScript source files rather than - * compiled dist output — producing ERR_MODULE_NOT_FOUND at runtime. - * - * This script bundles server/index.ts with ALL dependencies inlined (including - * npm packages), so the deployed function is self-contained. Only packages - * with native bindings are kept external. - * - * Run from the apps/objectos directory during the Vercel build step. - */ - -import { build } from 'esbuild'; - -// Packages that cannot be bundled (native bindings / optional drivers) -const EXTERNAL = [ - // Optional knex database drivers — never used at runtime, but knex requires() them - 'pg', - 'pg-native', - 'pg-query-stream', - 'mysql', - 'mysql2', - 'sqlite3', - 'oracledb', - 'tedious', - // macOS-only native file watcher - 'fsevents', - // LibSQL client — has native bindings, must remain external for Vercel - '@libsql/client', - // Logging libraries - use dynamic require, must be external - 'pino', - 'pino-pretty', -]; - -await build({ - entryPoints: ['server/index.ts'], - bundle: true, - platform: 'node', - format: 'esm', - target: 'es2022', - outfile: 'api/_handler.js', - sourcemap: true, - external: EXTERNAL, - // Silence warnings about optional/unused require() calls in knex drivers - logOverride: { 'require-resolve-not-external': 'silent' }, - // Vercel resolves ESM .js files correctly when "type": "module" is set. - // CJS format would conflict with the project's "type": "module" setting, - // causing Node.js to fail parsing require()/module.exports as ESM syntax. - // - // The createRequire banner provides a real `require` function in the ESM - // scope. esbuild's __require shim (generated for CJS→ESM conversion) - // checks `typeof require !== "undefined"` and uses it when available, - // which fixes "Dynamic require of is not supported" errors - // from CJS dependencies like knex/tarn that require() Node.js built-ins. - banner: { - js: [ - '// Bundled by esbuild — see scripts/bundle-api.mjs', - 'import { createRequire } from "module";', - 'const require = createRequire(import.meta.url);', - ].join('\n'), - }, -}); - -console.log('[bundle-api] Bundled server/index.ts → api/_handler.js'); diff --git a/apps/objectos/scripts/deploy-cloudflare.sh b/apps/objectos/scripts/deploy-cloudflare.sh deleted file mode 100755 index bf6b56080..000000000 --- a/apps/objectos/scripts/deploy-cloudflare.sh +++ /dev/null @@ -1,173 +0,0 @@ -#!/usr/bin/env bash -# ───────────────────────────────────────────────────────────────────────────── -# deploy-cloudflare.sh — build → push → deploy ObjectOS to Cloudflare Containers -# -# Idempotent end-to-end pipeline. Run from anywhere; resolves the repo root -# automatically. Reads config from `apps/objectos/.env.cloudflare` (gitignored) -# or from environment variables (for CI). -# -# pnpm --filter @objectstack/objectos cf:deploy # full pipeline -# pnpm --filter @objectstack/objectos cf:deploy -- --skip-build -# pnpm --filter @objectstack/objectos cf:deploy -- --tag v2 -# -# Required config (env or .env.cloudflare): -# CLOUDFLARE_ACCOUNT_ID Cloudflare account id (alias: CF_ACCOUNT_ID) -# CLOUDFLARE_API_TOKEN API token (or run `npx wrangler login` once) -# Needed scopes: Workers Scripts:Edit, -# Account → Cloudflare Images:Edit (containers). -# CF_IMAGE_REGISTRY Default: registry.cloudflare.com/$CLOUDFLARE_ACCOUNT_ID -# CF_IMAGE_NAME Default: objectos -# CF_IMAGE_TAG Default: $(git rev-parse --short HEAD) -# CF_PLATFORM Default: linux/amd64 (Cloudflare Containers requirement) -# ───────────────────────────────────────────────────────────────────────────── -set -euo pipefail - -APP_NAME="objectos" -APP_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" -REPO_ROOT="$(cd "$APP_DIR/../.." && pwd)" -WRANGLER_TOML="$APP_DIR/wrangler.toml" -DOCKERFILE="$APP_DIR/Dockerfile" -ENV_FILE="$APP_DIR/.env.cloudflare" - -# ── Load .env.cloudflare if present ───────────────────────────────────────── -if [[ -f "$ENV_FILE" ]]; then - echo "→ loading $ENV_FILE" - set -a; source "$ENV_FILE"; set +a -fi - -# ── Defaults ──────────────────────────────────────────────────────────────── -: "${CF_IMAGE_NAME:=$APP_NAME}" -: "${CF_IMAGE_TAG:=$(cd "$REPO_ROOT" && git rev-parse --short HEAD 2>/dev/null || echo latest)}" -: "${CF_PLATFORM:=linux/amd64}" - -# ── Parse flags ───────────────────────────────────────────────────────────── -SKIP_BUILD=0 -SKIP_PUSH=0 -SKIP_DEPLOY=0 -DRY_RUN=0 -while [[ $# -gt 0 ]]; do - case "$1" in - --) shift ;; - --skip-build) SKIP_BUILD=1; shift ;; - --skip-push) SKIP_PUSH=1; shift ;; - --skip-deploy) SKIP_DEPLOY=1; shift ;; - --dry-run) DRY_RUN=1; shift ;; - --tag) CF_IMAGE_TAG="$2"; shift 2 ;; - --tag=*) CF_IMAGE_TAG="${1#--tag=}"; shift ;; - -h|--help) - grep -E '^#( |$)' "$0" | sed -E 's/^# ?//' - exit 0 ;; - *) echo "unknown arg: $1" >&2; exit 2 ;; - esac -done - -# ── Validate ──────────────────────────────────────────────────────────────── -# Accept both modern (CLOUDFLARE_*) and legacy (CF_*) env names. -# Wrangler v4 emits a deprecation warning for CF_ACCOUNT_ID, so promote -# whatever we have to CLOUDFLARE_ACCOUNT_ID and re-export. -: "${CLOUDFLARE_ACCOUNT_ID:=${CF_ACCOUNT_ID:-}}" -: "${CF_ACCOUNT_ID:=${CLOUDFLARE_ACCOUNT_ID:-}}" -: "${CLOUDFLARE_API_TOKEN:=${CF_API_TOKEN:-}}" -export CLOUDFLARE_ACCOUNT_ID CF_ACCOUNT_ID CLOUDFLARE_API_TOKEN - -if [[ -z "${CLOUDFLARE_ACCOUNT_ID:-}" ]]; then - echo "✗ CLOUDFLARE_ACCOUNT_ID is required (set in $ENV_FILE or env)" >&2 - echo " Run: npx wrangler whoami" >&2 - exit 1 -fi - -# Preflight auth: wrangler emits a cryptic 'Unauthorized' when neither a -# token nor a `wrangler login` session exists. Catch it early. -if [[ -z "${CLOUDFLARE_API_TOKEN:-}" ]]; then - if ! npx --yes wrangler whoami >/dev/null 2>&1; then - echo "✗ Not authenticated with Cloudflare." >&2 - echo " Add one of these to $ENV_FILE (or export to env):" >&2 - echo " CLOUDFLARE_API_TOKEN=" >&2 - echo " …or run \`npx wrangler login\` once on this machine." >&2 - echo " Token needs: Workers Scripts:Edit, Account → Cloudflare Images:Edit (containers)." >&2 - exit 1 - fi -fi - -: "${CF_IMAGE_REGISTRY:=registry.cloudflare.com/$CLOUDFLARE_ACCOUNT_ID}" -IMAGE="$CF_IMAGE_REGISTRY/$CF_IMAGE_NAME:$CF_IMAGE_TAG" - -echo "════════════════════════════════════════════════════════════════" -echo " App : $APP_NAME" -echo " Repo : $REPO_ROOT" -echo " Image : $IMAGE" -echo " Platform: $CF_PLATFORM" -echo " Wrangler: $WRANGLER_TOML" -echo "════════════════════════════════════════════════════════════════" - -run() { - if [[ $DRY_RUN -eq 1 ]]; then echo "[dry-run] $*"; else "$@"; fi -} - -# ── 1. Build ──────────────────────────────────────────────────────────────── -if [[ $SKIP_BUILD -eq 0 ]]; then - echo "" - echo "▶ [1/3] docker buildx build" - command -v docker >/dev/null || { echo "✗ docker not installed" >&2; exit 1; } - run docker buildx build \ - --platform "$CF_PLATFORM" \ - -f "$DOCKERFILE" \ - -t "$IMAGE" \ - --provenance=false \ - --sbom=false \ - --load \ - "$REPO_ROOT" -else - echo "▶ [1/3] skipped (--skip-build)" -fi - -# ── 2. Push ───────────────────────────────────────────────────────────────── -if [[ $SKIP_PUSH -eq 0 ]]; then - echo "" - echo "▶ [2/3] wrangler containers push" - if [[ $DRY_RUN -eq 1 ]]; then - echo "[dry-run] npx --yes wrangler containers push $IMAGE" - else - if ! npx --yes wrangler containers push "$IMAGE"; then - echo "" >&2 - echo "✗ wrangler containers push failed." >&2 - echo " Common causes:" >&2 - echo " • API token / OAuth session missing scope" >&2 - echo " Cloudflare Images:Edit (container registry push)" >&2 - echo " Workers Scripts:Edit (cf:deploy step)" >&2 - echo " • Account not enrolled in Cloudflare Containers beta" >&2 - echo " https://developers.cloudflare.com/containers/" >&2 - echo " • Token regenerated; refresh CLOUDFLARE_API_TOKEN in $ENV_FILE" >&2 - echo "" >&2 - exit 1 - fi - fi -else - echo "▶ [2/3] skipped (--skip-push)" -fi - -# ── 3. Sync wrangler.toml + deploy ────────────────────────────────────────── -if [[ $SKIP_DEPLOY -eq 0 ]]; then - echo "" - echo "▶ [3/3] update wrangler.toml image → $IMAGE" - if [[ $DRY_RUN -eq 0 ]]; then - # Replace the entire `image = "..."` line under [[containers]]. - # Cross-platform sed (BSD on macOS needs '' after -i). - if [[ "$OSTYPE" == "darwin"* ]]; then - sed -i '' -E "s|^image = \".*\"|image = \"$IMAGE\"|" "$WRANGLER_TOML" - else - sed -i -E "s|^image = \".*\"|image = \"$IMAGE\"|" "$WRANGLER_TOML" - fi - fi - - echo "" - echo "▶ wrangler deploy" - run npx --yes wrangler deploy --config "$WRANGLER_TOML" -else - echo "▶ [3/3] skipped (--skip-deploy)" -fi - -echo "" -echo "✓ done — $IMAGE" -echo " Tail logs : (cd $APP_DIR && npx wrangler tail)" -echo " Health : curl https://$APP_NAME..workers.dev/api/v1/health" diff --git a/apps/objectos/scripts/setup-cloudflare-secrets.sh b/apps/objectos/scripts/setup-cloudflare-secrets.sh deleted file mode 100755 index 83fd9c457..000000000 --- a/apps/objectos/scripts/setup-cloudflare-secrets.sh +++ /dev/null @@ -1,101 +0,0 @@ -#!/usr/bin/env bash -# ───────────────────────────────────────────────────────────────────────────── -# setup-cloudflare-secrets.sh — bulk-push secrets to a Cloudflare Worker. -# -# Reads values from `.env.cloudflare.secrets` (gitignored) and pipes each -# one to `wrangler secret put`. Safe to re-run — wrangler upserts secrets. -# -# pnpm --filter @objectstack/objectos cf:secrets -# pnpm --filter @objectstack/cloud cf:secrets -# -# .env.cloudflare.secrets format (one key=value per line, # for comments): -# -# OS_DATABASE_URL=libsql://my-control.turso.io -# OS_DATABASE_AUTH_TOKEN=eyJhbGciOi... -# AUTH_SECRET= -# # Cloud-only: -# TURSO_API_TOKEN=... -# TURSO_ORG_NAME=... -# # ObjectOS multi-project mode: -# OS_CLOUD_URL=https://objectstack-cloud..workers.dev -# OS_CLOUD_API_KEY=... -# -# Any unset variable is *skipped* (not cleared) so you can keep one shared -# file across both apps. -# ───────────────────────────────────────────────────────────────────────────── -set -euo pipefail - -APP_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" -WRANGLER_TOML="$APP_DIR/wrangler.toml" -SECRETS_FILE="${SECRETS_FILE:-$APP_DIR/.env.cloudflare.secrets}" - -# Per-app key allow-list. Secrets not in the list are ignored so we can -# share one .env.cloudflare.secrets file across apps without leaking -# unrelated keys to a Worker that has no use for them. -APP_BASENAME="$(basename "$APP_DIR")" -case "$APP_BASENAME" in - objectos) - KEYS=( OS_DATABASE_URL OS_DATABASE_AUTH_TOKEN AUTH_SECRET - OS_CLOUD_URL OS_CLOUD_API_KEY OS_COOKIE_DOMAIN OS_ROOT_DOMAIN ) ;; - cloud) - KEYS=( OS_DATABASE_URL OS_CONTROL_DATABASE_URL OS_DATABASE_AUTH_TOKEN AUTH_SECRET - OS_CONTROL_PG_POOL_MIN OS_CONTROL_PG_POOL_MAX - TURSO_API_TOKEN TURSO_ORG_NAME - OS_CLOUD_API_KEY OS_COOKIE_DOMAIN OS_ROOT_DOMAIN ) ;; - *) - echo "✗ unknown app dir: $APP_BASENAME" >&2; exit 1 ;; -esac - -if [[ ! -f "$SECRETS_FILE" ]]; then - echo "✗ no $SECRETS_FILE — copy .env.cloudflare.secrets.example and fill it in" >&2 - exit 1 -fi - -# Parse KEY=VALUE lines manually instead of `set -a; source`. -# Why: `source` runs the file as bash, so values containing shell -# metachars (& ; $ < > |) silently break — e.g. a Postgres URL with -# `?sslmode=require&channel_binding=require` would have everything -# after the `&` treated as a background command and the variable would -# be set to the truncated prefix (or empty). Manual parsing reads the -# raw bytes and supports optional surrounding single/double quotes. -# -# Note: macOS ships bash 3.2 which lacks associative arrays, so we -# extract one key at a time on demand rather than building a map. -read_secret() { - local target="$1" - local line key value - while IFS= read -r line || [[ -n "$line" ]]; do - [[ -z "${line//[[:space:]]/}" ]] && continue - [[ "$line" =~ ^[[:space:]]*# ]] && continue - line="${line#export }" - [[ "$line" =~ ^([A-Za-z_][A-Za-z0-9_]*)=(.*)$ ]] || continue - key="${BASH_REMATCH[1]}" - [[ "$key" != "$target" ]] && continue - value="${BASH_REMATCH[2]}" - if [[ "$value" =~ ^\"(.*)\"$ ]]; then value="${BASH_REMATCH[1]}" - elif [[ "$value" =~ ^\'(.*)\'$ ]]; then value="${BASH_REMATCH[1]}" - fi - value="${value%$'\r'}" - printf '%s' "$value" - return 0 - done < "$SECRETS_FILE" - return 0 -} - -echo "→ pushing secrets to Worker defined in $WRANGLER_TOML" -PUSHED=0; SKIPPED=0 -for key in "${KEYS[@]}"; do - value="$(read_secret "$key")" - if [[ -z "$value" ]]; then - echo " · $key (skipped — not set)" - SKIPPED=$((SKIPPED+1)) - continue - fi - echo " ✓ $key (${#value} chars)" - printf '%s' "$value" | npx --yes wrangler secret put "$key" \ - --config "$WRANGLER_TOML" >/dev/null - PUSHED=$((PUSHED+1)) -done - -echo "" -echo "✓ pushed=$PUSHED skipped=$SKIPPED" diff --git a/apps/objectos/server/index.ts b/apps/objectos/server/index.ts deleted file mode 100644 index f7b758374..000000000 --- a/apps/objectos/server/index.ts +++ /dev/null @@ -1,419 +0,0 @@ -// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. - -/** - * Vercel Serverless API Entrypoint - * - * Boots the ObjectStack kernel from the shared `objectstack.config.ts` - * and delegates all `/api/*` traffic to the Hono adapter. The same - * `ensureApp()` / `ensureBoot()` singletons are reused by the E2E test - * harness — local `pnpm dev` is served by the `objectstack dev` CLI and - * does not import this file. - */ - -import { createHonoApp } from '@objectstack/hono'; -import { createOriginMatcher, hasWildcardPattern, HonoHttpServer } from '@objectstack/plugin-hono-server'; -import { getRequestListener } from '@hono/node-server'; -import { ObjectKernel, createRestApiPlugin, createDispatcherPlugin, KernelManager } from '@objectstack/runtime'; -import type { EnvironmentDriverRegistry } from '@objectstack/runtime'; -import type { Hono } from 'hono'; -import stackConfig from '../objectstack.config.js'; - -// --------------------------------------------------------------------------- -// Runtime shape returned by ensureBoot() -// --------------------------------------------------------------------------- - -export interface BootResult { - kernel: ObjectKernel; - kernelManager?: KernelManager; - envRegistry?: EnvironmentDriverRegistry; -} - -// --------------------------------------------------------------------------- -// Singleton state — persists across warm Vercel invocations -// --------------------------------------------------------------------------- - -let _boot: BootResult | null = null; -let _app: Hono | null = null; - -/** Shared boot promise — prevents concurrent cold-start races. */ -let _bootPromise: Promise | null = null; - -async function bootKernel(): Promise { - const kernel = new ObjectKernel(); - - // 0. Register an `http.server` (IHttpServer) adapter BEFORE plugins so - // that any plugin's start() hook can resolve `ctx.getService('http.server')` - // and register routes on it. This is the official ObjectStack - // protocol for plugin-supplied HTTP routes (see IHttpServer in - // @objectstack/spec/contracts and HonoServerPlugin's reference - // implementation). The Vercel entrypoint cannot use HonoServerPlugin - // itself because we don't want plugin-hono-server to call listen() — - // Vercel hands us a request directly. Reusing the same adapter class - // keeps route-registration semantics identical between local - // (`objectstack dev`) and serverless deployments. - const httpServer = new HonoHttpServer(); - kernel.registerService('http.server', httpServer); - kernel.registerService('http-server', httpServer); // alias for backward compatibility - - // Unknown-environment hostname guard. - // - // In multi-tenant cloud deployments (objectos.app), every public - // hostname is expected to map to a `sys_environment` row whose - // hostname column matches the request `Host`. Without this guard, - // an unknown subdomain like `demo-xxx.objectos.app` happily renders - // the control-plane console (because the console SPA is served - // statically and ignores the host), making the deployment look like - // it has data when it doesn't. We respond with a clear 404 instead. - // - // Activation: only when OS_ROOT_DOMAIN is set (e.g. "objectos.app"). - // Reserved subdomains (cloud/www/api/docs/admin) bypass the check so - // the platform's own surfaces and infra endpoints keep working. - // Custom domains that aren't subdomains of the root are passed - // through unchanged — a tenant's bring-your-own-domain still needs - // to be looked up via envRegistry, but a miss there falls back to - // the legacy behaviour to avoid blocking unknown-yet-valid hosts. - const rootDomain = (process.env.OS_ROOT_DOMAIN || '').trim().toLowerCase(); - if (rootDomain) { - const RESERVED = new Set(['', 'cloud', 'www', 'api', 'docs', 'admin', 'app']); - const rawApp = httpServer.getRawApp(); - let envRegistryRef: any; - const getEnvRegistry = async () => { - if (envRegistryRef !== undefined) return envRegistryRef; - try { - envRegistryRef = await (kernel as any).getServiceAsync?.('env-registry') ?? null; - } catch { - envRegistryRef = null; - } - return envRegistryRef; - }; - rawApp.use('*', async (c: any, next: any) => { - const rawHost = c.req.header('host') || ''; - const host = rawHost.split(':')[0].toLowerCase(); - if (!host) return next(); - const isPlatformHost = host === rootDomain || host.endsWith('.' + rootDomain); - if (!isPlatformHost) return next(); - const sub = host === rootDomain ? '' : host.slice(0, -(rootDomain.length + 1)); - // Treat any reserved subdomain (and apex) as platform infra, - // not a tenant env. Also allow nested platform prefixes like - // `api.cloud.objectos.app`. - const head = sub.split('.').pop() || ''; - if (RESERVED.has(sub) || RESERVED.has(head)) return next(); - // Always allow platform-level infra endpoints regardless of host. - const p = c.req.path; - if (p.startsWith('/_admin/') || p === '/_admin' || p.startsWith('/.well-known/')) { - return next(); - } - const registry = await getEnvRegistry(); - if (!registry || typeof registry.resolveByHostname !== 'function') { - // Registry unavailable — don't synthesize a 404 (could be - // a boot-time race or a non-cloud deployment). - return next(); - } - try { - const hit = await registry.resolveByHostname(host); - if (hit) return next(); - } catch { - return next(); - } - return c.json( - { - error: 'environment_not_found', - message: `No environment is bound to hostname '${host}'.`, - hostname: host, - }, - 404, - ); - }); - } - - // 1. Config plugins (control-plane preset + MultiProjectPlugin + Auth/Security/Audit). - // AuthPlugin registers the platform Setup App via its manifest - // (definition lives in @objectstack/platform-objects/apps). - for (const plugin of stackConfig.plugins ?? []) { - await kernel.use(plugin as any); - } - - // 2. REST API + Dispatcher — consume the scoping config from stackConfig.api - const api = (stackConfig as any).api ?? {}; - try { - await kernel.use( - createRestApiPlugin({ api: { api } } as any), - ); - } catch { /* optional */ } - try { - await kernel.use( - createDispatcherPlugin({ scoping: api }), - ); - } catch { /* optional */ } - - await kernel.bootstrap(); - - const getOptionalService = async (name: string): Promise => { - try { return await (kernel as any).getServiceAsync(name) as T; } catch { return undefined; } - }; - const envRegistry = await getOptionalService('env-registry'); - const kernelManager = await getOptionalService('kernel-manager'); - - return { kernel, kernelManager, envRegistry }; -} - -async function ensureBoot(): Promise { - if (_boot) return _boot; - if (_bootPromise) return _bootPromise; - - _bootPromise = (async () => { - console.log('[ObjectStack] Booting kernel...'); - try { - const result = await bootKernel(); - _boot = result; - console.log('[ObjectStack] Kernel ready.'); - return result; - } catch (err) { - _bootPromise = null; - console.error('[ObjectStack] Kernel boot failed:', (err as any)?.message || err); - throw err; - } - })(); - - return _bootPromise; -} - -// --------------------------------------------------------------------------- -// Hono app factory -// --------------------------------------------------------------------------- - -async function ensureApp(): Promise { - if (_app) return _app; - - const { kernel } = await ensureBoot(); - - // Plugins have already registered their routes onto the IHttpServer - // (HonoHttpServer) we created in bootKernel(). Pull out its underlying - // Hono so those plugin routes are matched FIRST, then mount the - // dispatcher app underneath via `outer.route('/', inner)` — Hono uses - // registration-order priority, so the plugin routes win the match - // against the dispatcher's catch-all `/api/v1/*` handler. - const httpServer = kernel.getService('http.server'); - const outer = httpServer.getRawApp(); - - const inner = createHonoApp({ kernel, prefix: '/api/v1' }); - outer.route('/', inner); - - _app = outer; - return _app; -} - -export { ensureApp, ensureBoot }; - -// --------------------------------------------------------------------------- -// CORS headers — applied to responses that bypass the Hono app -// (bootstrap failures, preflight short-circuit). Mirrors the defaults of -// `createHonoApp()` so behaviour is identical whether or not the kernel -// has finished booting. See packages/adapters/hono/src/index.ts. -// -// Controlled by the same environment variables as the Hono adapter: -// CORS_ENABLED, CORS_ORIGIN, CORS_CREDENTIALS, CORS_MAX_AGE. -// --------------------------------------------------------------------------- - -const CORS_ALLOW_METHODS = 'GET,POST,PUT,DELETE,PATCH,HEAD,OPTIONS'; -const CORS_ALLOW_HEADERS = 'Content-Type,Authorization,X-Requested-With'; - -function corsEnabled(): boolean { - return process.env.CORS_ENABLED !== 'false'; -} - -function corsCredentials(): boolean { - return process.env.CORS_CREDENTIALS !== 'false'; -} - -function corsMaxAge(): number { - return process.env.CORS_MAX_AGE ? parseInt(process.env.CORS_MAX_AGE, 10) : 86400; -} - -function originMatches(pattern: string, origin: string): boolean { - if (pattern === origin) return true; - if (!pattern.includes('*')) return false; - const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, '\\$&').replace(/\*/g, '.*'); - return new RegExp(`^${escaped}$`).test(origin); -} - -function resolveAllowOrigin(requestOrigin: string | null): string | null { - const credentials = corsCredentials(); - const envOrigin = process.env.CORS_ORIGIN?.trim(); - - if (!envOrigin) { - if (requestOrigin) return requestOrigin; - return credentials ? null : '*'; - } - - if (envOrigin === '*') { - if (credentials) return requestOrigin || null; - return '*'; - } - - if (hasWildcardPattern(envOrigin)) { - if (!requestOrigin) return null; - return createOriginMatcher(envOrigin)(requestOrigin); - } - - const allowed = envOrigin.includes(',') - ? envOrigin.split(',').map((s: string) => s.trim()).filter(Boolean) - : [envOrigin]; - - if (requestOrigin && allowed.some(pattern => originMatches(pattern, requestOrigin))) return requestOrigin; - if (allowed.length === 1 && !requestOrigin) return allowed[0]; - return null; -} - -function withCorsHeaders(response: Response, request: Request): Response { - if (!corsEnabled()) return response; - - const requestOrigin = request.headers.get('origin'); - const allowOrigin = resolveAllowOrigin(requestOrigin); - if (!allowOrigin) return response; - - const headers = new Headers(response.headers); - headers.set('Access-Control-Allow-Origin', allowOrigin); - if (corsCredentials()) { - headers.set('Access-Control-Allow-Credentials', 'true'); - } - const existingVary = headers.get('Vary'); - if (!existingVary) { - headers.set('Vary', 'Origin'); - } else if (!/(^|,\s*)Origin(\s*,|$)/i.test(existingVary)) { - headers.set('Vary', `${existingVary}, Origin`); - } - - return new Response(response.body, { - status: response.status, - statusText: response.statusText, - headers, - }); -} - -function buildPreflightResponse(request: Request): Response { - const requestOrigin = request.headers.get('origin'); - const allowOrigin = resolveAllowOrigin(requestOrigin); - - if (!allowOrigin) { - return new Response(null, { status: 204 }); - } - - const requestedHeaders = request.headers.get('access-control-request-headers'); - const headers = new Headers({ - 'Access-Control-Allow-Origin': allowOrigin, - 'Access-Control-Allow-Methods': CORS_ALLOW_METHODS, - 'Access-Control-Allow-Headers': requestedHeaders || CORS_ALLOW_HEADERS, - 'Access-Control-Max-Age': String(corsMaxAge()), - Vary: 'Origin, Access-Control-Request-Headers', - }); - if (corsCredentials()) { - headers.set('Access-Control-Allow-Credentials', 'true'); - } - return new Response(null, { status: 204, headers }); -} - -// --------------------------------------------------------------------------- -// Body extraction — reads Vercel's pre-buffered request body. -// --------------------------------------------------------------------------- - -interface VercelIncomingMessage { - rawBody?: Buffer | string; - body?: unknown; - headers?: Record; -} - -interface VercelEnv { - incoming?: VercelIncomingMessage; -} - -function extractBody( - incoming: VercelIncomingMessage, - method: string, - contentType: string | undefined, -): any { - if (method === 'GET' || method === 'HEAD' || method === 'OPTIONS') return null; - - if (incoming.rawBody != null) { - return incoming.rawBody; - } - - if (incoming.body != null) { - if (typeof incoming.body === 'string') return incoming.body; - if (contentType?.includes('application/json')) return JSON.stringify(incoming.body); - return String(incoming.body); - } - - return null; -} - -function resolvePublicUrl( - requestUrl: string, - incoming: VercelIncomingMessage | undefined, -): string { - if (!incoming) return requestUrl; - const fwdProto = incoming.headers?.['x-forwarded-proto']; - const rawProto = Array.isArray(fwdProto) ? fwdProto[0] : fwdProto; - const proto = rawProto === 'https' || rawProto === 'http' ? rawProto : undefined; - if (proto === 'https' && requestUrl.startsWith('http:')) { - return requestUrl.replace(/^http:/, 'https:'); - } - return requestUrl; -} - -// --------------------------------------------------------------------------- -// Vercel Node.js serverless handler -// --------------------------------------------------------------------------- - -export default getRequestListener(async (request, env) => { - const method = request.method.toUpperCase(); - const incoming = (env as VercelEnv)?.incoming; - const url = resolvePublicUrl(request.url, incoming); - - if (method === 'OPTIONS') { - console.log(`[Vercel] OPTIONS ${url} (preflight short-circuit)`); - return buildPreflightResponse(request); - } - - let app: Hono; - try { - app = await ensureApp(); - } catch (err: unknown) { - const message = err instanceof Error ? err.message : String(err); - console.error('[Vercel] Handler error — bootstrap did not complete:', message); - const errorResponse = new Response( - JSON.stringify({ - success: false, - error: { - message: 'Service Unavailable — kernel bootstrap failed.', - code: 503, - }, - }), - { status: 503, headers: { 'content-type': 'application/json' } }, - ); - return withCorsHeaders(errorResponse, request); - } - - console.log(`[Vercel] ${method} ${url}`); - - if (method !== 'GET' && method !== 'HEAD' && incoming) { - const contentType = incoming.headers?.['content-type']; - const contentTypeStr = Array.isArray(contentType) ? contentType[0] : contentType; - const body = extractBody(incoming, method, contentTypeStr); - if (body != null) { - const response = await app.fetch( - new Request(url, { method, headers: request.headers, body }), - ); - return withCorsHeaders(response, request); - } - } - - const response = await app.fetch( - new Request(url, { method, headers: request.headers }), - ); - return withCorsHeaders(response, request); -}); - -export const config = { - maxDuration: 60, -}; diff --git a/apps/objectos/server/templates/blank.ts b/apps/objectos/server/templates/blank.ts deleted file mode 100644 index e13ed40cd..000000000 --- a/apps/objectos/server/templates/blank.ts +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. - -import type { ProjectTemplate } from './types.js'; - -export const blankTemplate: ProjectTemplate = { - id: 'blank', - label: 'Blank', - description: 'Empty project — start from scratch.', - category: 'starter', - async load() { - return { manifest: { id: 'blank', namespace: 'blank' }, objects: [] } as any; - }, -}; diff --git a/apps/objectos/server/templates/crm.ts b/apps/objectos/server/templates/crm.ts deleted file mode 100644 index 03039a3a6..000000000 --- a/apps/objectos/server/templates/crm.ts +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. - -import type { ProjectTemplate } from './types.js'; - -export const crmTemplate: ProjectTemplate = { - id: 'crm', - label: 'CRM Starter', - description: 'Accounts, Contacts, Opportunities — full CRM example.', - category: 'business', - async load() { - // Lazy import — only resolved when a project is provisioned from this template. - // bundleRequire/esbuild inlines the module at bundle time but defers execution, - // so the CRM metadata (~8k lines) is not loaded into the control-plane kernel - // on server startup. The rootDir TS error below is a tsc-only constraint; - // esbuild (used by bundleRequire) handles cross-root imports correctly. - // @ts-ignore — outside tsconfig rootDir but safe under bundleRequire/esbuild - const mod = await import('../../../../examples/app-crm/objectstack.config.js'); - return mod.default ?? mod; - }, -}; diff --git a/apps/objectos/server/templates/extract.ts b/apps/objectos/server/templates/extract.ts deleted file mode 100644 index d9a96582d..000000000 --- a/apps/objectos/server/templates/extract.ts +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. - -/** - * Flatten an `ObjectStackDefinition` bundle into the `{type, name, data}` - * shape consumed by `MetadataPlugin.bulkRegister`. - * - * Object names are kept as the canonical short name. The registry stores - * objects by short name and disambiguates cross-package collisions via the - * package/namespace tag — adding `${ns}__` here would surface FQN names in - * URLs and queries (forbidden by the naming convention). - * - * Skipped on purpose: - * - apis / actions — handler refs require kernel code, not metadata only - * - translations — needs i18n plugin - * - sharingRules / roles — needs security plugin - * - onEnable hooks — code, not metadata - */ -export interface ExtractedItem { - type: string; - name: string; - data: unknown; -} - -export function extractMetadataItems(bundle: any): ExtractedItem[] { - const items: ExtractedItem[] = []; - - const pushAll = (type: string, arr?: any[]) => { - for (const item of arr ?? []) { - if (!item?.name) continue; - items.push({ type, name: item.name, data: item }); - } - }; - - pushAll('object', bundle?.objects); - pushAll('view', bundle?.views); - pushAll('dashboard', bundle?.dashboards); - pushAll('report', bundle?.reports); - pushAll('flow', bundle?.flows); - pushAll('agent', bundle?.agents); - pushAll('app', bundle?.apps); - - return items; -} diff --git a/apps/objectos/server/templates/registry.ts b/apps/objectos/server/templates/registry.ts deleted file mode 100644 index 4f6b66152..000000000 --- a/apps/objectos/server/templates/registry.ts +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. - -import { blankTemplate } from './blank.js'; -import { crmTemplate } from './crm.js'; -import { todoTemplate } from './todo.js'; -import type { ProjectTemplate } from './types.js'; - -export const templateRegistry: Record = { - [blankTemplate.id]: blankTemplate, - [crmTemplate.id]: crmTemplate, - [todoTemplate.id]: todoTemplate, -}; - -export const DEFAULT_TEMPLATE_ID = 'blank'; - -export function listTemplates(): Array> { - return Object.values(templateRegistry).map(({ id, label, description, category }) => ({ - id, - label, - description, - category, - })); -} - -export type { ProjectTemplate } from './types.js'; diff --git a/apps/objectos/server/templates/todo.ts b/apps/objectos/server/templates/todo.ts deleted file mode 100644 index 504b226d1..000000000 --- a/apps/objectos/server/templates/todo.ts +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. - -import type { ProjectTemplate } from './types.js'; - -export const todoTemplate: ProjectTemplate = { - id: 'todo', - label: 'Todo List', - description: 'Lightweight task tracker — single-object example.', - category: 'starter', - async load() { - // Lazy import — see crm.ts for rationale. - // @ts-ignore — outside tsconfig rootDir but safe under bundleRequire/esbuild - const mod = await import('../../../../examples/app-todo/objectstack.config.js'); - return mod.default ?? mod; - }, -}; diff --git a/apps/objectos/server/templates/types.ts b/apps/objectos/server/templates/types.ts deleted file mode 100644 index 6eb51e0d9..000000000 --- a/apps/objectos/server/templates/types.ts +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. - -/** - * Project Template — descriptor used by the provisioning seeder. - * - * A template's `load()` returns an `ObjectStackDefinition`-shaped bundle - * (objects/views/dashboards/flows/agents/apps/data) which is then fanned - * out into `bulkRegister` calls against the freshly-provisioned project - * kernel. Loading is async + lazy so example bundles are evaluated only - * when the template is actually selected — a Zod drift in one example - * cannot crash control-plane bootstrap. - */ -export interface ProjectTemplate { - /** Stable id used by the API / Studio selector. */ - id: string; - /** Human-readable label shown in Studio. */ - label: string; - /** Short description for the picker. */ - description: string; - /** Optional category tag. */ - category?: string; - /** Lazy bundle loader. Must be cheap to call repeatedly. */ - load(): Promise; -} diff --git a/apps/objectos/test/multi-project-bundles.test.ts b/apps/objectos/test/multi-project-bundles.test.ts deleted file mode 100644 index 26d0d1131..000000000 --- a/apps/objectos/test/multi-project-bundles.test.ts +++ /dev/null @@ -1,238 +0,0 @@ -// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. - -/** - * Multi-Bundle E2E Smoke Test - * - * Boots the Server in `cloud` mode and provisions two projects, each - * bound to a different compiled bundle via `metadata.artifact_path` - * (Mode 3 in apps/objectos/README.md). Verifies that: - * - * 1. Each project kernel loads its own compiled bundle. - * 2. Hooks from the bundle fire on writes routed via - * `/api/v1/projects//...` URLs (CRM `account.beforeInsert`). - * 3. Cross-project schema isolation: project A (bound to CRM) sees - * `account` but project B (bound to TODO) does not. - * 4. The `X-Project-Id` header path resolves to the same kernel as - * the path-scoped URL (verifies Step 5a end-to-end). - * - * Mode 2 (`OS_PROJECT_ARTIFACTS` env override) shares the same code - * path inside `createFsAppBundleResolver` and is unit-tested in - * `packages/services/service-cloud/test/fs-bundle-resolver.test.ts`. - * - * pnpm --filter @objectstack/objectos test:multi-bundles - */ - -import { mkdtempSync, rmSync, existsSync } from 'node:fs'; -import { tmpdir } from 'node:os'; -import { join, resolve, dirname } from 'node:path'; -import { fileURLToPath } from 'node:url'; - -const __dirname = dirname(fileURLToPath(import.meta.url)); - -const crmBundle = resolve(__dirname, '../../../examples/app-crm/dist/objectstack.json'); -const todoBundle = resolve(__dirname, '../../../examples/app-todo/dist/objectstack.json'); - -if (!existsSync(crmBundle) || !existsSync(todoBundle)) { - console.error( - `\n[skip] Bundles missing:\n` + - ` crm: ${existsSync(crmBundle) ? 'ok' : 'MISSING'} (${crmBundle})\n` + - ` todo: ${existsSync(todoBundle) ? 'ok' : 'MISSING'} (${todoBundle})\n` + - ` Run \`pnpm -r --filter @example/app-crm --filter @example/app-todo build\` first.\n`, - ); - process.exit(0); -} - -const workdir = mkdtempSync(join(tmpdir(), 'objectstack-multi-bundle-')); -const controlDb = join(workdir, 'control.db'); - -process.env.OS_MODE = 'cloud'; -process.env.OS_DATABASE_URL = `file:${controlDb}`; -process.env.AUTH_SECRET = 'multi-bundle-test-secret-must-be-at-least-32-chars-long'; -process.env.PORT = '0'; -process.env.OS_KERNEL_CACHE_SIZE = '8'; -delete process.env.OS_PROJECT_ARTIFACTS; -delete process.env.OS_ARTIFACT_PATH; - -const { ensureApp, ensureBoot } = await import('../server/index.js'); - -type Init = { method?: string; body?: unknown; headers?: Record; projectId?: string }; - -async function call(path: string, init: Init = {}): Promise<{ status: number; body: any }> { - const app = await ensureApp(); - const headers: Record = { - 'content-type': 'application/json', - host: 'localhost', - ...(init.headers ?? {}), - }; - if (init.projectId) headers['x-project-id'] = init.projectId; - const reqInit: RequestInit = { method: init.method ?? 'GET', headers }; - if (init.body !== undefined) reqInit.body = JSON.stringify(init.body); - const res = await app.fetch(new Request(`http://localhost${path}`, reqInit)); - const text = await res.text(); - let body: any = text; - try { body = text ? JSON.parse(text) : null; } catch { /* leave as text */ } - return { status: res.status, body }; -} - -async function waitForActive(projectId: string, timeoutMs = 15_000): Promise { - const deadline = Date.now() + timeoutMs; - while (Date.now() < deadline) { - const { status, body } = await call(`/api/v1/cloud/projects/${projectId}`); - if (status === 200 && body?.data?.project?.status === 'active') return; - if (status === 200 && body?.data?.project?.status === 'failed') { - throw new Error(`Project ${projectId} failed: ${JSON.stringify(body?.data?.project?.metadata)}`); - } - await new Promise((r) => setTimeout(r, 100)); - } - throw new Error(`Project ${projectId} did not become active within ${timeoutMs}ms`); -} - -function assert(cond: any, msg: string) { - if (!cond) throw new Error(`Assertion failed: ${msg}`); -} - -const tests: Array<{ name: string; run: () => Promise }> = []; -function test(name: string, run: () => Promise) { tests.push({ name, run }); } - -const state = { - orgId: '', - projectCrm: '', - projectTodo: '', -}; - -test('seed organization', async () => { - const boot = await ensureBoot(); - const ql = (boot.kernel as any).getService('objectql'); - if (!ql || typeof ql.insert !== 'function') { - throw new Error('control-plane objectql service unavailable'); - } - state.orgId = (globalThis as any).crypto.randomUUID(); - await ql.insert('sys_organization', { - id: state.orgId, - name: 'Multi-Bundle Test Org', - slug: `mb-${state.orgId.slice(0, 8)}`, - created_at: new Date().toISOString(), - updated_at: new Date().toISOString(), - }); -}); - -test('provision project bound to CRM bundle', async () => { - const { status, body } = await call('/api/v1/cloud/projects', { - method: 'POST', - body: { - organization_id: state.orgId, - display_name: 'CRM Bundle', - driver: 'sqlite', - metadata: { artifact_path: crmBundle, __simulateDelayMs: 0 }, - }, - }); - if (status < 200 || status >= 300) { - throw new Error(`crm project create failed: ${status} ${JSON.stringify(body)}`); - } - state.projectCrm = body?.data?.project?.id ?? body?.data?.id; - assert(state.projectCrm, 'no projectCrm id returned'); - await waitForActive(state.projectCrm); -}); - -test('provision project bound to TODO bundle', async () => { - const { status, body } = await call('/api/v1/cloud/projects', { - method: 'POST', - body: { - organization_id: state.orgId, - display_name: 'TODO Bundle', - driver: 'sqlite', - metadata: { artifact_path: todoBundle, __simulateDelayMs: 0 }, - }, - }); - if (status < 200 || status >= 300) { - throw new Error(`todo project create failed: ${status} ${JSON.stringify(body)}`); - } - state.projectTodo = body?.data?.project?.id ?? body?.data?.id; - assert(state.projectTodo, 'no projectTodo id returned'); - await waitForActive(state.projectTodo); -}); - -test('CRM project: bad website triggers account.beforeInsert hook (HTTP 400)', async () => { - const { status, body } = await call(`/api/v1/projects/${state.projectCrm}/data/account`, { - method: 'POST', - body: { name: 'Acme', website: 'bogus' }, - }); - assert(status === 400, `expected 400, got ${status} body=${JSON.stringify(body)}`); - assert( - typeof body?.error === 'string' && /website must start with/i.test(body.error), - `expected hook error text, got ${JSON.stringify(body)}`, - ); -}); - -test('CRM project: valid POST persists with uppercased account_number', async () => { - const { status, body } = await call(`/api/v1/projects/${state.projectCrm}/data/account`, { - method: 'POST', - body: { name: 'Acme OK', website: 'https://acme.com', account_number: 'mb-1' }, - }); - assert(status === 200 || status === 201, `expected 2xx, got ${status} body=${JSON.stringify(body)}`); - assert( - body?.record?.account_number === 'MB-1', - `expected uppercased MB-1, got ${JSON.stringify(body?.record)}`, - ); -}); - -test('TODO project: account is NOT registered (cross-bundle isolation)', async () => { - const { status, body } = await call(`/api/v1/projects/${state.projectTodo}/data/account`, { - method: 'POST', - body: { name: 'should not work' }, - }); - // Either 404 (object not registered) or some other error — must NOT be 2xx, - // and must NOT trigger the CRM hook. - assert(status >= 400, `expected error, got ${status} body=${JSON.stringify(body)}`); - const text = JSON.stringify(body); - assert( - !/website must start with/i.test(text), - `CRM hook leaked into TODO project: ${text}`, - ); -}); - -test('TODO project: task object is registered and writable', async () => { - const { status, body } = await call(`/api/v1/projects/${state.projectTodo}/data/task`, { - method: 'POST', - body: { - subject: 'Write multi-bundle test', - status: 'in_progress', - priority: 'high', - owner: 'tester', - }, - }); - assert(status === 200 || status === 201, `expected 2xx, got ${status} body=${JSON.stringify(body)}`); - assert(body?.record?.subject === 'Write multi-bundle test', `record not returned: ${JSON.stringify(body)}`); -}); - -test('X-Project-Id header routes to the CRM kernel (Step 5a)', async () => { - const { status, body } = await call('/api/v1/data/account', { - method: 'POST', - projectId: state.projectCrm, - body: { name: 'Header Routed', website: 'bogus' }, - }); - assert(status === 400, `expected 400 via header, got ${status} body=${JSON.stringify(body)}`); - assert( - typeof body?.error === 'string' && /website must start with/i.test(body.error), - `expected hook error via header, got ${JSON.stringify(body)}`, - ); -}); - -let exitCode = 0; -console.log(`[multi-bundle] workdir: ${workdir}`); -for (const t of tests) { - process.stdout.write(` • ${t.name} ... `); - try { - await t.run(); - console.log('OK'); - } catch (err) { - exitCode = 1; - console.log('FAIL'); - console.error((err as Error).message); - if ((err as Error).stack) console.error((err as Error).stack); - break; - } -} - -try { rmSync(workdir, { recursive: true, force: true }); } catch { /* best-effort */ } -process.exit(exitCode); diff --git a/apps/objectos/test/multi-project-e2e.test.ts b/apps/objectos/test/multi-project-e2e.test.ts deleted file mode 100644 index df59d4031..000000000 --- a/apps/objectos/test/multi-project-e2e.test.ts +++ /dev/null @@ -1,430 +0,0 @@ -// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. - -/** - * Multi-Project End-to-End Smoke Test - * - * Boots the Server in `multi-project` mode against an ephemeral - * SQLite control DB, then exercises the complete Supabase-style flow: - * - * 1. Create an organization - * 2. Create two projects (A, B) under it, each with its own driver - * 3. Define a custom object in project A - * 4. Write data in project A - * 5. Read metadata + data back via both `X-Project-Id` and `Host` header - * 6. Assert project B does NOT see A's object or data (isolation) - * - * Runs in-process — no child processes, no random port. The Hono app is - * obtained via `ensureApp()` and driven with synthetic `Request` objects. - * This keeps the test fast enough to run on every commit. - * - * pnpm --filter @objectstack/objectos test:e2e - */ - -import { mkdtempSync, rmSync } from 'node:fs'; -import { tmpdir } from 'node:os'; -import { join } from 'node:path'; - -// --------------------------------------------------------------------------- -// Minimal expect() — matches the style of the retired e2e.test.ts so nobody -// has to learn a new DSL. Kept local to the file (no vitest dependency). -// --------------------------------------------------------------------------- - -function expect(actual: any) { - return { - toBe: (expected: any) => { - if (actual !== expected) { - throw new Error(`Expected ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}`); - } - }, - toBeDefined: () => { - if (actual === undefined || actual === null) { - throw new Error(`Expected defined, got ${actual}`); - } - }, - toContain: (item: any) => { - if (!Array.isArray(actual)) { - throw new Error(`Expected array, got ${typeof actual}`); - } - if (!actual.includes(item)) { - throw new Error(`Expected array to contain "${item}", but got: ${JSON.stringify(actual)}`); - } - }, - not: { - toContain: (item: any) => { - if (Array.isArray(actual) && actual.includes(item)) { - throw new Error(`Expected array to NOT contain "${item}", but got: ${JSON.stringify(actual)}`); - } - }, - }, - toMatchObject: (shape: Record) => { - for (const [k, v] of Object.entries(shape)) { - if ((actual as any)?.[k] !== v) { - throw new Error(`Field ${k}: expected ${JSON.stringify(v)}, got ${JSON.stringify((actual as any)?.[k])}`); - } - } - }, - }; -} - -// --------------------------------------------------------------------------- -// Env setup must happen BEFORE importing the server — objectstack.config.ts -// reads OS_DATABASE_URL when selecting the control-plane driver. -// --------------------------------------------------------------------------- - -const workdir = mkdtempSync(join(tmpdir(), 'objectstack-e2e-')); -const controlDb = join(workdir, 'control.db'); - -process.env.OS_MODE = 'cloud'; -process.env.OS_DATABASE_URL = `file:${controlDb}`; -process.env.AUTH_SECRET = 'e2e-test-secret-must-be-at-least-32-characters-long-xxxx'; -process.env.PORT = '0'; -// Keep kernel LRU small so an eviction test could be added later without -// needing to reconfigure. Doesn't affect the current assertions. -process.env.OS_KERNEL_CACHE_SIZE = '8'; -process.env.OS_ENV_CACHE_TTL_MS = '60000'; - -// Defer import until env is set. -const { ensureApp } = await import('../server/index.js'); - -// --------------------------------------------------------------------------- -// Helpers -// --------------------------------------------------------------------------- - -type FetchInit = { - method?: string; - headers?: Record; - body?: unknown; - host?: string; - projectId?: string; -}; - -async function call(path: string, init: FetchInit = {}): Promise<{ status: number; body: any }> { - const app = await ensureApp(); - const method = (init.method ?? 'GET').toUpperCase(); - const host = init.host ?? 'localhost'; - const headers: Record = { - 'content-type': 'application/json', - host, - ...(init.headers ?? {}), - }; - if (init.projectId) { - headers['x-project-id'] = init.projectId; - } - const url = `http://${host}${path}`; - const reqInit: RequestInit = { method, headers }; - if (init.body !== undefined) { - reqInit.body = JSON.stringify(init.body); - } - const res = await app.fetch(new Request(url, reqInit)); - const text = await res.text(); - let body: any = text; - try { body = text ? JSON.parse(text) : null; } catch { /* leave as text */ } - return { status: res.status, body }; -} - -async function waitForActive(projectId: string, timeoutMs = 10_000): Promise { - const deadline = Date.now() + timeoutMs; - while (Date.now() < deadline) { - const { status, body } = await call(`/api/v1/cloud/projects/${projectId}`); - if (status === 200 && body?.data?.project?.status === 'active') return; - if (status === 200 && body?.data?.project?.status === 'failed') { - const meta = body.data.project.metadata; - throw new Error(`Project ${projectId} provisioning failed: ${meta}`); - } - await new Promise((r) => setTimeout(r, 100)); - } - throw new Error(`Project ${projectId} did not become active within ${timeoutMs}ms`); -} - -function id(): string { - // RFC4122 v4 (crypto.randomUUID is available on Node 18+) - return (globalThis as any).crypto.randomUUID(); -} - -// --------------------------------------------------------------------------- -// Test runner — a tiny sequential framework so failures abort immediately -// and we always hit the cleanup path. -// --------------------------------------------------------------------------- - -const tests: Array<{ name: string; run: () => Promise }> = []; -function test(name: string, run: () => Promise) { tests.push({ name, run }); } - -// --------------------------------------------------------------------------- -// Scenarios -// --------------------------------------------------------------------------- - -// Shared state across the sequence. Each test appends to it. -const state = { - orgId: '' as string, - projectA: '' as string, - hostnameA: 'acme.e2e.local', - projectB: '' as string, - hostnameB: 'tasks.e2e.local', -}; - -test('health check returns ok', async () => { - const { status, body } = await call('/api/v1/health'); - expect(status).toBe(200); - // health may return { status: 'ok' } or similar — just assert non-error shape - expect(body).toBeDefined(); -}); - -test('create organization (synthetic — control-plane direct insert)', async () => { - // Normally an org is created through better-auth's /auth/organization/create - // by a logged-in user. The E2E harness skips auth by seeding sys_organization - // directly via the kernel's ObjectQL service, so the rest of the test can - // focus on project provisioning + hostname routing. - state.orgId = id(); - const { ensureBoot } = await import('../server/index.js'); - const boot = await ensureBoot(); - const ql = (boot.kernel as any).getService('objectql'); - if (!ql || typeof ql.insert !== 'function') { - throw new Error('Control-plane ObjectQL service not available on boot.kernel'); - } - await ql.insert('sys_organization', { - id: state.orgId, - name: `E2E Org ${state.orgId.slice(0, 6)}`, - slug: `e2e-${state.orgId.slice(0, 6)}`, - created_at: new Date().toISOString(), - updated_at: new Date().toISOString(), - }); -}); - -test('provision project A (sqlite driver, hostname a.e2e.local)', async () => { - const { status, body } = await call('/api/v1/cloud/projects', { - method: 'POST', - body: { - organization_id: state.orgId, - display_name: 'Project A', - driver: 'sqlite', - hostname: state.hostnameA, - metadata: { __simulateDelayMs: 0 }, - }, - }); - // Provisioning is fire-and-forget: the response is 202 Accepted while - // the background job runs. A.waitForActive() polls until status flips. - if (status < 200 || status >= 300) { - throw new Error(`project A create failed: ${status} ${JSON.stringify(body)}`); - } - state.projectA = body?.data?.project?.id ?? body?.data?.id; - expect(state.projectA).toBeDefined(); - await waitForActive(state.projectA); -}); - -test('provision project B (sqlite driver, hostname b.e2e.local)', async () => { - const { status, body } = await call('/api/v1/cloud/projects', { - method: 'POST', - body: { - organization_id: state.orgId, - display_name: 'Project B', - driver: 'sqlite', - hostname: state.hostnameB, - metadata: { __simulateDelayMs: 0 }, - }, - }); - if (status < 200 || status >= 300) { - throw new Error(`project B create failed: ${status} ${JSON.stringify(body)}`); - } - state.projectB = body?.data?.project?.id ?? body?.data?.id; - expect(state.projectB).toBeDefined(); - await waitForActive(state.projectB); -}); - -test('hostname collision returns 409 HOSTNAME_TAKEN', async () => { - const { status, body } = await call('/api/v1/cloud/projects', { - method: 'POST', - body: { - organization_id: state.orgId, - display_name: 'Dup', - driver: 'sqlite', - hostname: state.hostnameA, // duplicates project A - }, - }); - expect(status).toBe(409); - expect(body?.error?.details?.code).toBe('HOSTNAME_TAKEN'); -}); - -test('define custom object "task" inside project A', async () => { - // PUT /metadata/object/task registers a new object definition via - // protocol.saveMetaItem — that stamps env_id = projectA into sys_metadata - // and mirrors to the in-memory SchemaRegistry. - const { status, body } = await call('/api/v1/meta/object/task', { - method: 'PUT', - projectId: state.projectA, - body: { - name: 'task', - label: 'Task', - pluralLabel: 'Tasks', - fields: { - id: { label: 'ID', type: 'text', required: true, readonly: true }, - title: { label: 'Title', type: 'text', required: true }, - done: { label: 'Done', type: 'boolean', defaultValue: false }, - }, - }, - }); - if (status !== 200 && status !== 201) { - throw new Error(`saveMetaItem failed: ${status} ${JSON.stringify(body)}`); - } -}); - -test('kernelManager.getOrCreate(projectA) succeeds', async () => { - const { ensureBoot } = await import('../server/index.js'); - const boot = await ensureBoot(); - if (!boot.kernelManager) throw new Error('no kernelManager on boot result'); - const k = await boot.kernelManager.getOrCreate(state.projectA); - if (!k) throw new Error('kernelManager returned falsy kernel'); -}); - -test('project A can read its own "task" object from /meta', async () => { - // SchemaRegistry is updated synchronously on saveMetaItem, so this - // round-trip should succeed without any kernel reload. - const { status, body } = await call('/api/v1/meta/objects/task', { - projectId: state.projectA, - }); - expect(status).toBe(200); - const obj = body?.data ?? body; - expect(obj?.name).toBe('task'); -}); - -test('project B does NOT see project A\'s "task" object (isolation)', async () => { - const { status, body } = await call('/api/v1/meta/objects/task', { - projectId: state.projectB, - }); - // Acceptable responses: - // - 404 Not Found - // - 200 with `{ item: undefined }` — protocol.getMetaItem returns a - // "found nothing" shape rather than throwing, so a truthy body - // without an `item` field counts as isolation-OK. - if (status === 200) { - const data = body?.data ?? body; - const item = data?.item; - const hasFields = item && typeof item === 'object' && 'fields' in item; - if (hasFields) { - throw new Error(`isolation breach: B sees object with body ${JSON.stringify(body)}`); - } - } -}); - -test('hostname routing routes A\'s host to project A', async () => { - const { status, body } = await call('/api/v1/meta/objects/task', { - host: state.hostnameA, - }); - expect(status).toBe(200); - const obj = body?.data ?? body; - expect(obj?.name).toBe('task'); -}); - -test('hostname routing routes B\'s host — should NOT see task', async () => { - const { status, body } = await call('/api/v1/meta/objects/task', { - host: state.hostnameB, - }); - if (status === 200) { - const item = (body?.data ?? body)?.item; - const hasFields = item && typeof item === 'object' && 'fields' in item; - if (hasFields) { - throw new Error(`isolation breach via hostname routing: ${JSON.stringify(body)}`); - } - } -}); - -// --------------------------------------------------------------------------- -// Template seeding tests -// --------------------------------------------------------------------------- - -test('GET /cloud/templates returns a list including "blank"', async () => { - const { status, body } = await call('/api/v1/cloud/templates'); - expect(status).toBe(200); - const templates: Array<{ id: string }> = body?.data?.templates ?? []; - const ids = templates.map((t) => t.id); - expect(ids).toContain('blank'); -}); - -test('provision project C with template_id=blank (no-op seeding, project becomes active)', async () => { - const { status, body } = await call('/api/v1/cloud/projects', { - method: 'POST', - body: { - organization_id: state.orgId, - display_name: 'Project C (blank template)', - driver: 'sqlite', - template_id: 'blank', - metadata: { __simulateDelayMs: 0 }, - }, - }); - if (status < 200 || status >= 300) { - throw new Error(`project C create failed: ${status} ${JSON.stringify(body)}`); - } - const projectC = body?.data?.project?.id ?? body?.data?.id; - expect(projectC).toBeDefined(); - await waitForActive(projectC); - // Confirm no templateSeedError was written (blank is a no-op) - const { body: detail } = await call(`/api/v1/cloud/projects/${projectC}`); - const meta = detail?.data?.project?.metadata ?? {}; - if (meta.templateSeedError) { - throw new Error(`Unexpected templateSeedError for blank template: ${JSON.stringify(meta.templateSeedError)}`); - } -}); - -test('provision project D with unknown template_id — error recorded in metadata, project still active', async () => { - const { status, body } = await call('/api/v1/cloud/projects', { - method: 'POST', - body: { - organization_id: state.orgId, - display_name: 'Project D (bad template)', - driver: 'sqlite', - template_id: 'nonexistent-template', - metadata: { __simulateDelayMs: 0 }, - }, - }); - if (status < 200 || status >= 300) { - throw new Error(`project D create failed: ${status} ${JSON.stringify(body)}`); - } - const projectD = body?.data?.project?.id ?? body?.data?.id; - expect(projectD).toBeDefined(); - await waitForActive(projectD); - // The project must be active despite the seed error (non-fatal) - const { body: detail } = await call(`/api/v1/cloud/projects/${projectD}`); - const proj = detail?.data?.project; - expect(proj?.status).toBe('active'); - // templateSeedError should be captured in metadata - if (!proj?.metadata?.templateSeedError) { - throw new Error('Expected templateSeedError in metadata for unknown template, got none'); - } -}); - -// --------------------------------------------------------------------------- -// Main -// --------------------------------------------------------------------------- - -async function main() { - console.log(`[E2E] work dir: ${workdir}`); - let failed = 0; - for (const t of tests) { - process.stdout.write(` • ${t.name} ... `); - try { - await t.run(); - console.log('OK'); - } catch (err) { - failed++; - console.log('FAIL'); - console.error((err as Error).message); - console.error((err as Error).stack); - break; // abort on first failure — later tests depend on earlier state - } - } - - // cleanup - try { rmSync(workdir, { recursive: true, force: true }); } catch { /* ignore */ } - - if (failed > 0) { - console.error(`\nE2E: ${failed} test(s) failed.`); - process.exit(1); - } - console.log('\nE2E: all tests passed.'); - process.exit(0); -} - -main().catch((err) => { - console.error('E2E harness crashed:', err); - try { rmSync(workdir, { recursive: true, force: true }); } catch { /* ignore */ } - process.exit(1); -}); diff --git a/apps/objectos/test/provisioning.test.ts b/apps/objectos/test/provisioning.test.ts deleted file mode 100644 index 4d0dc4831..000000000 --- a/apps/objectos/test/provisioning.test.ts +++ /dev/null @@ -1,158 +0,0 @@ -// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. - -/** - * Integration test for EnvironmentProvisioningService + TursoEnvironmentDatabaseAdapter. - * - * Requires TURSO_ORG_NAME and TURSO_API_TOKEN — loaded from .env.local if present. - * - * Run: pnpm --filter @objectstack/objectos test:provisioning - */ - -import { readFileSync } from 'node:fs'; -import { resolve, dirname } from 'node:path'; -import { fileURLToPath } from 'node:url'; - -// --------------------------------------------------------------------------- -// Load .env.local -// --------------------------------------------------------------------------- - -const __dirname = dirname(fileURLToPath(import.meta.url)); -const envLocalPath = resolve(__dirname, '../.env.local'); -try { - const lines = readFileSync(envLocalPath, 'utf-8').split('\n'); - for (const line of lines) { - const trimmed = line.trim(); - if (!trimmed || trimmed.startsWith('#')) continue; - const eq = trimmed.indexOf('='); - if (eq === -1) continue; - const key = trimmed.slice(0, eq).trim(); - const raw = trimmed.slice(eq + 1).trim(); - const value = raw.startsWith('"') && raw.endsWith('"') ? raw.slice(1, -1) : raw; - if (!(key in process.env)) process.env[key] = value; - } -} catch { - // .env.local absent — rely on environment variables already being set -} - -// --------------------------------------------------------------------------- -// Assertions -// --------------------------------------------------------------------------- - -function assert(condition: boolean, message: string): asserts condition { - if (!condition) throw new Error(`FAIL: ${message}`); -} - -function ok(label: string) { - console.log(` ✓ ${label}`); -} - -// --------------------------------------------------------------------------- -// Test: TursoEnvironmentDatabaseAdapter -// --------------------------------------------------------------------------- - -import { - TursoEnvironmentDatabaseAdapter, - createDefaultEnvironmentAdapters, - EnvironmentProvisioningService, -} from '@objectstack/service-tenant'; -import { TursoPlatformClient } from '@objectstack/service-tenant'; - -const orgName = process.env.TURSO_ORG_NAME; -const apiToken = process.env.TURSO_API_TOKEN; - -if (!orgName || !apiToken) { - console.error('SKIP: TURSO_ORG_NAME and TURSO_API_TOKEN must be set.'); - process.exit(0); -} - -console.log('\n── Environment Provisioning Integration Test ──\n'); -console.log(` org: ${orgName}`); - -const createdDbs: string[] = []; - -async function cleanup(client: TursoPlatformClient) { - for (const name of createdDbs) { - try { - await client.deleteDatabase(name); - console.log(` 🗑 cleaned up: ${name}`); - } catch (e) { - console.warn(` ⚠ failed to delete ${name}:`, (e as Error).message); - } - } -} - -async function run() { - const client = new TursoPlatformClient({ apiToken: apiToken!, organization: orgName! }); - const adapter = new TursoEnvironmentDatabaseAdapter({ apiToken: apiToken!, organization: orgName! }); - - // ── Test 1: createDefaultEnvironmentAdapters picks Turso when TURSO_ORG_NAME is set - { - const adapters = createDefaultEnvironmentAdapters(); - assert(adapters.length === 1, 'should return exactly one adapter'); - assert(adapters[0].driver === 'turso', `expected driver=turso, got ${adapters[0].driver}`); - ok('createDefaultEnvironmentAdapters → TursoEnvironmentDatabaseAdapter'); - } - - // ── Test 2: TursoEnvironmentDatabaseAdapter.createDatabase - let databaseUrl = ''; - let plaintextSecret = ''; - let databaseName = ''; - { - const envId = `test-${Date.now()}`; - databaseName = `env-${envId}`; - createdDbs.push(databaseName); - - const result = await adapter.createDatabase({ - environmentId: envId, - databaseName, - region: 'us-east-1', - storageLimitMb: 256, - }); - - assert(result.databaseUrl.startsWith('libsql://'), `expected libsql:// URL, got: ${result.databaseUrl}`); - assert(result.plaintextSecret.length > 0, 'expected non-empty JWT token'); - ok(`createDatabase → ${result.databaseUrl}`); - - databaseUrl = result.databaseUrl; - plaintextSecret = result.plaintextSecret; - } - - // ── Test 3: EnvironmentProvisioningService.provisionEnvironment end-to-end - { - const svc = new EnvironmentProvisioningService({ - adapters: [adapter], - defaultDriver: 'turso', - }); - - const dbName2 = `env-svc-${Date.now()}`; - createdDbs.push(dbName2); - - const result = await svc.provisionEnvironment({ - organizationId: 'org-integration-test', - slug: 'test-env', - envType: 'test', - createdBy: 'integration-test', - driver: 'turso', - }); - - assert(result.environment.databaseUrl.startsWith('libsql://'), 'environment URL must be libsql://'); - assert(result.environment.databaseDriver === 'turso', 'driver must be turso'); - assert(result.credential.secretCiphertext.length > 0, 'credential must have ciphertext'); - ok(`provisionEnvironment → ${result.environment.databaseUrl}`); - - // Track the actual db name for cleanup - const hostname = result.environment.databaseUrl.replace('libsql://', ''); - const actualDbName = hostname.split('.')[0]; - if (!createdDbs.includes(actualDbName)) createdDbs.push(actualDbName); - } - - await cleanup(client); - console.log('\n✅ All integration tests passed.\n'); -} - -run().catch(async (err) => { - const client = new TursoPlatformClient({ apiToken: apiToken!, organization: orgName! }); - await cleanup(client); - console.error('\n❌', err.message); - process.exit(1); -}); diff --git a/apps/objectos/test/single-project-crm-bundle.test.ts b/apps/objectos/test/single-project-crm-bundle.test.ts deleted file mode 100644 index 10cde471a..000000000 --- a/apps/objectos/test/single-project-crm-bundle.test.ts +++ /dev/null @@ -1,177 +0,0 @@ -// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. - -/** - * Single-Project CRM Bundle E2E Smoke Test - * - * Verifies the production-mode boot path that lets `apps/objectos` host a - * third-party app bundle (`examples/app-crm`) compiled by `objectstack - * build`, and that: - * - * 1. The compiled JSON manifest **and** its sibling - * `objectstack-runtime..mjs` are loaded together so hook - * handlers (functions) survive across the JSON boundary. - * 2. Bare URLs like `/api/v1/data/account` (no `/projects/` prefix, - * no hostname mapping, no X-Project-Id header) route into the lone - * project kernel via the `default-project` service registered by - * `createSingleProjectPlugin`. - * 3. The CRM `account.beforeInsert` hook actually fires — both the - * validation branch (HTTP 400) and the side-effect branch - * (`account_number` lowercased input → uppercased on disk). - * 4. SQLite auto-DDL stands up the `account` table on first write. - * - * Runs in-process. No child processes, no random port. - * - * pnpm --filter @objectstack/objectos test:crm-bundle - */ - -import { mkdtempSync, rmSync, existsSync } from 'node:fs'; -import { tmpdir } from 'node:os'; -import { join, resolve, dirname } from 'node:path'; -import { fileURLToPath } from 'node:url'; - -const __dirname = dirname(fileURLToPath(import.meta.url)); - -const workdir = mkdtempSync(join(tmpdir(), 'objectstack-crm-bundle-')); - -const crmBundle = resolve(__dirname, '../../../examples/app-crm/dist/objectstack.json'); - -if (!existsSync(crmBundle)) { - console.error( - `\n[skip] CRM bundle not found at ${crmBundle}\n` + - ` Run \`pnpm --filter @objectstack/app-crm build\` before this test.\n`, - ); - process.exit(0); -} - -// Env must be set BEFORE importing objectstack.config — it reads them at -// module load. The single-project default (no OS_CLOUD_URL set) is what -// activates createSingleProjectPlugin's `default-project` registration. -process.env.OS_ARTIFACT_PATH = crmBundle; -process.env.OS_DATA_DIR = workdir; -process.env.AUTH_SECRET = 'crm-bundle-test-secret-must-be-at-least-32-chars-long'; -process.env.PORT = '0'; -delete process.env.OS_CLOUD_URL; -delete process.env.OS_MODE; - -const { ensureApp } = await import('../server/index.js'); - -async function call(path: string, init: { method?: string; body?: unknown } = {}): Promise<{ status: number; body: any }> { - const app = await ensureApp(); - const reqInit: RequestInit = { - method: init.method ?? 'GET', - headers: { 'content-type': 'application/json', host: 'localhost' }, - }; - if (init.body !== undefined) reqInit.body = JSON.stringify(init.body); - const res = await app.fetch(new Request(`http://localhost${path}`, reqInit)); - const text = await res.text(); - let body: any = text; - try { body = text ? JSON.parse(text) : null; } catch { /* leave as text */ } - return { status: res.status, body }; -} - -function assert(cond: any, msg: string) { - if (!cond) throw new Error(`Assertion failed: ${msg}`); -} - -const tests: Array<{ name: string; run: () => Promise }> = []; -function test(name: string, run: () => Promise) { tests.push({ name, run }); } - -test('bare URL — bad website triggers CRM hook (HTTP 400)', async () => { - const { status, body } = await call('/api/v1/data/account', { - method: 'POST', - body: { name: 'BareInvalid', website: 'bogus' }, - }); - assert(status === 400, `expected 400, got ${status} body=${JSON.stringify(body)}`); - assert( - typeof body?.error === 'string' && /website must start with/i.test(body.error), - `expected hook error, got ${JSON.stringify(body)}`, - ); -}); - -test('bare URL — valid POST persists with uppercased account_number', async () => { - const { status, body } = await call('/api/v1/data/account', { - method: 'POST', - body: { name: 'BareValid', website: 'https://acme.com', account_number: 'abc-9' }, - }); - assert(status === 200 || status === 201, `expected 2xx, got ${status} body=${JSON.stringify(body)}`); - assert( - body?.record?.account_number === 'ABC-9', - `expected account_number ABC-9, got ${JSON.stringify(body?.record)}`, - ); -}); - -test('scoped URL /projects/proj_local/... still works (regression)', async () => { - const { status, body } = await call('/api/v1/projects/proj_local/data/account', { - method: 'POST', - body: { name: 'ScopedReg', website: 'bogus' }, - }); - assert(status === 400, `expected 400, got ${status} body=${JSON.stringify(body)}`); - assert( - typeof body?.error === 'string' && /website must start with/i.test(body.error), - `expected hook error, got ${JSON.stringify(body)}`, - ); -}); - -test('seed data from bundle is queryable (Acme Corporation)', async () => { - const { status, body } = await call('/api/v1/data/account?limit=20', { method: 'GET' }); - assert(status === 200, `expected 200, got ${status} body=${JSON.stringify(body)}`); - const records: any[] = body?.records ?? []; - const hit = records.find(r => r?.name === 'Acme Corporation'); - assert(!!hit, `expected seed row "Acme Corporation" in records, got ${JSON.stringify(records.map(r => r?.name))}`); -}); - -test('action body — mark_primary flips contact.is_primary via QuickJS sandbox', async () => { - // Pick a seeded contact whose is_primary is currently false. The CRM - // contact_integrity hook makes ad-hoc inserts fragile, so we operate - // on existing seed data and assert the action body's mutation lands. - const list = await call('/api/v1/data/contact?limit=50', { method: 'GET' }); - assert(list.status === 200, `contact list: expected 200, got ${list.status} ${JSON.stringify(list.body)}`); - const records: any[] = list.body?.records ?? []; - const target = records.find(r => r?.is_primary === false || r?.is_primary == null); - assert(!!target?.id, `no seeded contact with is_primary=false found: ${JSON.stringify(records.map(r => ({ id: r?.id, p: r?.is_primary })))}`); - - // Invoke the action whose body lives in metadata only. - // Use the project-scoped URL form to make the routing explicit. - const invoke = await call(`/api/v1/projects/proj_local/actions/contact/mark_primary/${target.id}`, { method: 'POST', body: {} }); - assert( - invoke.status === 200, - `action invoke: expected 200, got ${invoke.status} ${JSON.stringify(invoke.body)}`, - ); - const findOkPayload = (v: any, depth = 0): any => { - if (!v || typeof v !== 'object' || depth > 5) return null; - if (v.ok === true || v.is_primary === true) return v; - if (v.data) { - const d = findOkPayload(v.data, depth + 1); - if (d) return d; - } - if (v.result) return findOkPayload(v.result, depth + 1); - return null; - }; - const ret = findOkPayload(invoke.body); - assert( - ret, - `action body did not return success payload: ${JSON.stringify(invoke.body)}`, - ); - - // Confirm the database mutation happened (sandbox -> ctx.api.update). - const after = await call(`/api/v1/data/contact/${target.id}`, { method: 'GET' }); - assert(after.status === 200, `contact get: expected 200, got ${after.status} ${JSON.stringify(after.body)}`); - assert( - after.body?.record?.is_primary === true, - `expected contact.is_primary=true after action, got ${JSON.stringify(after.body?.record)}`, - ); -}); - -let exitCode = 0; -for (const t of tests) { - try { - await t.run(); - console.log(` ✓ ${t.name}`); - } catch (e: any) { - exitCode = 1; - console.log(` ✗ ${t.name}\n ${e?.message ?? e}`); - } -} - -try { rmSync(workdir, { recursive: true, force: true }); } catch { /* best-effort */ } -process.exit(exitCode); diff --git a/apps/objectos/tsconfig.cloudflare.json b/apps/objectos/tsconfig.cloudflare.json deleted file mode 100644 index de13c1614..000000000 --- a/apps/objectos/tsconfig.cloudflare.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "compilerOptions": { - "target": "es2022", - "module": "esnext", - "moduleResolution": "bundler", - "types": ["@cloudflare/workers-types"], - "skipLibCheck": true, - "noEmit": true, - "strict": true, - "esModuleInterop": true - }, - "include": ["cloudflare/**/*.ts"] -} diff --git a/apps/objectos/tsconfig.json b/apps/objectos/tsconfig.json deleted file mode 100644 index d45b287b2..000000000 --- a/apps/objectos/tsconfig.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": "./dist", - "rootDir": ".", - "module": "NodeNext", - "skipLibCheck": true - }, - "include": ["*.ts", "lib/**/*.ts", "server/**/*.ts", "api/**/*.ts", "types/**/*.d.ts"], - "exclude": ["node_modules", "dist", "test"] -} diff --git a/apps/objectos/tsup.config.ts b/apps/objectos/tsup.config.ts deleted file mode 100644 index 81b6a8f72..000000000 --- a/apps/objectos/tsup.config.ts +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. - -import { defineConfig } from 'tsup'; - -// Compile the host config to dist/ so production `start` can run via -// `objectstack serve dist/objectstack.config.js --prebuilt` and skip -// the esbuild/bundle-require runtime overhead used in dev. -export default defineConfig({ - entry: ['objectstack.config.ts'], - outDir: 'dist', - format: ['esm'], - target: 'node20', - splitting: false, - sourcemap: true, - clean: true, - dts: false, - // All workspace deps + native modules stay external — they resolve - // from node_modules at runtime just like in dev. - external: [/^@objectstack\//, /^@example\//, /^@hono\//, 'hono', '@libsql/client'], -}); diff --git a/apps/objectos/types/service-tenant.d.ts b/apps/objectos/types/service-tenant.d.ts deleted file mode 100644 index 23662dbec..000000000 --- a/apps/objectos/types/service-tenant.d.ts +++ /dev/null @@ -1 +0,0 @@ -declare module '@objectstack/service-tenant'; diff --git a/apps/objectos/types/template-bundles.d.ts b/apps/objectos/types/template-bundles.d.ts deleted file mode 100644 index e53100005..000000000 --- a/apps/objectos/types/template-bundles.d.ts +++ /dev/null @@ -1,8 +0,0 @@ -// Ambient declaration for example-app template bundles statically imported -// by `server/templates/*.ts`. Those paths live outside this package's -// `include` scope so TS cannot discover their types; we accept `any` here -// and rely on the `StackBundle` shape returned by the seeder at runtime. -declare module '*/objectstack.config' { - const bundle: any; - export default bundle; -} diff --git a/apps/objectos/vercel.json b/apps/objectos/vercel.json deleted file mode 100644 index 00812404b..000000000 --- a/apps/objectos/vercel.json +++ /dev/null @@ -1,70 +0,0 @@ -{ - "$schema": "https://openapi.vercel.sh/vercel.json", - "framework": null, - "installCommand": "cd ../.. && pnpm install", - "buildCommand": "bash scripts/build-vercel.sh", - "build": { - "env": { - "VITE_RUNTIME_MODE": "server", - "VITE_SERVER_URL": "" - } - }, - "functions": { - "api/**/*.js": { - "maxDuration": 60, - "includeFiles": "api/node_modules/**,api/dist/**" - } - }, - "headers": [ - { - "source": "/_studio/assets/(.*)", - "headers": [ - { "key": "Cache-Control", "value": "public, max-age=31536000, immutable" } - ] - }, - { - "source": "/_account/assets/(.*)", - "headers": [ - { "key": "Cache-Control", "value": "public, max-age=31536000, immutable" } - ] - }, - { - "source": "/_console/assets/(.*)", - "headers": [ - { "key": "Cache-Control", "value": "public, max-age=31536000, immutable" } - ] - }, - { - "source": "/_studio/:path((?!assets/).*)?", - "headers": [ - { "key": "Cache-Control", "value": "public, max-age=0, must-revalidate" } - ] - }, - { - "source": "/_account/:path((?!assets/).*)?", - "headers": [ - { "key": "Cache-Control", "value": "public, max-age=0, must-revalidate" } - ] - }, - { - "source": "/_console/:path((?!assets/).*)?", - "headers": [ - { "key": "Cache-Control", "value": "public, max-age=0, must-revalidate" } - ] - } - ], - "redirects": [ - { "source": "/", "destination": "/_studio/", "permanent": false }, - { "source": "/_studio", "destination": "/_studio/", "permanent": false }, - { "source": "/_account", "destination": "/_account/", "permanent": false }, - { "source": "/_dashboard", "destination": "/_console/", "permanent": false }, - { "source": "/_console", "destination": "/_console/", "permanent": false } - ], - "rewrites": [ - { "source": "/api/:path*", "destination": "/api/[[...route]]" }, - { "source": "/_studio/:path*", "destination": "/_studio/index.html" }, - { "source": "/_account/:path*", "destination": "/_account/index.html" }, - { "source": "/_dashboard/:path*", "destination": "/_console/:path*" }, - { "source": "/_console/:path*", "destination": "/_console/index.html" } - ] -} diff --git a/apps/objectos/wrangler.toml b/apps/objectos/wrangler.toml deleted file mode 100644 index 08f551754..000000000 --- a/apps/objectos/wrangler.toml +++ /dev/null @@ -1,67 +0,0 @@ -# wrangler.toml — Cloudflare Containers deployment for ObjectOS. -# -# ObjectOS runs as a long-lived Node.js process (Hono + better-sqlite3 + -# child_process), so it is incompatible with Workers V8 isolates. Cloudflare -# Containers (GA 2025) lets us deploy the production Docker image as a -# Durable Object-backed container, fronted by a tiny Worker that proxies -# HTTP to it. -# -# ────────────────────────── Deployment workflow ────────────────────────── -# -# 1. Build the image from the *repository root* (the Dockerfile expects the -# full pnpm workspace as its build context): -# -# docker build \ -# -f apps/objectos/Dockerfile \ -# -t registry.cloudflare.com//objectos:latest . -# -# 2. Push to the Cloudflare container registry (or any OCI registry the -# account is authorised against): -# -# wrangler containers push \ -# registry.cloudflare.com//objectos:latest -# -# 3. Set required secrets (Turso URL + token, auth secret, etc.): -# -# wrangler secret put OS_DATABASE_URL --config apps/objectos/wrangler.toml -# wrangler secret put OS_DATABASE_AUTH_TOKEN --config apps/objectos/wrangler.toml -# wrangler secret put AUTH_SECRET --config apps/objectos/wrangler.toml -# -# 4. Deploy the Worker + Container binding: -# -# wrangler deploy --config apps/objectos/wrangler.toml -# -# ───────────────────────────────────────────────────────────────────────── - -name = "objectos" -main = "cloudflare/worker.ts" -compatibility_date = "2025-04-01" -compatibility_flags = ["nodejs_compat"] - -# ── Container definition ──────────────────────────────────────────────── -# `image` points at the OCI tag pushed in step 2 above. Update -# to your Cloudflare account id. The image tag is the source of truth — -# rebuild + re-push to ship a new version, then run `wrangler deploy`. -[[containers]] -class_name = "ObjectOSContainer" -image = "registry.cloudflare.com/2846eb40a60f4738e292b90dcd8cce10/objectos:c3a7d383" -max_instances = 5 -instance_type = "standard-1" - -# Non-secret environment is set on the `ObjectOSContainer` class -# (`envVars` field in cloudflare/worker.ts). Secrets (OS_DATABASE_URL, -# AUTH_SECRET, …) are injected via `wrangler secret put` and surface as -# env vars inside the container at process spawn time. - -# ── Durable Object binding for the container class ────────────────────── -[[durable_objects.bindings]] -name = "OBJECTOS" -class_name = "ObjectOSContainer" - -[[migrations]] -tag = "v1" -new_sqlite_classes = ["ObjectOSContainer"] - -# ── Observability ─────────────────────────────────────────────────────── -[observability] -enabled = true diff --git a/package.json b/package.json index 0913d6ee5..9da8fac10 100644 --- a/package.json +++ b/package.json @@ -5,8 +5,6 @@ "description": "ObjectStack Protocol & Specification - Monorepo for TypeScript Interfaces, JSON Schemas, and Convention Configurations", "scripts": { "build": "turbo run build --filter=!@objectstack/docs", - "dev": "pnpm --filter @objectstack/objectos dev", - "start": "pnpm --filter @objectstack/objectos start", "dev:crm": "pnpm --filter @objectstack/example-crm build && OS_PROJECT_ID=proj_crm pnpm --filter @objectstack/example-crm dev", "studio:start": "pnpm --filter @objectstack/studio start", "dev:console": "pnpm --filter @objectstack/console dev", @@ -15,7 +13,6 @@ "test:e2e": "turbo run test:e2e", "test:e2e:install": "pnpm --filter @objectstack/studio test:e2e:install", "clean": "turbo run clean && rm -rf dist", - "doctor": "pnpm --filter @objectstack/objectos doctor", "setup": "pnpm install && pnpm --filter @objectstack/spec build", "version": "changeset version", "release": "pnpm run build && changeset publish", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 396ba3e9c..b635ce346 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -348,118 +348,6 @@ importers: specifier: 4.4.3 version: 4.4.3 - apps/objectos: - dependencies: - '@example/app-todo': - specifier: workspace:* - version: link:../../examples/app-todo - '@hono/node-server': - specifier: ^2.0.3 - version: 2.0.3(hono@4.12.21) - '@libsql/client': - specifier: ^0.17.3 - version: 0.17.3 - '@objectstack/account': - specifier: workspace:* - version: link:../account - '@objectstack/cli': - specifier: workspace:* - version: link:../../packages/cli - '@objectstack/console': - specifier: workspace:* - version: link:../console - '@objectstack/driver-memory': - specifier: workspace:* - version: link:../../packages/plugins/driver-memory - '@objectstack/driver-mongodb': - specifier: workspace:* - version: link:../../packages/plugins/driver-mongodb - '@objectstack/driver-sql': - specifier: workspace:* - version: link:../../packages/plugins/driver-sql - '@objectstack/driver-turso': - specifier: workspace:* - version: link:../../packages/plugins/driver-turso - '@objectstack/example-crm': - specifier: workspace:* - version: link:../../examples/app-crm - '@objectstack/hono': - specifier: workspace:* - version: link:../../packages/adapters/hono - '@objectstack/metadata': - specifier: workspace:* - version: link:../../packages/metadata - '@objectstack/objectql': - specifier: workspace:* - version: link:../../packages/objectql - '@objectstack/plugin-audit': - specifier: workspace:* - version: link:../../packages/plugins/plugin-audit - '@objectstack/plugin-auth': - specifier: workspace:* - version: link:../../packages/plugins/plugin-auth - '@objectstack/plugin-hono-server': - specifier: workspace:* - version: link:../../packages/plugins/plugin-hono-server - '@objectstack/plugin-security': - specifier: workspace:* - version: link:../../packages/plugins/plugin-security - '@objectstack/runtime': - specifier: workspace:* - version: link:../../packages/runtime - '@objectstack/service-ai': - specifier: workspace:* - version: link:../../packages/services/service-ai - '@objectstack/service-analytics': - specifier: workspace:* - version: link:../../packages/services/service-analytics - '@objectstack/service-automation': - specifier: workspace:* - version: link:../../packages/services/service-automation - '@objectstack/service-feed': - specifier: workspace:* - version: link:../../packages/services/service-feed - '@objectstack/service-i18n': - specifier: workspace:* - version: link:../../packages/services/service-i18n - '@objectstack/service-package': - specifier: workspace:* - version: link:../../packages/services/service-package - '@objectstack/service-tenant': - specifier: workspace:* - version: link:../../packages/services/service-tenant - '@objectstack/spec': - specifier: workspace:* - version: link:../../packages/spec - hono: - specifier: ^4.12.21 - version: 4.12.21 - devDependencies: - '@cloudflare/containers': - specifier: ^0.3.4 - version: 0.3.4 - '@cloudflare/workers-types': - specifier: ^4.20260520.1 - version: 4.20260520.1 - esbuild: - specifier: ^0.28.0 - version: 0.28.0 - ts-node: - specifier: ^10.9.2 - version: 10.9.2(@types/node@25.9.1)(typescript@6.0.3) - tsup: - specifier: ^8.5.1 - version: 8.5.1(jiti@2.7.0)(postcss@8.5.15)(tsx@4.22.3)(typescript@6.0.3)(yaml@2.9.0) - tsx: - specifier: ^4.22.3 - version: 4.22.3 - typescript: - specifier: ^6.0.3 - version: 6.0.3 - wrangler: - specifier: ^4.93.0 - version: 4.93.0(@cloudflare/workers-types@4.20260520.1) - apps/studio: dependencies: '@ai-sdk/anthropic': @@ -2498,59 +2386,9 @@ packages: '@changesets/write@0.4.0': resolution: {integrity: sha512-CdTLvIOPiCNuH71pyDu3rA+Q0n65cmAbXnwWH84rKGiFumFzkmHNT8KHTMEchcxN+Kl8I54xGUhJ7l3E7X396Q==} - '@cloudflare/containers@0.3.4': - resolution: {integrity: sha512-6kWodmgBSug/rr9fjcWrZcKhxmWTEc1BTKC6nVv7yvYaRRvV3rZPZjq9R17eN0l9cl2LNc03wp/Z6OV+0uDwXA==} - - '@cloudflare/kv-asset-handler@0.5.0': - resolution: {integrity: sha512-jxQYkj8dSIzc0cD6cMMNdOc1UVjqSqu8BZdor5s8cGjW2I8BjODt/kWPVdY+u9zj3ms75Q5qaZgnxUad83+eAg==} - engines: {node: '>=22.0.0'} - - '@cloudflare/unenv-preset@2.16.1': - resolution: {integrity: sha512-ECxObrMfyTl5bhQf/lZCXwo5G6xX9IAUo+nDMKK4SZ8m4Jvvxp52vilxyySSWh2YTZz8+HQ07qGH/2rEom1vDw==} - peerDependencies: - unenv: 2.0.0-rc.24 - workerd: '>1.20260305.0 <2.0.0-0' - peerDependenciesMeta: - workerd: - optional: true - - '@cloudflare/workerd-darwin-64@1.20260518.1': - resolution: {integrity: sha512-IhZEf5kDd0CLRtFxGS9AUqfM5SY3EFScqqCY1VF9twNMdYpJDYrDZDJAkQitHF8sF/sPVVHYR4Aifpdq6tzmaA==} - engines: {node: '>=16'} - cpu: [x64] - os: [darwin] - - '@cloudflare/workerd-darwin-arm64@1.20260518.1': - resolution: {integrity: sha512-uqlNP1psd8SWfN1Lg5p8ePv8/piOOXt+ycvb8+NQopXECGeh9+PQ/yr/IQjpurxBhYpvSaMC+vEeihejahjkJg==} - engines: {node: '>=16'} - cpu: [arm64] - os: [darwin] - - '@cloudflare/workerd-linux-64@1.20260518.1': - resolution: {integrity: sha512-D9p8Hl0lIQ46nYs4fQZp5F+9hhvgOcQJTF1SMQWpAxQSS5f8oX+vL5YdCrETUYnyoaoyEQETtkRrWYKJkPTFeg==} - engines: {node: '>=16'} - cpu: [x64] - os: [linux] - - '@cloudflare/workerd-linux-arm64@1.20260518.1': - resolution: {integrity: sha512-+vNRkuOp9E/uRKHgQXVDUBPF5cwtTeXK6+ucLK50QUFzMYycqVl8kTFN2b//BX2H5BI4bjMRhXoBpe/zAlGRWQ==} - engines: {node: '>=16'} - cpu: [arm64] - os: [linux] - - '@cloudflare/workerd-windows-64@1.20260518.1': - resolution: {integrity: sha512-tnqofUq+ZvKliQHhboygbH7iy/Zm/MaCCotIlrqVj5a988+tPtndxyLM0r4vaAIC10iy/2LWCkwnE67VFTFiUA==} - engines: {node: '>=16'} - cpu: [x64] - os: [win32] - '@cloudflare/workers-types@4.20260520.1': resolution: {integrity: sha512-wdmf9Fwabp06OgK9ZyCl8Q77GZ94k9J7OA9qA65FbgxZ/hd3hYRkVNiY7Bvsx4tKim+sWmKqGOblecZInAvoRg==} - '@cspotcode/source-map-support@0.8.1': - resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} - engines: {node: '>=12'} - '@date-fns/tz@1.4.1': resolution: {integrity: sha512-P5LUNhtbj6YfI3iJjw5EL9eUAG6OitD0W3fWQcpQjDRc/QIsL0tRNuO1PcDvPccWL1fSTXXdE1ds+l95DV/OFA==} @@ -2585,12 +2423,6 @@ packages: '@emnapi/wasi-threads@1.2.1': resolution: {integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==} - '@esbuild/aix-ppc64@0.27.3': - resolution: {integrity: sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [aix] - '@esbuild/aix-ppc64@0.27.7': resolution: {integrity: sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==} engines: {node: '>=18'} @@ -2603,12 +2435,6 @@ packages: cpu: [ppc64] os: [aix] - '@esbuild/android-arm64@0.27.3': - resolution: {integrity: sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [android] - '@esbuild/android-arm64@0.27.7': resolution: {integrity: sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==} engines: {node: '>=18'} @@ -2621,12 +2447,6 @@ packages: cpu: [arm64] os: [android] - '@esbuild/android-arm@0.27.3': - resolution: {integrity: sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==} - engines: {node: '>=18'} - cpu: [arm] - os: [android] - '@esbuild/android-arm@0.27.7': resolution: {integrity: sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==} engines: {node: '>=18'} @@ -2639,12 +2459,6 @@ packages: cpu: [arm] os: [android] - '@esbuild/android-x64@0.27.3': - resolution: {integrity: sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [android] - '@esbuild/android-x64@0.27.7': resolution: {integrity: sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==} engines: {node: '>=18'} @@ -2657,12 +2471,6 @@ packages: cpu: [x64] os: [android] - '@esbuild/darwin-arm64@0.27.3': - resolution: {integrity: sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [darwin] - '@esbuild/darwin-arm64@0.27.7': resolution: {integrity: sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==} engines: {node: '>=18'} @@ -2675,12 +2483,6 @@ packages: cpu: [arm64] os: [darwin] - '@esbuild/darwin-x64@0.27.3': - resolution: {integrity: sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==} - engines: {node: '>=18'} - cpu: [x64] - os: [darwin] - '@esbuild/darwin-x64@0.27.7': resolution: {integrity: sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==} engines: {node: '>=18'} @@ -2693,12 +2495,6 @@ packages: cpu: [x64] os: [darwin] - '@esbuild/freebsd-arm64@0.27.3': - resolution: {integrity: sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==} - engines: {node: '>=18'} - cpu: [arm64] - os: [freebsd] - '@esbuild/freebsd-arm64@0.27.7': resolution: {integrity: sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==} engines: {node: '>=18'} @@ -2711,12 +2507,6 @@ packages: cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-x64@0.27.3': - resolution: {integrity: sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==} - engines: {node: '>=18'} - cpu: [x64] - os: [freebsd] - '@esbuild/freebsd-x64@0.27.7': resolution: {integrity: sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==} engines: {node: '>=18'} @@ -2729,12 +2519,6 @@ packages: cpu: [x64] os: [freebsd] - '@esbuild/linux-arm64@0.27.3': - resolution: {integrity: sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [linux] - '@esbuild/linux-arm64@0.27.7': resolution: {integrity: sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==} engines: {node: '>=18'} @@ -2747,12 +2531,6 @@ packages: cpu: [arm64] os: [linux] - '@esbuild/linux-arm@0.27.3': - resolution: {integrity: sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==} - engines: {node: '>=18'} - cpu: [arm] - os: [linux] - '@esbuild/linux-arm@0.27.7': resolution: {integrity: sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==} engines: {node: '>=18'} @@ -2765,12 +2543,6 @@ packages: cpu: [arm] os: [linux] - '@esbuild/linux-ia32@0.27.3': - resolution: {integrity: sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==} - engines: {node: '>=18'} - cpu: [ia32] - os: [linux] - '@esbuild/linux-ia32@0.27.7': resolution: {integrity: sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==} engines: {node: '>=18'} @@ -2783,12 +2555,6 @@ packages: cpu: [ia32] os: [linux] - '@esbuild/linux-loong64@0.27.3': - resolution: {integrity: sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==} - engines: {node: '>=18'} - cpu: [loong64] - os: [linux] - '@esbuild/linux-loong64@0.27.7': resolution: {integrity: sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==} engines: {node: '>=18'} @@ -2801,12 +2567,6 @@ packages: cpu: [loong64] os: [linux] - '@esbuild/linux-mips64el@0.27.3': - resolution: {integrity: sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==} - engines: {node: '>=18'} - cpu: [mips64el] - os: [linux] - '@esbuild/linux-mips64el@0.27.7': resolution: {integrity: sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==} engines: {node: '>=18'} @@ -2819,12 +2579,6 @@ packages: cpu: [mips64el] os: [linux] - '@esbuild/linux-ppc64@0.27.3': - resolution: {integrity: sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [linux] - '@esbuild/linux-ppc64@0.27.7': resolution: {integrity: sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==} engines: {node: '>=18'} @@ -2837,12 +2591,6 @@ packages: cpu: [ppc64] os: [linux] - '@esbuild/linux-riscv64@0.27.3': - resolution: {integrity: sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==} - engines: {node: '>=18'} - cpu: [riscv64] - os: [linux] - '@esbuild/linux-riscv64@0.27.7': resolution: {integrity: sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==} engines: {node: '>=18'} @@ -2855,12 +2603,6 @@ packages: cpu: [riscv64] os: [linux] - '@esbuild/linux-s390x@0.27.3': - resolution: {integrity: sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==} - engines: {node: '>=18'} - cpu: [s390x] - os: [linux] - '@esbuild/linux-s390x@0.27.7': resolution: {integrity: sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==} engines: {node: '>=18'} @@ -2873,12 +2615,6 @@ packages: cpu: [s390x] os: [linux] - '@esbuild/linux-x64@0.27.3': - resolution: {integrity: sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==} - engines: {node: '>=18'} - cpu: [x64] - os: [linux] - '@esbuild/linux-x64@0.27.7': resolution: {integrity: sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==} engines: {node: '>=18'} @@ -2891,12 +2627,6 @@ packages: cpu: [x64] os: [linux] - '@esbuild/netbsd-arm64@0.27.3': - resolution: {integrity: sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==} - engines: {node: '>=18'} - cpu: [arm64] - os: [netbsd] - '@esbuild/netbsd-arm64@0.27.7': resolution: {integrity: sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==} engines: {node: '>=18'} @@ -2909,12 +2639,6 @@ packages: cpu: [arm64] os: [netbsd] - '@esbuild/netbsd-x64@0.27.3': - resolution: {integrity: sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==} - engines: {node: '>=18'} - cpu: [x64] - os: [netbsd] - '@esbuild/netbsd-x64@0.27.7': resolution: {integrity: sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==} engines: {node: '>=18'} @@ -2927,12 +2651,6 @@ packages: cpu: [x64] os: [netbsd] - '@esbuild/openbsd-arm64@0.27.3': - resolution: {integrity: sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openbsd] - '@esbuild/openbsd-arm64@0.27.7': resolution: {integrity: sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==} engines: {node: '>=18'} @@ -2945,12 +2663,6 @@ packages: cpu: [arm64] os: [openbsd] - '@esbuild/openbsd-x64@0.27.3': - resolution: {integrity: sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [openbsd] - '@esbuild/openbsd-x64@0.27.7': resolution: {integrity: sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==} engines: {node: '>=18'} @@ -2963,12 +2675,6 @@ packages: cpu: [x64] os: [openbsd] - '@esbuild/openharmony-arm64@0.27.3': - resolution: {integrity: sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openharmony] - '@esbuild/openharmony-arm64@0.27.7': resolution: {integrity: sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==} engines: {node: '>=18'} @@ -2981,12 +2687,6 @@ packages: cpu: [arm64] os: [openharmony] - '@esbuild/sunos-x64@0.27.3': - resolution: {integrity: sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==} - engines: {node: '>=18'} - cpu: [x64] - os: [sunos] - '@esbuild/sunos-x64@0.27.7': resolution: {integrity: sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==} engines: {node: '>=18'} @@ -2999,12 +2699,6 @@ packages: cpu: [x64] os: [sunos] - '@esbuild/win32-arm64@0.27.3': - resolution: {integrity: sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==} - engines: {node: '>=18'} - cpu: [arm64] - os: [win32] - '@esbuild/win32-arm64@0.27.7': resolution: {integrity: sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==} engines: {node: '>=18'} @@ -3017,12 +2711,6 @@ packages: cpu: [arm64] os: [win32] - '@esbuild/win32-ia32@0.27.3': - resolution: {integrity: sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==} - engines: {node: '>=18'} - cpu: [ia32] - os: [win32] - '@esbuild/win32-ia32@0.27.7': resolution: {integrity: sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==} engines: {node: '>=18'} @@ -3035,12 +2723,6 @@ packages: cpu: [ia32] os: [win32] - '@esbuild/win32-x64@0.27.3': - resolution: {integrity: sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==} - engines: {node: '>=18'} - cpu: [x64] - os: [win32] - '@esbuild/win32-x64@0.27.7': resolution: {integrity: sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==} engines: {node: '>=18'} @@ -3341,9 +3023,6 @@ packages: '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} - '@jridgewell/trace-mapping@0.3.9': - resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} - '@libsql/client@0.17.3': resolution: {integrity: sha512-HXk9wiAoJbKFbyBH4O+aEhN6ir5ERXuXvwE5OD2eR4/5RUa3Pw/8L9zrnVdU+iNJitRvisPWaIwmhkO3bH7giA==} @@ -3857,15 +3536,6 @@ packages: '@polka/url@1.0.0-next.29': resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==} - '@poppinss/colors@4.1.6': - resolution: {integrity: sha512-H9xkIdFswbS8n1d6vmRd8+c10t2Qe+rZITbbDHHkQixH5+2x1FDGmi/0K+WgWiqQFKPSlIYB7jlH6Kpfn6Fleg==} - - '@poppinss/dumper@0.6.5': - resolution: {integrity: sha512-NBdYIb90J7LfOI32dOewKI1r7wnkiH6m920puQ3qHUeZkxNkQiFnXVWoE6YtFSv6QOiPPf7ys6i+HWWecDz7sw==} - - '@poppinss/exception@1.2.3': - resolution: {integrity: sha512-dCED+QRChTVatE9ibtoaxc+WkdzOSjYTKi/+uacHWIsfodVfpsueo3+DKpgU5Px8qXjgmXkSvhXvSCz3fnP9lw==} - '@radix-ui/number@1.1.1': resolution: {integrity: sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==} @@ -4845,10 +4515,6 @@ packages: '@shikijs/vscode-textmate@10.0.2': resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==} - '@sindresorhus/is@7.2.0': - resolution: {integrity: sha512-P1Cz1dWaFfR4IR+U13mqqiGsLFf1KbayybWwdd2vfctdV6hDpUkgCY0nKOLLTMSoRd/jJNjtbqzf13K8DCCXQw==} - engines: {node: '>=18'} - '@sindresorhus/merge-streams@2.3.0': resolution: {integrity: sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==} engines: {node: '>=18'} @@ -4889,9 +4555,6 @@ packages: resolution: {integrity: sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==} engines: {node: '>=14.0.0'} - '@speed-highlight/core@1.2.15': - resolution: {integrity: sha512-BMq1K3DsElxDWawkX6eLg9+CKJrTVGCBAWVuHXVUV2u0s2711qiChLSId6ikYPfxhdYocLNt3wWwSvDiTvFabw==} - '@standard-schema/spec@1.1.0': resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} @@ -5173,18 +4836,6 @@ packages: '@tokenizer/token@0.3.0': resolution: {integrity: sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==} - '@tsconfig/node10@1.0.12': - resolution: {integrity: sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==} - - '@tsconfig/node12@1.0.11': - resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==} - - '@tsconfig/node14@1.0.3': - resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==} - - '@tsconfig/node16@1.0.4': - resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} - '@turbo/darwin-64@2.9.14': resolution: {integrity: sha512-t7QiPflaEyBE4oayeZtSmu4mEfjgIrcNlNNl1z1dmIVPqEdtA7+CfTf8d7KXsOGPh6aNgWjKxyvQg9uGfDQF+A==} cpu: [x64] @@ -5529,10 +5180,6 @@ packages: peerDependencies: acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 - acorn-walk@8.3.5: - resolution: {integrity: sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==} - engines: {node: '>=0.4.0'} - acorn@8.16.0: resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} engines: {node: '>=0.4.0'} @@ -5598,9 +5245,6 @@ packages: any-promise@1.3.0: resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} - arg@4.1.3: - resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} - argparse@1.0.10: resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} @@ -5834,9 +5478,6 @@ packages: bl@4.1.0: resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} - blake3-wasm@2.1.5: - resolution: {integrity: sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g==} - body-parser@2.2.2: resolution: {integrity: sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==} engines: {node: '>=18'} @@ -6093,9 +5734,6 @@ packages: resolution: {integrity: sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==} engines: {node: '>= 0.10'} - create-require@1.1.1: - resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} - croner@9.1.0: resolution: {integrity: sha512-p9nwwR4qyT5W996vBZhdvBCnMhicY5ytZkR4D1Xj0wuTDEiMnjwR57Q3RXYY/s0EpX6Ay3vgIcfaR+ewGHsi+g==} engines: {node: '>=18.0'} @@ -6254,10 +5892,6 @@ packages: devlop@1.1.0: resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} - diff@4.0.4: - resolution: {integrity: sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==} - engines: {node: '>=0.3.1'} - diff@8.0.4: resolution: {integrity: sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==} engines: {node: '>=0.3.1'} @@ -6368,9 +6002,6 @@ packages: resolution: {integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==} engines: {node: '>=18'} - error-stack-parser-es@1.0.5: - resolution: {integrity: sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA==} - es-define-property@1.0.1: resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} engines: {node: '>= 0.4'} @@ -6399,11 +6030,6 @@ packages: esast-util-from-js@2.0.1: resolution: {integrity: sha512-8Ja+rNJ0Lt56Pcf3TAmpBZjmx8ZcK5Ts4cAzIOjsjevg9oSXJnl6SUQ2EevU8tv3h6ZLWmoKL5H4fgWvdvfETw==} - esbuild@0.27.3: - resolution: {integrity: sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==} - engines: {node: '>=18'} - hasBin: true - esbuild@0.27.7: resolution: {integrity: sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==} engines: {node: '>=18'} @@ -7498,9 +7124,6 @@ packages: resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} engines: {node: '>=10'} - make-error@1.3.6: - resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} - maplibre-gl@5.24.0: resolution: {integrity: sha512-ALyFxgtd5R+65UqZ/++lOqwWcC0SNho9c27fYSyLmG7AfnAul2o46F05aDJGPbFU57wos9dgcIySHs0Xe6ia3A==} engines: {node: '>=16.14.0', npm: '>=8.1.0'} @@ -7727,11 +7350,6 @@ packages: mingo@7.2.1: resolution: {integrity: sha512-MEIQPOSJS2sVCueyQeE2rzgEeW3HpIIhizPbeuwD4v7+miVj7NI3ZVPqqw8t3YPIWCivpIaXA4KsoRI7koyNOA==} - miniflare@4.20260518.0: - resolution: {integrity: sha512-jbvp43zWa66tuQ+P7bl7s25VJWzGMv4mVhxEEZEEATPvuqAQhGn2wj3rQViVZkZZBZmXQtZ5ZV5kX9VtmWGzuA==} - engines: {node: '>=22.0.0'} - hasBin: true - minimatch@10.2.3: resolution: {integrity: sha512-Rwi3pnapEqirPSbWbrZaa6N3nmqq4Xer/2XooiOKyV3q12ML06f7MOuc5DVH8ONZIFhwIYQ3yzPH4nt7iWHaTg==} engines: {node: 18 || 20 || >=22} @@ -9034,10 +8652,6 @@ packages: supercluster@8.0.1: resolution: {integrity: sha512-IiOea5kJ9iqzD2t7QJq/cREyLHTtSmUT6gQsweojg9WH2sYJqZK9SswTu6jrscO6D1G5v5vYZ9ru/eq85lXeZQ==} - supports-color@10.2.2: - resolution: {integrity: sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==} - engines: {node: '>=18'} - supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} @@ -9216,20 +8830,6 @@ packages: ts-interface-checker@0.1.13: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} - ts-node@10.9.2: - resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==} - hasBin: true - peerDependencies: - '@swc/core': '>=1.2.50' - '@swc/wasm': '>=1.2.50' - '@types/node': '*' - typescript: '>=2.7' - peerDependenciesMeta: - '@swc/core': - optional: true - '@swc/wasm': - optional: true - tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} @@ -9324,17 +8924,10 @@ packages: undici-types@7.24.6: resolution: {integrity: sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==} - undici@7.24.8: - resolution: {integrity: sha512-6KQ/+QxK49Z/p3HO6E5ZCZWNnCasyZLa5ExaVYyvPxUwKtbCPMKELJOqh7EqOle0t9cH/7d2TaaTRRa6Nhs4YQ==} - engines: {node: '>=20.18.1'} - undici@7.25.0: resolution: {integrity: sha512-xXnp4kTyor2Zq+J1FfPI6Eq3ew5h6Vl0F/8d9XU5zZQf1tX9s2Su1/3PiMmUANFULpmksxkClamIZcaUqryHsQ==} engines: {node: '>=20.18.1'} - unenv@2.0.0-rc.24: - resolution: {integrity: sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw==} - unicorn-magic@0.1.0: resolution: {integrity: sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==} engines: {node: '>=18'} @@ -9427,9 +9020,6 @@ packages: util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} - v8-compile-cache-lib@3.0.1: - resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} - validate-npm-package-license@3.0.4: resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} @@ -9608,21 +9198,6 @@ packages: wordwrap@1.0.0: resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==} - workerd@1.20260518.1: - resolution: {integrity: sha512-rLquk/eeqqJCbdGljSSuIZWW25vzYjTblXkD/tXQXKR5YsSIC91EtlqrzA1L4TJDZCxXKeFXPYqkW7R16UipXQ==} - engines: {node: '>=16'} - hasBin: true - - wrangler@4.93.0: - resolution: {integrity: sha512-qNsPr0oWRTc85SG7s1MjX+mWNTvkNV1zEQvRpTsV6eo8uqtvZoEAq8t8strQi9TtrDP3BOsxmy+N/G3ML6hH2w==} - engines: {node: '>=22.0.0'} - hasBin: true - peerDependencies: - '@cloudflare/workers-types': ^4.20260518.1 - peerDependenciesMeta: - '@cloudflare/workers-types': - optional: true - wrap-ansi@7.0.0: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} engines: {node: '>=10'} @@ -9630,18 +9205,6 @@ packages: wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - ws@8.18.0: - resolution: {integrity: sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==} - 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 - ws@8.20.1: resolution: {integrity: sha512-It4dO0K5v//JtTXuPkfEOaI3uUN87iYPnqo/ZzqCoG3g8uhA66QUMs/SrM0YK7/NAu+r4LMh/9dq2A7k+rHs+w==} engines: {node: '>=10.0.0'} @@ -9709,16 +9272,6 @@ packages: yazl@2.5.1: resolution: {integrity: sha512-phENi2PLiHnHb6QBVot+dJnaAZ0xosj7p3fWl+znIjBDlnMI2PsZCJZ306BPTFOaHf5qdDEI8x5qFrSOBN5vrw==} - yn@3.1.1: - resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} - engines: {node: '>=6'} - - youch-core@0.3.3: - resolution: {integrity: sha512-ho7XuGjLaJ2hWHoK8yFnsUGy2Y5uDpqSTq1FkHLK4/oqKtyUU1AFbOOxY4IpC9f0fTLjwYbslUz0Po5BpD1wrA==} - - youch@4.1.0-beta.10: - resolution: {integrity: sha512-rLfVLB4FgQneDr0dv1oddCVZmKjcJ6yX6mS4pU82Mq/Dt9a3cLZQ62pDBL4AUO+uVrCvtWz3ZFUL2HFAFJ/BXQ==} - zimmerframe@1.1.4: resolution: {integrity: sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==} @@ -10474,37 +10027,9 @@ snapshots: human-id: 4.1.3 prettier: 2.8.8 - '@cloudflare/containers@0.3.4': {} - - '@cloudflare/kv-asset-handler@0.5.0': {} - - '@cloudflare/unenv-preset@2.16.1(unenv@2.0.0-rc.24)(workerd@1.20260518.1)': - dependencies: - unenv: 2.0.0-rc.24 - optionalDependencies: - workerd: 1.20260518.1 - - '@cloudflare/workerd-darwin-64@1.20260518.1': - optional: true - - '@cloudflare/workerd-darwin-arm64@1.20260518.1': - optional: true - - '@cloudflare/workerd-linux-64@1.20260518.1': - optional: true - - '@cloudflare/workerd-linux-arm64@1.20260518.1': - optional: true - - '@cloudflare/workerd-windows-64@1.20260518.1': + '@cloudflare/workers-types@4.20260520.1': optional: true - '@cloudflare/workers-types@4.20260520.1': {} - - '@cspotcode/source-map-support@0.8.1': - dependencies: - '@jridgewell/trace-mapping': 0.3.9 - '@date-fns/tz@1.4.1': {} '@dnd-kit/accessibility@3.1.1(react@19.2.6)': @@ -10548,234 +10073,156 @@ snapshots: tslib: 2.8.1 optional: true - '@esbuild/aix-ppc64@0.27.3': - optional: true - '@esbuild/aix-ppc64@0.27.7': optional: true '@esbuild/aix-ppc64@0.28.0': optional: true - '@esbuild/android-arm64@0.27.3': - optional: true - '@esbuild/android-arm64@0.27.7': optional: true '@esbuild/android-arm64@0.28.0': optional: true - '@esbuild/android-arm@0.27.3': - optional: true - '@esbuild/android-arm@0.27.7': optional: true '@esbuild/android-arm@0.28.0': optional: true - '@esbuild/android-x64@0.27.3': - optional: true - '@esbuild/android-x64@0.27.7': optional: true '@esbuild/android-x64@0.28.0': optional: true - '@esbuild/darwin-arm64@0.27.3': - optional: true - '@esbuild/darwin-arm64@0.27.7': optional: true '@esbuild/darwin-arm64@0.28.0': optional: true - '@esbuild/darwin-x64@0.27.3': - optional: true - '@esbuild/darwin-x64@0.27.7': optional: true '@esbuild/darwin-x64@0.28.0': optional: true - '@esbuild/freebsd-arm64@0.27.3': - optional: true - '@esbuild/freebsd-arm64@0.27.7': optional: true '@esbuild/freebsd-arm64@0.28.0': optional: true - '@esbuild/freebsd-x64@0.27.3': - optional: true - '@esbuild/freebsd-x64@0.27.7': optional: true '@esbuild/freebsd-x64@0.28.0': optional: true - '@esbuild/linux-arm64@0.27.3': - optional: true - '@esbuild/linux-arm64@0.27.7': optional: true '@esbuild/linux-arm64@0.28.0': optional: true - '@esbuild/linux-arm@0.27.3': - optional: true - '@esbuild/linux-arm@0.27.7': optional: true '@esbuild/linux-arm@0.28.0': optional: true - '@esbuild/linux-ia32@0.27.3': - optional: true - '@esbuild/linux-ia32@0.27.7': optional: true '@esbuild/linux-ia32@0.28.0': optional: true - '@esbuild/linux-loong64@0.27.3': - optional: true - '@esbuild/linux-loong64@0.27.7': optional: true '@esbuild/linux-loong64@0.28.0': optional: true - '@esbuild/linux-mips64el@0.27.3': - optional: true - '@esbuild/linux-mips64el@0.27.7': optional: true '@esbuild/linux-mips64el@0.28.0': optional: true - '@esbuild/linux-ppc64@0.27.3': - optional: true - '@esbuild/linux-ppc64@0.27.7': optional: true '@esbuild/linux-ppc64@0.28.0': optional: true - '@esbuild/linux-riscv64@0.27.3': - optional: true - '@esbuild/linux-riscv64@0.27.7': optional: true '@esbuild/linux-riscv64@0.28.0': optional: true - '@esbuild/linux-s390x@0.27.3': - optional: true - '@esbuild/linux-s390x@0.27.7': optional: true '@esbuild/linux-s390x@0.28.0': optional: true - '@esbuild/linux-x64@0.27.3': - optional: true - '@esbuild/linux-x64@0.27.7': optional: true '@esbuild/linux-x64@0.28.0': optional: true - '@esbuild/netbsd-arm64@0.27.3': - optional: true - '@esbuild/netbsd-arm64@0.27.7': optional: true '@esbuild/netbsd-arm64@0.28.0': optional: true - '@esbuild/netbsd-x64@0.27.3': - optional: true - '@esbuild/netbsd-x64@0.27.7': optional: true '@esbuild/netbsd-x64@0.28.0': optional: true - '@esbuild/openbsd-arm64@0.27.3': - optional: true - '@esbuild/openbsd-arm64@0.27.7': optional: true '@esbuild/openbsd-arm64@0.28.0': optional: true - '@esbuild/openbsd-x64@0.27.3': - optional: true - '@esbuild/openbsd-x64@0.27.7': optional: true '@esbuild/openbsd-x64@0.28.0': optional: true - '@esbuild/openharmony-arm64@0.27.3': - optional: true - '@esbuild/openharmony-arm64@0.27.7': optional: true '@esbuild/openharmony-arm64@0.28.0': optional: true - '@esbuild/sunos-x64@0.27.3': - optional: true - '@esbuild/sunos-x64@0.27.7': optional: true '@esbuild/sunos-x64@0.28.0': optional: true - '@esbuild/win32-arm64@0.27.3': - optional: true - '@esbuild/win32-arm64@0.27.7': optional: true '@esbuild/win32-arm64@0.28.0': optional: true - '@esbuild/win32-ia32@0.27.3': - optional: true - '@esbuild/win32-ia32@0.27.7': optional: true '@esbuild/win32-ia32@0.28.0': optional: true - '@esbuild/win32-x64@0.27.3': - optional: true - '@esbuild/win32-x64@0.27.7': optional: true @@ -10835,7 +10282,8 @@ snapshots: dependencies: hono: 4.12.21 - '@img/colour@1.1.0': {} + '@img/colour@1.1.0': + optional: true '@img/sharp-darwin-arm64@0.34.5': optionalDependencies: @@ -11030,11 +10478,6 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 - '@jridgewell/trace-mapping@0.3.9': - dependencies: - '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.5.5 - '@libsql/client@0.17.3': dependencies: '@libsql/core': 0.17.3 @@ -12018,18 +11461,6 @@ snapshots: '@polka/url@1.0.0-next.29': {} - '@poppinss/colors@4.1.6': - dependencies: - kleur: 4.1.5 - - '@poppinss/dumper@0.6.5': - dependencies: - '@poppinss/colors': 4.1.6 - '@sindresorhus/is': 7.2.0 - supports-color: 10.2.2 - - '@poppinss/exception@1.2.3': {} - '@radix-ui/number@1.1.1': {} '@radix-ui/primitive@1.1.3': {} @@ -12977,8 +12408,6 @@ snapshots: '@shikijs/vscode-textmate@10.0.2': {} - '@sindresorhus/is@7.2.0': {} - '@sindresorhus/merge-streams@2.3.0': {} '@smithy/core@3.24.3': @@ -13029,8 +12458,6 @@ snapshots: '@smithy/util-buffer-from': 2.2.0 tslib: 2.8.1 - '@speed-highlight/core@1.2.15': {} - '@standard-schema/spec@1.1.0': {} '@standard-schema/utils@0.3.0': {} @@ -13331,14 +12758,6 @@ snapshots: '@tokenizer/token@0.3.0': {} - '@tsconfig/node10@1.0.12': {} - - '@tsconfig/node12@1.0.11': {} - - '@tsconfig/node14@1.0.3': {} - - '@tsconfig/node16@1.0.4': {} - '@turbo/darwin-64@2.9.14': optional: true @@ -13705,10 +13124,6 @@ snapshots: dependencies: acorn: 8.16.0 - acorn-walk@8.3.5: - dependencies: - acorn: 8.16.0 - acorn@8.16.0: {} agent-base@7.1.4: {} @@ -13758,8 +13173,6 @@ snapshots: any-promise@1.3.0: {} - arg@4.1.3: {} - argparse@1.0.10: dependencies: sprintf-js: 1.0.3 @@ -13943,8 +13356,6 @@ snapshots: inherits: 2.0.4 readable-stream: 3.6.2 - blake3-wasm@2.1.5: {} - body-parser@2.2.2: dependencies: bytes: 3.1.2 @@ -14182,8 +13593,6 @@ snapshots: object-assign: 4.1.1 vary: 1.1.2 - create-require@1.1.1: {} - croner@9.1.0: {} cross-spawn@7.0.6: @@ -14307,8 +13716,6 @@ snapshots: dependencies: dequal: 2.0.3 - diff@4.0.4: {} - diff@8.0.4: {} dir-glob@3.0.1: @@ -14410,8 +13817,6 @@ snapshots: environment@1.1.0: {} - error-stack-parser-es@1.0.5: {} - es-define-property@1.0.1: {} es-errors@1.3.0: {} @@ -14445,35 +13850,6 @@ snapshots: esast-util-from-estree: 2.0.0 vfile-message: 4.0.3 - esbuild@0.27.3: - optionalDependencies: - '@esbuild/aix-ppc64': 0.27.3 - '@esbuild/android-arm': 0.27.3 - '@esbuild/android-arm64': 0.27.3 - '@esbuild/android-x64': 0.27.3 - '@esbuild/darwin-arm64': 0.27.3 - '@esbuild/darwin-x64': 0.27.3 - '@esbuild/freebsd-arm64': 0.27.3 - '@esbuild/freebsd-x64': 0.27.3 - '@esbuild/linux-arm': 0.27.3 - '@esbuild/linux-arm64': 0.27.3 - '@esbuild/linux-ia32': 0.27.3 - '@esbuild/linux-loong64': 0.27.3 - '@esbuild/linux-mips64el': 0.27.3 - '@esbuild/linux-ppc64': 0.27.3 - '@esbuild/linux-riscv64': 0.27.3 - '@esbuild/linux-s390x': 0.27.3 - '@esbuild/linux-x64': 0.27.3 - '@esbuild/netbsd-arm64': 0.27.3 - '@esbuild/netbsd-x64': 0.27.3 - '@esbuild/openbsd-arm64': 0.27.3 - '@esbuild/openbsd-x64': 0.27.3 - '@esbuild/openharmony-arm64': 0.27.3 - '@esbuild/sunos-x64': 0.27.3 - '@esbuild/win32-arm64': 0.27.3 - '@esbuild/win32-ia32': 0.27.3 - '@esbuild/win32-x64': 0.27.3 - esbuild@0.27.7: optionalDependencies: '@esbuild/aix-ppc64': 0.27.7 @@ -15666,8 +15042,6 @@ snapshots: dependencies: semver: 7.8.0 - make-error@1.3.6: {} - maplibre-gl@5.24.0: dependencies: '@mapbox/jsonlint-lines-primitives': 2.0.2 @@ -16167,18 +15541,6 @@ snapshots: mingo@7.2.1: {} - miniflare@4.20260518.0: - dependencies: - '@cspotcode/source-map-support': 0.8.1 - sharp: 0.34.5 - undici: 7.24.8 - workerd: 1.20260518.1 - ws: 8.18.0 - youch: 4.1.0-beta.10 - transitivePeerDependencies: - - bufferutil - - utf-8-validate - minimatch@10.2.3: dependencies: brace-expansion: 5.0.6 @@ -17348,6 +16710,7 @@ snapshots: '@img/sharp-win32-arm64': 0.34.5 '@img/sharp-win32-ia32': 0.34.5 '@img/sharp-win32-x64': 0.34.5 + optional: true shebang-command@2.0.0: dependencies: @@ -17566,8 +16929,6 @@ snapshots: dependencies: kdbush: 4.1.0 - supports-color@10.2.2: {} - supports-color@7.2.0: dependencies: has-flag: 4.0.0 @@ -17757,24 +17118,6 @@ snapshots: ts-interface-checker@0.1.13: {} - ts-node@10.9.2(@types/node@25.9.1)(typescript@6.0.3): - dependencies: - '@cspotcode/source-map-support': 0.8.1 - '@tsconfig/node10': 1.0.12 - '@tsconfig/node12': 1.0.11 - '@tsconfig/node14': 1.0.3 - '@tsconfig/node16': 1.0.4 - '@types/node': 25.9.1 - acorn: 8.16.0 - acorn-walk: 8.3.5 - arg: 4.1.3 - create-require: 1.1.1 - diff: 4.0.4 - make-error: 1.3.6 - typescript: 6.0.3 - v8-compile-cache-lib: 3.0.1 - yn: 3.1.1 - tslib@2.8.1: {} tsup@8.5.1(jiti@2.7.0)(postcss@8.5.15)(tsx@4.22.3)(typescript@6.0.3)(yaml@2.9.0): @@ -17872,14 +17215,8 @@ snapshots: undici-types@7.24.6: {} - undici@7.24.8: {} - undici@7.25.0: {} - unenv@2.0.0-rc.24: - dependencies: - pathe: 2.0.3 - unicorn-magic@0.1.0: {} unicorn-magic@0.3.0: {} @@ -17976,8 +17313,6 @@ snapshots: util-deprecate@1.0.2: {} - v8-compile-cache-lib@3.0.1: {} - validate-npm-package-license@3.0.4: dependencies: spdx-correct: 3.2.0 @@ -18164,31 +17499,6 @@ snapshots: wordwrap@1.0.0: {} - workerd@1.20260518.1: - optionalDependencies: - '@cloudflare/workerd-darwin-64': 1.20260518.1 - '@cloudflare/workerd-darwin-arm64': 1.20260518.1 - '@cloudflare/workerd-linux-64': 1.20260518.1 - '@cloudflare/workerd-linux-arm64': 1.20260518.1 - '@cloudflare/workerd-windows-64': 1.20260518.1 - - wrangler@4.93.0(@cloudflare/workers-types@4.20260520.1): - dependencies: - '@cloudflare/kv-asset-handler': 0.5.0 - '@cloudflare/unenv-preset': 2.16.1(unenv@2.0.0-rc.24)(workerd@1.20260518.1) - blake3-wasm: 2.1.5 - esbuild: 0.27.3 - miniflare: 4.20260518.0 - path-to-regexp: 6.3.0 - unenv: 2.0.0-rc.24 - workerd: 1.20260518.1 - optionalDependencies: - '@cloudflare/workers-types': 4.20260520.1 - fsevents: 2.3.3 - transitivePeerDependencies: - - bufferutil - - utf-8-validate - wrap-ansi@7.0.0: dependencies: ansi-styles: 4.3.0 @@ -18197,8 +17507,6 @@ snapshots: wrappy@1.0.2: {} - ws@8.18.0: {} - ws@8.20.1: {} wsl-utils@0.1.0: @@ -18247,21 +17555,6 @@ snapshots: dependencies: buffer-crc32: 0.2.13 - yn@3.1.1: {} - - youch-core@0.3.3: - dependencies: - '@poppinss/exception': 1.2.3 - error-stack-parser-es: 1.0.5 - - youch@4.1.0-beta.10: - dependencies: - '@poppinss/colors': 4.1.6 - '@poppinss/dumper': 0.6.5 - '@speed-highlight/core': 1.2.15 - cookie: 1.1.1 - youch-core: 0.3.3 - zimmerframe@1.1.4: {} zod-to-json-schema@3.25.2(zod@4.4.3):