Skip to content

v2.0.0

Choose a tag to compare

@drepkovsky drepkovsky released this 17 Feb 00:32
· 1024 commits to main since this release

Major Changes

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 data
  • compute — 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 QueueService with 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, idempotent start/stop, owned vs shared client tracking, handler cleanup
  • RedisStreamsAdapter: graceful error handling in read loop, no longer auto-disconnects client on stop()
  • streamedQuery from @tanstack/react-query integrated as first-class citizen in collection query options

Access Control (BREAKING)

  • Removed access.fields from collection/global builder — field-level access is now defined per-field via access: { 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/restore now accept object params { id, data } instead of positional args
  • Relation field names are automatically transformed to FK columns in create/update operations
  • updateMany and deleteMany added 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 qa namespace: 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 CollectionBuilder and GlobalBuilder completely rewritten — all schema methods (.fields(), .list(), .form()) removed; only UI-specific methods remain (.meta(), .preview(), .autoSave(), .use())

Reactive Fields UI (NEW)

  • useReactiveFields hook evaluates server-defined reactive config (hidden/readOnly/disabled/compute) client-side with automatic dependency tracking
  • useFieldOptions hook 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, RealtimeAPI from core client
  • Collection .find, .findOne, .count option builders produce streamedQuery-based options for SSE real-time updates

Batch Operations (NEW)

  • updateMany and deleteMany mutation option builders for collections
  • key builders 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 rpc config to mount standalone RPC router trees alongside CRUD routes
  • Formatting standardized (tabs → spaces alignment)
  • @questpie/hono: questpieHono now 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

1.1.0

Minor Changes

  • a7efd1e Thanks @drepkovsky! - feat: add defaultAccess for global access control defaults

    New defaultAccess option 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,