Skip to content

Commit 5e3147d

Browse files
committed
Update files
1 parent a5657ed commit 5e3147d

7 files changed

Lines changed: 106 additions & 66 deletions

File tree

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Changed — Slimmer production Docker images (`apps/objectos`, `apps/cloud`)
11+
- **3-stage builder → pruner → runner Dockerfiles**: full pnpm workspace is restored only in the builder; the pruner runs `pnpm --filter ... deploy --prod --legacy /deploy` to materialize a flat, devDeps-stripped tree; the runner copies just `/deploy` plus the freshly built `dist/` of the target app. CMD now runs `node node_modules/@objectstack/cli/bin/run.js serve dist/objectstack.config.js --prebuilt` directly — no `pnpm` at runtime.
12+
- **`@objectstack/cli` promoted to `dependencies`** in both `apps/objectos/package.json` and `apps/cloud/package.json` so the production entrypoint survives `--prod` pruning.
13+
- **Surgical `.pnpm/` pruning** in the pruner stage removes large transitive packages that are not exercised by the runtime path (`next`, `@next/swc-*`, `playwright-core`, `@playwright/*`, `typescript`, `happy-dom`, `@rolldown/*`, `@img/sharp-libvips-*`, `@cloudflare/workers-types`, `@esbuild/*`, `lightningcss-*`, `caniuse-lite`). Together with stripping `*.map`, test directories, and Markdown, this lands the final image at ~690 MB (down from 1.93 GB), a 64% reduction. Both images still pass `/api/v1/health` and Docker's HEALTHCHECK end-to-end.
14+
1015
### Added — Cloudflare Containers deployment for `apps/objectos` & `apps/cloud`
1116
- **`apps/{objectos,cloud}/scripts/deploy-cloudflare.sh`** — Idempotent `build → push → deploy` pipeline. Reads config from `.env.cloudflare` (gitignored) or env vars; auto-tags images with the current git short SHA; in-place rewrites the `image = "..."` line in `wrangler.toml` (BSD/GNU sed compatible); supports `--tag`, `--skip-build`, `--skip-push`, `--skip-deploy`, `--dry-run`. Forces `--platform linux/amd64` (Cloudflare Containers requirement).
1217
- **`apps/{objectos,cloud}/scripts/setup-cloudflare-secrets.sh`** — Bulk `wrangler secret put` from a local `.env.cloudflare.secrets` file. Per-app key allow-list lets one shared file feed both Workers; unset keys are skipped (not cleared). Safe to re-run.

apps/cloud/Dockerfile

Lines changed: 31 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -3,25 +3,14 @@
33
# ObjectStack Cloud — production container image
44
# --------------------------------------------------
55
# Cloud-only host: multi-project control plane + Studio template registry.
6-
#
7-
# Build with the **repository root** as the build context so the entire pnpm
8-
# workspace is available to the builder stage. Example:
9-
#
10-
# docker build -f apps/cloud/Dockerfile -t objectstack/cloud:dev .
11-
#
12-
# Default port is 4000 to mirror `pnpm dev` (apps/cloud sets PORT=4000).
13-
# At runtime the container reads project config from env vars (see
14-
# apps/cloud/README.md for the full list). The control plane DB should be
15-
# pointed at a remote libSQL/Turso instance via OS_DATABASE_URL — the
16-
# container filesystem is ephemeral on most platforms (Cloudflare Containers,
17-
# Fly.io machines without volumes, etc.).
6+
# See apps/objectos/Dockerfile for the architectural notes — this is the
7+
# same 3-stage builder → pruner → runner pipeline, scoped to @objectstack/cloud.
188

199
# ──────────────────────────────────────────────────────────────────────────
2010
# Stage 1 — install workspace deps + build required packages
2111
# ──────────────────────────────────────────────────────────────────────────
2212
FROM node:22-slim AS builder
2313

24-
# native bindings for better-sqlite3 and friends
2514
RUN apt-get update \
2615
&& apt-get install -y --no-install-recommends \
2716
python3 make g++ ca-certificates \
@@ -30,32 +19,47 @@ RUN apt-get update \
3019
WORKDIR /repo
3120
RUN corepack enable
3221

33-
# Bring in lockfile + workspace manifest first to maximise layer cache hits
34-
# on subsequent rebuilds where only source code changes.
3522
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml turbo.json tsconfig.json tsup.config.ts ./
36-
37-
# Copy every workspace member required at runtime.
38-
# Studio / docs are excluded to keep the image slim (see .dockerignore).
3923
COPY packages ./packages
4024
COPY examples ./examples
4125
COPY apps/cloud ./apps/cloud
4226

4327
RUN pnpm install --frozen-lockfile --prod=false
44-
45-
# Build upstream packages that apps/cloud depends on via workspace: protocol.
46-
# `...` expands to "package + all transitive deps in the workspace".
4728
RUN pnpm --filter '@objectstack/cloud...' build
4829

30+
# ──────────────────────────────────────────────────────────────────────────
31+
# Stage 2 — production prune (pnpm deploy)
32+
# ──────────────────────────────────────────────────────────────────────────
33+
FROM builder AS pruner
34+
WORKDIR /repo
35+
RUN pnpm --filter @objectstack/cloud deploy --prod --legacy /deploy \
36+
&& find /deploy -type f \( -name "*.map" -o -name "*.test.*" -o -name "*.spec.*" -o -name "*.md" -o -name "*.markdown" \) -delete \
37+
&& 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 {} + \
38+
&& rm -rf /deploy/.turbo /deploy/.cache \
39+
# Surgical removal of dev-only deps that get pulled in by package peers
40+
# but are NOT used by the Cloud runtime path (verified by smoke test).
41+
&& rm -rf \
42+
/deploy/node_modules/.pnpm/next@* \
43+
/deploy/node_modules/.pnpm/@next+* \
44+
/deploy/node_modules/.pnpm/playwright-core@* \
45+
/deploy/node_modules/.pnpm/@playwright+* \
46+
/deploy/node_modules/.pnpm/typescript@* \
47+
/deploy/node_modules/.pnpm/happy-dom@* \
48+
/deploy/node_modules/.pnpm/@rolldown+* \
49+
/deploy/node_modules/.pnpm/@img+sharp-libvips-* \
50+
/deploy/node_modules/.pnpm/@cloudflare+workers-types@* \
51+
/deploy/node_modules/.pnpm/@esbuild+* \
52+
/deploy/node_modules/.pnpm/lightningcss-* \
53+
/deploy/node_modules/.pnpm/caniuse-lite@*
4954

5055
# ──────────────────────────────────────────────────────────────────────────
51-
# Stage 2 — slim runtime image
56+
# Stage 3 — slim runtime image
5257
# ──────────────────────────────────────────────────────────────────────────
5358
FROM node:22-slim AS runner
5459

5560
RUN apt-get update \
5661
&& apt-get install -y --no-install-recommends ca-certificates wget \
57-
&& rm -rf /var/lib/apt/lists/* \
58-
&& corepack enable
62+
&& rm -rf /var/lib/apt/lists/*
5963

6064
WORKDIR /app
6165

@@ -64,20 +68,15 @@ ENV NODE_ENV=production \
6468
HOST=0.0.0.0 \
6569
OS_DISABLE_CONSOLE=1
6670

67-
# Copy the full workspace (with built dist/ directories) and the shared
68-
# node_modules tree. pnpm uses hard-link dedup so the transfer is cheap.
69-
COPY --from=builder /repo /app
71+
COPY --from=pruner /deploy /app
72+
COPY --from=builder /repo/apps/cloud/dist /app/dist
7073

7174
EXPOSE 4000
7275
VOLUME ["/data"]
7376

74-
# Default to a local SQLite control DB on the mounted volume. Override
75-
# with a remote libSQL/Turso URL in production (Cloudflare Containers,
76-
# Fly without volumes, etc. have ephemeral filesystems).
7777
ENV OS_DATABASE_URL=file:/data/control.db
7878

7979
HEALTHCHECK --interval=30s --timeout=5s --start-period=30s --retries=3 \
8080
CMD wget -qO- "http://127.0.0.1:${PORT}/api/v1/health" >/dev/null 2>&1 || exit 1
8181

82-
WORKDIR /app/apps/cloud
83-
CMD ["pnpm", "start"]
82+
CMD ["node", "node_modules/@objectstack/cli/bin/run.js", "serve", "dist/objectstack.config.js", "--prebuilt"]

apps/cloud/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,12 @@
4242
"@objectstack/service-package": "workspace:*",
4343
"@objectstack/service-tenant": "workspace:*",
4444
"@objectstack/spec": "workspace:*",
45+
"@objectstack/cli": "workspace:*",
4546
"hono": "^4.12.18"
4647
},
4748
"devDependencies": {
4849
"@cloudflare/containers": "^0.3.3",
4950
"@cloudflare/workers-types": "^4.20260510.1",
50-
"@objectstack/cli": "workspace:*",
5151
"esbuild": "^0.28.0",
5252
"ts-node": "^10.9.2",
5353
"tsup": "^8.5.1",

apps/objectos/Dockerfile

Lines changed: 53 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,22 @@
11
# syntax=docker/dockerfile:1.7
22
#
3-
# ObjectStack Server — production container image
3+
# ObjectOS — production container image
44
# --------------------------------------------------
5-
# Build with the **repository root** as the build context so the entire pnpm
6-
# workspace (packages, examples, apps/objectos itself) is available to the
7-
# builder stage. Example:
5+
# Build context = repo root (Dockerfile expects the full pnpm workspace):
86
#
9-
# docker build -f apps/objectos/Dockerfile -t objectstack/objectos:dev .
10-
# fly deploy --dockerfile apps/objectos/Dockerfile
7+
# docker buildx build --platform linux/amd64 \
8+
# -f apps/objectos/Dockerfile \
9+
# -t objectos:latest .
1110
#
12-
# At runtime the container reads project config from env vars (see
13-
# apps/objectos/README.md for the full list) and optionally mounts a
14-
# persistent volume at /data for the local-SQLite control plane.
11+
# Stage 1 (`builder`) installs the full dev tree and builds every workspace
12+
# package apps/objectos transitively depends on.
13+
#
14+
# Stage 2 (`pruner`) uses `pnpm deploy --prod` to extract a self-contained
15+
# tree under /deploy that contains only production deps (workspace packages
16+
# are flattened to plain `node_modules/@objectstack/*` entries).
17+
#
18+
# Stage 3 (`runner`) starts from a clean slim base and only COPYs /deploy,
19+
# yielding an image that is ~3× smaller than `COPY --from=builder /repo /app`.
1520

1621
# ──────────────────────────────────────────────────────────────────────────
1722
# Stage 1 — install workspace deps + build required packages
@@ -27,52 +32,75 @@ RUN apt-get update \
2732
WORKDIR /repo
2833
RUN corepack enable
2934

30-
# Bring in lockfile + workspace manifest first to maximise layer cache hits
31-
# on subsequent rebuilds where only source code changes.
3235
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml turbo.json tsconfig.json tsup.config.ts ./
33-
34-
# Copy every workspace member required at runtime.
35-
# Studio / docs are excluded to keep the image slim (see .dockerignore).
3636
COPY packages ./packages
3737
COPY examples ./examples
3838
COPY apps/objectos ./apps/objectos
3939

4040
RUN pnpm install --frozen-lockfile --prod=false
4141

42-
# Build upstream packages that apps/objectos depends on via workspace: protocol.
43-
# `...` expands to "package + all transitive deps in the workspace".
42+
# Build apps/objectos + all its workspace transitive deps.
4443
RUN pnpm --filter '@objectstack/objectos...' build
4544

45+
# ──────────────────────────────────────────────────────────────────────────
46+
# Stage 2 — production prune (pnpm deploy)
47+
# ──────────────────────────────────────────────────────────────────────────
48+
# Creates a /deploy tree containing apps/objectos with its full prod
49+
# dependency graph, devDependencies stripped, workspace packages
50+
# materialised as real folders under node_modules/. Source `src/`, tests,
51+
# and `.map` files inside the workspace package dists are kept (they are
52+
# tiny next to node_modules); strip more aggressively if you need to.
53+
FROM builder AS pruner
54+
WORKDIR /repo
55+
RUN pnpm --filter @objectstack/objectos deploy --prod --legacy /deploy \
56+
&& find /deploy -type f \( -name "*.map" -o -name "*.test.*" -o -name "*.spec.*" -o -name "*.md" -o -name "*.markdown" \) -delete \
57+
&& 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 {} + \
58+
&& rm -rf /deploy/.turbo /deploy/.cache \
59+
# Surgical removal of dev-only deps that get pulled in by package peers
60+
# but are NOT used by the ObjectOS runtime path (verified by smoke test).
61+
&& rm -rf \
62+
/deploy/node_modules/.pnpm/next@* \
63+
/deploy/node_modules/.pnpm/@next+* \
64+
/deploy/node_modules/.pnpm/playwright-core@* \
65+
/deploy/node_modules/.pnpm/@playwright+* \
66+
/deploy/node_modules/.pnpm/typescript@* \
67+
/deploy/node_modules/.pnpm/happy-dom@* \
68+
/deploy/node_modules/.pnpm/@rolldown+* \
69+
/deploy/node_modules/.pnpm/@img+sharp-libvips-* \
70+
/deploy/node_modules/.pnpm/@cloudflare+workers-types@* \
71+
/deploy/node_modules/.pnpm/@esbuild+* \
72+
/deploy/node_modules/.pnpm/lightningcss-* \
73+
/deploy/node_modules/.pnpm/caniuse-lite@*
4674

4775
# ──────────────────────────────────────────────────────────────────────────
48-
# Stage 2 — slim runtime image
76+
# Stage 3 — slim runtime image
4977
# ──────────────────────────────────────────────────────────────────────────
5078
FROM node:22-slim AS runner
5179

5280
RUN apt-get update \
5381
&& apt-get install -y --no-install-recommends ca-certificates wget \
54-
&& rm -rf /var/lib/apt/lists/* \
55-
&& corepack enable
82+
&& rm -rf /var/lib/apt/lists/*
5683

5784
WORKDIR /app
5885

5986
ENV NODE_ENV=production \
6087
PORT=3000 \
6188
HOST=0.0.0.0
6289

63-
# Copy the full workspace (with built dist/ directories) and the shared
64-
# node_modules tree. pnpm uses hard-link dedup so the transfer is cheap.
65-
COPY --from=builder /repo /app
90+
COPY --from=pruner /deploy /app
91+
COPY --from=builder /repo/apps/objectos/dist /app/dist
6692

6793
EXPOSE 3000
6894
VOLUME ["/data"]
6995

7096
# The container talks to the control plane (local SQLite at /data or remote
71-
# libSQL/Turso) via env. Override in fly.toml / `docker run -e …`.
97+
# libSQL/Turso) via env. The local file path is only viable on platforms
98+
# with a persistent volume mount (Fly volumes, Docker volumes) — on
99+
# Cloudflare Containers it MUST be overridden to a remote URL.
72100
ENV OS_DATABASE_URL=file:/data/control.db
73101

74102
HEALTHCHECK --interval=30s --timeout=5s --start-period=30s --retries=3 \
75103
CMD wget -qO- "http://127.0.0.1:${PORT}/api/v1/health" >/dev/null 2>&1 || exit 1
76104

77-
WORKDIR /app/apps/objectos
78-
CMD ["pnpm", "start"]
105+
# Invoke the CLI binary directly — no pnpm process needed at runtime.
106+
CMD ["node", "node_modules/@objectstack/cli/bin/run.js", "serve", "dist/objectstack.config.js", "--prebuilt"]

apps/objectos/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,12 +48,12 @@
4848
"@objectstack/service-package": "workspace:*",
4949
"@objectstack/service-tenant": "workspace:*",
5050
"@objectstack/spec": "workspace:*",
51+
"@objectstack/cli": "workspace:*",
5152
"hono": "^4.12.18"
5253
},
5354
"devDependencies": {
5455
"@cloudflare/containers": "^0.3.3",
5556
"@cloudflare/workers-types": "^4.20260510.1",
56-
"@objectstack/cli": "workspace:*",
5757
"esbuild": "^0.28.0",
5858
"ts-node": "^10.9.2",
5959
"tsup": "^8.5.1",

packages/objectql/src/engine.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,15 @@ function planFormulaProjection(
5656
// see complete data. Static dependency analysis on AST is M9.7 work.
5757
if (Array.isArray(requestedFields) && requestedFields.length > 0) {
5858
if (!projected.has('id')) projected.add('id');
59-
for (const fname of allFieldNames) projected.add(fname);
59+
for (const fname of allFieldNames) {
60+
// Skip formula fields themselves — they are virtual and not
61+
// projectable by the underlying driver. Without this guard the
62+
// SQL driver emits `SELECT response_rate ...` which fails as
63+
// "no such column" and the driver returns [] (silently).
64+
const fdef = (schema.fields as any)[fname];
65+
if (fdef?.type === 'formula') continue;
66+
projected.add(fname);
67+
}
6068
return { plan, projected: Array.from(projected) };
6169
}
6270
// Implicit/full projection — leave projected undefined so the driver

pnpm-lock.yaml

Lines changed: 6 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)