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
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
name: CI

on:
workflow_call:
push:
branches: [master]
pull_request:
Expand Down
5 changes: 5 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,12 @@ permissions:
id-token: write

jobs:
v1-gate:
uses: ./.github/workflows/ci.yml

publish:
name: Publish to npm
needs: v1-gate
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
Expand All @@ -40,3 +44,4 @@ jobs:
- run: npm ci

- run: npm publish --loglevel verbose
working-directory: packages/core
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
node_modules/
dist/
tests/perf/generated/
tests/performance/generated/
*.log
26 changes: 25 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,30 @@ Notable changes to this project, following [Keep a Changelog](https://keepachang

## [Unreleased]

### Architecture

- Removed runtime statement parsing from the `node:sqlite` adapter. Driver column metadata now selects the row path, older `node:sqlite` versions fall back to `all()` without write metadata, and SQLite counters provide write metadata without interpreting user SQL.
- Moved the schema model out of Compiler into its own internal Schema boundary and changed architecture checks from partial forbidden-import rules to an allowlisted dependency graph that also recognizes TypeScript import expressions and CommonJS import-equals declarations.
- Unified public output and parameter inference around one Language parse result. Parameter inference remains schema-aware in Compiler and consumes only fragments already delimited by shallow Query IR.
- Made the release workflow depend on the complete reusable CI gate before publishing.

### Changed

- **Breaking (pre-v1):** removed the accidental advanced exports `ParseSelect`, `ParseStatement`, `ParsedStatement`, `Source`, and `FunctionReturnTypes`. They exposed the retired parser and compiler internals and have no supported replacement. Use `Query`, `Row`, `StrictQuery`, `StrictRow`, and `Params` for the public type-inference contract.
- The reviewed v1 type-instantiation baseline is 135,217, down from 244,919 after the Legacy compiler and accidental parser exports were removed. The public hard ceiling is reduced from 250,000 to 150,000; eight isolated stress cases add a 10% relative gate and a 200,000 absolute ceiling.

### Strict mode

- Write targets, write columns, DML join predicates, and the SELECT half of `INSERT ... SELECT` now receive the strict checks listed under Fixed. Correlated subqueries that are valid SQL no longer produce an outer-scope error.

### Type mapping

- Corrected inferred row mappings for parenthesized columns, function-call projections, set-operation branches, and CTE shadowing are listed under Fixed. `Params` corrections are called out individually because tuple arity and element types are product API.

### SQL and dialect support

- The supported union now includes the documented PostgreSQL `DISTINCT ON` spellings, parenthesized set-operation branches, and T-SQL `OUTPUT ... FROM` forms. Dialect capability and placeholder differences are executable in the v1 dialect matrix.

### Fixed

- `WITH t AS MATERIALIZED (...)` and its `NOT MATERIALIZED` twin parse again. The parser wanted the body's opening paren directly after `as`, so the Postgres 12 planner hint took the whole WITH clause down with it and the query degraded into an index signature row instead of reporting anything ([#283](https://github.com/tiagolauer/OwlSQL/issues/283)).
Expand Down Expand Up @@ -41,7 +65,7 @@ Notable changes to this project, following [Keep a Changelog](https://keepachang

### Changed

- **Breaking:** the editor plugin moved out of this package into its own, [`@owlsql/ts-plugin`](ts-plugin/README.md). The `@owlsql/core/ts-plugin` subpath is gone. To migrate, install `@owlsql/ts-plugin` as a dev dependency and change the plugin name in your `tsconfig.json`:
- **Breaking:** the editor plugin moved out of this package into its own, [`@owlsql/ts-plugin`](packages/ts-plugin/README.md). The `@owlsql/core/ts-plugin` subpath is gone. To migrate, install `@owlsql/ts-plugin` as a dev dependency and change the plugin name in your `tsconfig.json`:

```json
{
Expand Down
11 changes: 6 additions & 5 deletions COMPARISON.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,16 +134,17 @@ check the source link and open an issue.
- **Bundle**: zero runtime dependencies (`package.json` has no
`dependencies` field), and the runtime surface is `createTypedDb`,
`defineSchema`, and the `Result` helpers — about 175 lines of source
across [`src/index.ts`](src/index.ts) and [`src/result.ts`](src/result.ts)
across [`packages/core/src/index.ts`](packages/core/src/index.ts) and
[`packages/core/src/runtime/result.ts`](packages/core/src/runtime/result.ts)
combined, most of which is type declarations erased at compile time. The
parser itself (a few thousand lines across `src/parse.ts`/`src/from.ts`/
etc.) is 100% types — it ships zero bytes to any runtime.
compiler under `packages/core/src/language` and `packages/core/src/compiler`
is 100% types — it ships zero bytes to any runtime.
- **DX trade-off, stated plainly**: this is the smallest surface area of
the five because it does the least. No migrations, no relation loading, no
query builder ergonomics (autocomplete for chained methods) — you write
SQL, you get a type back. If you want an ORM's feature set, this isn't
one; see the [Supported SQL subset](README.md#supported-sql-subset) and
[Limitations](README.md#limitations) for exactly where the parser's
one; see the [Supported SQL subset](packages/core/README.md#supported-sql-subset) and
[Limitations](packages/core/README.md#limitations) for exactly where the parser's
coverage ends.

## Methodology notes
Expand Down
14 changes: 8 additions & 6 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ Keep a PR to one fix or one feature. A PR that touches three unrelated things is

If the change alters what a query infers to, say so in the description and name the bump it implies under [VERSIONING.md](VERSIONING.md). A row shape that gains, loses, or retypes a key is a breaking change even when no runtime signature moved.

Every behavior change needs a test that would fail without the fix. If you're touching `src/parse.ts`, `src/where.ts`, or another type-level file, that usually means a `.test-d.ts` case with `@ts-expect-error` or an `Equal<>` assertion; runtime behavior (adapters, the CLI, the editor plugin) gets a `.test.ts` case instead. A PR without a regression test is a PR someone else will eventually re-break by accident.
Every behavior change needs a test that would fail without the fix. If you're touching `packages/core/src/language`, `packages/core/src/compiler`, or another type-level file, that usually means a `.test-d.ts` case with `@ts-expect-error` or an `Equal<>` assertion; runtime behavior (adapters, the CLI, the editor plugin) gets a `.test.ts` case instead. A PR without a regression test is a PR someone else will eventually re-break by accident.

### Architecture changes

Expand All @@ -41,7 +41,7 @@ ADRs in `docs/adr/`. A change that supersedes one of those decisions needs a new

You'll need Node 20 or later. The `node:sqlite` adapter and the CLI's SQLite introspection need Node 22.5+, since `node:sqlite` is newer than the rest of the runtime surface this library targets.

This repository holds two packages, as an npm workspace: `@owlsql/core` at the root, and the editor plugin in [`ts-plugin/`](ts-plugin/README.md). They are apart because they don't reach the same TypeScript versions — the library type-checks clean on TypeScript 7, while the plugin needs the classic compiler API, which TypeScript 7 does not ship at all. One package can only declare one peer range, and either choice would have been a lie about half the code.
This repository holds two npm workspaces: [`@owlsql/core`](packages/core) and the editor plugin in [`packages/ts-plugin/`](packages/ts-plugin/README.md). They are apart because they don't reach the same TypeScript versions — the library type-checks clean on TypeScript 7, while the plugin needs the classic compiler API, which TypeScript 7 does not ship at all. One package can only declare one peer range, and either choice would have been a lie about half the code.

```bash
npm install
Expand All @@ -57,6 +57,8 @@ npm test --workspace @owlsql/ts-plugin # the editor plugin, independently

Nothing in the root `npm test` runs plugin code, and that's deliberate: a release of the library should not be gated on a plugin whose supported TypeScript range is narrower and whose future is upstream's to decide.

`npm run test:perf` compiles the public fixture and each case under `tests/performance/cases` independently. Baseline or ceiling changes must include the measured result and rationale in the same commit.

One wrinkle worth knowing before you touch the tsconfigs: the TypeScript 7 CI job runs `test:types:core` rather than `test:types`, because `tests/cli-codegen-edge.test.ts` uses the compiler API (it parses generated schema output to prove it's syntactically valid) and so cannot run there. That one file lives in `tsconfig.compiler-api-tests.json` so it can be left out of the TypeScript 7 run without being quietly dropped from every other one.

### Fixing a bug
Expand All @@ -70,16 +72,16 @@ Open an issue before writing the implementation if the feature touches the publi
### Design preferences

- No runtime SQL parsing, ever. If a change needs to inspect the query string at runtime to work, it probably belongs in the ts-plugin (which already does its own lightweight runtime scanning for editor support), not in the core library.
- Adapters (`src/adapters/*.ts`) import the driver's types only, never the driver package itself as a value. This keeps `@owlsql/core/pg` usable without `pg` actually being installed, for anyone who only imports a different adapter.
- Adapters (`packages/core/src/adapters/*.ts`) import the driver's types only, never the driver package itself as a value. This keeps `@owlsql/core/pg` usable without `pg` actually being installed, for anyone who only imports a different adapter.
- If you extend the SQL subset the parser accepts, update the "Supported SQL subset" and "Limitations" sections in the README in the same PR. A parser change nobody can discover from the docs is half a feature.
- Prefer a documented scope boundary over a half-correct implementation. Several existing features (LATERAL correlation, WHERE-clause diagnostics with parens) deliberately do less than a full SQL engine would, and say so in the README, rather than guessing.

## Testing

Three layers, and they test different things:

- **Type tests** (`tests/*.test-d.ts`) are pure type assertions. If they compile, the inference is correct; there's no runtime assertion to run. They cover column/alias projection, `@ts-expect-error` cases for queries that should fail to type, permissive-inference locks, and deep-recursion stress.
- **Runtime tests** (`tests/*.test.ts`) run under vitest and cover the executor/`Result` contract, adapter parameter handling, and the CLI. Drivers are faked here, so these prove the adapter's own logic, not what a real server sends back. The plugin's own tests live beside it in `ts-plugin/tests/`.
- **Type tests** (`packages/core/tests/*.test-d.ts`) are pure type assertions. If they compile, the inference is correct; there's no runtime assertion to run. They cover column/alias projection, `@ts-expect-error` cases for queries that should fail to type, permissive-inference locks, and deep-recursion stress.
- **Runtime tests** (`packages/core/tests/*.test.ts`) run under vitest and cover the executor/`Result` contract, adapter parameter handling, and the CLI. Drivers are faked here, so these prove the adapter's own logic, not what a real server sends back. The plugin's own tests live in `packages/ts-plugin/tests/`.
- **Integration tests** (`tests/integration/*.test.ts`) run the adapters and the `generate` CLI against real PostgreSQL, MySQL, and SQL Server instances. They cover what a fake driver can't: how each driver actually decodes a column (`bigint`, `numeric`, `tinyint(1)`, `bit`), the metadata a real result carries, and whether a rolled-back transaction really left no rows behind.

CI runs the type tests against a matrix of TypeScript versions, since a template-literal-type change that works on one TypeScript release can silently stop working (or start working differently) on another.
Expand Down Expand Up @@ -128,7 +130,7 @@ npm version <patch|minor|major>
git push --follow-tags origin master
```

Then run **Actions → Release → Run workflow**, or publish a GitHub Release for the tag — either triggers it. The workflow runs `npm publish --provenance`, and `prepublishOnly` puts the type tests, the runtime tests and the build in front of that, so a red build cannot reach the registry.
Then run **Actions → Release → Run workflow**, or publish a GitHub Release for the tag — either triggers it. The release calls the same reusable CI workflow used by pushes and pull requests before publishing, including the TypeScript and Node matrices, plugin, architecture, package, performance, and real-database integration gates. `prepublishOnly` repeats the core type, runtime, architecture, and build checks immediately before npm receives the package.

This needs the package's *Trusted publisher* to be configured once on npmjs.com (package → Settings → Trusted publisher → GitHub Actions, repository `tiagolauer/OwlSQL`, workflow `release.yml`).

Expand Down
Loading