popcomputer-web
Health Pass
- License — License: MIT
- Description — Repository has a description
- Active repo — Last push 0 days ago
- Community trust — 11 GitHub stars
Code Fail
- spawnSync — Synchronous process spawning in exams/check.ts
- exec() — Shell command execution in exams/grade.ts
- spawnSync — Synchronous process spawning in exams/grade.ts
- Hardcoded secret — Potential hardcoded credential in exams/shop/hidden.exam.ts
Permissions Pass
- Permissions — No dangerous permissions requested
No AI report is available for this listing yet.
A batteries-included Effect framework for full-stack apps on Cloudflare Workers, in the spirit of Laravel: Hono routing, Inertia and React pages, Better Auth sign-in, Drizzle with D1 or Postgres, and secure defaults tested against other frameworks' CVEs.
@popcomputer/web
@popcomputer/web, popweb for short, is a batteries-included Effect framework
for full-stack apps on Cloudflare Workers, in the spirit of Laravel. Hono routes
the requests, Inertia and React render the pages, Better Auth signs people in,
and Drizzle reads and writes D1 or Postgres. Forms, validation, caching,
background work and tests come with it, and so do a rulebook and lint rules for
coding agents. Its website is web.popcomputer.com.
- Laravel's request-to-page workflow, on Workers. A route loads its record,
its action validates the request and renders a page or redirects, and a
failed form goes back with its errors. Pages, forms, scheduled jobs and
queues run in one Cloudflare Worker. - Effect from request to page, with typed failures. Each action is an
Effect that names the services it needs and the failures it can return. A
route whose services are missing or whose failures go unhandled is a type
error, and so is a page given the wrong props. - Secure defaults, tested against other frameworks' advisories. The
security suite attacks the framework the way published
vulnerabilities attacked Next.js, Laravel, Hono, Better Auth and others, and
fails if one works here.
The list names each
one.
To start a new app from the template:
bun create PatrickOgilvie/popcomputer-web-demo my-app
To build with an AI, paste the
setup prompt into it. It tells your AI
where the docs are, how to make the app from the template, and how to check its
work.
An action that shows a project:
// Only its owner loads a project: anyone else gets the 404 a missing one gets.
const bindings = { project: routeBinding(projects, { ownedBy: 'userId' }) }
export const showProject = action(
Effect.gen(function* () {
const project = yield* bound('project')
return yield* render('Projects/Show', { project })
})
)
Documentation
The documentation is one short file per task, in docs/. The package
ships it as node_modules/@popcomputer/web/docs/, matching the installed
version. Which API for which task names the API for each
task and the file that explains it.
Start here:
- Which API for which task: the one API for each task
- Reading the type errors: what each type error means, and what to do
- Lint rules: what each rule reports, and what to use instead
- The agent fence: the commit gate, the command hook that refuses a bypass and asks before a deploy, and the debt report
Setting up:
- Installation: the demo, the packages,
tsconfig.json, Oxlint, Vite's dev server and the supported versions - Quick start: the bindings,
setupWeband its registration, the database, the Worker's entry - Vars and secrets:
defineEnv, the check when an isolate starts, andcheckEnvfor scripts - Deploying: version values, and what the framework does around a deploy
Building:
- Pages and props: page schemas, React pages, shared props, the error page and the page map
- Forms: form schemas, form pages, flash messages, failed forms and error bags
- Validating input:
validateRequest, the validators, dates, uploads and filters - Actions and routes: paths, actions, registrations, the response helpers and defects
- Cookies, files and headers: declared cookies, files, polling, the Origin check and security headers
- JSON clients: who gets JSON, the error envelope, JSON declared by a schema, and
call - JSON endpoints: contracts, their actions,
routes.endpointandopenApi - Route model binding:
bindings,bound(), and models scoped to their parent - Application services:
web.services,config, and rules in services - The database:
dbQuery,dbMutation,dbTransaction,dbBatch, migrations and cursors - Constraints: invariants concurrent requests would break, D1 batches and
rowOf - Webhooks: verifying the raw body, and recording each event once
Auth:
- Auth:
authorize(), the signed-in user, Better Auth's client and limiting password guessing - Better Auth's tables: on Postgres and on Cloudflare D1
- Signing in and out: sign-in and sign-up actions, guest and auth guards,
betterAuthHandler - Better Auth's plugins: two-factor, magic links, organizations and OAuth providers
- Guards and roles: layers that guard routes, and roles on memberships
Running:
- The data cache:
cache, its tags, and an API that authenticates each request - Page caching: the default page cache, Workers Cache and purges
- Background, scheduled and queued work:
background, Cron Triggers and Queues
Testing and reference:
- Testing: requests through the application, the readers, one visitor, fakes and time
- Fuzzing: properties on generated inputs, generated requests to the application's routes and the rules their answers keep, and sequences of steps
- A database for tests: PGlite for Postgres, and D1 through Miniflare
- Lower-level composition: the middleware without
setupWeb - State machines: what the framework keeps across requests, the rule each keeps, and the models that check them
Why this shape
- Declare once; the rest derives.
setupWebtakes each of the
application's declarations as a value: the database factory, the Drizzle
schema and the tables route models load from, the signed-in user's schema,
pages, route paths, shared props and flash. Registering its result once
types everything else:bound('project')is the table's row,render
checks a page's props,redirectandhreffill a path's placeholders, and
a React page's props follow its schema. A renamed column, prop or
placeholder is a type error wherever it is used. - Actions are ordinary Effect values. They declare only the services and
failures they use, so small actions stay small and complex workflows
compose.routesaccepts an action only when the setup provides its
services and handles its failures; otherwise the registration is a type
error naming the missing service or the unhandled error. - External data is parsed at the edge. Sessions, request input, route
parameters, database rows, cached values and webhook bodies are checked with
Effect Schema before application code relies on them, and what reaches the
browser is encoded through the page's schema. - Correct by default where it is easy to get wrong: cached pages vary on
cookies and are purged by the records they show, failed forms go back to the
form with their errors, and a record a visitor may not see is a 404. - The standard Inertia protocol. Existing Inertia clients keep using
X-Inertia, partial reloads, asset versions, error bags and redirects.
Getting started
To start from a complete application, create one from the
demo:
bun create PatrickOgilvie/popcomputer-web-demo my-app
To add the package to an application of your own:
bun add @popcomputer/web effect hono
Installation lists the optional integrations, thetsconfig.json and the supported versions, and the Quick start
sets an application up.
Coding agents should read node_modules/@popcomputer/web/AGENTS.md, a short
rulebook naming the one API to use for each task, and the files innode_modules/@popcomputer/web/docs/ as a task needs them.
The agent fence holds them to it with checks: a commit
gate, a hook that refuses a command skipping it and asks the owner before a
deploy, and a count of disable directives that only shrinks.
Data flow
flowchart LR
Request["HTTP request"] --> Hono["Hono middleware"]
Hono --> Boundary["Schema and auth boundaries"]
Boundary --> Action["Effect action"]
Action --> Services["Typed application services"]
Services --> Result["Page, redirect, JSON, or typed failure"]
Result --> Protocol["Inertia or HTTP response"]
setupWeb builds the request-scoped Effect runtime and provides only the
services configured for that request. A missing database or auth service is
reported as a configuration error rather than being silently replaced with an
untyped value.
Patterns from real applications
These patterns come from applications built on the package, most of them by
coding agents. Each replaces a first attempt that compiled and then
misbehaved.
Each lives in the file for its task:
- Rules live in services; actions decide what a failure means
- Constraints keep invariants that concurrent requests would break
- A guard guards a group of routes
- Roles belong to memberships, read by a guard
- Webhooks verify the raw body, then record the event once
- User input in, files out: filters and uploads,
serving an upload back, and
requests from other sites - Lists page through with a cursor
- Several forms on one page keep their errors apart
- Other patterns: in Pages and props,
Validating input,
Application services,
Page caching and
Background work
Upgrading
UPGRADING.md, shipped in the package asnode_modules/@popcomputer/web/UPGRADING.md, takes an application on any
earlier release, or on the honertia package, to this one. It moves an
application's own code from Effect 3 to Effect 4 too, which most of an upgrade
from honertia is, maps every former Honertia* name to its replacement,
and lists the changes no type error points at.
Tradeoffs and current limits
- This is a server-driven page architecture. It is not a replacement for a
client-side router when the browser must own navigation and data fetching. - Effect is a deliberate application-level dependency. The package is most
valuable when actions, failures, services, and background work all use the
same Effect model. - Route model binding currently relies on Drizzle metadata. Other database
clients still work throughDatabaseService, but do not receive automatic
binding queries. - Better Auth and Cloudflare-specific caching are optional integrations rather
than hidden requirements. - The package does not run database migrations. Use
drizzle-kitorwrangler d1 migrationsdirectly. - Cloudflare D1 has no interactive transactions.
dbBatchapplies several
writes atomically there;dbTransactionneeds Postgres, MySQL or libsql,
and is a type error for synchronous drivers such asbun:sqlite. - Page props cross to the browser as JSON.
Jsonifydescribes the result
(dates and bigints become strings); page schemas should describe plain
data rather than classes or Effect data types. - A table binding checks each column's database type, nullability and enum
values, but not a narrowing declared only in TypeScript, such astext('role').$type<Role>(): name an Effect Schema for that binding, or
give the column an enum. - Workers Cache answers before the Worker runs, so a page cached with the
cacheoption is shared only by visitors who send no cookies. Visitors
with analytics cookies render it afresh; the default page cache, which runs
in the Worker, ignores those cookies but caches only pages that read no
data.
Developing this package
bun run test
After changing AGENTS.md, regenerate the skill withbun scripts/build-skill.ts; a test fails while it is stale.
The repository verifies runtime behavior, strict TypeScript and module
augmentation contracts, that every TypeScript example in this README, indocs/, in AGENTS.md and in UPGRADING.md type-checks, that every link
between them resolves and no file in docs/ passes 250 lines, and that each
built entrypoint imports under plain Node.
License
MIT
Reviews (0)
Sign in to leave a review.
Leave a reviewNo results found