diff --git a/packages/sqlfu/docs/adapters.md b/packages/sqlfu/docs/adapters.md index 46dcb56b..e7fcc3ff 100644 --- a/packages/sqlfu/docs/adapters.md +++ b/packages/sqlfu/docs/adapters.md @@ -77,6 +77,10 @@ const rows = await stmt.all({slug: 'hello-world'}); Prepared handles expose `.all(params)`, `.run(params)`, and `.iterate(params)`. `params` can be a positional array (`[id]`) or a named object (`{slug}`). Adapters that have native prepared statements hold the driver handle and dispose it when the `using` scope exits. Adapters whose driver only exposes an `exec`/`execute` API provide a compatible shim: the method still exists, but each call re-issues the SQL through the driver. +Named object keys are bare identifiers. SQL written as `:slug`, `@slug`, or +`$slug` all bind from `{slug: 'hello-world'}`; sqlfu adapters translate that +shape to the underlying driver, including sqlite-wasm. For positional `?` +placeholders, pass an array. ## Compatibility matrix diff --git a/packages/sqlfu/docs/typegen.md b/packages/sqlfu/docs/typegen.md index 5e79cf66..f445f306 100644 --- a/packages/sqlfu/docs/typegen.md +++ b/packages/sqlfu/docs/typegen.md @@ -128,7 +128,34 @@ statement in that file must have its own `@name`. ## Parameter forms -Plain params use sqlfu's normal `:name` placeholder syntax. +Generated query wrappers use sqlfu's `:name` placeholder syntax. The placeholder +name becomes the application-facing param name exactly as you wrote it, so write +`:publishedSince` if you want callers to pass `{publishedSince}`. +`generate.casing` does not rewrite placeholder names. + +Use the SQL shape to describe richer params: + +- `where id = :id`: one scalar value. +- `where id in (:ids)`: one non-empty scalar array. +- `values (:post.slug, :post.published_at)`: one object named `post`; its + generated member fields follow `generate.casing`. +- `insert into posts (slug, title) values :posts`: one object or a non-empty + object array whose generated member fields come from the INSERT column list. +- `where (slug, published_at) in (:keys)`: a non-empty object array whose + generated member fields come from the row-value tuple. + +Top-level placeholder names are preserved literally: `:post`, `:posts`, and +`:keys` become `post`, `posts`, and `keys`. Object member fields are generated +SQL-derived fields, so the default `generate.casing: 'camel'` boundary applies +to them. Use `generate.casing: 'preserve'` if callers should pass raw SQL names +such as `published_at`. + +Runtime-expanded forms (`IN (:ids)`, row-value `IN (:keys)`, and +`values :posts`) rewrite the SQL before execution so the generated wrapper can +bind the right number of driver args for each call. That is why they reject +empty arrays and why each expanded param can appear only once in a query. + +Plain params are the default form. ```sql /** @name getPost */ @@ -164,43 +191,46 @@ Use dot paths when a query naturally accepts one object. ```sql /** @name insertPost */ -insert into posts (slug, title) -values (:post.slug, :post.title) -returning id, slug, title; +insert into posts (slug, published_at) +values (:post.slug, :post.published_at) +returning id, slug, published_at; ``` ```ts await insertPost(client, { post: { slug: 'hello-world', - title: 'Hello world', + publishedAt: '2026-05-14', }, }); ``` -The generated params type is `{post: {slug: string; title: string}}`. One object -path segment is supported today; nested paths such as `:post.author.id` are -intentionally rejected until the type shape is designed. +The generated params type is `{post: {slug: string; publishedAt: string}}` +under the default camel casing. One object path segment is supported today; +nested paths such as `:post.author.id` are intentionally rejected until the type +shape is designed. Use an object param directly after `values` when an INSERT column list already -names the object fields. The generated param accepts either one object or a -non-empty array. +names the object fields. The placeholder name is still preserved, but the object +fields are column-derived, so the default `generate.casing: 'camel'` boundary +applies to them. The generated param accepts either one object or a non-empty +array. ```sql /** @name insertPosts */ -insert into posts (slug, title) +insert into posts (slug, published_at) values :posts; ``` ```ts await insertPosts(client, { - posts: {slug: 'first', title: 'First'}, + posts: {slug: 'first', publishedAt: '2026-05-14'}, }); await insertPosts(client, { posts: [ - {slug: 'second', title: 'Second'}, - {slug: 'third', title: 'Third'}, + {slug: 'second', publishedAt: '2026-05-14'}, + {slug: 'third', publishedAt: '2026-05-15'}, ], }); ``` diff --git a/packages/sqlfu/src/sqlite-text.ts b/packages/sqlfu/src/sqlite-text.ts index 0853ab9f..d8c79eb4 100644 --- a/packages/sqlfu/src/sqlite-text.ts +++ b/packages/sqlfu/src/sqlite-text.ts @@ -35,9 +35,10 @@ export function sqlReturnsRows(sql: string): boolean { * tokenizer skips string literals and comments so colons/dollars inside * quoted strings aren't mistaken for placeholders. * - * Adapters whose driver natively accepts `Record` bindings (better-sqlite3, - * node:sqlite, libsql, sqlite-wasm) skip this helper and pass the params - * straight through. + * Adapters whose driver natively accepts bare-key `Record` bindings + * (better-sqlite3, node:sqlite, libsql) skip this helper and pass the params + * straight through. sqlite-wasm accepts prefixed keys, so its adapter maps + * sqlfu's bare keys to the SQL placeholder prefix before binding. */ export function rewriteNamedParamsToPositional( sql: string, diff --git a/tasks/improve-docs.md b/tasks/improve-docs.md index 941ab6dd..4cdcfe1c 100644 --- a/tasks/improve-docs.md +++ b/tasks/improve-docs.md @@ -4,6 +4,33 @@ Go through all of the docs and improve them. Look for inconsistencies, informati I want you to think holistically about the docs, as mentioned in your agent instructions for this repo. From time to time that will mean re-thinking docs entirely. Sometimes it will mean creating a separate task in the branch you create for *changing the product* instead. Docs are most useful when not thought of as something entirely downstream from the product code - if there's no clear way to write about a concept, the concept might be poorly thought through. You are free to flag that as an alternative to documenting weirdness. +## 2026-05-14 pass + +Status: done for this bedtime pass. The first decision was to keep the pass small and focused on generated query docs after recent merges: #113 extracted parameter expansion internals, #108 clarified the generated casing boundary, and #101 fixed sqlite-wasm named-parameter binding. The docs now clarify generated wrapper parameter forms in `packages/sqlfu/docs/typegen.md` and prepared-statement named binding in `packages/sqlfu/docs/adapters.md`; checks are green for the focused docs path and PR #115 has the reviewer-facing summary. + +Scope: + +- Tighten docs users read when choosing parameter shapes for generated query wrappers. +- Keep `packages/sqlfu/README.md` mostly stable unless a tiny overview sentence is needed. +- Prefer `packages/sqlfu/docs/typegen.md` and, if useful, the prepared statement / sqlite-wasm docs over landing-page or website information architecture work. +- Do not move this evergreen task to `tasks/complete/`. + +Checklist: + +- [x] Create an isolated worktree from `origin/main` and commit this status note first. _worktree is `/Users/mmkal/src/worktrees/sqlfu/bedtime-2026-05-14-improve-docs` on branch `bedtime/2026-05-14-improve-docs`._ +- [x] Open the early PR after the task-only commit. _PR #115 opened after `2afe32a` so the scope note is reviewable before implementation._ +- [x] Improve generated query parameter docs based on the recent merged parameter and casing work. _`typegen.md` now separates authored placeholder names from column-derived object fields and calls out runtime-expanded param limits; `adapters.md` records bare-key binding for `:name` / `@name` / `$name` prepared statements._ +- [x] Run relevant docs/package checks. _After `pnpm install`, `pnpm --dir packages/sqlfu exec vitest run test/generate/query-parameters.test.ts`, `pnpm --filter @sqlfu/ui build`, and `pnpm --filter sqlfu-website build` passed. An initial mis-scoped `pnpm --filter sqlfu test:node -- test/generate/query-parameters.test.ts` ran the full sqlfu suite and hit existing non-doc failures in `resolve-sqlfu-ui` / strict imports._ +- [x] Update the PR body with the net reviewer/user effect. _PR #115 now explains the docs change from the reader/reviewer perspective and lists the checks run._ + +Implementation notes: + +- 2026-05-14: Read the repo instructions and `writing-well` checklist before editing. The main checkout has unrelated website edits, so this pass is staying entirely in the requested worktree. +- 2026-05-14: Recent merge scan points at `packages/sqlfu/docs/typegen.md` as the primary docs surface. The current page has the right facts, but the parameter section can better separate authored placeholder names, column-derived object fields, runtime placeholder expansion, and sqlite-wasm/prepared-statement named binding. +- 2026-05-14: Chose not to churn the README. It already has a compact parameter summary; the detailed mental model belongs in the Type generation page and the prepared-statement note belongs with adapters. +- 2026-05-14: Em-dash scan found existing occurrences in historical task logs, agent docs, code comments, and UI test labels. The touched source docs are clean for new em-dashes; the only matches in `tasks/improve-docs.md` are older pass notes. +- 2026-05-14: Updated PR #115 body after pushing the docs commit. No follow-up task was split out because the broader README already points at the deep-dive page and did not need another local summary. + ## 2026-05-09 pass Status: done for this bedtime pass. The main docs work was the aggressive rewrite of `website/src/content/blog/introducing-sqlfu.md`: it dropped from about 2,750 words to about 1,250 while preserving the SQL-first argument, iterate/agent motivation, example query, feature inventory, limits, and acknowledgements. A small em-dash sweep also cleaned the newly-added Cloudflare D1 docs/README prose. Website build is green.