Skip to content

fix(objectql,rest): single-item meta reads must revalidate (no max-age=3600)#2264

Merged
os-zhuang merged 1 commit into
mainfrom
fix/meta-cache-control-no-cache
Jun 24, 2026
Merged

fix(objectql,rest): single-item meta reads must revalidate (no max-age=3600)#2264
os-zhuang merged 1 commit into
mainfrom
fix/meta-cache-control-no-cache

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Problem

GET /api/v1/meta/object/:name (and the other single-item meta reads served by the cached path) sent:

Cache-Control: public, max-age, max-age=3600

Two bugs in one header:

  1. Stale metadata for up to an hour. Object metadata is invalidated by publish, but the 1-hour TTL let browsers (and any CDN/proxy) serve a stale schema without revalidating — e.g. the AI-build New create form kept rendering pre-publish fields until the TTL lapsed. The list endpoint GET /api/v1/meta/object is uncached, which is why list views updated but single-object reads didn't.
  2. Malformed header. The directives array carried a bare max-age placeholder and the REST layer appended max-age=3600 from the maxAge field, concatenating into the invalid public, max-age, max-age=3600.

The ETag is already correct (it changes on publish), so the fix is to let the validator — not a TTL — gate freshness.

Fix

  • packages/objectql/src/protocol.tsgetMetaItemCached now returns directives: ['private', 'no-cache'] with no maxAge. no-cache forces an If-None-Match revalidation on every use (cheap 304 when unchanged, fresh data the instant a publish bumps the ETag); private keeps per-tenant metadata out of shared caches.
  • packages/rest/src/rest-server.ts — the header builder strips the bare max-age placeholder before appending the real value, so a directive array + maxAge can never collide into …, max-age, max-age=N again (defends all callers).

Verification

Cache-Control
Before public, max-age, max-age=3600
After private, no-cache

End-to-end against a real booted showcase stack: the single-object meta endpoint now returns private, no-cache, and replaying the ETag with If-None-Match returns 304 Not Modified (validator path intact; a publish bumps the ETag → fresh 200).

Tests (added/extended):

  • protocol-meta.test.ts — asserts the protocol returns ['private','no-cache'] with no maxAge.
  • rest.test.ts — asserts the emitted header is private, no-cache (no max-age), plus a regression guard that ['public','max-age'] + maxAge:3600 now yields a single well-formed public, max-age=3600.

Context: the consumer-side symptom was previously worked around in objectui (getObjectSchema reads with cache: 'no-cache'); this server header is the actual root cause affecting all consumers.

🤖 Generated with Claude Code

…e=3600)

GET /api/v1/meta/object/:name served `Cache-Control: public, max-age, max-age=3600`. A 1h TTL let browsers/CDNs serve a stale object schema without revalidating (the AI-build "New" form kept rendering pre-publish fields), and the directive list collided with the appended max-age into a malformed triple.

getMetaItemCached now returns directives ['private','no-cache'] with no maxAge, so the ETag validator (already correct on publish) gates freshness — a cheap 304 when unchanged, fresh data the moment a publish bumps the ETag. The REST header builder strips the bare max-age placeholder before appending the real value, so a maxAge emits once as a well-formed max-age=N. 'private' keeps per-tenant metadata out of shared caches.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@vercel

vercel Bot commented Jun 24, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
spec Building Building Preview, Comment Jun 24, 2026 4:27am

Request Review

@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling size/m labels Jun 24, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/objectql, @objectstack/rest.

15 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/concepts/core/services.mdx (via @objectstack/objectql)
  • content/docs/concepts/implementation-status.mdx (via @objectstack/objectql, @objectstack/rest)
  • content/docs/concepts/metadata-lifecycle.mdx (via @objectstack/objectql)
  • content/docs/concepts/packages.mdx (via @objectstack/objectql, @objectstack/rest)
  • content/docs/guides/api-reference.mdx (via @objectstack/rest)
  • content/docs/guides/authentication.mdx (via @objectstack/objectql)
  • content/docs/guides/deployment-vercel.mdx (via @objectstack/objectql)
  • content/docs/guides/formula.mdx (via packages/objectql)
  • content/docs/guides/kernel-services.mdx (via @objectstack/objectql)
  • content/docs/guides/objectql-migration.mdx (via @objectstack/objectql)
  • content/docs/guides/packages.mdx (via @objectstack/objectql, @objectstack/rest)
  • content/docs/guides/plugins.mdx (via @objectstack/objectql, @objectstack/rest)
  • content/docs/protocol/objectos/index.mdx (via @objectstack/objectql)
  • content/docs/protocol/objectql/state-machine.mdx (via @objectstack/objectql)
  • content/docs/releases/v9.mdx (via @objectstack/objectql)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

@os-zhuang
os-zhuang merged commit 359c0aa into main Jun 24, 2026
14 of 15 checks passed
@os-zhuang
os-zhuang deleted the fix/meta-cache-control-no-cache branch June 24, 2026 04:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant