Skip to content

no-orm Core v1 #1

@heiwen

Description

@heiwen

Goal

Build no-orm as a tiny, database-independent persistence core for TypeScript libraries.

The immediate benchmark is the hebo-gateway storage layer: the same core should be able to describe its persistence model once and support multiple backends without choosing the database up front.

What This Must Cover

hebo-gateway does not need a full ORM. It needs:

  • a canonical schema model
  • TypeScript types derived from that schema for authoring code
  • an adapter / dialect contract for backend-specific behavior
  • a thin execution contract for reads, writes, and transactions
  • minimal schema bootstrap from that schema
  • a minimal query / operation language
  • extensibility hooks for schema, writes, and queries

Concretely, the current storage layer needs support for:

Schema requirements

  • string fields with logical length constraints
  • number fields
  • boolean fields
  • timestamp fields
  • JSON fields
  • nullable fields
  • single-field and composite primary keys
  • ordered indexes

Type inference requirements

  • TypeScript types derived from the canonical schema
  • stable type-level mapping for field types and nullability
  • enough typing support to write host-library code against schema-defined models

Query and write requirements

  • insert/get/list/delete/upsert-style operations
  • filtering
  • ordering
  • cursor pagination
  • transactions

Backend adaptation requirements

  • backend-specific type mapping
  • backend-specific JSON extraction/select behavior
  • backend-specific quoting, placeholders, upsert behavior, and limit handling

Extensibility requirements

  • schema extension by consumers of the host library
  • write-time injection or enforcement of field values
  • query-time enforcement of required filters or scoped values

Core Capabilities

1. Canonical schema

One portable schema representation:

  • Schema
  • Model
  • Field
  • Index

2. Logical types, not physical types

The schema should describe intent, not storage implementation.

Initial field types:

  • string
  • number
  • boolean
  • timestamp
  • json

Adapters decide how those map to Postgres, SQLite, MySQL, MongoDB, memory, or other targets.

3. TypeScript inference layer

The canonical schema should also be usable as a source of static TypeScript types for application and library code.

At minimum, the product should support:

  • deriving model types from schema
  • preserving field names
  • mapping field types into useful TypeScript types
  • mapping nullability into TypeScript nullability

This should remain separate from backend runtime mapping. The schema spec defines the canonical schema shape; type inference defines how developers write strongly typed code against that shape.

4. Adapter / dialect contract

Adapters must be able to consume the canonical schema and provide backend-specific behavior for:

  • physical type mapping
  • DDL emission
  • quoting and placeholders
  • JSON select/extract semantics
  • upsert semantics
  • limit/pagination quirks

5. Execution contract

The core also needs a minimal, generic execution boundary that adapters can target when running compiled persistence operations.

  • execute read operations
  • execute write operations
  • support transactions where available

The exact method surface is still open. The important requirement is that the execution boundary stays small, generic, and usable across multiple backend adapters without introducing a full query builder or ORM runtime.

6. Minimal schema bootstrap

To match the current hebo-gateway storage layer, v1 should support minimal migration behavior:

  • create model if not exists
  • create index if needed
  • backend-specific idempotent DDL where supported

This is not a full migration framework. It is just enough to bootstrap storage from the canonical schema.

7. Minimal query / operation layer

To match the hebo-gateway storage layer without becoming storage-specific, the core needs a minimal, generic, database-independent operation model for common persistence actions.

At minimum:

  • create / insert
  • read one
  • read many
  • delete
  • upsert-style write
  • portable filtering
  • ordering
  • cursor pagination

This layer should be generic enough to support multiple backends and host-library use cases, but intentionally too small to become a general-purpose query builder.

8. Extensibility hooks

Because no-orm is intended to be embedded inside other libraries, it must support controlled extension by consumers of the host library.

At minimum, the product needs hooks for:

  • extending the canonical schema before it is finalized
  • adding or enforcing field values during write operations
  • adding or enforcing constraints during query operations

This is required for use cases such as:

  • tenant scoping
  • audit fields
  • plugin-owned fields
  • policy-enforced query filtering

Non-Goals for v1

Do not build these into the core yet:

  • relations
  • foreign keys
  • defaults
  • full migration framework
  • codegen
  • full query builder
  • generated client
  • reactive client cache/state
  • dedicated schema validation layer
  • broad arbitrary lifecycle hooks
  • mixing backend runtime mapping rules into the canonical schema spec

Differentiation

Versus Drizzle

Drizzle is SQL-first and dialect-aware from the start. no-orm should be database-independent at the schema layer and lower-level: a portable schema + adapter core, not a SQL-first ORM toolkit.

Versus Prisma

Prisma is an integrated ORM workflow with schema, client generation, and migrations. no-orm should stay much smaller and embeddable: one portable schema model plus adapter/runtime contracts.

Versus Kysely

Kysely is a type-safe SQL query builder. no-orm should be schema-first, not query-builder-first, and must remain portable beyond SQL-shaped tooling.

Versus unadapter

unadapter standardizes CRUD across multiple backends and ORMs. no-orm should be narrower and lower-level: the canonical schema model and backend projection layer underneath that kind of abstraction.

Versus Better Auth database adapters

Better Auth provides a database adapter factory for implementing database persistence behind Better Auth, including methods such as create, update, delete, find, count, schema generation, and field/model name transformation. Its default adapters are built around tools such as Kysely and Prisma.

That is useful inspiration for adapter ergonomics, but the scope is different.

no-orm should differ by:

  • being a reusable persistence core for third-party libraries, not an adapter layer owned by one host framework
  • centering the canonical schema model first
  • keeping adapter contracts generic across host-library use cases, rather than being shaped around Better Auth's adapter factory and model pipeline

Versus TanStack DB

TanStack DB is a reactive client-side data layer. no-orm should stay focused on backend persistence definition and execution, not client reactivity or caching.

Success Criteria

This ticket is complete when:

  • the hebo-gateway conversation storage schema can be represented cleanly in the canonical schema
  • useful TypeScript types can be derived from that schema for host-library code
  • at least two materially different backends can consume the same schema
  • the adapter layer can cover the backend differences hebo-gateway currently handles manually
  • minimal schema bootstrap can create the required storage structures for at least one SQL backend
  • the minimal query/operation layer can express the current hebo-gateway storage operations without hand-written SQL in the storage implementation
  • consumers of a host library can extend schema, write behavior, and query enforcement without backend-specific code
  • the public API remains clearly smaller than Drizzle, Prisma, Kysely, unadapter, or TanStack DB

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions