Skip to content

Adopt HTTP-verb server actions (#488) across monorepo apps + all 3 scaffolds #576

@vivek7405

Description

@vivek7405

Problem

HTTP-verb server actions (#488) let a 'use server' action declare its transport semantics via reserved sibling exports (export const method = 'GET', cache, tags, invalidates, validate, middleware). Reads become cacheable, ETag/304-aware, SSR-seeded GETs; mutations declare the tags they evict. The feature shipped in #494 and is documented, but our own dogfood surface barely uses it:

  • examples/blog has a synthetic modules/verbdemo/ demo (GET getGreeting/getSlow, mutation bumpGreeting, streaming streamTokens), but its REAL domain code (modules/posts, modules/comments, modules/auth queries) is all POST-default. Reads that should be cached GETs are not.
  • website modules/blog/queries/* and modules/changelog/queries/* are plain POST-default 'use server' reads, ideal GET + cache + tags candidates.
  • docs and packages/ui/packages/website have no server actions (nothing to convert; verify and skip).
  • All 3 scaffold templates ship ZERO HTTP-verb actions. The full-stack/api modules/users/queries/list-users.server.ts read and actions/create-user.server.ts mutation (in packages/cli/lib/create.js ~L777/L787) are POST-default; saas modules/auth/queries/current-user.server.ts (in packages/cli/lib/saas-template.js ~L146) is a POST-default read. New apps therefore never see the idiom.

This is a dogfooding + teaching gap: the framework's flagship reads/writes don't model the verb idiom we tell users to adopt.

Design / approach

Convert genuine reads to export const method = 'GET' with a sensible cache window and tags, and annotate mutations with invalidates for the matching tags, so the browser-cache coordinator revalidates after a write. Follow the reference idioms already in examples/blog/modules/verbdemo/ (get-greeting.server.ts for a cached/tagged GET, bump-greeting.server.ts for an invalidating mutation).

SAFETY: cache is private by default; only set { public: true } (or a page-style shared cache) on data identical for every visitor (public blog/changelog reads qualify; per-user/session reads like current-user do NOT, keep them private and short or uncached). A GET action must be idempotent.

Scope: convert where it is genuinely correct, do not force verbs onto write-shaped or per-user actions. Keep one function per file (a configured file with >1 callable fn is a webjs check error).

Implementation notes (for the implementing agent)

  • Reference idiom: examples/blog/modules/verbdemo/queries/get-greeting.server.ts (GET + cache + tags), examples/blog/modules/verbdemo/actions/bump-greeting.server.ts (mutation + invalidates). Read these first.
  • Apps to edit:
    • website/modules/blog/queries/{get-post,list-posts}.server.ts, website/modules/changelog/queries/list-entries.server.ts -> GET + cache + tags (public, file-backed reads, safe to share).
    • examples/blog/modules/posts/queries/*, modules/comments/queries/list-comments.server.ts -> GET + cache + tags; modules/posts/actions/{create-post,delete-post}.server.ts, modules/comments/actions/create-comment.server.ts -> add invalidates for the post/comment tags. modules/auth/queries/current-user.server.ts stays private/POST (per-session).
    • docs, packages/ui/packages/website: confirm no 'use server' actions exist; nothing to convert.
  • Scaffold templates (the higher-value change, every new app inherits it):
    • packages/cli/lib/create.js: modules/users/queries/list-users.server.ts (~L777) -> add export const method = 'GET'; export const cache = 30; export const tags = () => ['users'];. modules/users/actions/create-user.server.ts (~L787) -> add export const invalidates = () => ['users'];. Applies to full-stack AND api templates (both write this users module).
    • packages/cli/lib/saas-template.js: modules/auth/queries/current-user.server.ts (~L146) is per-session, leave POST-default (good teaching counter-example); consider a brief comment noting why it is NOT a public GET.
  • Landmines:
    • One function per file is enforced; do not co-locate the config exports with a second fn.
    • public: true on a per-user read is a data-leak; reviewers will reject it. Default private.
    • The scaffold users query returns hard-coded sample data with a webjs-scaffold-placeholder marker; keep the marker/TODO, only add the verb config exports.
    • Bun-mode scaffold is DERIVED from the canonical node template via runtime-rewrite.js transforms; editing the canonical strings in create.js/saas-template.js covers both runtimes (no parallel bun template).
    • Other AI agents are active in this repo; keep the change tightly scoped to action/query files + their tests/docs and rebase before merge.
  • Invariants: AGENTS.md "HTTP-verb actions via config exports (feat: HTTP-verb server actions via config exports (GET/POST/PUT/PATCH/DELETE) [epic] #488)" section + invariant 1 (server-only stays in .server.*). public cache safety mirrors a page export const revalidate.
  • Tests + docs:
    • Scaffold: test/scaffolds/scaffold-integration.test.js (assert the generated users query declares method = 'GET' and the action declares invalidates); counterfactual: the assertion fails before the create.js edit.
    • Apps: app-level smoke/e2e where present (test/examples/*), assert a converted GET responds with Cache-Control/ETag.
    • Docs: update scaffold-facing templates/CONVENTIONS.md/AGENTS.md if they describe the example modules; run the webjs-doc-sync skill to catch every surface. Run webjs check and fix violations.

Acceptance criteria

  • website public blog/changelog reads are GET + cached + tagged
  • examples/blog real-domain reads are GET + tagged and mutations declare invalidates (verbdemo already covers the standalone demo)
  • docs + ui-website confirmed to have no convertible actions
  • all 3 scaffolds emit at least one GET query (with cache+tags) and one mutation with invalidates; new apps model the idiom out of the box
  • webjs check passes; one-function-per-file respected; no public: true on per-user reads
  • scaffold-integration test asserts the new verb config exports, with a counterfactual that fails when reverted
  • docs/AGENTS/CONVENTIONS surfaces synced for any changed example modules

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Type

No type
No fields configured for issues without a type.

Projects

Status
Done

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions