Repository navigation
v2.0.0
Major Changes
- #16
dd3ea44Thanks @drepkovsky!
Ship field builder platform, server-driven admin, and standalone RPC API
questpie (core)
Field Builder System (NEW)
Replace raw Drizzle column definitions with a type-safe field builder. Collections and globals now define fields via a callback that receives a field builder proxy f:
// Before
collection("posts").fields({
title: varchar("title", { length: 255 }),
content: text("content"),
});
// After
q.collection("posts").fields((f) => ({
title: f.text({ required: true }),
content: f.textarea({ localized: true }),
publishedAt: f.datetime(),
}));Built-in field types: text, textarea, number, boolean, date, datetime, time, email, url, select, upload, json, object, array, relation. Each field produces Drizzle columns, Zod validation schemas, typed operators for filtering, and serializable metadata for admin introspection — all from a single declaration.
Custom field types — define your own field types with the field<TConfig, TValue>() factory. A custom field implements toColumn (Drizzle column), toZodSchema (validation), getOperators (query filtering), and getMetadata (introspection). Register custom fields on the builder via q.fields({ myField }) and they become available as f.myField() in all collections:
const slugField = field<SlugFieldConfig, string>()({
type: "slug",
_value: undefined as unknown as string,
toColumn: (name, config) => varchar(name, { length: 255 }),
toZodSchema: (config) => z.string().regex(/^[a-z0-9-]+$/),
getOperators: (config) => ({
column: stringColumnOperators,
jsonb: stringJsonbOperators,
}),
getMetadata: (config) => ({
type: "slug",
label: config.label,
required: config.required ?? false,
localized: false,
readOnly: false,
writeOnly: false,
}),
});
// Register:
const app = q({ name: "app" }).fields({ slug: slugField });
// Use:
collection("pages").fields((f) => ({ slug: f.slug({ required: true }) }));Custom operators — the operator<TValue>() helper creates typed filter functions from (column, value, ctx) => SQL. Each field's getOperators returns context-aware operator sets for both column and JSONB access. Operators are automatically used by the query builder and exposed via the client SDK's where parameter.
Reactive Field System (NEW)
Server-evaluated reactive behaviors on fields via meta.admin:
hidden/readOnly/disabled— conditionally toggle field state based on form datacompute— auto-compute values from other fields- Dynamic
options— load select/relation options on the server with dependency tracking and debounce
Reactive handlers run server-side with full access to ctx.db, ctx.user, ctx.req. A proxy-based dependency tracker automatically detects which form fields each handler reads and serializes that info to the client for efficient re-evaluation.
Standalone RPC API (NEW)
New q.rpc() builder for defining type-safe remote procedures outside collection/global CRUD. RPC procedures are routed through the HTTP adapter at /rpc/<path> with nested routers, access control, and full type inference on the client SDK.
const r = q.rpc<typeof app>();
export const dashboardRouter = r.router({
stats: r.fn({
handler: async ({ app }) => {
/* ... */
},
}),
});Collections and globals also support scoped .functions() for entity-specific RPC, routed at /collections/:slug/rpc/:name and /globals/:slug/rpc/:name.
Callable q Builder
The q export is now a callable builder: use q({ name: "my-app" }) to create a fresh QuestpieBuilder, or access q.collection(), q.global(), q.job() etc. as methods. Default field types are auto-registered. Standalone function exports (collection, global, job, fn, email, auth, config, rpc) are are also re-exported.
Introspection API (NEW)
Full server-side introspection of collection and global schemas for admin consumption: field metadata, access permissions, relation info, reactive config, validation schemas — all serialized from builder state. Admin UI consumes this directly instead of relying on client-side config.
Queue Runtime Redesign (BREAKING)
- Redesigned
QueueServicewith proper lifecycle (start/stop/drain), graceful shutdown, and health checks - New Cloudflare Queues adapter alongside pg-boss
- Worker handlers now receive
{ payload, app }instead of(payload, ctx) - Workflow builder API refined with better type inference
Realtime Pipeline Hardening (BREAKING)
PgNotifyAdapter: proper connection lifecycle, idempotentstart/stop, owned vs shared client tracking, handler cleanupRedisStreamsAdapter: graceful error handling in read loop, no longer auto-disconnects client onstop()streamedQueryfrom@tanstack/react-queryintegrated as first-class citizen in collection query options
Access Control (BREAKING)
- Removed
access.fieldsfrom collection/global builder — field-level access is now defined per-field viaaccess: { read, update }in the field definition itself - CRUD generator evaluates field-level access at runtime, filtering output and validating input per field
CRUD API Alignment (BREAKING)
- Client SDK
update/delete/restorenow accept object params{ id, data }instead of positional args - Relation field names are automatically transformed to FK columns in create/update operations
updateManyanddeleteManyadded to HTTP adapter, client SDK, and tanstack-query- Better Auth drizzle adapter now correctly uses transactions
Server-Driven Admin Config
Admin configuration (sidebar, dashboard, branding, actions) is now defined server-side and served via introspection. The server emits serializable ComponentReference objects ({ type, props }) instead of React elements. A typed component factory c is available in all admin config callbacks:
// Server-side (serializable, no React imports):
.admin(({ c }) => ({
icon: c.icon("ph:article"), // => { type: "icon", props: { name: "ph:article" } }
badge: c.badge({ text: "New" }), // => { type: "badge", props: { text: "New" } }
}))The client resolves these references via ComponentRenderer which looks up the matching React component from the admin builder's component registry. Built-in components (icon → Iconify, badge) are registered by default; custom ones are added via qa().components({ myComponent: MyReactComponent }).
@questpie/admin
Server-Driven Schema (BREAKING)
Admin UI now consumes field schemas, sidebar config, dashboard config, and branding from server introspection instead of client-side builder config. defineAdminConfig is replaced by server-defined metadata.
Builder API Cleanup (BREAKING)
- Removed from
qanamespace:qa.collection(),qa.global(),qa.block(),qa.sidebar(),qa.dashboard(),qa.branding()— these are now server-side concerns - Kept:
qa.field(),qa.listView(),qa.editView(),qa.widget(),qa.page()for client-only UI registrations - Admin
CollectionBuilderandGlobalBuildercompletely rewritten — all schema methods (.fields(),.list(),.form()) removed; only UI-specific methods remain (.meta(),.preview(),.autoSave(),.use())
Reactive Fields UI (NEW)
useReactiveFieldshook evaluates server-defined reactive config (hidden/readOnly/disabled/compute) client-side with automatic dependency trackinguseFieldOptionshook for dynamic options loading with search debounce and SSE streaming
Block Editor Rework
- Full drag-and-drop block editor with canvas layout, block library sidebar, tree navigation
- Block field metadata unified between collections and blocks
- Block prefetch values inferred from field definitions
Actions System (NEW)
Collection-level actions system with both client and server handler modes:
- Handler types:
navigate(routing),api(HTTP call),form(dialog with field inputs),dialog(custom component),custom(arbitrary code),server(server-side execution with full app context) - Scopes:
header(list view toolbar — primary buttons + secondary dropdown),bulk(selected items toolbar),single/row(per-item) - Server actions run handler on the server with access to
app,db,session; return typed results (success,error,redirect,download) with side-effects (invalidate,toast,navigate) - Form actions accept field definitions from the field registry (
f.text(),f.select(), etc.) for type-safe input collection in a dialog - Confirmation dialogs configurable per action with destructive styling support
- Built-in action presets:
create,save,delete,deleteMany,duplicate
Realtime Multiplexor
Migrated from example code into core admin package for SSE-based live updates.
Test Migration
All admin tests migrated from vitest to bun:test; vitest dependency removed.
@questpie/tanstack-query
RPC Query Options (NEW)
Full type-safe query/mutation option builders for RPC procedures with nested router support. The createQuestpieQueryOptions factory now accepts a TRPC generic for RPC router types, producing .rpc.* namespaced option builders.
Realtime Streaming (NEW)
- Re-exports
buildCollectionTopic,buildGlobalTopic,TopicConfig,RealtimeAPIfrom core client - Collection
.find,.findOne,.countoption builders producestreamedQuery-based options for SSE real-time updates
Batch Operations (NEW)
updateManyanddeleteManymutation option builders for collectionskeybuilders for all collection/global operations
@questpie/openapi (NEW PACKAGE)
OpenAPI 3.1 spec generator for QUESTPIE instances. Generates schemas for collections (CRUD + search), globals, auth, and RPC endpoints. Includes a Scalar-powered API reference UI mountable via the adapter.
@questpie/elysia / @questpie/hono / @questpie/next
- All adapters accept
rpcconfig to mount standalone RPC router trees alongside CRUD routes - Formatting standardized (tabs → spaces alignment)
@questpie/hono:questpieHononow correctly forwards RPC router to fetch handler
create-questpie (NEW PACKAGE)
Interactive CLI (bunx create-questpie) for scaffolding new QUESTPIE projects. Ships with a TanStack Start template including pre-configured collections, globals, admin setup, migrations, and dev tooling.
1.1.1
Patch Changes
7172275Thanks @drepkovsky! - fix: public uploads set visibility flag
1.1.0
Minor Changes
-
a7efd1eThanks @drepkovsky! - feat: add defaultAccess for global access control defaultsNew
defaultAccessoption in CMS config sets default access rules for all collections and globals:const cms = q({ name: "app" }).build({ defaultAccess: { read: ({ session }) => !!session, create: ({ session }) => !!session,