yumemi

Have a sweet Gleam. 設計を書けば、コードが生まれる。

A framework for Gleam on Cloudflare Workers. You describe an application in five words — Entity, Property, Type, Service, Authorization — and yumemi derives the Gleam implementation and every entrance to it: HTTP, MCP, CLI.

Open source, coming soon. https://gleam.canon-ical.com/

Status

Extracted from the framework/ directory of a production application on 2026-09-16, history included. Module namespace is still framework/*; the package name is yumemi. Published on Hex as yumemi (0.2.0 = the 2026-09-16 extraction; the generator and gen-3/gen-4 changes ship from 0.3.0).

Layout

Module What
framework/spec framework/er Entity / Property / Type ── the model and its derivation
framework/entry framework/verb framework/party framework/require Entrances and Authorization
framework/step framework/effect framework/query framework/io Service logic: read / guard / apply / call / done
framework/connector framework/page framework/blob framework/vector Connectors, SSR pages, R2, Vectorize
framework/secret framework/sealed framework/time Secrets, sealed values, time
gleam build

framework/front ── Style (0.11.10)

framework/front/css's Style is the typed vocabulary a Page / Area / component carries; framework/front/sketch_css maps it to CSS (sketch classes, so a Style used by an island also lands in the island's shadow <style>). Color values are strings passed straight through, so var(--ma-color-bg) and color-mix(...) work — the app owns its palette, yumemi bakes in no hex. Every word also works inside State(Hover / Focus / Disabled / Current, ..) and Responsive(SP / PC / Tablet, ..).

Style CSS
Color(value) color
Background(value) background-color
Border(edge: AllEdges / BottomEdge, width, style: Solid / Dashed / Dotted, color) border / border-bottom (e.g. the 2px tab underline)
Outline(width, offset, color) outline-style: solid / outline-width / outline-offset / outline-color — the focus ring, inside State(Focus, ..)
Space(property:, value:) margin / padding / gap / width / height / border-radius, and (0.11.7) min-width / min-height / max-width
Text(family:, size:, weight:, line_height:) font-family (System / SansSerif / Serif / Monospace, or Named("var(--ma-font-ui)") / Named("\"Noto Sans JP\", sans-serif)")), font-size, font-weight (Normal 400 / Medium 500 / SemiBold 600 / Bold 700), line-height
Crop(fit: Cover / Contain / Fill / ScaleDown / FitNone, ratio: Ratio(w, h)) object-fit and aspect-ratio
Sizing(box: BorderBox / ContentBox) (0.11.8) box-sizing — BorderBox counts padding and border inside width / min-height, so an input or an <a> button with min-height: 48px and padding stays 48px
Marker(marker: NoMarker) (0.11.8) list-style: none — drops the ul / li bullet (clear the indent with Space(Padding, Px(0.0)))
Decoration(line: NoDecoration / Underline) (0.11.8) text-decoration: none / underline — e.g. a row link without the underline, underlined again inside State(Hover / Focus, ..)
Wrap(wrap: Anywhere / BreakWord / WrapNormal) (0.11.10) overflow-wrap: anywhere / break-word / normal — Anywhere breaks a long URL or an unbroken word at the line's width, so the row's scrollWidth stays its clientWidth (it also lowers the min-content width, so the word does not widen a flex / grid item); BreakWord breaks it too but keeps the min-content width
State(Current, styles) (0.11.9) [aria-current]:not([aria-current="false"]) — the item that is where the user is (aria-current="page", also step / location / true). Mark the nav item with aria-current (a Layout block learns the page from CurrentRoute, below) and give it e.g. the 2px underline with Border(BottomEdge, ..)
Flow(..) State(..) Responsive(..) Animation(..) layout, interaction states, breakpoints, animations (unchanged)

Lengths (css.Length) are Px(n) / Rem(n) and, from 0.11.9:

Length CSS
Var(name) var(--name) — e.g. Var("ma-space-2"). The name passes only [a-z0-9-]; any other name (;, }, ), a space, upper case, empty) is written as unset by sketch_css, and the generator stops on it in an Area's gap
Env(SafeTop / SafeRight / SafeBottom / SafeLeft) env(safe-area-inset-top, 0px) … (0px where the device has no safe area)
Dvh(n) n dvh — e.g. Space(MinHeight, Dvh(100.0)) for a short page that still fills the screen

They work everywhere a Length does: Space, Text, Border, Outline, Flow gaps, and an Area's flow gap in the generated grid CSS (literal or a style constant).

framework/front ── Grid style and the bottom bar (0.11.10)

framework/front ── Layout vars and Overlay (0.11.9)

framework/front ── Area flow (0.11.8)

An Area's flow in a Layout or a Page's Frame now reaches the generated grid CSS (the <style> that htmlWithGridCss puts in SSR pages and _yumemi/style.css, which carry the same text): the Area's rule gets the flow's declarations after grid-area, so the blocks placed in the Area are laid out by it. Before 0.11.8 the Area stayed display: block and the flow was ignored.

flow declarations in the Area's rule
Stack(gap:) display: flex; flex-direction: column; align-items: stretch; gap — blocks keep the Area's full width
Row(gap:, wrap:) display: flex; flex-direction: row; flex-wrap: wrap / nowrap; gap
Grid(cols:, gap:) display: grid; grid-template-columns: repeat(cols, minmax(0, 1fr)); gap
GridTracks(cols:, gap:) unchanged (already written since 0.11.x: display: grid, the tracks, gap)
Scroller display: flex; flex-direction: row; overflow-x: auto

framework/server ── what the app must provide (0.11.1)

The back-end runtime (framework/server/*.mjs) is JavaScript that the generated src/gen/*.mjs imports. It knows no application names: route names, cookie names, key bindings and the party a queue consumer reads its borrowed root as come from the app's src/server.gleam (attached_roles, browser, hooks, roots' QueueParty). The generated face gate (<face>/src/gen/gate.mjs, declared in <face>/src/gate.gleam with framework/gate) reads the session through the ReadSession attached route. Gleam packages cannot declare npm dependencies, so the app supplies the following itself.

Generated live modules (0.11.2) send Args by their declared type: Bool as a JSON boolean (the live field's "true" / "false"), Int / Float as numbers. On a GET route the Args that are not path holes go on the query string (an empty Option is left out), and the runtime reads bool (true / false) and float spellings from the query of GET / HEAD requests. POST / PUT / DELETE bodies are read as JSON as before.

A live field for a List(X) Arg (X a scalar, value type, id or enum) holds a JSON array of strings (["a","b"], an empty field is []); each item is sent by X's rule. A field for a record, tuple, Dict or a List of those holds the JSON body itself (for example {"background":"#112233"}), which is sent as is and read by the back-end decoder. A field that does not parse is sent as a string, and the back end answers invalid_argument. Sum types with several constructors are sent as strings.

A Service whose logic runs step.commit and continues after it (and is not a queue consumer that only uses the commit as a boundary) ends in Accepted over HTTP: the runtime answers 202 with a one-field body ({"<root>": id}, a respond hook may rename the field). The generated live for such a Service (0.11.3) holds Reply instead of the Service's Out: Accepted(id) for the 202 body and Replied(out) for a 200 that ends before the commit. Both arrive as Done(Ok(_)) and go on to after_send (for example ReloadPage). Lives of other Services are unchanged.

0.11.4 adds, without changing the existing public types:

0.11.5 adds, without changing or removing the existing public types:

0.11.6 changes when the outbox is swept, without changing the public types:

0.11.7 adds, without changing the existing variants' meaning or output:

0.11.8 adds, without changing the existing variants' meaning or output:

0.11.9 adds, without changing the existing variants' meaning or output:

0.11.10 adds, without changing the existing variants' meaning or output:

0.11.11 changes documentation only (one README sentence and one source comment). No code, type or generated output changes.

Imports outside the package

Import Imported by Provided by
@neondatabase/serverless framework/server/driver.mjs (Neon HTTP transport, database(env, observe)) the app's package.json
cloudflare:workers framework/server/worker.mjs (DurableObject / WorkerEntrypoint) the Workers runtime (wrangler / workerd); not an npm package, so modules that import worker.mjs (the generated shell.mjs) do not load under plain node

SQL — the runtime runs these keys through the app's SQL bundle (src/gen/sql.mjs, built from db/queries/**). The app writes them as db/queries/framework/<name>.sql against its own framework schema (DDL is the app's). Holes are positional, in the order below.

Key Holes Used for
framework/session_resolve_staff session id, at, first try resolve a session cookie (a row with retry asks for a second pass)
framework/api_key_resolve key digest, at resolve an API key entrance
framework/session_issue party, session id, credential version, expires at issue a session (auth binding)
framework/credential_floor party, floor raise the credential version floor
framework/session_revoke_party party, version revoke a party's sessions below a version
framework/session_revoke session id revoke one session (auth binding)
framework/session_onboard session id, party, subject id bind a created subject to the session
framework/session_subject_staff session id, party, kind, subject id the SwitchSubject attached route
framework/browser browser id, at the DeclareBrowser attached route
framework/audit seed, stage, service, party, outcome, at one row per entrance stage
framework/outbox_parent id, dedupe key, payload (json), event id, at, folded outbox parent row of a write
framework/outbox_child id, kind, payload (json), parent id, at outbox child row (one queue message)
framework/outbox_done event id mark a consumed event done
framework/outbox_get id load a queued message
framework/outbox_sent id mark a swept message sent (not called since 0.11.6)
framework/outbox_sweep kinds list unsent messages to resend
framework/outbox_claim id claim one swept row before sending it (0.11.6; generated by default, see above)

Worker env — DATABASE_URL, COOKIE_DOMAIN, OUTBOX (queue binding), <ENTRY>_HOST per entrance, the key_binding of browser (a Secret Store binding), the KEK bindings of Sealed properties, and optionally ISOLATE_MARKER=1 (test header).