JSPM

  • Created
  • Published
  • Downloads 878
  • Score
    100M100P100Q106045F
  • License AGPL-3.0-or-later

Hibernate-inspired multi-dialect ORM for Node.js/TypeScript โ€” One API, 13 databases, zero lock-in

Package Exports

  • @mostajs/orm
  • @mostajs/orm/bridge
  • @mostajs/orm/register
  • @mostajs/orm/validator

Readme

@mostajs/orm

Plug & Play ORM to Drive 13 Databases at Once

npm version npm downloads License: AGPL-3.0-or-later dialects Types: TypeScript bundle size

Hibernate-inspired multi-dialect ORM for Node.js & TypeScript โ€” one API, 13 databases, zero lock-in, bundler-friendly.

๐Ÿ“ฆ npm ยท https://www.npmjs.com/package/@mostajs/orm ๐Ÿ™ GitHub ยท https://github.com/apolocine/mosta-orm ๐Ÿ“š Docs ยท (coming soon) ๐Ÿš€ Product Hunt ยท (launch link to be added)


Why @mostajs/orm ?

  • ๐ŸŽฏ One API, 13 dialects. Switch from PostgreSQL to MongoDB to SQLite without rewriting a single repository call.
  • ๐Ÿชถ Zero lock-in. Native drivers, no proprietary query DSL โ€” your SQL/NoSQL stays portable.
  • ๐Ÿงฌ Hibernate / JPA semantics. @OneToMany, cascade types, SAVEPOINT, schema strategies (validate/update/create/create-drop) โ€” concepts battle-tested for 25 years, ported to TypeScript.
  • ๐ŸŒ‰ Drop-in Prisma replacement. @mostajs/orm-bridge lets you keep your Prisma code while running on any of 13 databases.
  • ๐Ÿ” Cross-dialect replication built-in. @mostajs/replicator โ€” CDC + master/slave + failover across SQL โ†” MongoDB.
  • ๐Ÿงช Bundler-friendly. Tree-shakable ESM, no eval, works with esbuild / Vite / Next.js / Bun out of the box.

60-second demo

npm install @mostajs/orm better-sqlite3
import { getDialect } from '@mostajs/orm'
import { UserSchema } from './schemas/user.schema'

const db = await getDialect({ dialect: 'sqlite', uri: ':memory:' }, [UserSchema])
const userRepo = db.repo<typeof UserSchema>('User')

await userRepo.create({ email: 'alice@example.com', name: 'Alice' })
const alice = await userRepo.findOne({ email: 'alice@example.com' })

Want PostgreSQL instead ? Change one line :

const db = await getDialect({ dialect: 'postgres', uri: process.env.DATABASE_URL }, [UserSchema])

That's it. Same repo.create(), same repo.findOne(), same TypeScript types โ€” different dialect.

How it compares

@mostajs/orm Prisma Drizzle TypeORM
SQL dialects 9 (PG, MySQL, MariaDB, SQLite, MSSQL, Oracle, DB2, HANA, Cockroachโ€ฆ) 5 5 8
NoSQL dialects MongoDB native โŒ โŒ โŒ
Same API across SQL & NoSQL โœ… โŒ โŒ โŒ
Cross-dialect replication โœ… (via @mostajs/replicator) โŒ โŒ โŒ
Schema-as-code (no DSL) โœ… TypeScript objects DSL .prisma TS objects Decorators
Code generation step โŒ (zero codegen) โœ… required โŒ โŒ
Drop-in Prisma replacement โœ… (via @mostajs/orm-bridge) โ€” โŒ โŒ
Migration from Prisma โœ… (automated CLI) โ€” manual manual
Hibernate / JPA semantics โœ… โŒ โŒ partial
License AGPL-3.0 (+ commercial) Apache-2.0 Apache-2.0 MIT

Numbers as of v1.13.1 โ€” see @mostajs/orm-cli for the automated Prisma โ†’ @mostajs migration tool.

Star ยท Sponsor ยท Contribute

If @mostajs/orm saves you days of glue code, please :

  • โญ Star the repo โ€” visibility helps me keep maintaining it.
  • ๐Ÿ’– Sponsor development โ†’ github.com/sponsors/apolocine
  • ๐Ÿ› Report issues / submit PRs โ€” every contribution counts.
  • โœ‰๏ธ Commercial license & support : drmdh@msn.com

Databases

SQLite ยท PostgreSQL ยท MySQL ยท MariaDB ยท MongoDB ยท Oracle ยท SQL Server ยท CockroachDB ยท DB2 ยท SAP HANA ยท HSQLDB ยท Spanner ยท Sybase


Demos

1. Initialize the app

2. Initialize the database

3. Configure the app

4. Setup replication

5. Cross-dialect CDC rules & live sync

6. Frontend CRUD app

7. Prisma project (before migration)

8. Prisma โ†’ @mostajs/orm migration (bootstrap)


Install

npm install @mostajs/orm
# + the driver for your dialect :
npm install better-sqlite3      # or: pg, mysql2, mongoose, oracledb, mssql, ibm_db, mariadb, @sap/hana-client, @google-cloud/spanner

Define a schema

import type { EntitySchema } from '@mostajs/orm'

export const UserSchema: EntitySchema = {
  name: 'User',
  collection: 'users',
  timestamps: true,
  fields: {
    email: { type: 'string', required: true, unique: true },
    name:  { type: 'string', required: true },
  },
  relations: {
    roles: { target: 'Role', type: 'many-to-many', through: 'user_roles' },
  },
  indexes: [{ fields: { email: 'asc' }, unique: true }],
}

Unique keys

A field can be marked unique, or several fields can be combined into a composite unique constraint via indexes.

export const MemberSchema: EntitySchema = {
  name: 'Member',
  collection: 'members',
  fields: {
    email:    { type: 'string', required: true, unique: true },  // single unique
    tenantId: { type: 'string', required: true },
    slug:     { type: 'string', required: true },
  },
  indexes: [
    { fields: { tenantId: 'asc', slug: 'asc' }, unique: true },  // composite unique
  ],
}

Both shapes enforce a DDL UNIQUE constraint on SQL dialects and a unique index on MongoDB. Lookup works the same way :

await repo.findOne({ email: 'a@b.com' })                       // single unique
await repo.findOne({ tenantId: 't1', slug: 'admin' })          // composite unique

Connect & CRUD

import { registerSchemas, getDialect, BaseRepository } from '@mostajs/orm'

registerSchemas([UserSchema])
const dialect = await getDialect()       // reads DB_DIALECT + SGBD_URI from env
const repo = new BaseRepository(UserSchema, dialect)

await repo.create({ email: 'a@b.com', name: 'Admin' })
await repo.findOne({ email: 'a@b.com' })
await repo.findAll({ status: 'active' }, { sort: { name: 1 }, limit: 10 })
await repo.update(id, { name: 'Updated' })
await repo.delete(id)
await repo.findByIdWithRelations(id, ['roles'])
await repo.upsert({ email: 'a@b.com' }, { name: 'Upserted' })
await repo.count({ status: 'active' })

Transactions

Group multiple operations into a single atomic unit. SQL dialects (PostgreSQL, MySQL/MariaDB, SQLite, SQL Server, Oracle, DB2, CockroachDB, HANA, Sybase, HSQLDB, Spanner) wrap the callback in BEGIN / COMMIT / ROLLBACK. If any operation throws, every write inside the block is rolled back.

import { getDialect } from '@mostajs/orm'

const dialect = await getDialect()

await dialect.$transaction(async (tx) => {
  await tx.create('accounts', { id: 'a', balance: 100 })
  await tx.update('accounts', { id: 'b' }, { $inc: { balance: -50 } })
  await tx.update('accounts', { id: 'a' }, { $inc: { balance:  50 } })
  // throw here โ†’ both updates are rolled back, `accounts.a` row is removed
})

Isolation : default per dialect (SQL โ†’ READ COMMITTED, SQLite โ†’ DEFERRED). Pass { isolation: 'SERIALIZABLE' } as 2nd argument to override (SQL only).

All SQL dialects listed above support ACID natively โ€” PostgreSQL, MySQL/MariaDB, SQL Server, Oracle, DB2, SQLite, CockroachDB, HANA, Sybase, HSQLDB, Spanner. No configuration required beyond the usual connection.

MongoDB is the only exception : multi-document ACID transactions require a replica set (a single-node mongod --replSet rs0 is enough for dev โ€” this is a MongoDB server requirement, not a limitation of this library). On a standalone server, $transaction runs the callback without wrapping โ€” safe for read-heavy flows, non-atomic for writes.

Manual transactions โ€” beginTx / commitTx / rollbackTx (v1.11+)

When the $transaction(cb) callback pattern is too restrictive (transaction spans several unrelated functions, commit depends on an external event), use the manual API :

const tx = await dialect.beginTx()
try {
  await dialect.create(UserSchema, { email: 'a@b.c', name: 'A' })
  await someExternalCheck()          // could be async, could take seconds
  if (ok) await dialect.commitTx(tx)
  else    await dialect.rollbackTx(tx)
} catch (e) {
  await dialect.rollbackTx(tx)
  throw e
}

Nested transactions โ€” SAVEPOINTs are used automatically :

const outer = await dialect.beginTx()               // โ†’ BEGIN
await dialect.create(UserSchema, { email: 'o@x.io', name: 'Outer' })

const inner = await dialect.beginTx()               // โ†’ SAVEPOINT mosta_sp_2_xxxx
await dialect.create(UserSchema, { email: 'i@x.io', name: 'Inner' })
await dialect.rollbackTx(inner)                     // โ†’ ROLLBACK TO SAVEPOINT
//                                                     (inner row gone, outer untouched)

await dialect.commitTx(outer)                       // โ†’ COMMIT
//                                                     (outer row persisted)

Depth unbounded as long as the engine supports SAVEPOINT (every SQL dialect above except Spanner). MSSQL / Sybase use SAVE TRANSACTION / ROLLBACK TRANSACTION internally โ€” transparent to the API. commitTx / rollbackTx enforce LIFO order (out-of-order commit throws, out-of-order rollback is silent).

Environment

DB_DIALECT=postgres
SGBD_URI=postgresql://user:pass@localhost:5432/mydb
DB_SCHEMA_STRATEGY=update    # validate | update | create | create-drop | none
DB_SHOW_SQL=true

The naming mirrors Hibernate's hibernate.hbm2ddl.auto / hibernate.show_sql properties (see Hibernate User Guide ยง schema strategies). Values have identical semantics : validate / update / create / create-drop / none.

The dialect matching DB_DIALECT is lazy-loaded at runtime (v1.9.3+). Only the driver you actually use is evaluated โ€” no other dialect module enters your bundle. This is what makes @mostajs/orm safe to pull into a Next.js / Vite / SvelteKit project without bundler workarounds.

Profile cascade with MOSTA_ENV (v1.13+)

Powered by @mostajs/config. Keep one .env file with profile-prefixed overrides and switch via a single MOSTA_ENV variable โ€” exactly like Spring Boot profiles (spring.profiles.active=test loading application-test.properties).

# .env โ€” committed (non-secret) defaults
MOSTA_ENV=TEST

# Base defaults (used when no profile, or as fallback)
DB_DIALECT=sqlite
SGBD_URI=./data.sqlite

# Profile overrides
TEST_DB_DIALECT=sqlite
TEST_SGBD_URI=./test.sqlite
TEST_DB_SCHEMA_STRATEGY=create-drop

DEV_DB_DIALECT=postgres
DEV_SGBD_URI=postgres://localhost:5432/devdb
DEV_DB_SCHEMA_STRATEGY=update

PROD_DB_DIALECT=mongodb
PROD_SGBD_URI=${SCALEWAY_MONGO_URI}    # secret injected by orchestrator
PROD_DB_SCHEMA_STRATEGY=validate

Resolution cascade (first non-empty wins) :

  1. ${MOSTA_ENV}_${KEY} โ€” profile-prefixed
  2. ${KEY} โ€” plain
  3. fallback argument
  4. undefined โ€” no crash, caller decides whether that's fatal

Silent fallback is guaranteed : a missing profile override never throws, it just falls through to the plain variable or to the default. Empty strings (TEST_DB_DIALECT=) are treated as "not set" so they don't silently leak a blank value.

For generic use outside @mostajs/orm, import directly from the config package :

import { getEnv, getEnvBool, getEnvNumber, getCurrentProfile } from '@mostajs/config'

const url = getEnv('REDIS_URL', 'redis://localhost:6379')
console.log(`Profile : ${getCurrentProfile() ?? 'none'}`)

Switch databases with one env var

DB_DIALECT=sqlite     SGBD_URI=./data.sqlite
DB_DIALECT=postgres   SGBD_URI=postgres://...
DB_DIALECT=mongodb    SGBD_URI=mongodb://...
# same code in both cases

Subpaths

Subpath When to use
@mostajs/orm The core ORM API : getDialect, registerSchemas, BaseRepository, EntityService, schema types, diffSchemas, errors.
@mostajs/orm/bridge JDBC bridge (v1.9.4+) : JdbcNormalizer, BridgeManager, JDBC_REGISTRY, jar upload. Pulled out of the root to keep child_process / fs spawn out of client bundles.
@mostajs/orm/register Zero-code registration side-effect for dynamic schema loading.
@mostajs/orm/validator v1.14+ โ€” ORMConceptValidator : algorithmic linter for EntitySchema sets. Detects 18 conceptual anomalies (empty relations, FK naming inconsistency, soft-delete patterns, dead code, missing audit, unbounded blobsโ€ฆ). See below.

๐Ÿ” ORMConceptValidator (v1.14+)

Algorithmic linter for your ORM schemas โ€” detects 18 conceptual anomalies before they bite in production. Zero IA, zero heuristics flou, fully generic (no hardcoded entity name โ€” KNOWN_ENTITY_REFS is derived at runtime from the schemas you pass).

Quick start

# CLI โ€” point it at your schemas directory
npx mostajs-orm-validator ./schemas

# With cross-file rules (R005, R007, R008, R011, R012, R014, R015) :
npx mostajs-orm-validator ./schemas --src ./lib

# In a CI pipeline :
npx mostajs-orm-validator ./schemas --src ./lib --ci --max-warnings 0

Or programmatically :

import { validateSchemas, formatText } from '@mostajs/orm/validator'
import * as schemas from './schemas'

const report = await validateSchemas(Object.values(schemas), {
  sourceRoot: './lib',
})

console.log(formatText(report))
console.log(`${report.findings.length} findings`)

What it detects (18 rules)

ID Severity Detection
R001-EMPTY-RELATIONS warning String field named like another entity (e.g. project, respondent) but relations: {} empty โ†’ loses ORM cascade & FK validation
R002-FK-NAMING-INCONSISTENT warning Mix of conventions in same set (parentId vs project, questionId vs section) โ€” flags the minority
R003-SOFT-DELETE-INCONSISTENT warning/info Multiple soft-delete patterns concurrent (deleted/cancelled/archived) OR manual deleted/deletedAt while softDelete: true is available natively
R004-DUPLICATE-ENTITY-SHAPE info Pair of schemas with Jaccard on field names โ‰ฅ threshold (default 0.7) โ€” possible legacy
R004B-LEGACY-ENTITY info/warning Name overlap (substring โ‰ฅ 4 chars or Jaro-Winkler โ‰ฅ 0.75) โ€” flags the smaller schema. Bumps to warning if legacy/deprecated comment found in sources
R005-ANY-TYPED-REPO warning BaseRepository<any> in source files โ€” typing lost. Needs --src
R006-JSON-AS-RELATION info *sJson field containing list of FK slugs/ids โ€” should be normalized into junction table
R007-REDUNDANT-DERIVED-FIELD info Persisted field duplicate of a pure function of its id (e.g. blobPath derivable from archiveBlobPath(id)). Needs --src
R008-BEST-EFFORT-FK-RESOLVER warning best-effort/TODO V2/HACK comment + ?? null fallback โ†’ root cause hidden. Needs --src
R009-MISSING-LOOKUP-INDEX info/hint unique field without dedicated index, OR FK string without index for inverse lookups
R010-MISSING-AUDIT-TABLE hint No schema resembling AuditLog (actor + action + timestamp) โ€” sensitive actions untraceable
R011-LEGACY-DEAD-CODE info TS source file never imported (entry points like page.tsx/route.ts excluded). Needs --src
R012-DUPLICATE-IMPLEMENTATION info Pair of source files exporting overlapping function signatures (Jaccard โ‰ฅ 0.85). Needs --src
R013-MISSING-CASCADE warning many-to-one relation without explicit onDelete โ†’ orphans on parent delete
R014-REPO-FACTORY-BOILERPLATE info โ‰ฅ 5 get*Repo() helpers in same file โ€” suggest factory. Needs --src
R015-FLAT-LIB-STRUCTURE hint Directory with > 25 flat files โ€” suggest sub-directory organisation. Needs --src
R016-AUDIT-EMAIL-AS-STRING info createdBy/validatedBy/etc. typed string instead of FK User โ†’ loses ref. integrity if email changes
R017-UNBOUNDED-BLOB-FIELD hint *Json/*Payload/*Blob/*Manifest without documented size limit
R018-EXTERNAL-SCHEMA-OVERSCOPED info (stub V2 โ€” full impl in V3 with ts-morph) External schema with many unused fields

Output formats

# Console output (TTY-aware ANSI colors)
npx mostajs-orm-validator ./schemas

# JSON (for CI / diff)
npx mostajs-orm-validator ./schemas --format json --out report.json

# Markdown (human-readable report)
npx mostajs-orm-validator ./schemas --format markdown --out REPORT.md

Example output :

โœ— Section.project           R001-EMPTY-RELATIONS                  warning
    Field 'Section.project' looks like an FK to 'Project' but no ORM
    relation declared.
    Suggestion:
      relations: {
        project: { type: 'many-to-one', target: 'Project',
                   required: true, onDelete: 'cascade' },
      },

Configuration

All thresholds and patterns are configurable โ€” no hardcoded business strings. Pass a config to validateSchemas :

const report = await validateSchemas(schemas, {
  sourceRoot: './lib',
  ignore: ['R015', 'R017'],   // skip these rules entirely
  rules: { R001: 'error' },   // override severity (e.g. block CI on R001)
  softDeletePatterns: [
    { flag: 'deleted',   timestamp: 'deletedAt' },
    { flag: 'cancelled', timestamp: 'cancelledAt' },
    { flag: 'archived',  timestamp: 'archivedAt' },
    // add your project-specific patterns here
  ],
  auditByFields: ['createdBy', 'validatedBy', 'reviewedBy'],
  thresholds: {
    duplicateEntityJaccard: 0.7,        // R004
    duplicateImplJaroWinkler: 0.85,     // R012
    flatLibMaxFiles: 25,                // R015
  },
})

CI integration

// package.json
{
  "scripts": {
    "lint:schemas": "mostajs-orm-validator ./schemas --src ./lib --ci --max-warnings 0"
  }
}

The --ci flag exits with code 1 if the number of error + warning findings exceeds --max-warnings (default 0). Bind it to your pre-commit hook or GitHub Actions to block regressions.

Generic by design

The validator is fully generic โ€” no hardcoded entity name, no project-specific assumption. The set of "known entities" (KNOWN_ENTITY_REFS) is derived at runtime from the schemas you pass. Same binary detects the same anti-patterns in any consumer codebase.

TypeScript schemas

The CLI loads .ts/.tsx/.js/.mjs files directly via jiti โ€” no pre-compile step required. TypeScript paths aliases are resolved automatically.

EntityService (for @mostajs/net)

import { EntityService } from '@mostajs/orm'

const service = new EntityService(dialect)
const res = await service.execute({
  op: 'findAll',
  entity: 'User',
  filter: { status: 'active' },
  relations: ['roles'],
  options: { limit: 10 },
})

Operations : findAll, findOne, findById, create, update, delete, deleteMany, count, search, aggregate, upsert, updateMany, addToSet, pull, increment.

Schema management

await dialect.initSchema(getAllSchemas())      // create / update DDL per strategy
await dialect.truncateTable?.('users')
await dialect.truncateAll?.(getAllSchemas())
await dialect.dropTable?.('users')
await dialect.dropSchema?.(getAllSchemas())
await dialect.dropAllTables?.()

Dialect-level guarantees (v1.13+)

Two classes of correctness fixes ship with 1.13, both driven by real production pain encountered during @mostajs/replicator runs.

SQL dialects (AbstractSqlDialect)

  • FK columns preserve falsy-but-valid values (0, false). The previous short-circuit data[name] || null silently replaced legitimate zero IDs and boolean false with null, breaking FK writes whose source-side PK happened to be 0. The insert/update path now uses value === '' ? null : (value ?? null) โ€” empty strings still null-out (SQL foreign-key constraints reject them on most dialects) but numeric zero, false and any non-empty value round-trip intact.
  • One-to-one relations get a column-level UNIQUE constraint. Emitted both at CREATE TABLE time and at ALTER TABLE ADD time when growing an existing schema. Matches the JPA / Hibernate semantics where an @OneToOne FK must be injective (otherwise the "one" side of the relation is not actually single-valued).

Mongo dialect

  • FK fields accept UUID strings in addition to native ObjectId. buildMongooseSchema now declares FK refs as Schema.Types.Mixed rather than Schema.Types.ObjectId. Replicated documents originating from a SQL dialect (SQLite / Postgres / โ€ฆ using UUID primary keys) are no longer rejected by Mongoose path validation. A native Mongo app writing proper ObjectId refs keeps working unchanged.
  • findAll() / findOne() fall back to { id: fkValue } when populate() returns null. When a UUID-string FK cannot be resolved through the default Mongoose _id lookup (which expects matching type), the dialect keeps a raw lean() query alongside the populated one and patches the missing refs post-hoc by a direct findOne({ id: fk }) on the target collection. Transparent to the caller, prevented silent data loss during cross-dialect reads.

Together, these four items unblock bidirectional SQL โ†” Mongo sync through @mostajs/replicator.

Multiple simultaneous connections

import { createIsolatedDialect, registerNamedConnection, getNamedConnection } from '@mostajs/orm'

const oracle = await createIsolatedDialect({ dialect: 'oracle', uri: '...' }, [UserSchema])
const mongo  = await createIsolatedDialect({ dialect: 'mongodb', uri: '...' }, [AuditLogSchema])
registerNamedConnection('audit', mongo)

// Later, anywhere in the codebase :
const conn = getNamedConnection('audit')

Ecosystem

Package Description
@mostajs/orm-bridge Keep your Prisma code, run it on any of the 13 databases (createPrismaLikeDb() is a drop-in replacement for new PrismaClient()).
@mostajs/orm-cli npx @mostajs/orm-cli โ€” interactive CLI : convert schemas, init databases, scaffold services, replicator + monitor, seeding, bootstrap Prisma migration.
@mostajs/orm-adapter Convert Prisma / JSON Schema / OpenAPI / native .mjs to EntitySchema[] (bidirectional).
@mostajs/replicator Cross-dialect replication : CQRS master/slave, CDC rules (snapshot + incremental), wildcard *, failover (promoteToMaster). As of @mostajs/orm v1.13, Mongo FK columns accept UUID strings coming from SQL dialects (populate falls back to { id: uuid } lookup).
@mostajs/orm-copy-data Cross-dialect data copy : 1 source (DB / CSV / JSON / SQL dump) โ†’ N destinations. Backup, migration, seeding. CLI (mostajs-copy) + API. Cron-ready.
@mostajs/replica-monitor Live web dashboard โ€” replicas status, CDC stats, activity stream. Zero DB connections (reads tree + stats files).
@mostajs/media Screen capture + video editor (split, speed, stickers, subtitles) + server-side ffmpeg export + project persistence (ORM + SQLite).
@mostajs/config Env loader with MOSTA_ENV profile cascade (Spring-Boot-style). Used by orm/auth/payment/music.

Design inspirations

@mostajs/orm draws from three decades of mature ORM engineering in the Java ecosystem, adapted to the TypeScript / Node.js runtime :

Borrowed concept Source @mostajs/orm equivalent
SessionFactory / EntityManagerFactory Hibernate ยท JPA getDialect() returning a cached singleton
Entity metadata (annotations / XML) Hibernate, JPA @Entity EntitySchema โ€” declarative TypeScript schema
@OneToMany / @ManyToOne / @OneToOne / @ManyToMany JPA relations: { ..., type: 'one-to-many' | ... }
@JoinColumn, @JoinTable JPA joinColumn, through
CascadeType / FetchType JPA cascade, fetch in RelationDef
Cascade types (PERSIST, REMOVE, ALL) JPA cascade: ['persist', 'remove', 'all']
Schema-generation strategies (validate, update, create, create-drop) Hibernate hibernate.hbm2ddl.auto DB_SCHEMA_STRATEGY (same names, same semantics)
Show-SQL / format-SQL / highlight-SQL Hibernate (hibernate.show_sql, hibernate.format_sql) DB_SHOW_SQL, DB_FORMAT_SQL, DB_HIGHLIGHT_SQL
SAVEPOINT for nested transactions SQL standard, JPA spec beginTx() inside beginTx() emits SAVEPOINT
Repository pattern Spring Data BaseRepository<T> with typed CRUD
Profile-based configuration Spring Boot profiles (spring.profiles.active=test) MOSTA_ENV=TEST + TEST_KEY=value cascade (via @mostajs/config)
Environment-aware externalized config Spring Boot application-${profile}.properties One .env with ${PROFILE}_${KEY} overrides

Why borrow from the Java ecosystem ?

Hibernate (2001), JPA (2006, JSR 220), Spring Data (2008), Spring Boot (2014) have collectively survived two decades of production workloads. Their vocabulary and semantics are industry defaults : developers who have worked with any of them recognize @OneToMany, CascadeType.ALL, spring.profiles.active, hibernate.hbm2ddl.auto=update, SAVEPOINT, etc. immediately. Reusing those names in @mostajs/orm cuts the learning curve and avoids inventing a parallel dialect.

Further reading :

License

AGPL-3.0-or-later + commercial license available.

For closed-source commercial use : drmdh@msn.com

Author

Dr Hamid MADANI drmdh@msn.com