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 AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ The current implementation is documented in `doc/README.md` and public Dartdoc.
Old code and upstream architecture are retired.

- Keep one product package. Add abstractions only to support real use cases.
- During beta, do not add old API or schema compatibility unless explicitly requested.
- Use independent Dart libraries; never use `part` or `part of`.
- Keep implementation in `lib/src/` and public entrypoints as explicit exports.
- Document public behavior, ownership and failure boundaries with Dartdoc.
Expand Down
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,21 @@
## Unreleased

Breaking PostgreSQL schema change: table identities now include the database
schema, including `public`. Regenerate clients and snapshots and review the
required migration. Old unqualified snapshots are not automatically normalized;
generated table replacement requires explicit destructive opt-in and deletes data.

- Discover PostgreSQL models in `schema/{schema}/*.dart` and other supported
databases in `schema/*.dart`, alongside an optional default `schema.dart`.
- Generate schema-grouped clients when PostgreSQL models use a non-default
namespace. Preserve mixed-case identifiers and qualify table references,
cross-schema relationships, migrations and catalog verification explicitly.
- Reject dotted model table names and conflicting declarations. Keep snapshots
stable when definitions are split or renamed within the same database schema.
- PostgreSQL cursor tokens now identify schema-qualified tables, including
`public`. Regenerated clients reject tokens issued with the earlier unqualified
table identity; applications that persist cursors must reissue them.

## 6.0.0-beta.3

Breaking schema authoring change: replace annotated entities and hand-written row
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4889,7 +4889,7 @@ $S:21}
A.fO.prototype={
W(a6,a7){var s=0,r=A.aM(t.X),q,p=2,o=[],n=this,m,l,k,j,i,h,g,f,e,d,c,b,a,a0,a1,a2,a3,a4,a5
var $async$W=A.aN(function(a8,a9){if(a8===1){o.push(a9)
s=p}for(;;)A:switch(s){case 0:if(a6==="hello"){q=A.x([1,"1ffcc1c93a300242a8c4ca7badc270240d0550d23254799eda18232f8bd3312d"],t.f)
s=p}for(;;)A:switch(s){case 0:if(a6==="hello"){q=A.x([1,"1a12f6159d3f3b9a21811c2431bd7cd0d7986d0d8530f6085246adfbff6d049e"],t.f)
s=1
break}s=a6==="open"?3:4
break
Expand Down
1 change: 1 addition & 0 deletions build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ builders:
builder_factories: ["ormBuilder"]
build_extensions:
".dart": [".orm.dart", ".snapshot.dart"]
"$lib$": ["schema.orm.dart", "schema.snapshot.dart"]
Comment thread
medz marked this conversation as resolved.
auto_apply: none
build_to: source
queries:
Expand Down
4 changes: 3 additions & 1 deletion doc/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ Define a schema, generate typed models and queries, and evolve your database wit
reviewed Dart migrations. Start with [schema declarations](https://github.com/medz/dart-orm/blob/main/doc/authoring.md)
and the [company example](https://github.com/medz/dart-orm/blob/main/example/company/README.md).

> **Version:** These guides describe `6.0.0-beta.3` and its Record schema API.
> **Version:** These guides follow the repository source. Use the corresponding
> release tag for the exact behavior of a published package.
> When upgrading, replace annotated entities with `model(...)` and regenerate
> clients. Keep existing migration history unchanged.

Expand All @@ -13,6 +14,7 @@ and the [company example](https://github.com/medz/dart-orm/blob/main/example/com
| Task | Guide |
| --- | --- |
| Define models, columns, keys and relationships | [Schema declarations](https://github.com/medz/dart-orm/blob/main/doc/authoring.md) |
| Organize files and PostgreSQL database schemas | [Database schemas](https://github.com/medz/dart-orm/blob/main/doc/namespaces.md) |
| Generate a client and use build_runner | [Generation](https://github.com/medz/dart-orm/blob/main/doc/generation.md) |
| Choose imports and open a database | [Entrypoints](https://github.com/medz/dart-orm/blob/main/doc/api.md) |
| Filter, select, join and paginate | [Queries](https://github.com/medz/dart-orm/blob/main/doc/queries.md) |
Expand Down
8 changes: 8 additions & 0 deletions doc/authoring.md
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,14 @@ The `relations` callback must return a literal named Record containing direct

## Split schemas and static boundaries

For automatic file collection, use `schema/{schema}/*.dart` on PostgreSQL or
`schema/*.dart` on SQLite, MySQL and MariaDB. PostgreSQL directory names establish
database schema ownership; model declarations remain `model(...)`. Neither
pattern is recursive. See [database schemas](https://github.com/medz/dart-orm/blob/main/doc/namespaces.md)
for layouts, generated accessors, cross-schema relationships and single-file coexistence.

A single-file root can also select definitions through Dart exports:

Use ordinary independent Dart libraries. A schema root can export selected models:

```dart
Expand Down
8 changes: 8 additions & 0 deletions doc/capabilities.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,14 @@ roles, command, mode and expressions plus enabled/forced row-security flags.
Baseline does not certify grants, every extension, authorization behavior or the
entire database environment. See [migrations](https://github.com/medz/dart-orm/blob/main/doc/migrations.md) and [importing](https://github.com/medz/dart-orm/blob/main/doc/importing.md).

## Database schemas

PostgreSQL models can belong to named schemas in the same database, including
cross-schema relationships. The generator records the default `public` schema
explicitly when PostgreSQL is selected. MySQL/MariaDB database namespaces and
SQLite attached databases are not exposed by this model layout feature. See
[database schemas](https://github.com/medz/dart-orm/blob/main/doc/namespaces.md).

## Connections, platforms and tools

PostgreSQL uses its driver's pool and supports borrowed-pool ownership. SQLite
Expand Down
16 changes: 12 additions & 4 deletions doc/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,8 @@ renames/conversions, baseline and recovery behavior.
These commands also work without project configuration:

```sh
dart run orm generate lib/schema.dart lib/generated/database.dart
dart run orm generate lib/schema.dart lib/generated/database.dart --database sqlite
dart run orm generate lib/schema --database postgres
dart run orm migration registry migrations --dialect sqlite
dart run orm db inspect --sqlite app.sqlite --table tasks
dart run orm db import --sqlite app.sqlite --output lib/imported.dart
Expand All @@ -116,9 +117,16 @@ verifyFull|require|disable` configures server TLS; `--database-schema` is specif
to PostgreSQL. Catalog import writes a Dart draft and a separate review report.
It does not migrate an existing database.

Without a config, `generate` uses `lib/schema.dart`. If a config's generated
snapshot was deleted, recover with the explicit source command
`dart run orm generate lib/schema.dart`, which does not load that config.
Generation uses the project configuration even when a schema path is supplied.
`--database` selects the engine without loading a configuration, which is useful
when recreating a deleted snapshot:
`dart run orm generate lib/schema --database postgres`. When an explicitly loaded
configuration and engine disagree, generation rejects the mismatch. Without a
configuration or path, the source defaults to `lib/schema.dart`.

Directory layouts require an engine. `lib/schema`, `lib/schema/` and
`lib/schema.dart` identify the same root and combine the file and directory when
both exist. See [database schemas](https://github.com/medz/dart-orm/blob/main/doc/namespaces.md).

## Help and automation

Expand Down
41 changes: 38 additions & 3 deletions doc/generation.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@ output path:
dart run orm generate lib/schema.dart lib/generated/database.dart
```

For directory generation, the output must not match a schema input path,
including the optional sibling `schema.dart`. Choose a path outside the layout
or use an excluded `.orm.dart` filename inside it. Invalid paths are rejected
before either generated file is written, including on the first run.

The CLI checks source errors and resolves imports using the project's package
configuration. Use it when a single explicit generation step suits the project.

Expand Down Expand Up @@ -73,7 +78,7 @@ dart run build_runner build
dart run build_runner watch
```

The builder is opt-in and uses the explicit `generate_for` list. Select schema
For individual libraries, the builder is opt-in and uses the explicit `generate_for` list. Select schema
libraries containing or exporting `model` declarations. Referenced models
are included transitively; unrelated imports are not additional schema roots.
Do not select every
Expand All @@ -82,10 +87,40 @@ next to their input files. Import generated clients with prefixes if their
declaration names overlap.

The output of `lib/schema.dart` is always `lib/schema.orm.dart` and
`lib/schema.snapshot.dart`. There are no ORM-specific builder options. Use the CLI's
output argument for other locations. `build_runner` owns its build cache and output
`lib/schema.snapshot.dart`. Set `options.database: postgres` for a PostgreSQL
file root, so default tables explicitly belong to `public`. Use the CLI's output
argument for other locations. `build_runner` owns its build cache and output
cleanup; do not manually edit that cache or generated source.

## Definition directories

For a directory, select its root and engine through builder options. This runs
once for the root, without requiring an empty `schema.dart` or a `generate_for`
entry for each model file:

```yaml
targets:
$default:
builders:
orm:orm:
enabled: true
options:
schema: lib/schema
database: postgres
```

PostgreSQL collects `lib/schema/{schema}/*.dart`; `sqlite`, `mysql` and `mariadb`
collect `lib/schema/*.dart`. Both also include a sibling `lib/schema.dart` when
present. Additional nesting is not recursive. The builder tracks matching file
additions and deletions as well as imported metadata. Outputs remain
`lib/schema.orm.dart` and `lib/schema.snapshot.dart`.

The CLI accepts `lib/schema`, `lib/schema/` and `lib/schema.dart` for this same root.
It reads the engine from the project history, or accepts an explicit
`--database postgres` when no configuration should be loaded. See
[database schemas](https://github.com/medz/dart-orm/blob/main/doc/namespaces.md) for
namespace ownership, name collisions, relationships and migration behavior.

## What triggers regeneration

The builder resolves source through `BuildStep.resolver` and writes through
Expand Down
7 changes: 7 additions & 0 deletions doc/importing.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,13 @@ PostgreSQL uses a read-only, repeatable-read transaction in the selected schema.
Each invocation uses one catalog snapshot. Existing outer transactions are rejected;
a borrowed session without an active transaction is allowed.

For a non-default PostgreSQL schema, place the imported file in its matching
directory, for example `--database-schema auth --output lib/schema/auth/imported.dart`,
then run `dart run orm generate lib/schema --database postgres`. Import currently
reads one database schema per invocation. Cross-schema relationships remain
reported as unmanaged and must be declared against the imported target models
before generation. See [database schema layouts](https://github.com/medz/dart-orm/blob/main/doc/namespaces.md).

To import one table, use `--table accounts`. For a selected related group, use
`importSchema(db, tables: ['accounts', 'notes'])` in Dart. Without a selection,
discovery covers SQLite main, the current PostgreSQL schema or the selected
Expand Down
135 changes: 135 additions & 0 deletions doc/namespaces.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
# Database schemas and definition files

Keep model declarations unchanged when splitting a schema. PostgreSQL uses the
first directory name as the database schema. MySQL, MariaDB and SQLite use a flat
directory of definition files for one configured database.

| Source | PostgreSQL | MySQL, MariaDB, SQLite |
| --- | --- | --- |
| `lib/schema.dart` | Default `public` schema | Configured database |
| `lib/schema/{schema}/*.dart` | Named database schema | Not a declaration layout |
| `lib/schema/*.dart` | Use a schema subdirectory | Split model declarations |

Neither directory pattern is recursive. Imported enums, codecs and default
factories can live elsewhere. In directory mode, referenced models must be
declared in the selected layout; an import or export cannot change their schema.
Generated `.orm.dart` and `.snapshot.dart` files are not declaration inputs.

## PostgreSQL

```text
lib/schema/
public/
profiles.dart
auth/
users.dart
sessions.dart
```

`lib/schema/auth/users.dart`:

```dart
import 'package:orm/schema.dart';

final user = model('Users', (
id: identity(),
displayName: text(name: 'DisplayName'),
));
```

`lib/schema/public/profiles.dart`:

```dart
import 'package:orm/schema.dart';
import '../auth/users.dart' as auth;

final profile = model('profiles', (
id: identity(),
accountId: integer(),
), relations: (p) => (
account: references(p.accountId, () => auth.user),
));
```

Generate offline using the project configuration, or select the engine explicitly:

```sh
dart run orm generate lib/schema --database postgres
```

The client and snapshot are `lib/schema.orm.dart` and `lib/schema.snapshot.dart`.
Only default `public` models produce flat access such as `db.user`. Declaring a
non-default schema groups all model access by schema:

```dart
final account = await db.auth.user.create(displayName: 'Alice');
await db.public.profile.create(accountId: account.id);
```

Grouped row types include their schema, such as `AuthUser` and `PublicProfile`.
Different schemas may declare the same model and table names. Names must remain
distinct within their schema. Schema directory names must also be usable as Dart
database members; invalid names and generated type collisions are reported.
Case-sensitive database names cannot always be represented on a case-insensitive
source filesystem; the generator does not silently rename or merge them.

Relationships use the target declaration's schema. Cross-schema foreign keys and
queries use the same PostgreSQL database and connection; they do not open another
database or introduce distributed transactions.

## Single files and generation roots

`lib/schema`, `lib/schema/` and `lib/schema.dart` select the same definition root.
If both the file and directory exist, generation combines their declarations.
The sibling file contributes to the default schema. Exporting a declaration that
was already discovered does not duplicate it; independent conflicting models
produce an error instead of overriding one another.

Select the engine through `OrmConfig.history.dialect` or `--database`. Directory
generation requires an engine and never connects to discover it. Single-file
programmatic generation without an engine retains engine-neutral metadata; select
PostgreSQL explicitly when generating its physical namespaces.

With an engine selected, table ordering is deterministic. Splitting declarations
or renaming a file within one schema does not change the physical snapshot.
Moving a model to another schema changes its database identity and requires a
reviewed migration. Existing migration definitions and fingerprints stay frozen.

PostgreSQL namespace identity is a breaking change. Older unqualified snapshots
are not automatically matched to `public` tables. Regenerate the client and
snapshot, then review the migration before applying it. A generated replacement
plan requires `--allow-destructive` and recreates the affected tables, deleting
their rows. Projects that need to retain data must author and review that data
migration explicitly. Historical migration files must not be rewritten.

## SQL names and migration ownership

The schema, table and column names are separate identifiers. The example above
queries `"auth"."Users"` and `"DisplayName"`. PostgreSQL generation also explicitly
qualifies default models with `"public"`. Querying these tables does not depend
on a later `SET search_path` or an identically named temporary table.

Do not write `model('auth.Users', ...)`: dotted table names are rejected.
Manual `TableSchema` definitions apply the same rule to table names, namespaces
and foreign-key targets when constructed, and also reject empty names and NUL
characters. Column names use snake_case unless
their helper specifies `name:`. Explicit physical
names retain their spelling and quotes are escaped for the selected dialect;
quoting does not change the database's own rules for case equality.

Generated PostgreSQL creation plans include the required `CREATE SCHEMA IF NOT
EXISTS` statements. Review these through the normal migration workflow and use a
role with the required privileges. Removing models never automatically drops a
schema or runs `DROP SCHEMA ... CASCADE`. Explicit table moves can use
`SchemaRenames.tables` with the before/after identities, for example
`{'auth.Users': 'archive.Users'}`; these keys match snapshot identities and are
not interpreted as arbitrary SQL.

Migration locks cover one PostgreSQL database, including work spanning several
schemas. Catalog verification, foreign keys, query inspection, cursors and change
subscriptions distinguish schema-qualified tables. Namespace getters do not grant
database permissions. Raw SQL remains trusted application SQL and follows its
own explicit names and session state.

MySQL/MariaDB cross-database models and SQLite attached-database models are outside
this layout feature. Their definition files continue to describe one database.
3 changes: 2 additions & 1 deletion lib/builder.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/// Optional build_runner factories for model and named SQL generation.
///
/// Select source libraries with `generate_for` in `build.yaml`. Applications
/// Select libraries with `generate_for`, or configure `schema` and `database`
/// options to collect a definition directory as one client. Applications
/// import the generated files; these builders run only during development.
///
/// {@category Tooling}
Expand Down
Loading