You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
// Select multiple fieldsconstresult=awaitBlog.select("name","tagline").where({id: 1}).exec();// Or using array formconstresult=awaitBlog.select(["name","tagline"]).where({id: 1}).exec();
SELECTT.name, T.taglineFROM blog T
WHERET.id=1
// Field aliasesconstresult=awaitBlog.select_as({name: "blog_name",tagline: "blog_tagline"}).where({id: 1}).exec();
SELECTT.nameAS blog_name, T.taglineAS blog_tagline
FROM blog T
WHERET.id=1
SELECTSUM(T.price) AS price_sum,
AVG(T.price) AS price_avg,
MAX(T.price) AS price_max,
MIN(T.price) AS price_min,
COUNT(T.id) AS book_count
FROM book T
// HAVING clauseconstresult=awaitBook.group_by("name").annotate([Sum("price")]).having({price_sum__gt: 100}).exec();
SELECTT.name, SUM(T.price) AS price_sum
FROM book T
GROUP BYT.nameHAVINGSUM(T.price) >100
// Complex HAVING conditionsconstresult=awaitBook.group_by("name").annotate([Sum("price")]).having(Q({price_sum__lt: 100}).or(Q({price_sum__gt: 200}))).exec();
SELECTT.name, SUM(T.price) AS price_sum
FROM book T
GROUP BYT.nameHAVING (SUM(T.price) <100) OR (SUM(T.price) >200)
Field Expressions
JavaScript
SQL
// Field operationsconstresult=awaitBook.annotate({double_price: F("price").mul(2)}).exec();
SELECT (T.price/T.pages) AS price_per_page
FROM book T
// String concatenationconstresult=awaitEntry.update({headline: F("headline")+" suffix by function"}).where({id: 1}).exec();
UPDATE entry T
SET headline = (T.headline||' suffix by function')
WHERET.id=1
Related Query Counting
JavaScript
SQL
// Left join countingconstresult=awaitBlog.annotate({entry_count: Count("entry")}).exec();
SELECTCOUNT(T1.id) AS entry_count
FROM blog T
LEFT JOIN entry T1 ON (T.id=T1.blog_id)
Insert Operations
Basic Insert
JavaScript
SQL
// Insert single recordconstresult=awaitBlog.insert({name: "New Blog",tagline: "New blog tagline"}).exec();
INSERT INTO blog (name, tagline)
VALUES ('New Blog', 'New blog tagline')
// Insert and return specified fieldsconstresult=awaitBlog.insert({name: "Return Test Blog",tagline: "Return test tagline"}).returning("id","name").exec();
INSERT INTO blog (name, tagline)
VALUES ('Return Test Blog', 'Return test tagline')
RETURNING id, name
// Return all fieldsconstresult=awaitBlog.insert({name: "All Fields Blog"}).returning("*").exec();
INSERT INTO blog (name)
VALUES ('All Fields Blog')
RETURNING *
INSERT INTO blog (name, tagline)
VALUES
('bulk return 1', 'bulk return 1'),
('bulk return 2', 'bulk return 2')
RETURNING *
Insert from Subquery
JavaScript
SQL
// Insert from SELECT subqueryconstresult=awaitBlogBin.insert(Blog.where({name: "Second Blog"}).select("name","tagline")).exec();
INSERT INTO blog_bin (name, tagline)
SELECTT.name, T.taglineFROM blog T
WHERET.name='Second Blog'
// Insert with specified column namesconstresult=awaitBlogBin.insert(Blog.where({name: "First Blog"}).select("name","tagline").select_literal("select from another blog"),["name","tagline","note"]).exec();
INSERT INTO blog_bin (name, tagline, note)
SELECTT.name, T.tagline, 'select from another blog'FROM blog T
WHERET.name='First Blog'
// Insert from UPDATE RETURNINGconstresult=awaitBlogBin.insert(Blog.update({name: "update returning 2"}).where({name: "update returning"}).returning("name","tagline").returning_literal("'update from another blog'"),["name","tagline","note"]).returning("name","tagline","note").exec();
INSERT INTO blog_bin (name, tagline, note)
WITH updated AS (
UPDATE blog T
SET name ='update returning 2'WHERET.name='update returning'
RETURNING T.name, T.tagline, 'update from another blog'AS note
)
SELECT*FROM updated
RETURNING name, tagline, note
// Insert from DELETE RETURNINGconstresult=awaitBlogBin.insert(Blog.delete({name: "delete returning"}).returning("name","tagline").returning_literal("'deleted from another blog'"),["name","tagline","note"]).returning("name","tagline","note").exec();
INSERT INTO blog_bin (name, tagline, note)
WITH deleted AS (
DELETEFROM blog T
WHERET.name='delete returning'
RETURNING T.name, T.tagline, 'deleted from another blog'AS note
)
SELECT*FROM deleted
RETURNING name, tagline, note
Column-Specific Insert
JavaScript
SQL
// Insert only specified columnsconstresult=awaitBlogBin.insert({name: "Column Test Blog",tagline: "Column test tagline",note: "should not be inserted"},["name","tagline"]// Only insert these two columns).returning("name","tagline","note").exec();
INSERT INTO blog_bin (name, tagline)
VALUES ('Column Test Blog', 'Column test tagline')
RETURNING name, tagline, note
UPDATE entry T
SET rating = (T.rating+2)
WHERET.id=1
RETURNING T.rating
Update with Joins
JavaScript
SQL
// Update with foreign key conditionsconstresult=awaitEntry.update({headline: F("headline")+" from first blog"}).where({blog_id__name: "First Blog"}).returning("id","headline").exec();
UPDATE entry T
SET headline = (T.headline||' from first blog')
FROM blog T1
WHERET.blog_id=T1.idANDT1.name='First Blog'
RETURNING T.id, T.headline
Advanced Operations
MERGE Operations
JavaScript
SQL
// Basic merge (update if exists, insert if not)constresult=awaitBlog.merge([{name: "First Blog",tagline: "updated by merge"},{name: "Blog added by merge",tagline: "inserted by merge"}]).exec();
INSERT INTO blog (name, tagline)
VALUES
('First Blog', 'updated by merge'),
('Blog added by merge', 'inserted by merge')
ON CONFLICT (name)
DO UPDATESET tagline =EXCLUDED.tagline
// Insert only non-existing recordsconstresult=awaitBlog.merge([{name: "First Blog"},// exists, no update{name: "Blog added by merge"}// doesn't exist, insert]).exec();
INSERT INTO blog (name)
VALUES ('First Blog'), ('Blog added by merge')
ON CONFLICT (name) DO NOTHING
UPSERT Operations
JavaScript
SQL
// UPSERT (update if exists, insert if not, return both)constresult=awaitBlog.upsert([{name: "First Blog",tagline: "updated by upsert"},{name: "Blog added by upsert",tagline: "inserted by upsert"}]).exec();
INSERT INTO blog (name, tagline)
VALUES
('First Blog', 'updated by upsert'),
('Blog added by upsert', 'inserted by upsert')
ON CONFLICT (name)
DO UPDATESET tagline =EXCLUDED.tagline
RETURNING *
// UPSERT from subqueryconstresult=awaitBlog.upsert(BlogBin.update({tagline: "updated by upsert returning"}).returning("name","tagline")).returning("id","name","tagline").exec();
WITH source AS (
UPDATE blog_bin T
SET tagline ='updated by upsert returning'
RETURNING T.name, T.tagline
)
INSERT INTO blog (name, tagline)
SELECT name, tagline FROM source
ON CONFLICT (name)
DO UPDATESET tagline =EXCLUDED.tagline
RETURNING id, name, tagline
// UPSERT from SELECT subqueryconstresult=awaitBlog.upsert(BlogBin.where({name__notin: Blog.select("name").distinct()}).select("name","tagline").distinct("name")).returning("id","name","tagline").exec();
INSERT INTO blog (name, tagline)
SELECT DISTINCTT.name, T.taglineFROM blog_bin T
WHERET.name NOT IN (
SELECT DISTINCTT.nameFROM blog T
)
ON CONFLICT (name)
DO UPDATESET tagline =EXCLUDED.tagline
RETURNING id, name, tagline
UPDATES Operations
JavaScript
SQL
// Bulk update (only update existing records)constresult=awaitBlog.updates([{name: "Third Blog",tagline: "Updated by updates"},{name: "Fourth Blog",tagline: "wont update"}// doesn't exist, no update]).exec();
WITH V(name, tagline) AS (
VALUES
('Third Blog', 'Updated by updates'),
('Fourth Blog', 'wont update')
)
UPDATE blog T
SET tagline =V.taglineFROM V
WHEREV.name=T.name
// Bulk update from SELECT subqueryconstresult=awaitBlogBin.updates(Blog.where({name: "Second Blog"}).select("name","tagline"),"name"// match field).returning("*").exec();
WITH V(name, tagline) AS (
SELECTT.name, T.taglineFROM blog T
WHERET.name='Second Blog'
)
UPDATE blog_bin T
SET tagline =V.taglineFROM V
WHEREV.name=T.name
RETURNING *
// Bulk update from UPDATE subqueryconstresult=awaitBlogBin.updates(Blog.where({name: "Third Blog"}).update({tagline: "XXX"}).returning("name","tagline"),"name").exec();
WITH V(name, tagline) AS (
UPDATE blog T
SET tagline ='XXX'WHERET.name='Third Blog'
RETURNING T.name, T.tagline
)
UPDATE blog_bin T
SET tagline =V.taglineFROM V
WHEREV.name=T.name
MERGE_GETS Operations
JavaScript
SQL
// Merge then queryconstresult=awaitBlog.select("name").merge_gets([{id: 1,name: "Merged First Blog"},{id: 2,name: "Merged Second Blog"}],"id").exec();
WITH V(id, name) AS (
VALUES (1, 'Merged First Blog'), (2, 'Merged Second Blog')
)
INSERT INTO blog (id, name)
SELECT*FROM V
ON CONFLICT (id)
DO UPDATESET name =EXCLUDED.name;
SELECTT.nameFROM blog T
WHERET.idIN (1, 2)
// Merge then query (query after)constresult=awaitBlog.merge_gets([{id: 1,name: "Merged First Blog"},{id: 2,name: "Merged Second Blog"}],"id").select("name").exec();
WITH V(id, name) AS (
VALUES (1, 'Merged First Blog'), (2, 'Merged Second Blog')
)
INSERT INTO blog (id, name)
SELECT*FROM V
ON CONFLICT (id)
DO UPDATESET name =EXCLUDED.name;
SELECTT.nameFROM blog T
WHERET.idIN (1, 2)
Delete Operations
JavaScript
SQL
// Basic deleteconstresult=awaitBlog.delete({name: "Blog to delete"}).exec();
DELETEFROM blog T
WHERET.name='Blog to delete'
// Delete with returnconstresult=awaitBlog.delete({name: "Blog to delete"}).returning("*").exec();
DELETEFROM blog T
WHERET.name='Blog to delete'
RETURNING *
constModel=Xodel({table_name: "example",fields: {// Unique constraintusername: {unique: true},// Not null constraintemail: {null: false},// Default valuestatus: {default: "active"},// Label (for form display, etc.)name: {label: "Name"},// Compact storage (for long text)content: {compact: false}}});
Model Options
constModel=Xodel({// Table nametable_name: "my_table",// Auto create primary keyauto_primary_key: true,// default true// Composite unique constraintunique_together: ["field1","field2"],// Model inheritancemixins: [BaseModel],// Field definitionsfields: {// ...}});
Error Handling
try{// Unique constraint violationawaitBlog.insert({name: "First Blog"}).exec();}catch(error){console.error("Insert failed:",error.message);}try{// Field length exceededawaitBlog.insert({name: "This name is way too long and exceeds the maximum length"}).exec();}catch(error){console.error("Field validation failed:",error.message);}try{// Age out of rangeawaitAuthor.insert({name: "Tom",age: 101}).exec();}catch(error){console.error("Age validation failed:",error.message);}
Debugging
// Get SQL statement without executionconstsql=Blog.where({id: 1}).statement();console.log(sql);// Enable full SQL matching (for testing)process.env.SQL_WHOLE_MATCH=true;
Best Practices
Model Definition: Define related models in the same file for easier foreign key relationship management
Field Validation: Make full use of field constraints for data validation
Query Optimization: Use select() to query only needed fields
Transaction Handling: Consider using database transactions for complex operations
Error Handling: Always wrap database operations in try-catch blocks
Index Optimization: Add database indexes for frequently queried fields
This ORM provides rich query interfaces and flexible data manipulation methods that can meet the needs of most PostgreSQL applications.
Database Migration Tool
The library includes a powerful database migration tool that can generate SQL migration scripts by comparing model definitions.
Usage
import{generate_migration_sql,create_table_sql}from'./lib/migrate.mjs';// Define your modelconstuser_model={table_name: "users",field_names: ["id","name","email","created_at"],fields: {id: {name: "id",type: "integer",primary_key: true,serial: true,null: false,},name: {name: "name",type: "string",maxlength: 100,null: false,},email: {name: "email",type: "email",maxlength: 255,unique: true,null: false,},created_at: {name: "created_at",type: "datetime",auto_now_add: true,null: false,},},};// Create table SQLconstcreate_sql=create_table_sql(user_model);console.log(create_sql);// Generate migration SQL (from old model to new model)constmigration_sql=generate_migration_sql(old_model,new_model);console.log(migration_sql);
Supported Field Types
string: VARCHAR with specified length
text: TEXT field for long content
integer: Integer numbers
float: Floating point numbers with optional precision
boolean: Boolean true/false values
date: Date only (YYYY-MM-DD)
datetime: Timestamp with optional timezone
time: Time only with optional timezone
uuid: UUID with automatic generation
json: JSONB for structured data
foreignkey: Foreign key relationships
year/month: Integer fields for year/month values
year_month: VARCHAR for year-month combinations
Migration Features
Table Creation: Generate CREATE TABLE statements
Field Addition/Removal: Add or remove columns
Type Changes: Convert between compatible field types
Constraint Management: Handle PRIMARY KEY, UNIQUE, NOT NULL constraints
Index Management: Create and drop indexes
Foreign Key Management: Add, remove, and modify foreign key relationships
Default Values: Handle default value changes
Field Renaming: Automatic detection of field renames
-- Creating a new tableCREATETABLEusers(
id SERIALPRIMARY KEYNOT NULL,
name varchar(100) NOT NULL,
email varchar(255) NOT NULL UNIQUE,
created_at timestamp(0) NOT NULL DEFAULT CURRENT_TIMESTAMP
);
-- Adding a fieldALTERTABLE users ADD COLUMN phone varchar(20);
-- Modifying field typeALTERTABLE users ALTER COLUMN name TYPE text;
-- Adding foreign keyALTERTABLE products ADD CONSTRAINT products_category_id_fkey
FOREIGN KEY (category_id) REFERENCES"categories" ("id")
ON DELETE CASCADEONUPDATE CASCADE;
Testing
Run the test suite:
npm test
Run migration tool tests specifically:
npm test __test__/migrate.test.mjs
License
MIT
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
About
Declarative, intuitive and powerful PostgreSQL ORM library