Summary
This document defines the v1 schema model for no-orm.
The goal is a minimal, database-independent schema representation that:
- is small enough to stay practical as a core library
- can support SQL and non-SQL adapters later
- avoids backend-specific concepts in the public schema shape
- is sufficient for current storage needs such as the
hebo-gateway conversation storage layer
This spec defines the canonical schema shape directly. There is no separate internal AST in v1.
Goals
- Provide one canonical schema representation.
- Keep the schema model portable across databases.
- Keep the public API small and explicit.
- Support only the minimum structural concepts needed today.
- Leave query builders, migrations, and runtime insert behavior outside this spec.
Non-goals
v1 does not include:
- dedicated schema validation as a first-class feature
- defaults
- runtime defaults
- arbitrary function defaults
- foreign keys
- relations
- enums
- uniqueness constraints
- check constraints
- custom index methods
- custom backend metadata escape hatches
- migrations
- schema diffing
- introspection
- separate builder and compiled schema models
Canonical Type Definitions
export type Schema = Record<string, Model>;
export interface Model {
fields: Record<string, Field>;
primaryKey: {
fields: [string, ...string[]];
};
indexes?: Index[];
}
export interface Field {
type: FieldType;
nullable?: boolean;
}
export type FieldType =
| { type: "string"; max?: number }
| { type: "number" }
| { type: "boolean" }
| { type: "timestamp" }
| { type: "json" };
export interface Index {
fields: IndexField[];
}
export interface IndexField {
field: string;
order?: "asc" | "desc";
}
Naming
The schema vocabulary is:
These terms are intended to be more database-independent than table and column, while still remaining familiar to ORM users.
Schema Structure
A schema is a record where the keys are model names.
Example:
const schema: Schema = {
conversations: {
fields: {
id: { type: { type: "string", max: 255 } },
},
primaryKey: {
fields: ["id"],
},
},
};
There is no wrapper object such as { models: ... }.
Field Types
v1 supports exactly five field types:
string
number
boolean
timestamp
json
String
{ type: "string"; max?: number }
Semantics:
max is a logical maximum length
max is not a guarantee of a particular backend storage type
- adapters may map it to
VARCHAR(n), TEXT, or another appropriate physical representation
Number
Semantics:
- represents numeric data
- storage format is adapter-defined
- examples include SQL integer, float, double, numeric, or another backend-native numeric representation
Boolean
Semantics:
- represents boolean data
- storage format is adapter-defined
- examples include SQL
BOOLEAN, integer-backed booleans, or another backend-native boolean representation
Timestamp
Semantics:
- represents a point in time
- storage format is adapter-defined
- examples include SQL
BIGINT, SQL TIMESTAMP, or another backend-native representation
JSON
Semantics:
- represents structured JSON-compatible data
- storage format is adapter-defined
- examples include
JSONB, JSON, TEXT, or a document-native representation
Nullability
Fields are non-nullable by default.
Nullable fields are expressed with:
{
type: { type: "json" },
nullable: true,
}
Rules:
nullable is optional
- if omitted, it is treated as
false
- primary key fields must not be nullable
Primary Keys
Each model must define exactly one primary key.
Shape:
primaryKey: {
fields: [string, ...string[]];
}
Rules:
- supports both single-field and composite primary keys
- all referenced fields must exist in
fields
- field names in the primary key must be unique
- every referenced field must be non-nullable
Examples:
primaryKey: { fields: ["id"] }
primaryKey: { fields: ["conversation_id", "id"] }
Indexes
Indexes are optional and model-level.
Shape:
Each index is defined as:
{
fields: [
{ field: "created_at", order: "desc" },
{ field: "id", order: "desc" },
],
}
Rules:
- every referenced field must exist in the model
order is optional
order, if present, must be "asc" or "desc"
- index names are not part of the schema in v1
- index methods are not part of the schema in v1
- adapters may generate deterministic backend-specific index names internally if needed
Structural Expectations
Even though v1 does not require a dedicated validation layer, adapters may assume:
- schema keys are model names
fields is an object of field definitions
primaryKey.fields contains one or more existing field names
- primary key field names do not repeat
string.max, if present, is a positive integer
order, if present, is "asc" or "desc"
- primary key fields are not nullable
Adapter Responsibilities
Adapters consume the canonical schema and map it to backend-specific behavior.
Adapters are responsible for:
- choosing physical storage types
- choosing identifier quoting rules
- emitting primary key syntax
- emitting index syntax
- deciding how to represent ordered indexes in the target backend
- generating backend-specific index names when required
Examples:
- SQL adapters may map
string({ max: 255 }) to VARCHAR(255) or TEXT
- a Postgres adapter may store
json as JSONB
- a SQLite adapter may store
json as TEXT
- a MongoDB adapter may interpret a model as a collection and a field as a document field
The schema expresses portable intent. Adapters choose backend implementation details.
Design Constraints
The schema intentionally does not encode:
- whether a model is a SQL table or a document collection
- whether timestamps are numeric or native datetime values
- whether JSON is stored natively or as text
- which index implementation a backend should use
These decisions belong to adapters.
Minimal Schema Bootstrap
To match the current hebo-gateway storage layer, v1 should support minimal schema bootstrap behavior for adapters that can create storage structures.
That means:
- create models if they do not exist
- create indexes if needed
- use backend-specific idempotent DDL where possible
This does not require:
- migration history
- schema diffing
- rollback support
- generated migration files
- destructive schema changes
In other words, v1 needs schema bootstrap, not a full migration framework.
Example: Hebo Gateway Storage
This example reflects the current hebo-gateway conversation storage shape.
export const schema: Schema = {
conversations: {
fields: {
id: { type: { type: "string", max: 255 } },
created_at: { type: { type: "timestamp" } },
metadata: { type: { type: "json" }, nullable: true },
},
primaryKey: {
fields: ["id"],
},
indexes: [
{
fields: [
{ field: "created_at", order: "desc" },
{ field: "id", order: "desc" },
],
},
],
},
conversation_items: {
fields: {
id: { type: { type: "string", max: 255 } },
conversation_id: { type: { type: "string", max: 255 } },
created_at: { type: { type: "timestamp" } },
type: { type: { type: "string", max: 64 } },
data: { type: { type: "json" } },
},
primaryKey: {
fields: ["conversation_id", "id"],
},
indexes: [
{
fields: [
{ field: "conversation_id" },
{ field: "created_at", order: "desc" },
{ field: "id", order: "desc" },
],
},
],
},
};
Deferred Future Extensions
These may be added later if there is a concrete use case:
- defaults
- runtime-generated values
- more field types such as
integer or float
- uniqueness constraints
- foreign keys
- backend hint metadata
- explicit index naming
- logical index strategies
- schema builders on top of the canonical shape
Current Recommendation
Implement v1 directly around this canonical schema representation:
- define the exported TypeScript interfaces
- implement one or more adapters that consume this shape
- support minimal schema bootstrap for adapters that can create backend structures
Do not add a second schema representation unless there is a proven need for it.
Summary
This document defines the v1 schema model for
no-orm.The goal is a minimal, database-independent schema representation that:
hebo-gatewayconversation storage layerThis spec defines the canonical schema shape directly. There is no separate internal AST in v1.
Goals
Non-goals
v1 does not include:
Canonical Type Definitions
Naming
The schema vocabulary is:
SchemaModelFieldIndexThese terms are intended to be more database-independent than
tableandcolumn, while still remaining familiar to ORM users.Schema Structure
A schema is a record where the keys are model names.
Example:
There is no wrapper object such as
{ models: ... }.Field Types
v1 supports exactly five field types:
stringnumberbooleantimestampjsonString
Semantics:
maxis a logical maximum lengthmaxis not a guarantee of a particular backend storage typeVARCHAR(n),TEXT, or another appropriate physical representationNumber
Semantics:
Boolean
Semantics:
BOOLEAN, integer-backed booleans, or another backend-native boolean representationTimestamp
Semantics:
BIGINT, SQLTIMESTAMP, or another backend-native representationJSON
Semantics:
JSONB,JSON,TEXT, or a document-native representationNullability
Fields are non-nullable by default.
Nullable fields are expressed with:
Rules:
nullableis optionalfalsePrimary Keys
Each model must define exactly one primary key.
Shape:
Rules:
fieldsExamples:
Indexes
Indexes are optional and model-level.
Shape:
Each index is defined as:
Rules:
orderis optionalorder, if present, must be"asc"or"desc"Structural Expectations
Even though v1 does not require a dedicated validation layer, adapters may assume:
fieldsis an object of field definitionsprimaryKey.fieldscontains one or more existing field namesstring.max, if present, is a positive integerorder, if present, is"asc"or"desc"Adapter Responsibilities
Adapters consume the canonical schema and map it to backend-specific behavior.
Adapters are responsible for:
Examples:
string({ max: 255 })toVARCHAR(255)orTEXTjsonasJSONBjsonasTEXTThe schema expresses portable intent. Adapters choose backend implementation details.
Design Constraints
The schema intentionally does not encode:
These decisions belong to adapters.
Minimal Schema Bootstrap
To match the current
hebo-gatewaystorage layer, v1 should support minimal schema bootstrap behavior for adapters that can create storage structures.That means:
This does not require:
In other words, v1 needs schema bootstrap, not a full migration framework.
Example: Hebo Gateway Storage
This example reflects the current
hebo-gatewayconversation storage shape.Deferred Future Extensions
These may be added later if there is a concrete use case:
integerorfloatCurrent Recommendation
Implement v1 directly around this canonical schema representation:
Do not add a second schema representation unless there is a proven need for it.