noboil

Architecture

The mental model behind noboil — schema-first CRUD generation, branded types, lifecycle hooks, and escape hatches.

The mental model. Read this once and you can predict noboil's behavior instead of memorizing its API.

Schema-first design

You define a Zod schema once. Everything else — the database table definition, the CRUD endpoints, the TypeScript types for input validation and query results — is derived from that single source of truth.

import { schema } from 'noboil/convex/schema'
import { boolean, object, string } from 'zod/v4'

export const s = schema({
  owned: {
    blog: object({
      title: string().min(1),
      body: string(),
      published: boolean().default(false)
    })
  }
})

The schema is the contract. Change it, and the table shape, validators, and endpoint signatures all update. There is no separate v.object(...) definition to keep in sync.

Schema branding

noboil uses TypeScript's structural type system against you (in a good way). Each schema slot stamps the Zod object with an opaque brand that the table factory checks at the type level.

The brand registry below is auto-generated from lib/noboil/src/convex/server/types.ts (SchemaHintMap):

BrandMaker → Factory + Wrapper
baseCreated by makeBase() → use cacheCrud() + baseTable()
kvCreated by makeKv() → use kv() + kvTable()
logCreated by makeLog() → use log() + logTable()
orgDefCreated by makeOrg() → pass to setup({ orgSchema })
orgCreated by makeOrgScoped() → use orgCrud() + orgTable()
ownedCreated by makeOwned() → use crud() + ownedTable()
quotaCreated by makeQuota() → use quota() + quotaTable()
singletonCreated by makeSingleton() → use singletonCrud() + singletonTable()

Auto-injected fields and indexes per brand (sourced from lib/noboil/src/shared/factory-meta.ts):

SlotBrandWrapperAuto-injected fieldsIndexesDescription
basebasebaseTable / cacheCrudupdatedAt? (optional)(none — keyed by upstream id)External-data cache (TTL, refresh, invalidate)
childrenchildchildTable / childCrudparentId, updatedAtby_parentChild-of-parent CRUD with cascade options
kvkvkvTable / kvkey, updatedAt, createdAt, deletedAt? (when softDelete)by_key (unique)Named key → value, public reads + role-gated writes (kv)
logloglogTable / logparent, seq, userId, createdAt, idempotencyKey?, deletedAt? (when softDelete)by_parent_seq, by_idempotencyAppend-only event log with atomic seq + idempotency (log)
orgScopedorgorgTable / orgCruduserId, orgId, updatedAtby_org, by_org_userOrg-scoped CRUD with membership + role checks
orgorgDeforgTables (via setup)(n/a — org definition itself)(n/a)The org definition (passed as orgSchema to noboil())
ownedownedownedTable / cruduserId, updatedAtby_userUser-owned CRUD
quotaquotaquotaTable / quotaowner, timestamps[]by_owner (unique)Sliding-window rate limit primitive (quota)
singletonsingletonsingletonTable / singletonCruduserId, updatedAtby_userOne row per user (get + upsert)

Passing an owned-branded schema where the factory expects orgScoped is a compile-time error. The SchemaTypeError type produces a message like:

Schema mismatch: expected org-scoped (makeOrgScoped), got user-owned (makeOwned).
Created by makeOrgScoped() -> use orgCrud() + orgTable()

When to use which:

  • owned — most tables. Each row belongs to one user.
  • orgScoped — rows belong to an organization. Queries scope by orgId; mutations verify org membership and role.
  • base — shared/global data with no ownership (e.g., a cache of external API responses). No auth required on reads or writes.
  • singleton — exactly one row per user (e.g., user profile, user preferences).
  • children — rows that have a foreign key to a parent table; access control inherits from the parent.
  • log — append-only event tables (messages, votes, audit). Atomic per-parent seq, idempotent appends, soft-delete + restore, no-update by default. See log factory.
  • kv — string-keyed global state (banner, feature flags, system status). Public reads, role-gated writes, optional key whitelist, conflict detection. See kv factory.
  • quota — sliding-window rate limits per owner (anti-spam, ballot-stuffing, API throttling). check/record/consume triad with hooks. See quota factory.

The noboil() entry point

noboil() is the single entry point. Call it once with a config object that includes a tables callback mapping each table name to a registered table:

import { noboil } from 'noboil/convex/server'
import { action, internalMutation, internalQuery, mutation, query } from './_generated/server'
import { getAuthUserId } from '@convex-dev/auth/server'
import { s } from './s'

export const api = noboil({
  query, mutation, action, internalQuery, internalMutation, getAuthUserId,
  orgSchema: s.team,
  hooks: { /* global hooks */ },
  middleware: [auditLog(), inputSanitize()],
  tables: ({ table }) => ({
    blog: table(s.blog, { rateLimit: { max: 10, window: 60_000 }, search: 'content' }),
    wiki: table(s.wiki, { acl: true, softDelete: true }),
    profile: table(s.profile),
    movie: table(s.movie, { key: 'tmdbId', ttl: 86_400 })
  })
})

The table() helper detects each schema's brand at runtime (via a __bs marker written by schema()) and dispatches to the matching factory under the hood. The table name comes from the object key — no need to repeat it.

api is then used by per-module files to expose endpoints:

// convex/blog.ts
import { api } from '../lazy'
export const { create, update, rm, pub: { list, read, search } } = api.blog

Lower-level path: setup() + crud()

If you want explicit control over which factories are called, use setup() directly:

const { crud, orgCrud, childCrud, cacheCrud, singletonCrud, pq, q, m, cq, cm } = setup({
  query, mutation, action, internalQuery, internalMutation, getAuthUserId
})

const { create, update, rm, pub: { list, read } } = crud('blog', s.blog, { rateLimit: { max: 10, window: 60_000 } })

noboil() is built on top of this. Use whichever style you prefer.

Factories

FactoryInputReturnsPurpose
crud(table, schema, opts?)OwnedSchema{ create, update, rm, pub, auth, ... }User-owned tables
orgCrud(table, schema, opts?)OrgSchema{ create, update, rm, list, read, ... }Org-scoped tables
childCrud(table, meta, opts?)child config{ create, update, rm, list, get, ... }Tables with a foreign key to a parent
cacheCrud(opts)BaseSchema + key field{ load, get, invalidate, refresh, ... }External data cache with TTL
singletonCrud(table, schema, opts?)SingletonSchema{ get, upsert }One row per user

Custom builders

BuilderAuth requiredContextUse for
pqNo (viewer ID resolved but optional)viewerId, withAuthorPublic queries — anyone can call, viewer info available if logged in
qYes (throws if unauthenticated)user, viewerId, get, withAuthorAuthenticated queries with ownership checks
mYesuser, create, patch, delete, getAuthenticated mutations with ownership checks
cqNoemptyContext-free queries (internal tooling, cron jobs)
cmNoemptyContext-free mutations (internal tooling, cron jobs)

Auth/pub split

Every crud() call returns two read APIs: pub and auth.

export const { create, update, rm, pub, auth } = crud('blog', owned.blog)

// In your Convex module, re-export:
export const { list, read, search } = pub   // no auth required
// auth.list, auth.read, auth.search        // require login
  • pub.list, pub.read, pub.search — callable by anyone, including unauthenticated users. Use for public-facing content.
  • auth.list, auth.read, auth.search — require a logged-in user. auth.list automatically scopes to the current user's rows.
  • create, update, rm — always require authentication. Mutations verify the caller owns the document before allowing changes.

You can attach default where filters to either side:

crud('blog', owned.blog, {
  pub: { where: { published: true } },       // public list only shows published
  auth: { where: { archived: false } },      // authed list hides archived
})

Hooks and middleware

Lifecycle hooks

Six hooks fire around each mutation. before* hooks can transform data; after* hooks are side-effect-only.

HookFiresReceivesReturns
beforeCreateBefore insert{ data }Modified data
afterCreateAfter insert{ data, id }void
beforeUpdateBefore patch{ id, patch, prev }Modified patch
afterUpdateAfter patch{ id, patch, prev }void
beforeDeleteBefore delete{ id, doc }void
afterDeleteAfter delete{ id, doc }void

Global vs per-table hooks

Global hooks are passed to setup() and run on every table. Their context includes a table field so you can branch:

setup({
  // ...
  hooks: {
    afterCreate: (ctx, { id }) => {
      console.log(`Created ${id} in ${ctx.table}`)
    },
  },
})

Per-table hooks are passed in the options of each CRUD factory:

crud('blog', owned.blog, {
  hooks: {
    beforeCreate: (_ctx, { data }) => ({ ...data, slug: slugify(data.title) }),
  },
})

When both exist, global hooks run first. For before* hooks, the global hook's output is piped into the per-table hook.

Built-in middleware

Middleware is syntactic sugar over global hooks with a name field and an operation field in context. Pass them to setup():

setup({
  // ...
  middleware: [auditLog(), inputSanitize(), slowQueryWarn({ threshold: 300 })],
})
MiddlewareWhat it does
auditLog(opts?)Logs create/update/delete with table, userId, and id. verbose: true includes payload.
inputSanitize(opts?)Strips HTML/script tags from string fields in beforeCreate and beforeUpdate.
slowQueryWarn(opts?)Warns when a mutation exceeds threshold ms (default 500).

Multiple middleware compose left-to-right. They merge into the global hooks chain via composeMiddleware().

The where clause query language

All list, read, and search endpoints accept an optional where parameter. It is a typed object derived from your schema shape.

Field equality

// Exact match
where: { published: true }

// Multiple fields (AND)
where: { published: true, category: "tech" }

Comparison operators

For numeric fields, use comparison operator objects instead of a direct value:

where: { rating: { $gte: 4 } }
where: { price: { $between: [10, 50] } }

Available operators: $gt, $gte, $lt, $lte, $between.

The own filter

where: { own: true }

Shorthand for "only rows where userId matches the authenticated caller." This works on pub endpoints too — if the caller is logged in, it scopes; if not, it is ignored.

OR groups

where: {
  published: true,
  or: [
    { category: "tech" },
    { category: "science" },
  ],
}

Top-level fields are AND-ed. The or array creates a disjunction — each entry is a WhereGroupOf<S> with the same shape as the top-level (minus or itself).

Escape hatches

Custom queries alongside generated ones

The pq, q, m, cq, and cm builders returned by setup() are standard zCustomQuery/zCustomMutation wrappers. Use them in the same file as your generated CRUD:

export const { create, update, rm, pub } = crud('blog', owned.blog)

// Custom query alongside generated ones
export const trending = pq({
  args: { limit: z.number().default(10) },
  handler: async (ctx, { limit }) => {
    // Full access to ctx.db — write any query you want
    return ctx.db.query('blog')
      .filter(q => q.eq(q.field('published'), true))
      .order('desc')
      .take(limit)
  },
})

pq gives you viewer info without requiring auth. q requires auth and gives you ctx.user and ctx.get (ownership-checked document fetch). m gives you ctx.create, ctx.patch, ctx.delete with automatic timestamps and ownership.

Ejecting individual tables

If a table outgrows the CRUD pattern, stop calling the factory for that table and write raw Convex functions using pq/q/m. The schema branding and table definitions are independent of the CRUD layer — you keep ownedTable(owned.blog) in your schema even after ejecting the CRUD for blog. See the ejecting guide for a full walkthrough.

Architecture overview

graph TD
  S[schema] -->|brands with __bs| N[noboil]
  N -->|dispatches by brand| F[crud / orgCrud / cacheCrud / singletonCrud / childCrud]
  F -->|generates| E[create / list / read / update / rm endpoints]
  E -->|consumed by| H[useList / useMutate / useForm hooks]
  H -->|renders| C[Form / AutoForm / typed fields]

SpacetimeDB dev loop

After editing your schema or reducer logic:

sequenceDiagram
  participant Dev as Developer
  participant CLI as SpacetimeDB CLI
  participant Mod as SpacetimeDB Module
  participant Gen as spacetime generate
  participant App as Next.js App
  Dev->>CLI: bun spacetime:publish
  CLI->>Mod: Compile + deploy module
  Mod-->>CLI: Module published
  Dev->>Gen: bun spacetime:generate
  Gen-->>Dev: TypeScript bindings updated
  Dev->>App: bun dev
  App-->>Dev: Client reconnects with new schema

Schema changes require both steps. If you only publish, the module updates but the client TypeScript types are stale. If you only generate, you get updated types but the server module doesn't match.

Bundled shadcn UI

readonly/ui/ is synced from cnsync — never edit by hand. All demos and the generated forms render against this set.

55 top-level components: accordion, alert, alert-dialog, aspect-ratio, avatar, badge, breadcrumb, button, button-group, calendar, card, carousel, chart, checkbox, collapsible, combobox, command, context-menu, dialog, direction, drawer, dropdown-menu, empty, field, hover-card, input, input-group, input-otp, item, kbd, label, menubar, native-select, navigation-menu, pagination, popover, progress, radio-group, resizable, scroll-area, select, separator, sheet, sidebar, skeleton, slider, sonner, spinner, switch, table, tabs, textarea, toggle, toggle-group, tooltip

  • ai-elements (48): agent, artifact, attachments, audio-player, canvas, chain-of-thought, checkpoint, code-block, commit, confirmation, connection, context, controls, conversation, edge, environment-variables, file-tree, image, inline-citation, jsx-preview, message, mic-selector, model-selector, node, open-in-chat, package-info, panel, persona, plan, prompt-input, queue, reasoning, sandbox, schema-display, shimmer, snippet, sources, speech-input, stack-trace, suggestion, task, terminal, test-results, tool, toolbar, transcription, voice-selector, web-preview

Demo coverage matrix

Auto-generated grid showing every registered table, the noboil options it ships with in lazy.ts, and which of the 5 vertical demo apps consume it (per database). Use this to find a working example of any feature.

15 tables across 10 demo apps. ✓ = the demo's frontend imports from this table.

TableOptionscvx-blogcvx-chatcvx-moviecvx-orgcvx-pollstdb-blogstdb-chatstdb-moviestdb-orgstdb-poll
blogpub, rateLimit, search
blogProfile
chatcascade, pub, rateLimit
messagepub
moviekey
orgunique
orgProfile
poll
pollProfile
pollVoteQuota
projectacl, cascade
siteConfigsoftDelete
taskacl, aclFrom
votesoftDelete
wikiacl, rateLimit, softDelete

noboil() config options

Auto-generated from NoboilOptions in lib/noboil/src/convex/server/noboil.ts. Pass these to noboil({ ... }) at the entry point.

Plus tables: ({ table }) => ({ ... }) callback (always required). 11 top-level options on SetupConfig:

FieldTypeRequired
actionActionBuilder&lt;DM, 'public'&gt;required
getAuthUserId`(ctx: never) => Promise<null \string>`
internalMutationMutationBuilder&lt;DM, 'internal'&gt;required
internalQueryQueryBuilder&lt;DM, 'internal'&gt;required
mutationMutationBuilder&lt;DM, 'public'&gt;required
queryQueryBuilder&lt;DM, 'public'&gt;required
hooks?GlobalHooksoptional
middleware?Middleware[]optional
orgCascadeTables?OrgCascadeTableConfig&lt;DM&gt;[]optional
orgSchema?ZodObjectoptional
strictFilter?booleanoptional

Built-in middleware

Auto-generated list of every middleware factory exported from noboil/{convex,spacetimedb}/server. Compose them in the middleware: [...] array of noboil({ ... }).

3 middleware factories (combine via middleware: [a(), b()] in noboil({ ... })). Description column auto-extracted from leading JSDoc.

FactoryOptions argConvexSpacetimeDBDescription
auditLog`opts?: { logLevel?: 'debug' \'info'; verbose?: boolean }`
inputSanitizeopts?: \{ fields?: string[] \}Strips control chars + zero-width chars from string fields before insert/update. Restrict to specific fields via opts.fields.
slowQueryWarnopts?: \{ threshold?: number \}Emits warn-level log when any reducer-driven mutation exceeds threshold ms (default 500ms).

Schema relationships

Mermaid graph derived from every parent: '...', aclFrom: { table: '...' }, cascade: [{ table: '...' }], and foreignKey reference in backend/convex/s.ts.

16 tables, 5 relationships (parent / cascade / aclFrom). Color = factory slot.

graph LR
  subgraph base
    movie["movie"]
  end
  subgraph children
    message["message"]
    schema["schema"]
  end
  subgraph kv
    siteConfig["siteConfig"]
    schema["schema"]
  end
  subgraph log
    vote["vote"]
    schema["schema"]
  end
  subgraph org
    team["team"]
  end
  subgraph orgScoped
    project["project"]
    task["task"]
    wiki["wiki"]
  end
  subgraph owned
    blog["blog"]
    chat["chat"]
    poll["poll"]
  end
  subgraph quota
    pollVote["pollVote"]
  end
  subgraph singleton
    blogProfile["blogProfile"]
    orgProfile["orgProfile"]
    pollProfile["pollProfile"]
  end
  message -->|parent| chat
  children -->|parent| chat
  log -->|parent| poll
  chat -->|cascade| message
  task -->|aclFrom| project
  style movie fill:#fef3c7,stroke:#333
  style message fill:#fce7f3,stroke:#333
  style schema fill:#fce7f3,stroke:#333
  style siteConfig fill:#e0e7ff,stroke:#333
  style schema fill:#e0e7ff,stroke:#333
  style vote fill:#dcfce7,stroke:#333
  style schema fill:#dcfce7,stroke:#333
  style team fill:#fef9c3,stroke:#333
  style project fill:#fed7aa,stroke:#333
  style task fill:#fed7aa,stroke:#333
  style wiki fill:#fed7aa,stroke:#333
  style blog fill:#dbeafe,stroke:#333
  style chat fill:#dbeafe,stroke:#333
  style poll fill:#dbeafe,stroke:#333
  style pollVote fill:#fee2e2,stroke:#333
  style blogProfile fill:#f3e8ff,stroke:#333
  style orgProfile fill:#f3e8ff,stroke:#333
  style pollProfile fill:#f3e8ff,stroke:#333

Schema fields

Every user-defined Zod field per table, parsed from backend/convex/s.ts. Auto-injected fields (userId, updatedAt, _creationTime) live in auto-fields above and are not duplicated here.

Auto-extracted from backend/convex/s.ts. 12 tables, 44 user-defined fields (auto-injected fields like userId/updatedAt are added by factories — see auto-fields above).

slot: base

movie — 14 field(s)

FieldZod chain
backdrop_pathstring().nullable()
budgetnumber().nullable()
genresarray(object(\{ id: number(), name: string() \}))
original_titlestring()
overviewstring()
poster_pathstring().nullable()
release_datestring()
revenuenumber().nullable()
runtimenumber().nullable()
taglinestring().nullable()
titlestring()
tmdb_idnumber()
vote_averagenumber()
vote_countnumber()

slot: children

message — 3 field(s)

FieldZod chain
chatIdzid('chat')
partsarray(messagePart)
rolezenum(['user', 'assistant', 'system'])

slot: kv

siteConfig — 0 field(s)

(no inline fields parsed — see source)

slot: log

vote — 0 field(s)

(no inline fields parsed — see source)

slot: org

team — 0 field(s)

(no inline fields parsed — see source)

slot: orgScoped

project — 4 field(s)

FieldZod chain
descriptionstring().optional()
editorsarray(zid('users')).max(100).optional()
namestring().min(1)
statuszenum(['active', 'archived', 'completed']).optional()

task — 5 field(s)

FieldZod chain
assigneeIdzid('users').nullable().optional()
completedboolean().optional()
priorityzenum(['low', 'medium', 'high']).optional()
projectIdzid('project')
titlestring().min(1)

wiki — 6 field(s)

FieldZod chain
contentstring().optional()
deletedAtnumber().optional()
editorsarray(zid('users')).max(100).optional()
slugstring()
statuszenum(['draft', 'published'])
titlestring().min(1)

slot: owned

blog — 7 field(s)

FieldZod chain
attachmentsfiles.max(5).optional()
categoryzenum(['tech', 'life', 'tutorial'], \{ error: 'Select a category' \})
contentstring().min(3, 'At least 3 characters')
coverImagefile.nullable().optional()
publishedboolean()
tagsarray(string()).max(5, 'Max 5 tags').optional()
titlestring().min(1, 'Required')

chat — 2 field(s)

FieldZod chain
isPublicboolean()
titlestring().min(1)

poll — 3 field(s)

FieldZod chain
closedAtnumber().nullable().optional()
optionsarray(string().min(1)).min(2).max(10)
questionstring().min(1)

slot: quota

pollVote — 0 field(s)

(no inline fields parsed — see source)

Demo route map

Every Next.js page.tsx file in web/{cvx,stdb}/{demo}/src/app/. Use this to find the runtime entry point that exercises a feature.

79 Next.js page.tsx routes across all demo apps.

Convex

DemoRoutes
blog/, /[id], /[id]/edit, /dev, /login, /login/email, /pagination, /profile
chat/, /[id], /login, /login/email, /public
movie/, /fetch
org/, /dashboard, /invite/[token], /join/[slug], /login, /login/email, /members, /new, /onboarding, /projects, /projects/[projectId], /projects/[projectId]/edit, /projects/new, /settings, /wiki, /wiki/[wikiId], /wiki/[wikiId]/edit, /wiki/new
poll/, /[id], /[id]/edit, /login, /login/email, /profile

SpacetimeDB

DemoRoutes
blog/, /[id], /[id]/edit, /dev, /login, /login/email, /pagination, /profile
chat/, /[id], /login, /login/email, /public
movie/, /fetch
org/, /dashboard, /invite/[token], /join/[slug], /login, /login/email, /members, /new, /onboarding, /projects, /projects/[projectId], /projects/[projectId]/edit, /projects/new, /settings, /wiki, /wiki/[wikiId], /wiki/[wikiId]/edit, /wiki/new
poll/, /[id], /[id]/edit, /dev, /login, /login/email, /profile

Table options inventory

How many tables in backend/{convex,spacetimedb}/lazy.ts enable each option, and which ones.

11 known table options scanned across both backend lazy.ts files. Numbers are how many tables enable each option.

Optioncvx tablesstdb tablesWhere (cvx)Where (stdb)
rateLimit42blog, chat, task, wikiblog, chat
search10blog
softDelete33siteConfig, vote, wikisiteConfig, vote, wiki
pub22chat, messageblog, chat
acl20project, wiki
aclFrom10task
cascade21chat, projectproject
key01movie
unique01org
ttl00
staleWhileRevalidate00

Doc-snippet syntax check

Every ```ts/tsx code fence in doc/content/docs/*.mdx is scanned by Bun.Transpiler so silent rot (a renamed export breaking an embedded snippet) shows up here.

Transpiler.scan() (from bun) over every ```ts/tsx code fence in doc/content/docs/*.mdx. Catches syntax-level rot when source code changes break embedded snippets.

557/560 blocks parseable (99%). Snippets without TypeScript-shaped syntax (config JSON, shell, mermaid) are skipped — they're counted as parseable but not actually checked.

Failures:

  • doc/content/docs/base.mdx block #6: BuildMessage: Expected ";" but found ":"
  • doc/content/docs/recipes.mdx block #86: BuildMessage: Expected "*/" to terminate multi-line comment
  • doc/content/docs/singleton.mdx block #9: BuildMessage: Unexpected ...

Hook signature drift

When a hook signature in source diverges from how it's documented (e.g. arg order/name change), this generator flags it. Compares the first parameter name only — quick and high-precision.

Compares first argument names of every useXxx(...) call appearing in docs to the actual hook signature in lib/noboil/src/{convex,spacetimedb}/react/use-*.ts. Catches a common kind of doc rot.

31/31 hooks mentioned in docs. Of those, 0 have at least one const useX = (...)-style declaration in a code fence. 0 drift mismatches.

No drift detected.

Doc duplication audit

Long paragraphs that appear verbatim in 2+ .mdx files. Either consolidate or rewrite in place — duplication is maintenance debt.

Scans every .mdx for paragraphs ≥120 chars appearing in 2+ files. Catches accidental duplication that adds maintenance cost without adding info.

0 duplicate paragraph(s) found (across 33 doc files).

No duplicates above threshold — every long paragraph appears in exactly one file.

Factory parity audit

Every factory checked across both backends: source file present, at least one demo table registered, factory name referenced in tests, and dedicated/mentioning doc.

Per-factory parity. Each factory checked: source file present, at least one demo table registered in the entry point, table referenced by ≥1 demo app, factory name referenced in tests, and dedicated doc page.

9/9 factories at full parity.

Slot (Brand)TablesConvex (src · reg · demos · tests)SpacetimeDB (src · reg · demos · tests)DocsStatus
base (base)1✓ src · 1/1 reg · 1 demos · ✓ tests✓ src · 1/1 reg · 1 demos · ✓ tests🟢
children (child)1✓ src · 1/1 reg · 1 demos · ✓ tests✓ src · 1/1 reg · 1 demos · ✓ tests🟢
kv (kv)1✓ src · 1/1 reg · 1 demos · ✓ tests✓ src · 1/1 reg · 1 demos · ✓ tests🟢
log (log)1✓ src · 1/1 reg · 1 demos · ✓ tests✓ src · 1/1 reg · 1 demos · ✓ tests🟢
org (orgDef)1✓ src · 1/1 reg · 1 demos · ✓ tests✓ src · 1/1 reg · 1 demos · ✓ tests🟢
orgScoped (org)3✓ src · 3/3 reg · 3 demos · ✓ tests✓ src · 3/3 reg · 3 demos · ✓ tests🟢
owned (owned)3✓ src · 3/3 reg · 3 demos · ✓ tests✓ src · 3/3 reg · 3 demos · ✓ tests🟢
quota (quota)1✓ src · 1/1 reg · 1 demos · ✓ tests✓ src · 1/1 reg · 1 demos · ✓ tests🟢
singleton (singleton)3✓ src · 3/3 reg · 3 demos · ✓ tests✓ src · 3/3 reg · 3 demos · ✓ tests🟢

Factory depth (quantitative)

Beyond presence, this measures how much each factory has — source LOC, tests, hook LOC, doc mentions. Use it to spot uneven investment.

Quantitative depth per factory: source LOC, hook file LOC, test count (cases that reference the factory name), dedicated doc page LOC if any, and total mentions in all docs. Bigger numbers ≠ better quality, but large gaps signal uneven investment.

Factorysrc LOC (cvx/stdb)hook LOC (cvx/stdb)tests (cvx/stdb)doc pagetabs (cvx/stdb)
base251 / 17591 / 8618 / 25236L4 / 4 ✓
child275 / 16543 / 4113 / 27240L4 / 4 ✓
kv168 / 16343 / 4910 / 23270L4 / 4 ✓
log392 / 27477 / 7812 / 17357L5 / 5 ✓
orgScoped500 / 40243 / 4138 / 47255L4 / 4 ✓
owned440 / 14143 / 4154 / 57266L5 / 5 ✓
quota117 / 12536 / 8014 / 15257L4 / 4 ✓
singleton76 / 11530 / 4123 / 30227L4 / 4 ✓

Option/feature parity

Compares each factory's option surface (config keys it accepts) across both backends. Functional symmetry, not just documentation symmetry.

Per-factory option parity. For each factory, checks every expected option is textually referenced in both backends' factory file. The "intentional" column documents architecturally-justified backend-specific options (with rationale below).

52/62 option × backend cells satisfied. After intentional exemptions: 🟢 = no unaccounted-for gaps.

Factorycvx coveragestdb coverageintentional asymstatuscvx unaccountedstdb unaccounted
base5/52/53🟢
child2/53/55🟢
kv4/44/41🟢
log3/33/33🟢
orgScoped5/55/52🟢
owned4/44/43🟢
quota3/33/30🟢
singleton1/21/22🟢

Architectural backend-specific options (intentional)

Options that exist on one backend but not the other because the underlying database has a different runtime model:

  • base.fetcher (stdb-only): stdb cache fills client-side via reducers; server has no HTTP capability
  • base.hooks (stdb-only): stdb base uses table subscriptions; lifecycle hooks go on the wrapping reducer
  • base.staleWhileRevalidate (stdb-only): no server-side refresh in stdb model; SWR managed by client useCacheEntry
  • child.cascade (cvx-only): configured on parent table, not child — symmetric with stdb
  • child.rateLimit (cvx-only): shared with owned/orgScoped factories, not redeclared in child.ts
  • child.softDelete (cvx-only): shared rule from CrudOptions, not redeclared in child.ts
  • child.cascade (stdb-only): configured on parent table, not child — symmetric with cvx
  • child.pub (stdb-only): stdb uses subscription-based reads — pub-style filtering happens via subscription where clauses, not on child factory
  • kv.keys (stdb-only): stdb kv uses constant string keys without runtime whitelist (typed via TS only)
  • log.pub (stdb-only): stdb log uses subscription where clauses for visibility scoping
  • log.search (stdb-only): stdb log searches client-side over subscribed rows
  • log.withAuthor (stdb-only): stdb subscriptions return row data only; author lookup is a separate client-side join
  • orgScoped.aclFrom (stdb-only): stdb checks aclFrom in client-side query layer (subscription is owner-checked at the row, parent-derived ACL applied client-side)
  • orgScoped.unique (stdb-only): stdb declares unique constraints via column attributes in module bindings, not via factory option
  • owned.acl (cvx-only): owned tables in cvx may opt into ACL; stdb keeps ACL strictly within orgScoped factory
  • owned.pub (stdb-only): stdb uses subscription where clauses for visibility scoping (no separate pub option needed)
  • owned.search (stdb-only): stdb owned searches client-side over subscribed rows
  • singleton.hooks (cvx-only): cvx singletonCrud lifecycle hooks delegated to underlying mutation builder
  • singleton.rateLimit (stdb-only): stdb singleton has at most one row per user — rate-limit pressure is naturally bounded

Demo parity

Cross-backend audit of all 5 demo apps: routes, e2e tests, source LOC.

Per-demo parity audit. Each of the 5 demos compared across both backends: route count, e2e test count, source LOC. 5/5 demos at full parity. Backend-specific routes (intentional asymmetries) listed below.

DemoRoutes (cvx/stdb)E2E tests (cvx/stdb)Source LOC (cvx/stdb)Status
blog8/8 ✓52 / 52 ✓881 / 945 ✓🟢
chat5/5 ✓26 / 26 ✓488 / 410 ✓🟢
movie2/2 ✓14 / 14 ✓303 / 524 ✓🟢
org18/18 ✓128 / 128 ✓2366 / 2612 ✓🟢
poll6/7 ✓82 / 82 ✓852 / 888 ✓🟢

Backend-specific routes (intentional)

  • stdb/dev — SpacetimeDB SchemaPlayground dev tool (cvx has no equivalent component)
  • movie src LOC asymmetry — stdb-movie does TMDB fetching client-side (no server-side action available); cvx delegates to action — architectural difference, see base.fetcher in option-parity above

Utility (non-factory) parity

Beyond factories: file upload, presence, org membership, helpers, middleware, setup, test helpers, RLS. Cross-backend audit.

Per-domain parity for non-factory utilities. Compares the export surface of each utility module across both backends. Shared exports = symbols present on both sides; cvx-only/stdb-only counts exclude documented architectural exemptions.

3/8 domains at full parity.

Domainsharedcvx exportsstdb exportscvx-only (gap)stdb-only (gap)intentional asymstatus
File upload4440🟢
Presence4440🟢
Org membership6942CascadeTableConfig, OrgByUserIndexLike, OrgConfig, OrgExports, OrgFieldBuilders, OrgInviteByOrgIndexLike, OrgInviteByTokenIndexLike, OrgInvitePkLike, OrgInviteReducersConfig, OrgInviteReducersExports, OrgInviteRowLike, OrgInviteTableLike, OrgJoinReducersConfig, OrgJoinReducersExports, OrgJoinRequestByOrgIndexLike, OrgJoinRequestByOrgStatusIndexLike, OrgJoinRequestPkLike, OrgJoinRequestRowLike, OrgJoinRequestTableLike, OrgMemberByOrgIndexLike, OrgMemberPkLike, OrgMemberReducersConfig, OrgMemberReducersExports, OrgMemberRowLike, OrgMemberTableLike, OrgPkLike, OrgRole, OrgRowLike, OrgSlugIndexLike10🟡
Server helpers636781ConvexErrorData, hk, isSoftDeletedTypedFieldErrors, hkCtx18🟡
Middleware6660🟢
Setup / entry265CrudDefaults, OrgTypeBuilders5🟡
Test helpers3914OrgTestCrudConfig, TestAuthConfigErrorData, TestContext13🟡
RLS / subscriptions0016FieldBuilder, RlsCategory, RlsPub, StdbDeps, StdbTable, TableFields, ZodBridgeT9🟡

Intentional architectural asymmetries

  • Org membership makeInviteHandlers (cvx-only): cvx convention: handlers are server-side mutation builders (returns mutation defs)
  • Org membership makeJoinHandlers (cvx-only): cvx convention: handlers are server-side mutation builders
  • Org membership makeMemberHandlers (cvx-only): cvx convention: handlers are server-side mutation builders
  • Org membership canEdit (stdb-only): stdb-side ACL helper invoked client-side from RLS where clause; cvx checks server-side via requireOrgRole
  • Org membership makeInviteReducers (stdb-only): stdb convention: reducers are explicit table writers (parallel to cvx makeInviteHandlers)
  • Org membership makeInviteToken (stdb-only): stdb invite tokens are generated reducer-side; cvx uses crypto.randomUUID inline
  • Org membership makeJoinReducers (stdb-only): stdb convention: reducers (parallel to cvx makeJoinHandlers)
  • Org membership makeMemberReducers (stdb-only): stdb convention: reducers (parallel to cvx makeMemberHandlers)
  • Org membership makeOrgTables (stdb-only): stdb table-builder helpers; cvx tables defined declaratively in schema
  • Org membership requireOrgMember (stdb-only): stdb auth check helper exposed for direct reducer use; cvx uses requireOrgRole/requireOrgEditor inside CRUD wrappers
  • Server helpers handleConvexError (cvx-only): Convex-specific error wrapping; stdb uses SenderError class instead
  • Server helpers applyPatch (stdb-only): reducer arg patch helper — Convex uses ctx.db.patch() directly
  • Server helpers enforceRateLimit (stdb-only): stdb-side rate-limit enforcement helper — cvx uses checkRateLimit via setup
  • Server helpers getFieldErrors (stdb-only): stdb field-error parsing — cvx uses ConvexError shape
  • Server helpers getFirstFieldError (stdb-only): see getFieldErrors
  • Server helpers getOwnedRow (stdb-only): stdb ownership-checked row fetch — cvx uses requireOwn via context
  • Server helpers idFromWire (stdb-only): stdb id wire-format conversion (u32/u64) — cvx uses string Id throughout
  • Server helpers idToWire (stdb-only): see idFromWire
  • Server helpers identityEquals (stdb-only): stdb Identity comparison — cvx compares string ids with ===
  • Server helpers identityFromHex (stdb-only): stdb Identity hex conversion — cvx has no Identity type
  • Server helpers identityToHex (stdb-only): see identityFromHex
  • Server helpers makeError (stdb-only): stdb SenderError factory — cvx uses ConvexError
  • Server helpers makeOptionalFields (stdb-only): stdb reducer arg builder helper
  • Server helpers parseSenderMessage (stdb-only): stdb sender message parsing — cvx uses Convex auth context
  • Server helpers pickPatch (stdb-only): stdb patch shape builder — cvx uses spread/pick inline
  • Server helpers reducerArgs (stdb-only): stdb reducer arg mapper — cvx uses convex/values directly
  • Server helpers resetRateLimitState (stdb-only): stdb rate-limit state reset — cvx uses internal mutation
  • Server helpers timestampEquals (stdb-only): stdb Timestamp comparison — cvx uses number ===
  • Setup / entry api (cvx-only): cvx exports a unified api proxy wrapping all functions — stdb exposes tables/reducers directly
  • Setup / entry mergeCacheHooks (cvx-only): cvx hook composition for cache factory — stdb uses inline closures
  • Setup / entry mergeGlobalHooks (cvx-only): cvx hook composition for global hooks — stdb uses inline closures
  • Setup / entry mergeHooks (cvx-only): see mergeGlobalHooks
  • Setup / entry setupCrud (stdb-only): stdb-specific CRUD setup helper that registers reducers — cvx setup() returns helpers used per-table
  • Test helpers TEST_EMAIL (cvx-only): convex-test deterministic email constant — stdb uses createTestUser instead
  • Test helpers getOrgMembership (cvx-only): convex-test helper to inspect membership rows — stdb uses queryTable with filter
  • Test helpers makeOrgTestCrud (cvx-only): convex-test wrapper that exposes server-side org-scoped CRUD with auth pre-baked — stdb tests use callReducer with asUser instead
  • Test helpers makeTestAuth (cvx-only): convex-test auth helper — stdb uses asUser + connectAsTestUser pattern
  • Test helpers asUser (stdb-only): stdb test helper to call reducer as a specific identity — cvx uses ctx.withIdentity
  • Test helpers callReducer (stdb-only): stdb reducer-call wrapper — cvx uses ctx.mutation directly
  • Test helpers cleanup (stdb-only): stdb test cleanup helper — cvx uses ctx.run
  • Test helpers createTestUser (stdb-only): stdb deterministic test-user factory — cvx uses ensureTestUser
  • Test helpers extractErrorData (stdb-only): stdb SenderError data parsing — cvx uses err.data directly
  • Test helpers getErrorCode (stdb-only): see extractErrorData
  • Test helpers getErrorDetail (stdb-only): see extractErrorData
  • Test helpers getErrorMessage (stdb-only): see extractErrorData
  • Test helpers queryTable (stdb-only): stdb test-time table query helper — cvx uses ctx.run + ctx.db.query
  • RLS / subscriptions RLS_COL (stdb-only): stdb RLS column-name constants — cvx auth happens in handler, no constants
  • RLS / subscriptions RLS_TBL (stdb-only): see RLS_COL
  • RLS / subscriptions makeSchema (stdb-only): stdb table-builder for spacetimedb schema generation — cvx uses defineSchema
  • RLS / subscriptions rlsChildSql (stdb-only): stdb child-table RLS where-clause builder
  • RLS / subscriptions rlsJoinWhereSender (stdb-only): stdb join-based RLS builder
  • RLS / subscriptions rlsSql (stdb-only): stdb generic RLS SQL where-clause builder
  • RLS / subscriptions rlsWherePub (stdb-only): stdb pub-visibility RLS builder
  • RLS / subscriptions rlsWhereSender (stdb-only): stdb sender-scoped RLS builder
  • RLS / subscriptions zodToStdbFields (stdb-only): Zod → SpacetimeDB column-type mapper

Component parity

Per-file React component audit (form, fields, file upload, step form, error boundary, permission guard, editors, misc).

Per-file React component parity. Each *.tsx in lib/noboil/src/{convex,spacetimedb}/components/ cross-checked. Shared = symbol present in both files; -only = symbol present in only one.

7/9 component files at full parity.

Filecvxstdbshared exportscvx-onlystdb-onlystatus
editors-section.tsx✓ 2L✓ 2L1🟢
error-boundary.tsx✓ 10L✓ 10L1🟢
fields.tsx✓ 20L✓ 26L5🟢
file-field.tsx✓ 243L✓ 390L3FileApi, UploadOptions, UploadResponse🟡
form.tsx✓ 168L✓ 194L7🟢
index.ts✓ 11L✓ 12L21UploadOptions, UploadResponse🟡
misc.tsx✓ 12L✓ 16L3🟢
permission-guard.tsx✓ 25L✓ 25L1🟢
step-form.tsx✓ 75L✓ 79L1🟢

Mega parity (every file, every symbol)

Whole-repo audit of lib/noboil/src/{convex,spacetimedb}/ cross-backend. Walks every file, compares every export. The most exhaustive parity check — anything not on this list does not exist in the source.

Whole-repo audit. Walks every *.ts/.tsx/.json/.sh/.toml/.md/.yml/.yaml (excl. tests/codegen output via SKIP_DIRS) in 7 parallel cvx/stdb directory pairs (lib + backend + 5 demos). 150 file-level + 207 symbol-level architectural exemptions registered. Test files (*.test.ts) ARE included in file-presence check (symbol parity skipped for tests).

244 shared files · 86 cvx-only · 65 stdb-only · 31 files with cross-backend symbol divergence. Status: 🔴 104 unaccounted-for gap(s).

Pairsharedcvx-onlystdb-onlysymbol gapsstatus
lib/noboil/src8447 (43 unaccounted)50 (28 unaccounted)31🔴
backend431 (0 unaccounted)6 (0 unaccounted)0🟢
web/blog321 (0 unaccounted)0 (0 unaccounted)0🟢
web/chat231 (0 unaccounted)1 (0 unaccounted)0🟢
web/movie161 (0 unaccounted)3 (2 unaccounted)0🔴
web/org482 (0 unaccounted)4 (0 unaccounted)0🟢
web/poll373 (0 unaccounted)1 (0 unaccounted)0🟢
script (naming pair)0 matched2 (0 unaccounted)4 (0 unaccounted)🟢

Unaccounted gaps

  • lib/noboil/src: cvx-only file __tests__/_budget-fakes.ts, __tests__/audit.test.ts, __tests__/budget.property.test.ts, __tests__/budget.synthetic.test.ts, __tests__/budget.test.ts, __tests__/builder.test.ts, __tests__/devtools.test.ts, __tests__/eslint-smoke.test.ts, __tests__/manifest.test.ts, __tests__/optimistic-store.test.tsx, __tests__/use-bulk-mutate.test.tsx, __tests__/use-form.test.tsx, __tests__/use-list.test.tsx, __tests__/use-mutate.test.ts, server/__tests__/setup-hooks.test.ts, server/__tests__/test-harness.test.ts, server/audit.ts, server/budget.ts, server/test-harness.ts, tools/__tests__/boundary.test.ts, tools/__tests__/dispatch.test.ts, tools/__tests__/error.test.ts, tools/__tests__/manifest.test.ts, tools/__tests__/parser.test.ts, tools/__tests__/step-sink.test.ts, tools/__tests__/to-dispatch-error.test.ts, tools/builder.ts, tools/caller-runtime.ts, tools/codegen/emit.ts, tools/codegen/extract-meta.ts, tools/codegen/index.ts, tools/codegen/scan.ts, tools/codegen/schema.ts, tools/define-provider.ts, tools/error.ts, tools/hermetic.ts, tools/http.ts, tools/index.ts, tools/manifest.ts, tools/parser.ts, tools/prompt-blocks.ts, tools/types.ts, tools/validate.ts
  • lib/noboil/src: stdb-only file __tests__/check.test.ts, __tests__/migrate.test.ts, defaults.ts, react/__tests__/devtools.test.ts, react/__tests__/optimistic-store.test.tsx, react/__tests__/use-bulk-mutate.test.tsx, react/__tests__/use-list.test.tsx, react/__tests__/use-mutate.test.ts, react/use-hydrated.ts, server/__tests__/_helpers.ts, server/__tests__/cache-crud.test.ts, server/__tests__/child.test.ts, server/__tests__/crud.test.ts, server/__tests__/file.test.ts, server/__tests__/kv.test.ts, server/__tests__/log.test.ts, server/__tests__/org-crud.test.ts, server/__tests__/org-invites.test.ts, server/__tests__/org-join.test.ts, server/__tests__/org-members.test.ts, server/__tests__/org.test.ts, server/__tests__/presence.test.ts, server/__tests__/quota.test.ts, server/__tests__/rls.test.ts, server/__tests__/setup.test.ts, server/__tests__/singleton.test.ts, server/__tests__/stdb-tables.test.ts, server/__tests__/test.test.ts
  • lib/noboil/src/components/file-field.tsx — cvx-only: — · stdb-only: FileApi, UploadOptions, UploadResponse
  • lib/noboil/src/components/index.ts — cvx-only: — · stdb-only: UploadOptions, UploadResponse
  • lib/noboil/src/index.ts — cvx-only: Api, ConflictData, ConvexErrorData, DefType, DevError, DevSubscription, ErrorData, ErrorHandler, FieldKind, FieldMeta, FieldMetaMap, FileKind, FormReturn, OrgContextValue, OrgDoc, OrgProviderProps, SoftDeleteOpts, ToastFn, ZodSchema · stdb-only: BaseSchema, DEFAULT_HTTP_URI, DEFAULT_PORT, DEFAULT_TOKEN_KEY, DEFAULT_WS_URI, InferCreate, InferReducerArgs, InferReducerInputs, InferReducerOutputs, InferReducerReturn, InferRow, InferRows, InferUpdate, OrgDefSchema, OrgSchema, OwnedSchema, Register, RegisteredDefaultError, RegisteredMeta, SchemaPhantoms, SingletonSchema, TOKEN_COOKIE_KEY, wsToHttp
  • lib/noboil/src/next/index.ts — cvx-only: — · stdb-only: ActiveOrgQuery, SqlQueryConfig, TableQueryConfig
  • lib/noboil/src/react/devtools.ts — cvx-only: — · stdb-only: DevConnection
  • lib/noboil/src/react/error-toast.ts — cvx-only: ErrorHandler · stdb-only: —
  • lib/noboil/src/react/form.ts — cvx-only: — · stdb-only: FormToastOption, Widen
  • lib/noboil/src/react/index.ts — cvx-only: Api, ConvexCrudRefs, ConvexKvRefs, ConvexLogRefs, ConvexQuotaRefs, ConvexSingletonRefs, ListItems · stdb-only: ActiveOrgState, CreateSpacetimeClientOptions, ErrorData, ErrorHandler, FileRow, InfiniteListResult, InfiniteListWhere, KvRowBase, ListSort, ListWhere, LogRowBase, MutationFail, MutationOk, MutationResult, OrgMembership, PresenceHeartbeatArgs, QuotaRowBase, SingletonRowBase, SkipInfiniteListResult, SkipListResult, SortDirection, SortMap, SortObject, SpacetimeConnectionBuilder, SpacetimeConnectionFactory, StdbCrudRefs, StdbKvRefs, StdbLogRefs, StdbQuotaRefs, StdbSingletonRefs, TokenStore, TypedFieldErrors, UseListResult, WhereFieldValue, WhereGroup, Widen, getErrorDocsUrl, getErrorSuggestion, useStdbHydrated
  • lib/noboil/src/react/org.tsx — cvx-only: — · stdb-only: ActiveOrgState, OrgMembership
  • lib/noboil/src/react/use-crud.ts — cvx-only: ConvexCrudRefs · stdb-only: StdbCrudRefs
  • lib/noboil/src/react/use-infinite-list.ts — cvx-only: — · stdb-only: InfiniteListResult, InfiniteListWhere, SkipInfiniteListResult
  • lib/noboil/src/react/use-kv.ts — cvx-only: ConvexKvRefs · stdb-only: KvRowBase, StdbKvRefs
  • lib/noboil/src/react/use-list.ts — cvx-only: ListItems · stdb-only: ListWhere, SkipListResult, UseListResult, WhereGroup
  • lib/noboil/src/react/use-log.ts — cvx-only: ConvexLogRefs · stdb-only: LogRowBase, StdbLogRefs
  • lib/noboil/src/react/use-presence.ts — cvx-only: — · stdb-only: PresenceHeartbeatArgs, PresenceRow
  • lib/noboil/src/react/use-quota.ts — cvx-only: ConvexQuotaRefs · stdb-only: QuotaRowBase, StdbQuotaRefs
  • lib/noboil/src/react/use-singleton.ts — cvx-only: ConvexSingletonRefs · stdb-only: SingletonRowBase, StdbSingletonRefs
  • lib/noboil/src/react/use-upload.ts — cvx-only: — · stdb-only: UploadCallOptions
  • lib/noboil/src/server/helpers.ts — cvx-only: ConvexErrorData, hk, isSoftDeleted · stdb-only: TypedFieldErrors
  • lib/noboil/src/server/index.ts — cvx-only: AuditAppendInput, AuditExports, AuditHooks, AuditRow, BudgetAuditSummary, BudgetCheckResult, BudgetExports, BudgetHooks, BudgetReserveResult, ConvexErrorData, makeAudit, makeBudget, periodKeyFor · stdb-only: CrudDefaults, OrgRole, OrgTypeBuilders, StdbDeps, TestContext, TestUser
  • lib/noboil/src/server/kv.ts — cvx-only: — · stdb-only: KvConfig, KvExports, KvHooks, KvOptions, KvRow, KvTableLike
  • lib/noboil/src/server/log.ts — cvx-only: — · stdb-only: LogConfig, LogExports, LogHooks, LogOptions, LogRow, LogTableLike
  • lib/noboil/src/server/org-crud.ts — cvx-only: OrgCrudOptions · stdb-only: —
  • lib/noboil/src/server/org-invites.ts — cvx-only: InviteDocLike · stdb-only: OrgInviteByTokenIndexLike, OrgInvitePkLike, OrgInviteReducersConfig, OrgInviteReducersExports, OrgInviteRowLike, OrgInviteTableLike, OrgJoinRequestByOrgStatusIndexLike, OrgJoinRequestPkLike, OrgJoinRequestRowLike, OrgJoinRequestTableLike, OrgMemberRowLike, OrgMemberTableLike, OrgPkLike, OrgRowLike
  • lib/noboil/src/server/org-join.ts — cvx-only: JoinRequestItem · stdb-only: OrgJoinReducersConfig, OrgJoinReducersExports, OrgJoinRequestByOrgStatusIndexLike, OrgJoinRequestPkLike, OrgJoinRequestRowLike, OrgJoinRequestTableLike, OrgMemberRowLike, OrgMemberTableLike, OrgPkLike, OrgRowLike
  • lib/noboil/src/server/org-members.ts — cvx-only: OrgMemberItem · stdb-only: OrgMemberPkLike, OrgMemberReducersConfig, OrgMemberReducersExports, OrgMemberRowLike, OrgMemberTableLike, OrgPkLike, OrgRole, OrgRowLike
  • lib/noboil/src/server/org.ts — cvx-only: — · stdb-only: CascadeTableConfig, OrgByUserIndexLike, OrgConfig, OrgExports, OrgFieldBuilders, OrgInviteByOrgIndexLike, OrgInviteRowLike, OrgJoinRequestByOrgIndexLike, OrgJoinRequestRowLike, OrgMemberByOrgIndexLike, OrgMemberRowLike, OrgRowLike, OrgSlugIndexLike
  • lib/noboil/src/server/quota.ts — cvx-only: — · stdb-only: QuotaConfig, QuotaExports, QuotaRow, QuotaTableLike
  • lib/noboil/src/server/setup.ts — cvx-only: — · stdb-only: CrudDefaults, OrgTypeBuilders
  • lib/noboil/src/server/test.ts — cvx-only: OrgTestCrudConfig, TestAuthConfig · stdb-only: ErrorData, TestContext
  • lib/noboil/src/zod.ts — cvx-only: — · stdb-only: UndefinedToOptional
  • web/movie: stdb-only file src/app/_actions.ts, src/app/_movie-types.ts

On this page