Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .changeset/khaki-jars-repeat.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
'@noormdev/sdk': patch
---

Put `types` first in the package exports map

Export conditions are matched in order, so `types` sitting after `import` is
resolvable only by luck — it works today because there is no `require`
condition to shadow it, and would silently stop working the moment one was
added. `publint` reports it as an error. Also normalized both packages'
`repository.url` to the full `git+https://…​.git` form npm expects.
11 changes: 11 additions & 0 deletions .changeset/olive-pugs-shave.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
'@noormdev/cli': patch
---

Read change-history timestamps as UTC on postgres and mysql

`executed_at` has no time zone and noorm writes UTC into it, but `pg` and
`mysql2` both read that back through the host's local zone. On a UTC-4 host a
change applied a second ago was reported as four hours in the future, which
surfaced in the TUI as "Applied ... in 4 hours" on the home screen and in
change history. MSSQL is unchanged — its driver was not measured.
1 change: 1 addition & 0 deletions .claude/atomic.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
scope = "repo"
29 changes: 18 additions & 11 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -162,40 +162,47 @@ For setup wizards where the target database may not exist yet, use `testConnecti

Consistent hotkey conventions across all screens:

**Home navigation:**
**Home navigation** (`src/tui/screens/home.tsx`):
| Key | Action |
|-----|--------|
| `r` | run |
| `c` | config |
| `g` | changes |
| `r` | run |
| `d` | db |
| `l` | lock |
| `+` | more (settings, vault, identity, lock) |
| `s` | settings |
| `k` | secrets (keys) |
| `v` | vault |
| `i` | identity |
| `l` | lock |
| `u` | update |
| `1` / `2` / `3` | quick actions: run build, change ff, lock status |
| `q` | quit |

There is no `k` on Home — secrets belong to a config, so `k` opens them from
the config list.

**Common actions (sub-screens):**
| Key | Action | Mnemonic |
|-----|--------|----------|
| `a` | add | |
| `e` | edit | |
| `d` | delete | |
| `x` | export | e**x**port |
| `i` | import | |
| `u` | use/activate | |
| `v` | validate | |
| `k` | secrets | **k**eys |
| `k` | secrets | **k**eys (from the config list) |
| `+` | more | export / import / validate live here, not on the list |
| `Enter` | use/activate | selecting a config activates it |

**Context-dependent keys:**
- `[i]` = identity on Home, import in sub-screens
- `[x]` = export where applicable, extend in Lock Status
- `[i]` = identity on Home, import on the config More screen
- `[x]` = export on the config More screen, extend in Lock Status
- `[s]` = settings on Home, status in Lock List
- `[c]` = config on Home, copy on the config list, create on the DB screen

**Global shortcuts (available everywhere):**
| Key | Action |
|-----|--------|
| `Shift+L` | Toggle log viewer overlay |
| `Shift+Q` | Open the SQL terminal |
| `?` | Show help |

Use `numberNav` prop on `SelectList` for 1-9 quick selection in lists.

Expand Down
101 changes: 65 additions & 36 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,26 @@
<div align="center">

# noorm

A CLI for SQL-first database development. Manage SQL files, track changes, and run builds across dev, staging, and production — no ORM required.
### Write SQL. Skip the ORM.

A SQL-first schema and change manager for **PostgreSQL**, **MySQL**, **SQLite**, and **SQL Server**.<br>
Your schema lives in SQL files. noorm builds it, versions it, and keeps every environment in sync.

[![@noormdev/cli](https://img.shields.io/npm/v/@noormdev/cli?color=E05742&label=%40noormdev%2Fcli)](https://www.npmjs.com/package/@noormdev/cli)
[![@noormdev/sdk](https://img.shields.io/npm/v/@noormdev/sdk?color=916336&label=%40noormdev%2Fsdk)](https://www.npmjs.com/package/@noormdev/sdk)
[![CI](https://github.com/noormdev/noorm/actions/workflows/ci.yml/badge.svg)](https://github.com/noormdev/noorm/actions/workflows/ci.yml)
[![license](https://img.shields.io/badge/license-MIT-blue)](./LICENSE)

**[Documentation](https://noorm.dev)** · **[Getting started](https://noorm.dev/getting-started/installation)** · **[Terminal UI](https://noorm.dev/tui)** · **[SDK](https://noorm.dev/reference/sdk)**

</div>

**[Documentation](https://noorm.dev)** | **[Getting Started](https://noorm.dev/getting-started/installation)** | **[npm](https://www.npmjs.com/package/@noormdev/cli)**
<br>

<img src="https://raw.githubusercontent.com/noormdev/noorm/master/docs/public/image/tui.gif" alt="noorm's terminal UI: adding a config, creating the database, building the schema, fast-forwarding changes, and browsing the result in the schema explorer" width="100%">

<br>

## Install

Expand All @@ -17,64 +34,63 @@ Or via npm:
npm install -g @noormdev/cli
```

The install script is [fully transparent and open source](https://github.com/noormdev/noorm/blob/master/install.sh) — it downloads a prebuilt binary from [GitHub Releases](https://github.com/noormdev/noorm/releases), built from this repository's source. It installs to a user-writable directory already in your PATH when possible (e.g. `~/.local/bin`), falling back to `~/.local/bin` if none is found.
The install script is [fully transparent and open source](https://github.com/noormdev/noorm/blob/master/install.sh) — it downloads a prebuilt binary from [GitHub Releases](https://github.com/noormdev/noorm/releases), built from this repository's source. It installs to a user-writable directory already in your PATH when possible, falling back to `~/.local/bin`.

> Corporate network? Use the GitHub mirror:
> `curl -fsSL https://raw.githubusercontent.com/noormdev/noorm/master/install.sh | sh`


### Coding agents

If you use an AI coding agent, install the noorm skill so it writes against the real conventions instead of guessing:
## Quick start

```bash
npx skills add noormdev/noorm/skills
```

It teaches the agent the SDK surface, the CLI's headless flags, template syntax, and config layout — the parts that are easy to get subtly wrong.
# Bootstrap a project — creates your identity and the sql/ and changes/ layout
noorm init

# Add a database config
noorm ui
```

## What It Does
Adding a config is the one step with no headless equivalent: it needs connection details and a live connection test, so `noorm config add` sends you to the TUI. From there, **[c] Config → [a] Add**, then **[r] Run → [b] Build** to execute your SQL files.

- **SQL files** define your current schema — no migration archaeology
- **Changes** evolve existing databases from any state to current
- **Stages** manage dev, staging, and production with different configs
- **SDK** provides type-safe programmatic access to your database
With a config in place, everything else runs headlessly:

You write SQL. noorm executes it, tracks what ran, and keeps multiple environments in sync.
```bash
noorm run build # Build the schema from SQL files
noorm change ff # Apply pending changes
noorm db explore --json # Inspect the database as JSON
```

Supports **PostgreSQL**, **MySQL**, **SQLite**, and **SQL Server**.
In CI there is no TUI to fall back on, so bootstrap from `NOORM_*` environment variables instead:

```bash
noorm ci init --name ci # reads NOORM_IDENTITY_* and NOORM_CONNECTION_*
noorm run build
```

## Quick Start

```bash
# Initialize a project
noorm init
## How it works

# Launch the interactive TUI
noorm ui
Migration tools make you describe your schema twice: once in the migrations that built it, and once in your head. The current state only exists if you replay every file in order.

# Or run commands headlessly
noorm config add
noorm run build
noorm change ff
```
noorm inverts that.

From the TUI:
| | |
|---|---|
| **SQL files** | Are your current schema. A fresh database runs them and is done. |
| **Changes** | Move an existing database from any state to current — forward/revert pairs that noorm tracks, checksums, and applies in order. |
| **Stages** | Keep dev, staging, and production apart, with access roles per environment. |
| **SDK** | Wraps it in a type-safe client — Kysely queries, stored procedures, and TVFs. |

1. **[i] Identity** — Set your name (for team tracking)
2. **[c] Config → [a] Add** — Create a database config
3. **[r] Run → Build** — Execute your SQL files
You write SQL. noorm executes it, tracks what ran, and keeps environments in sync.


## Why noorm?

ORMs push you toward surrogate IDs on every table and join-heavy queries. Proper relational design uses inherited keys, basetype-subtypes, and compound constraints — things ORMs can't express.
ORMs push you toward a surrogate ID on every table and join-heavy queries. Proper relational design uses inherited keys, basetype-subtypes, and compound constraints — things ORMs can't express, and that migration tools make painful to maintain.

noorm lets you write the SQL your database was designed for, then manages execution across environments.

Read more: [noorm.dev](https://noorm.dev)
Read the argument in full: **[The case for proper relational design](https://noorm.dev/guide/relational-design)**


## SDK
Expand All @@ -85,9 +101,22 @@ For programmatic access in your applications:
npm install @noormdev/sdk kysely
```

Build type-safe domain classes — consumers (queries), producers (mutations), and guards (validation). One package, used everywhere.
Build a dedicated database package with a domain class per area of your schema, each holding a typed `Context`. Kysely types mirror your database, integration tests run against a real one, and the same package works in servers, workers, and CLIs.

**[Building your SDK →](https://noorm.dev/getting-started/building-your-sdk)**


## Coding agents

If you use an AI coding agent, install the noorm skill so it writes against the real conventions instead of guessing:

```bash
npx skills add noormdev/noorm/skills
```

It teaches the agent the SDK surface, the CLI's headless flags, template syntax, and config layout — the parts that are easy to get subtly wrong.

[SDK Documentation](https://noorm.dev/getting-started/building-your-sdk)
noorm also runs as an [MCP server](https://noorm.dev/guide/automation/mcp), behind per-channel access roles: admin at your terminal, read-only for the agent, or invisible entirely.


## License
Expand Down
46 changes: 44 additions & 2 deletions docs/.vitepress/config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,14 @@ import { defineConfig } from 'vitepress';
import { withMermaid } from 'vitepress-plugin-mermaid';

// https://vitepress.dev/reference/site-config
const SITE_URL = 'https://noorm.dev';
const TITLE = 'noorm — Write SQL. Skip the ORM.';
const DESCRIPTION = 'A SQL-first schema and change manager for Postgres, MySQL, SQLite, and MSSQL. Your schema lives in SQL files. noorm builds it, versions it, and keeps every environment in sync.';

export default withMermaid(
defineConfig({
title: 'noorm',
description: 'Database Schema & Change Manager',
description: DESCRIPTION,
base: process.env.VITEPRESS_BASE || '/',

// `docs/wiki/` is generated repo-analysis output for tooling and
Expand All @@ -14,9 +18,45 @@ export default withMermaid(
// (`<steering note: ...>`) is parsed as a tag with an illegal
// attribute and fails the build — which is why the site stopped
// deploying after 2026-07-04.
srcExclude: ['wiki/**'],
//
// The rest are the same class of thing: specs, design notes, scratch
// output, and the tape sources, none of which are published pages.
srcExclude: ['wiki/**', 'spec/**', 'design/**', 'superpowers/**', 'tmp/**', 'tapes/**'],

markdown: {
// The terminal recordings are the heaviest assets on the site
// (tui.gif alone is ~1.6 MB) and none of them sit above the fold.
image: { lazyLoading: true },
},

// `title` above only sets the <title> suffix; titleTemplate gives the home
// page a real headline instead of the bare word "noorm".
titleTemplate: ':title · noorm',

// Crawlers and chat apps do not run JS, so og:* must be static and absolute.
// The `title`/`description` config fields cover <title> and <meta name>;
// everything social has to be spelled out here.
head: [
['link', { rel: 'icon', href: '/image/logo.svg', type: 'image/svg+xml' }],
['link', { rel: 'apple-touch-icon', href: '/image/logo.png' }],
['link', { rel: 'canonical', href: `${SITE_URL}/` }],
['meta', { name: 'theme-color', content: '#E05742' }],

['meta', { property: 'og:type', content: 'website' }],
['meta', { property: 'og:site_name', content: 'noorm' }],
['meta', { property: 'og:url', content: `${SITE_URL}/` }],
['meta', { property: 'og:title', content: TITLE }],
['meta', { property: 'og:description', content: DESCRIPTION }],
['meta', { property: 'og:image', content: `${SITE_URL}/image/og.png` }],
['meta', { property: 'og:image:width', content: '1200' }],
['meta', { property: 'og:image:height', content: '630' }],
['meta', { property: 'og:image:alt', content: 'noorm — Write SQL. Skip the ORM.' }],

['meta', { name: 'twitter:card', content: 'summary_large_image' }],
['meta', { name: 'twitter:title', content: TITLE }],
['meta', { name: 'twitter:description', content: DESCRIPTION }],
['meta', { name: 'twitter:image', content: `${SITE_URL}/image/og.png` }],

['link', { rel: 'preconnect', href: 'https://fonts.googleapis.com' }],
['link', { rel: 'preconnect', href: 'https://fonts.gstatic.com', crossorigin: '' }],
['script', { async: '', src: 'https://www.googletagmanager.com/gtag/js?id=G-Y69K95866J' }],
Expand Down Expand Up @@ -115,6 +155,7 @@ gtag('config', 'G-Y69K95866J');`],
items: [
{ text: 'CLI Reference', link: '/headless' },
{ text: 'Terminal UI', link: '/tui' },
{ text: 'Relational Design', link: '/guide/relational-design' },
],
},
{
Expand Down Expand Up @@ -150,6 +191,7 @@ gtag('config', 'G-Y69K95866J');`],
collapsed: true,
items: [
{ text: 'Explorer', link: '/guide/database/explore' },
{ text: 'Transfer', link: '/guide/database/transfer' },
{ text: 'Teardown', link: '/guide/database/teardown' },
{ text: 'Terminal', link: '/guide/database/terminal' },
],
Expand Down
Loading
Loading