ObjectStackObjectStack

Publish, Versioning & Preview

End-to-end pipeline — `objectstack compile` → `objectstack publish` → cloud control plane stores immutable revisions in object storage → ObjectOS runtime pulls via artifact-api. Includes rollback and pinned-commit preview.

Publish, Versioning & Preview

The cloud control plane keeps every publish as an immutable revision in object storage and lets runtime nodes boot off any historical commit. This page walks through the full loop:

┌───────────┐   compile    ┌──────────────────┐   publish   ┌────────────────────┐   pull (artifact-api)   ┌──────────────┐
│ source    │ ───────────▶ │ dist/object-     │ ──────────▶ │ Cloud control      │ ──────────────────────▶ │ ObjectOS     │
│ tree      │              │ stack.json       │             │ plane (revisions)  │                         │ runtime node │
└───────────┘              └──────────────────┘             └────────────────────┘                         └──────────────┘

                                                                      ├── object storage
                                                                      │   (file-storage / S3)
                                                                      └── sys_project_revision
                                                                          (commitId, checksum,
                                                                           is_current, …)

1. Compile

objectstack compile
# → dist/objectstack.json   (envelope: { schemaVersion, commitId, metadata, … })

The artifact is content-addressable: commitId is a sha256 prefix of the canonicalised metadata. Identical content always produces the same commitId, so re-publishing is a no-op upload.


2. Publish

objectstack publish dist/objectstack.json \
  --project proj_crm \
  --server  http://localhost:4000

Common flags:

FlagEnv varDefaultNotes
--server, -sOS_CLOUD_URLhttp://localhost:4000Control-plane URL
--project, -pOS_PROJECT_ID— (required)Target project id
--token, -tOS_CLOUD_API_KEYBearer token (when auth is enabled)
--note, -nOptional human-readable note attached to the revision (shown in Studio)
--timeoutOS_CLOUD_TIMEOUT_MS60000 (60 s)Set higher for slow networks; 0 disables

publish POSTs to /api/v1/cloud/projects/:id/metadata. The control plane:

  1. Computes / verifies commitId.
  2. Uploads to object storage at artifacts/orgs/${organizationId}/projects/${projectId}/${commitId}.json (no-op if the key already exists — content-addressable). Org-first prefixing makes per-tenant IAM policies, billing attribution, exports and GC trivial. Projects without an organization_id (rare, single-tenant installs) fall back to the legacy artifacts/${projectId}/${commitId}.json shape. Existing rows keep their original key in sys_project_revision.storage_key, so historical artifacts always remain readable regardless of layout changes.
  3. Inserts a sys_project_revision row, flips the previous current row to is_current = false, and marks the new one is_current = true.
  4. Updates sys_project.metadata.current_commit_id.

3. Storage backend

The plugin resolves storage in this order:

  1. Explicit options.storage.service (passed by host code)
  2. Kernel-registered file-storage service (@objectstack/service-storage)
  3. Local filesystem fallback (warns at boot)

createCloudStack() in @objectstack/service-cloud automatically wires StorageServicePlugin based on environment variables — no code changes needed when switching backends.

Env-driven configuration

Env varRequired whenDescription
OS_STORAGE_ADAPTERalways (default local)local | s3 | none (none skips registering, falls back to plugin's local-FS write — useful for tests)
OS_STORAGE_LOCAL_DIRlocal adapterRoot dir for local files (default ./storage)
OS_S3_BUCKETs3 adapter (required)Bucket name
OS_S3_REGIONs3 adapter (required)AWS region (e.g. us-east-1, auto for R2)
OS_S3_ENDPOINTS3-compatible onlyCustom endpoint (Cloudflare R2, MinIO, B2, etc.)
OS_S3_ACCESS_KEY_IDwhen not using SDK chainAccess key
OS_S3_SECRET_ACCESS_KEYwhen not using SDK chainSecret key
OS_S3_FORCE_PATH_STYLEMinIO / self-hosted1 / true to force path-style URLs

Example: native AWS S3

OS_STORAGE_ADAPTER=s3
OS_S3_BUCKET=objectstack-artifacts
OS_S3_REGION=us-east-1
OS_S3_ACCESS_KEY_ID=AKIA…
OS_S3_SECRET_ACCESS_KEY=

Example: Cloudflare R2

OS_STORAGE_ADAPTER=s3
OS_S3_BUCKET=objectstack-artifacts
OS_S3_REGION=auto
OS_S3_ENDPOINT=https://<account-id>.r2.cloudflarestorage.com
OS_S3_ACCESS_KEY_ID=
OS_S3_SECRET_ACCESS_KEY=

Example: MinIO (local development against S3 protocol)

OS_STORAGE_ADAPTER=s3
OS_S3_BUCKET=artifacts
OS_S3_REGION=us-east-1
OS_S3_ENDPOINT=http://localhost:9000
OS_S3_ACCESS_KEY_ID=minioadmin
OS_S3_SECRET_ACCESS_KEY=minioadmin
OS_S3_FORCE_PATH_STYLE=1

Vercel (and other serverless hosts): the local-FS fallback does not work — Vercel functions get a read-only filesystem (only /tmp is writable, and it is not shared across invocations). You must set OS_STORAGE_ADAPTER=s3 plus OS_S3_* in your project settings. Pair it with OS_CONTROL_DATABASE_URL pointing at Turso / libSQL for the control DB.


4. Cloud HTTP API

All endpoints live under /api/v1/cloud/projects/:id/.

Method & PathPurpose
POST /metadataPublish a new revision (called by CLI)
GET /artifactGet the current artifact (envelope)
GET /artifact?commit=<sha>Get a pinned revision
GET /revisions?limit=&cursor=List revisions (most-recent first)
POST /revisions/:commit/activateRoll back / forward — flip is_current
GET /resolve-hostname?host=<host>Tenant routing helper used by ObjectOS

Errors follow the standard { success: false, error: '…' } shape. Unknown commits return 404 with a descriptive message.


5. Runtime — artifact-api source

ObjectOS runtime nodes can boot off any cloud revision without ever touching the source tree:

import { MetadataPlugin } from '@objectstack/metadata';

new MetadataPlugin({
  bootstrap: 'artifact-only',           // skip filesystem watcher
  artifactSource: {
    mode: 'artifact-api',
    url:  'https://cloud.example.com',
    projectId: 'proj_crm',
    commitId: '9707264f0450e8ba',        // optional — omit for latest
    token: process.env.OS_CLOUD_API_KEY, // optional
    fetchTimeoutMs: 60_000,              // optional, default 60 s
  },
});

The plugin merges the cloud envelope’s metadata block, dynamically imports @objectstack/spec/cloud for Zod validation, and registers every metadata item before the kernel marks itself ready.

Tip — slow networks: override the timeout per-plugin (fetchTimeoutMs) or globally via OS_ARTIFACT_FETCH_TIMEOUT_MS.


6. Rollback

Every publish is preserved. The fastest way to roll back is the CLI:

# Accepts either the full 16-char commitId or any unambiguous prefix (≥ 8 chars).
OS_CLOUD_URL=http://localhost:4000 OS_PROJECT_ID=proj_crm \
  objectstack rollback --commit 9ce1bd48

Equivalent raw HTTP:

curl -X POST http://localhost:4000/api/v1/cloud/projects/proj_crm/revisions/9ce1bd48dd7022b8/activate

The next GET /artifact (no ?commit) — and any runtime node restarted without a pinned commitId — will pick up the older revision. Roll forward the same way.

Pruning old revisions

History grows with every publish. Use the prune endpoint to enforce a retention policy. The current revision is always preserved.

curl -X POST http://localhost:4000/api/v1/cloud/projects/proj_crm/revisions/prune \
  -H 'Content-Type: application/json' \
  -d '{"keepN": 50, "keepDays": 30}'

Defaults are keepN = 50 and keepDays = 30. Any row outside both windows is deleted from the table and the underlying object-storage key is removed (best-effort — orphaned keys won't block the row deletion).


7. Local end-to-end smoke test

# Terminal 1 — cloud control plane
cd apps/cloud
OS_MODE=cloud PORT=4000 \
  AUTH_SECRET=local-dev-secret-must-be-at-least-32-chars-long \
  pnpm dev

# Terminal 2 — compile + publish twice
cd hotcrm                                  # https://github.com/objectstack-ai/hotcrm
objectstack compile
OS_PROJECT_ID=proj_crm objectstack publish

# tweak any object label, recompile, publish again

# Terminal 3 — list revisions
curl http://localhost:4000/api/v1/cloud/projects/proj_crm/revisions | jq

You should see two sys_project_revision rows — the second one isCurrent: true — and the artifact body for either one accessible via GET /artifact?commit=<id>.


7. Public artifact API (visibility)

By default every project is private — only the authenticated /cloud/* routes can read it. To support marketplace listings, OSS templates, share links, or live documentation demos, switch a project to public or unlisted.

// sys_project.visibility — defined in packages/platform-objects/src/tenant/sys-project.object.ts
visibility: 'private' | 'unlisted' | 'public'   // default: 'private'
Mode/pub/v1/.../artifact (no commit)/pub/v1/.../artifact?commit=<id>/pub/v1/.../revisions/pub/v1/.../manifest.json
private404404404404
unlisted404✅ 200404404
public✅ 200 (current)✅ 200✅ 200✅ 200

unlisted requires the caller to know the exact commit — it is meant for share-preview links you mail to colleagues, not for crawling.

Public endpoints

  • GET /api/v1/pub/v1/projects/:id/artifact[?commit=<id>][&redirect=1] Returns the same envelope as the private route. No auth header required. Sets Cache-Control: public, max-age=31536000, immutable and ETag: "<commitId>" so a CDN (Cloudflare, CloudFront, …) in front of the control plane serves it from the edge for free. When the storage adapter is S3 / R2 (or anything that implements getSignedUrl), passing ?redirect=1 makes the control plane respond with a 302 to a short-lived (5 min) signed URL — bandwidth then comes straight from the bucket and the control plane is no longer the chokepoint.
  • GET /api/v1/pub/v1/projects/:id/revisions?limit=N — public history.
  • GET /api/v1/pub/v1/projects/:id/manifest.json — lightweight project info (projectId, displayName, currentCommitId, currentChecksum, builtAt).

Flipping visibility

# Via the platform REST (control plane)
curl -X PATCH https://cloud.example.com/api/v1/data/sys_project/proj_crm \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"visibility":"public"}'

Or via SQL on the control DB:

UPDATE sys_project SET visibility='public' WHERE id='proj_crm';

⚠️ Before going public, audit the artifact — it includes every field name, view config, AI prompt, and formula. Treat it like committing to a public Git repo: never embed credentials or PII in metadata.

Pulling a public artifact from another runtime

import { MetadataPlugin } from '@objectstack/metadata';

new MetadataPlugin({
    artifactSource: {
        mode: 'artifact-api',
        url: 'https://cloud.objectstack.dev',
        // No token needed for /pub routes
        // Use the public path explicitly:
        // url: 'https://cloud.objectstack.dev/api/v1/pub/v1/projects/proj_crm'
    },
    bootstrap: 'artifact-only',
});

See also

On this page