anolilab/lunora/lunora-functions
Claude Code and agent skill by anolilab/lunora
- community
Checking that the SKILL.md file is still in the repository.
Authoring rules for Lunora schema and server functions under `lunora/` —
Install
npx vskill@latest install anolilab/lunora/lunora-functionsSKILL.md
Lunora Functions
The core authoring rules for Lunora backend code. The verify loop after every edit:
lunora codegen— regenerateslunora/_generated/and prints advisor
findings (it does not run tsc).
lunora verify— codegen dry-run plustsc --noEmit, no files written. The
type-check needs a tsconfig.json (without one it warns and skips) and is off under --no-typecheck.
When to Use
- Writing or editing schema, queries, mutations, or actions.
- Reviewing
lunora/code for correctness and idiom. - Deciding query vs mutation vs action, or public vs internal.
When Not to Use
- Setting up a new project (
lunora-quickstart) or auth (lunora-setup-auth). - Diagnosing a slow query or write conflict (
lunora-performance-audit). - Changing an existing schema with data at rest (
lunora-migration-helper).
Schema: defineSchema + defineTable
lunora/schema.ts exports defineSchema as the default export. Every column is a v.* validator. Declare an index for every access pattern you query by.
- Lunora injects
_idand_creationTimeon every row, so don't declare them. lunorash/*is the umbrella package templates install;@lunora/serveris
the same module if the project depends on it directly.
.index("name", ["a", "b"])— columns are ordered; put equality columns
first, then the range/sort column.
.shardBy("ownerId")partitions the table across Durable Objects by key;
.global() replicates it to D1 for cross-region reads. Default (neither) is a single root-scoped ShardDO. They are not combined on one table — for choosing between them, see the side-by-side comparison in the lunora-performance-audit skill.
Validators (v.*)
string, number, boolean, id("table"), null, any, bigint, bytes, literal(value), array(item), object({...}), record(key, value), union(a, b, …), optional(inner), partial({...}), geoPoint(), the convenience types date, timestamp, and storage (an R2 object key), and v.from(schema) to adopt any Standard Schema v1 validator (Zod, Valibot, …).
- Required is the default.
v.optional(x)means the field may be absent;
x.nullable() means it may be null. They are different.
- Refinements chain on the validator and are enforced at the trust boundary:
v.string().min(1).max(4096), .email(), .url(), .pattern(re); v.number().int().min(0), .positive(); v.array(x).max(n). Bound every client-supplied string and array — templates cap text at .max(4096).
Functions: query / mutation / action
Each function declares its inputs with .input(...) (a v.* map), optionally adds .use(middleware) (rate limiting, RLS, masking) and .output(validator), and ends with a terminal .query / .mutation / .action taking a ({ ctx, args }) => … handler. Export them as named consts from lunora/*.ts. Codegen nests references by path: lunora/messages.ts → api.messages.<fn>, lunora/billing/invoices.ts → api.billing.invoices.<fn>. Public functions land in _generated/api.ts, internal* ones in _generated/internal.ts.
| Kind | Reads ctx.db | Writes ctx.db | Side effects / fetch | Reactive | |
|---|---|---|---|---|---|
query | yes | no | no | yes | |
mutation | yes | yes, transactional | no | — | |
action | yes | yes, autocommitting | yes | — |
Import the builders from _generated/server, not from lunorash/server: codegen binds them to your schema, which is what types ctx.db, v.id("channels"), and index names. lunorash/server exports the schema/HTTP/validator surface and LunoraError, never query / mutation / action.
Templates also map #lunora/* to ./lunora/* in package.json imports, so #lunora/_generated/server.js works from anywhere in the project.
- Pick the right kind. Reactive read →
query. Transactional write →
mutation. External I/O (fetch, third-party SDKs, calling other functions) → action. An action has a ctx.db, but its own writes are not transactional: each autocommits as it runs, so a later throw rolls nothing back. Reach data through ctx.runQuery / ctx.runMutation — a mutation called that way runs in the same all-or-nothing transaction a top-level one gets, so put every write that has to land together in one mutation rather than sequencing several from the action.
- `internal*` variants (
internalQuery,internalMutation,
internalAction) are not exposed to clients — use them for server-only logic called from actions, crons, or other functions.
- Throw `LunoraError(code, message?, options?)` from
lunorash/serverfor
expected failures ("UNAUTHORIZED", "NOT_FOUND", …); it serializes with its code to the client, where isUnauthorizedError and friends branch on it.
The ctx.db API
Reads:
Writes (mutations, and autocommitting in actions):
Prefer withIndex over .filter: a .filter(...) without a covering index scans the whole table (advisor: filter_without_index), and a bare .collect() grows with the table and with every subscriber's payload (unbounded_collect). Constrain with .withIndex, cap with .take(n), or paginate.
Pagination
A paginated query takes a paginationOpts arg and returns .paginate(...) as-is; the client hook (usePaginatedQuery, see lunora-realtime) supplies it:
Following foreign keys: ctx.db.related
Every v.id("table") column is an edge in a graph your schema already declares. ctx.db.related walks it, so "this customer's tickets, and those tickets' messages" is one call instead of a hand-written chain of withIndex lookups.
- Edge names are `"<table>.<column>"`.
edgesrestricts the walk to the
named ones; a name the schema does not declare is refused, not ignored.
- `direction` —
"out"follows the ids this row holds,"in"the rows that
point at it, "both" (the default) does both.
- `depth` defaults to
1, max4. `limit` defaults to50, max200.
Both caps refuse rather than clamp, so a probe for the ceiling just errors.
- Only a column is an edge: a bare
v.id(...),v.optional(v.id(...))or
v.array(v.id(...)). An id nested in a v.object / v.union / v.record is not. An array FK is followed outward only.
- It is an ordinary read — RLS, column masks, soft delete,
.global()
routing and reactivity all apply, because every hop goes back through ctx.db. Under a .rls("required") schema each hop gets exactly the verdict a direct read of that table would, so declare a read policy for every table the walk can reach — or narrow it with edges.
- Index the foreign keys. Each inward hop is a
WHERE fk IN (…)read, and
unindexed it scans — see the lunora-performance-audit skill for the cost model and the traversal caps.
Other ctx capabilities
Always available:
ctx.auth— the resolved session (ctx.auth.userId,nullwhen anonymous).ctx.secrets— Cloudflare Secrets Store.ctx.ip— the caller'sCF-Connecting-IP;undefinedoff Cloudflare.ctx.runQuery(all kinds),ctx.runMutation(mutations, actions),
ctx.runAction (actions) — call another function by its api.* / internal.* reference.
ctx.log,ctx.metrics,ctx.span/ctx.trace— logging, metrics, the
current wide-event span and a scoped tracing helper.
This is the start of the file. Read the full SKILL.md on GitHub.
Security
- Security scanPassed, 95 of 100Pattern checks on the skill's files.
- Model reviewPassed
Quality evaluation
- Task evaluationNot runNo evaluation yet. Run one in Skill Studio.
Source
- SKILL.md in the repositoryCheckingpackages/cli/skills/lunora-functions/SKILL.md
Popularity
- 7-day trend score
- 5
Not refreshed yet.
Works with
Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.
- Claude Code
- Cursor
- GitHub Copilot
- Windsurf
- Codex
- Gemini CLI
- Cline
- Amp
- Roo Code
- Goose
- Continue
- Kilo Code
- Junie
- Trae
- OpenHands
- Qwen Code
- AI Agent
- Tabnine Chat
- Sourcegraph Cody
- Amazon Q
- Firebase Genkit
- Sweep
- Devin
- Phind
- Mentat
- Claude Desktop
- Aider
- GPT Pilot
- Plandex
- bolt.new
- v0
- Replit Agent
- Anse
- TypingMind
- BetterChatGPT
- LibreChat
- Warp AI
- Pieces
- Fig AI
Embed badge
[](https://verified-skill.com/skills/anolilab/lunora/lunora-functions)