neoorm 0.8.0 → 0.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -6
- package/dist/bin/neoorm.d.ts +1 -1
- package/dist/bin/neoorm.d.ts.map +1 -1
- package/dist/bin/neoorm.js +148 -75
- package/dist/bin/neoorm.js.map +1 -1
- package/dist/codegen/diff-manifest.d.ts.map +1 -1
- package/dist/codegen/diff-manifest.js +106 -4
- package/dist/codegen/diff-manifest.js.map +1 -1
- package/dist/codegen/generate-summary.d.ts +5 -1
- package/dist/codegen/generate-summary.d.ts.map +1 -1
- package/dist/codegen/generate-summary.js +10 -1
- package/dist/codegen/generate-summary.js.map +1 -1
- package/dist/codegen/generate.d.ts +24 -4
- package/dist/codegen/generate.d.ts.map +1 -1
- package/dist/codegen/generate.js +150 -13
- package/dist/codegen/generate.js.map +1 -1
- package/dist/codegen/manifest-relations.d.ts +2 -0
- package/dist/codegen/manifest-relations.d.ts.map +1 -1
- package/dist/codegen/manifest-relations.js +2 -1
- package/dist/codegen/manifest-relations.js.map +1 -1
- package/dist/codegen/schema-to-manifest.d.ts +1 -1
- package/dist/codegen/schema-to-manifest.d.ts.map +1 -1
- package/dist/codegen/schema-to-manifest.js +349 -29
- package/dist/codegen/schema-to-manifest.js.map +1 -1
- package/dist/codegen/validation/emit-elysia.d.ts +4 -0
- package/dist/codegen/validation/emit-elysia.d.ts.map +1 -0
- package/dist/codegen/validation/emit-elysia.js +304 -0
- package/dist/codegen/validation/emit-elysia.js.map +1 -0
- package/dist/codegen/validation/emit-typebox.d.ts +4 -0
- package/dist/codegen/validation/emit-typebox.d.ts.map +1 -0
- package/dist/codegen/validation/emit-typebox.js +360 -0
- package/dist/codegen/validation/emit-typebox.js.map +1 -0
- package/dist/codegen/validation/emit-zod.d.ts +4 -0
- package/dist/codegen/validation/emit-zod.d.ts.map +1 -0
- package/dist/codegen/validation/emit-zod.js +288 -0
- package/dist/codegen/validation/emit-zod.js.map +1 -0
- package/dist/codegen/validation/from-manifest.d.ts +5 -0
- package/dist/codegen/validation/from-manifest.d.ts.map +1 -0
- package/dist/codegen/validation/from-manifest.js +284 -0
- package/dist/codegen/validation/from-manifest.js.map +1 -0
- package/dist/codegen/validation/infer.d.ts +47 -0
- package/dist/codegen/validation/infer.d.ts.map +1 -0
- package/dist/codegen/validation/infer.js +2 -0
- package/dist/codegen/validation/infer.js.map +1 -0
- package/dist/codegen/validation/json-generic-types.d.ts +7 -0
- package/dist/codegen/validation/json-generic-types.d.ts.map +1 -0
- package/dist/codegen/validation/json-generic-types.js +295 -0
- package/dist/codegen/validation/json-generic-types.js.map +1 -0
- package/dist/codegen/validation/types.d.ts +84 -0
- package/dist/codegen/validation/types.d.ts.map +1 -0
- package/dist/codegen/validation/types.js +6 -0
- package/dist/codegen/validation/types.js.map +1 -0
- package/dist/config.d.ts +8 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +30 -0
- package/dist/config.js.map +1 -1
- package/dist/datasource-provider.d.ts +5 -1
- package/dist/datasource-provider.d.ts.map +1 -1
- package/dist/datasource-provider.js +21 -2
- package/dist/datasource-provider.js.map +1 -1
- package/dist/dialect/column-kind-support.d.ts +9 -0
- package/dist/dialect/column-kind-support.d.ts.map +1 -0
- package/dist/dialect/column-kind-support.js +25 -0
- package/dist/dialect/column-kind-support.js.map +1 -0
- package/dist/dialect/fk.d.ts +30 -1
- package/dist/dialect/fk.d.ts.map +1 -1
- package/dist/dialect/fk.js +113 -0
- package/dist/dialect/fk.js.map +1 -1
- package/dist/dialect/index.d.ts +6 -1
- package/dist/dialect/index.d.ts.map +1 -1
- package/dist/dialect/index.js +5 -0
- package/dist/dialect/index.js.map +1 -1
- package/dist/dialect/mariadb.d.ts +4 -0
- package/dist/dialect/mariadb.d.ts.map +1 -0
- package/dist/dialect/mariadb.js +15 -0
- package/dist/dialect/mariadb.js.map +1 -0
- package/dist/dialect/mariadb.test.d.ts +2 -0
- package/dist/dialect/mariadb.test.d.ts.map +1 -0
- package/dist/dialect/mariadb.test.js +151 -0
- package/dist/dialect/mariadb.test.js.map +1 -0
- package/dist/dialect/mysql-family.d.ts +14 -0
- package/dist/dialect/mysql-family.d.ts.map +1 -0
- package/dist/dialect/mysql-family.js +453 -0
- package/dist/dialect/mysql-family.js.map +1 -0
- package/dist/dialect/mysql.d.ts +6 -0
- package/dist/dialect/mysql.d.ts.map +1 -0
- package/dist/dialect/mysql.js +16 -0
- package/dist/dialect/mysql.js.map +1 -0
- package/dist/dialect/mysql.test.d.ts +2 -0
- package/dist/dialect/mysql.test.d.ts.map +1 -0
- package/dist/dialect/mysql.test.js +211 -0
- package/dist/dialect/mysql.test.js.map +1 -0
- package/dist/dialect/placeholders.d.ts +7 -0
- package/dist/dialect/placeholders.d.ts.map +1 -0
- package/dist/dialect/placeholders.js +18 -0
- package/dist/dialect/placeholders.js.map +1 -0
- package/dist/dialect/postgres.d.ts +2 -1
- package/dist/dialect/postgres.d.ts.map +1 -1
- package/dist/dialect/postgres.js +93 -30
- package/dist/dialect/postgres.js.map +1 -1
- package/dist/dialect/resolve.d.ts +13 -0
- package/dist/dialect/resolve.d.ts.map +1 -0
- package/dist/dialect/resolve.js +66 -0
- package/dist/dialect/resolve.js.map +1 -0
- package/dist/dialect/shared.d.ts +8 -1
- package/dist/dialect/shared.d.ts.map +1 -1
- package/dist/dialect/shared.js +37 -0
- package/dist/dialect/shared.js.map +1 -1
- package/dist/dialect/sqlite.d.ts.map +1 -1
- package/dist/dialect/sqlite.js +50 -18
- package/dist/dialect/sqlite.js.map +1 -1
- package/dist/dialect/sqlite.test.js +43 -1
- package/dist/dialect/sqlite.test.js.map +1 -1
- package/dist/dialect/types.d.ts +60 -3
- package/dist/dialect/types.d.ts.map +1 -1
- package/dist/docs/pages.d.ts.map +1 -1
- package/dist/docs/pages.js +5 -0
- package/dist/docs/pages.js.map +1 -1
- package/dist/docs/render.js +1 -1
- package/dist/index.d.ts +6 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -2
- package/dist/index.js.map +1 -1
- package/dist/init/scaffold.d.ts +2 -1
- package/dist/init/scaffold.d.ts.map +1 -1
- package/dist/init/scaffold.js.map +1 -1
- package/dist/init/templates.d.ts +3 -2
- package/dist/init/templates.d.ts.map +1 -1
- package/dist/init/templates.js +19 -9
- package/dist/init/templates.js.map +1 -1
- package/dist/introspect/group-fks.d.ts +24 -0
- package/dist/introspect/group-fks.d.ts.map +1 -0
- package/dist/introspect/group-fks.js +48 -0
- package/dist/introspect/group-fks.js.map +1 -0
- package/dist/introspect/mysql/to-manifest.d.ts +4 -0
- package/dist/introspect/mysql/to-manifest.d.ts.map +1 -0
- package/dist/introspect/mysql/to-manifest.js +317 -0
- package/dist/introspect/mysql/to-manifest.js.map +1 -0
- package/dist/introspect/pull.d.ts +1 -0
- package/dist/introspect/pull.d.ts.map +1 -1
- package/dist/introspect/pull.js +186 -9
- package/dist/introspect/pull.js.map +1 -1
- package/dist/introspect/queries.d.ts +11 -1
- package/dist/introspect/queries.d.ts.map +1 -1
- package/dist/introspect/queries.js +43 -12
- package/dist/introspect/queries.js.map +1 -1
- package/dist/introspect/sqlite/to-manifest.d.ts.map +1 -1
- package/dist/introspect/sqlite/to-manifest.js +99 -26
- package/dist/introspect/sqlite/to-manifest.js.map +1 -1
- package/dist/introspect/to-manifest.d.ts.map +1 -1
- package/dist/introspect/to-manifest.js +154 -28
- package/dist/introspect/to-manifest.js.map +1 -1
- package/dist/migrate/dev-lock.d.ts +15 -0
- package/dist/migrate/dev-lock.d.ts.map +1 -0
- package/dist/migrate/dev-lock.js +101 -0
- package/dist/migrate/dev-lock.js.map +1 -0
- package/dist/migrate/runner.d.ts.map +1 -1
- package/dist/migrate/runner.js +143 -51
- package/dist/migrate/runner.js.map +1 -1
- package/dist/plugins/builtin.d.ts +62 -12
- package/dist/plugins/builtin.d.ts.map +1 -1
- package/dist/plugins/builtin.js +378 -30
- package/dist/plugins/builtin.js.map +1 -1
- package/dist/plugins/index.d.ts +1 -0
- package/dist/plugins/index.d.ts.map +1 -1
- package/dist/plugins/index.js.map +1 -1
- package/dist/plugins/json/operators.d.ts.map +1 -1
- package/dist/plugins/json/operators.js +69 -3
- package/dist/plugins/json/operators.js.map +1 -1
- package/dist/plugins/postgis/columns.d.ts.map +1 -1
- package/dist/plugins/postgis/columns.js +52 -0
- package/dist/plugins/postgis/columns.js.map +1 -1
- package/dist/plugins/postgis/columns.test.js +13 -0
- package/dist/plugins/postgis/columns.test.js.map +1 -1
- package/dist/plugins/postgis/operators.d.ts.map +1 -1
- package/dist/plugins/postgis/operators.js +15 -3
- package/dist/plugins/postgis/operators.js.map +1 -1
- package/dist/plugins/types.d.ts +3 -0
- package/dist/plugins/types.d.ts.map +1 -1
- package/dist/runtime/client.d.ts +31 -4
- package/dist/runtime/client.d.ts.map +1 -1
- package/dist/runtime/client.js +131 -6
- package/dist/runtime/client.js.map +1 -1
- package/dist/runtime/driver.d.ts +2 -0
- package/dist/runtime/driver.d.ts.map +1 -1
- package/dist/runtime/driver.js +12 -0
- package/dist/runtime/driver.js.map +1 -1
- package/dist/runtime/error-hints.d.ts.map +1 -1
- package/dist/runtime/error-hints.js +37 -10
- package/dist/runtime/error-hints.js.map +1 -1
- package/dist/runtime/executor.d.ts +1 -0
- package/dist/runtime/executor.d.ts.map +1 -1
- package/dist/runtime/executor.js +7 -1
- package/dist/runtime/executor.js.map +1 -1
- package/dist/runtime/mariadb-driver.d.ts +25 -0
- package/dist/runtime/mariadb-driver.d.ts.map +1 -0
- package/dist/runtime/mariadb-driver.js +103 -0
- package/dist/runtime/mariadb-driver.js.map +1 -0
- package/dist/runtime/mysql-driver.d.ts +25 -0
- package/dist/runtime/mysql-driver.d.ts.map +1 -0
- package/dist/runtime/mysql-driver.js +101 -0
- package/dist/runtime/mysql-driver.js.map +1 -0
- package/dist/runtime/mysql-error.d.ts +15 -0
- package/dist/runtime/mysql-error.d.ts.map +1 -0
- package/dist/runtime/mysql-error.js +119 -0
- package/dist/runtime/mysql-error.js.map +1 -0
- package/dist/runtime/mysql-family-pool.d.ts +50 -0
- package/dist/runtime/mysql-family-pool.d.ts.map +1 -0
- package/dist/runtime/mysql-family-pool.js +52 -0
- package/dist/runtime/mysql-family-pool.js.map +1 -0
- package/dist/runtime/mysql-family-pool.test.d.ts +2 -0
- package/dist/runtime/mysql-family-pool.test.d.ts.map +1 -0
- package/dist/runtime/mysql-family-pool.test.js +169 -0
- package/dist/runtime/mysql-family-pool.test.js.map +1 -0
- package/dist/runtime/mysql-placeholders.d.ts +18 -0
- package/dist/runtime/mysql-placeholders.d.ts.map +1 -0
- package/dist/runtime/mysql-placeholders.js +133 -0
- package/dist/runtime/mysql-placeholders.js.map +1 -0
- package/dist/runtime/mysql-placeholders.test.d.ts +2 -0
- package/dist/runtime/mysql-placeholders.test.d.ts.map +1 -0
- package/dist/runtime/mysql-placeholders.test.js +33 -0
- package/dist/runtime/mysql-placeholders.test.js.map +1 -0
- package/dist/runtime/query/aggregate.d.ts.map +1 -1
- package/dist/runtime/query/aggregate.js +3 -3
- package/dist/runtime/query/aggregate.js.map +1 -1
- package/dist/runtime/query/compile-aggregate.d.ts +1 -1
- package/dist/runtime/query/compile-aggregate.d.ts.map +1 -1
- package/dist/runtime/query/compile-aggregate.js +33 -28
- package/dist/runtime/query/compile-aggregate.js.map +1 -1
- package/dist/runtime/query/compile-where.d.ts +9 -9
- package/dist/runtime/query/compile-where.d.ts.map +1 -1
- package/dist/runtime/query/compile-where.js +108 -76
- package/dist/runtime/query/compile-where.js.map +1 -1
- package/dist/runtime/query/compile-write.d.ts +9 -9
- package/dist/runtime/query/compile-write.d.ts.map +1 -1
- package/dist/runtime/query/compile-write.js +77 -63
- package/dist/runtime/query/compile-write.js.map +1 -1
- package/dist/runtime/query/count.d.ts.map +1 -1
- package/dist/runtime/query/count.js +4 -1
- package/dist/runtime/query/count.js.map +1 -1
- package/dist/runtime/query/create.d.ts.map +1 -1
- package/dist/runtime/query/create.js +38 -12
- package/dist/runtime/query/create.js.map +1 -1
- package/dist/runtime/query/cursor.d.ts +1 -1
- package/dist/runtime/query/cursor.d.ts.map +1 -1
- package/dist/runtime/query/cursor.js +6 -7
- package/dist/runtime/query/cursor.js.map +1 -1
- package/dist/runtime/query/delete.d.ts.map +1 -1
- package/dist/runtime/query/delete.js +41 -15
- package/dist/runtime/query/delete.js.map +1 -1
- package/dist/runtime/query/execute.d.ts +1 -0
- package/dist/runtime/query/execute.d.ts.map +1 -1
- package/dist/runtime/query/execute.js +23 -8
- package/dist/runtime/query/execute.js.map +1 -1
- package/dist/runtime/query/find-or-create.d.ts +1 -1
- package/dist/runtime/query/find-or-create.d.ts.map +1 -1
- package/dist/runtime/query/find-or-create.js +16 -8
- package/dist/runtime/query/find-or-create.js.map +1 -1
- package/dist/runtime/query/find.d.ts +2 -0
- package/dist/runtime/query/find.d.ts.map +1 -1
- package/dist/runtime/query/find.js +116 -68
- package/dist/runtime/query/find.js.map +1 -1
- package/dist/runtime/query/group-by.d.ts.map +1 -1
- package/dist/runtime/query/group-by.js.map +1 -1
- package/dist/runtime/query/mutation-returning.d.ts +10 -0
- package/dist/runtime/query/mutation-returning.d.ts.map +1 -0
- package/dist/runtime/query/mutation-returning.js +85 -0
- package/dist/runtime/query/mutation-returning.js.map +1 -0
- package/dist/runtime/query/paginate.d.ts.map +1 -1
- package/dist/runtime/query/paginate.js +3 -3
- package/dist/runtime/query/paginate.js.map +1 -1
- package/dist/runtime/query/projection.d.ts.map +1 -1
- package/dist/runtime/query/projection.js +1 -1
- package/dist/runtime/query/projection.js.map +1 -1
- package/dist/runtime/query/relation-join.d.ts +5 -0
- package/dist/runtime/query/relation-join.d.ts.map +1 -0
- package/dist/runtime/query/relation-join.js +26 -0
- package/dist/runtime/query/relation-join.js.map +1 -0
- package/dist/runtime/query/relation-planner.d.ts +1 -1
- package/dist/runtime/query/relation-planner.d.ts.map +1 -1
- package/dist/runtime/query/relation-planner.js +72 -58
- package/dist/runtime/query/relation-planner.js.map +1 -1
- package/dist/runtime/query/relation-writes.d.ts +2 -2
- package/dist/runtime/query/relation-writes.d.ts.map +1 -1
- package/dist/runtime/query/relation-writes.js +272 -116
- package/dist/runtime/query/relation-writes.js.map +1 -1
- package/dist/runtime/query/table-index.d.ts +1 -1
- package/dist/runtime/query/table-index.d.ts.map +1 -1
- package/dist/runtime/query/table-index.js +17 -10
- package/dist/runtime/query/table-index.js.map +1 -1
- package/dist/runtime/query/unique.d.ts +3 -0
- package/dist/runtime/query/unique.d.ts.map +1 -1
- package/dist/runtime/query/unique.js +26 -6
- package/dist/runtime/query/unique.js.map +1 -1
- package/dist/runtime/query/unique.test.js +110 -7
- package/dist/runtime/query/unique.test.js.map +1 -1
- package/dist/runtime/query/update.d.ts.map +1 -1
- package/dist/runtime/query/update.js +89 -23
- package/dist/runtime/query/update.js.map +1 -1
- package/dist/runtime/query/updated-at.js +2 -2
- package/dist/runtime/query/updated-at.js.map +1 -1
- package/dist/runtime/query/upsert.d.ts.map +1 -1
- package/dist/runtime/query/upsert.js +29 -5
- package/dist/runtime/query/upsert.js.map +1 -1
- package/dist/runtime/sqlite-open.js +1 -1
- package/dist/runtime/sqlite-open.js.map +1 -1
- package/dist/runtime/transaction.d.ts +1 -0
- package/dist/runtime/transaction.d.ts.map +1 -1
- package/dist/runtime/transaction.js +8 -0
- package/dist/runtime/transaction.js.map +1 -1
- package/dist/runtime/types.d.ts +10 -2
- package/dist/runtime/types.d.ts.map +1 -1
- package/dist/schema/column-constraints.d.ts +26 -0
- package/dist/schema/column-constraints.d.ts.map +1 -0
- package/dist/schema/column-constraints.js +143 -0
- package/dist/schema/column-constraints.js.map +1 -0
- package/dist/schema/column.d.ts +92 -3
- package/dist/schema/column.d.ts.map +1 -1
- package/dist/schema/column.js +17 -11
- package/dist/schema/column.js.map +1 -1
- package/dist/schema/index.d.ts +9 -6
- package/dist/schema/index.d.ts.map +1 -1
- package/dist/schema/index.js +8 -2
- package/dist/schema/index.js.map +1 -1
- package/dist/schema/json-column.d.ts +9 -0
- package/dist/schema/json-column.d.ts.map +1 -0
- package/dist/schema/json-column.js +11 -0
- package/dist/schema/json-column.js.map +1 -0
- package/dist/schema/many-to-many.d.ts +1 -1
- package/dist/schema/many-to-many.d.ts.map +1 -1
- package/dist/schema/nested-relation-types.d.ts +10 -9
- package/dist/schema/nested-relation-types.d.ts.map +1 -1
- package/dist/schema/relation-types.d.ts +80 -19
- package/dist/schema/relation-types.d.ts.map +1 -1
- package/dist/schema/relation.d.ts +11 -1
- package/dist/schema/relation.d.ts.map +1 -1
- package/dist/schema/relation.js +6 -0
- package/dist/schema/relation.js.map +1 -1
- package/dist/schema/table.d.ts +74 -11
- package/dist/schema/table.d.ts.map +1 -1
- package/dist/schema/table.js +93 -7
- package/dist/schema/table.js.map +1 -1
- package/dist/schema/types.d.ts +7 -6
- package/dist/schema/types.d.ts.map +1 -1
- package/dist/sql/builder.d.ts +13 -3
- package/dist/sql/builder.d.ts.map +1 -1
- package/dist/sql/builder.js +155 -5
- package/dist/sql/builder.js.map +1 -1
- package/dist/sql/index.d.ts +1 -0
- package/dist/sql/index.d.ts.map +1 -1
- package/dist/sql/index.js.map +1 -1
- package/dist/sql/qualify-tables.d.ts +15 -0
- package/dist/sql/qualify-tables.d.ts.map +1 -0
- package/dist/sql/qualify-tables.js +206 -0
- package/dist/sql/qualify-tables.js.map +1 -0
- package/dist/sql/template.d.ts +5 -0
- package/dist/sql/template.d.ts.map +1 -1
- package/dist/sql/template.js +23 -3
- package/dist/sql/template.js.map +1 -1
- package/dist/utils/load-ts.d.ts +2 -1
- package/dist/utils/load-ts.d.ts.map +1 -1
- package/dist/utils/load-ts.js +13 -1
- package/dist/utils/load-ts.js.map +1 -1
- package/docs/cli.md +4 -4
- package/docs/configuration.md +12 -5
- package/docs/elysia.md +131 -0
- package/docs/errors.md +1 -1
- package/docs/examples.md +4 -2
- package/docs/getting-started.md +28 -2
- package/docs/mariadb.md +149 -0
- package/docs/migrations.md +5 -3
- package/docs/mysql.md +134 -0
- package/docs/plugins.md +2 -0
- package/docs/queries.md +14 -11
- package/docs/relations.md +19 -4
- package/docs/schema.md +100 -9
- package/docs/sqlite.md +14 -2
- package/docs/typebox.md +139 -0
- package/docs/zod.md +128 -0
- package/package.json +33 -3
package/docs/queries.md
CHANGED
|
@@ -30,7 +30,7 @@ await db.users.findFirst({
|
|
|
30
30
|
const user = await db.users.findUnique({ where: { slug: "hello" } });
|
|
31
31
|
```
|
|
32
32
|
|
|
33
|
-
Unique `where` (`findUnique`, `upsert`, `findOrCreate`, singular `update`/`delete`) is scalar equality per unique field — `{ email: "a@b.com" }`, not a filter. `{ equals: value }` is accepted and unwrapped to the scalar. Other operators (`contains`, `in`, `mode: "insensitive"`) throw `unique_where_invalid`.
|
|
33
|
+
Unique `where` (`findUnique`, `upsert`, `findOrCreate`, singular `update`/`delete`) is scalar equality per unique field — `{ email: "a@b.com" }`, not a filter. `{ equals: value }` is accepted and unwrapped to the scalar. Other operators (`contains`, `in`, `mode: "insensitive"`) throw `unique_where_invalid`. Partial unique indexes are valid targets: pass the indexed columns; the index `WHERE` is applied automatically so the lookup stays unique.
|
|
34
34
|
|
|
35
35
|
## Create
|
|
36
36
|
|
|
@@ -73,7 +73,7 @@ Unknown keys in `data` fail at compile time (`unknown_column`), same as `where`.
|
|
|
73
73
|
|
|
74
74
|
## Update
|
|
75
75
|
|
|
76
|
-
Singular `update` requires a unique `where` (primary key, `@unique` column, or
|
|
76
|
+
Singular `update` requires a unique `where` (primary key, `@unique` column, composite unique index, or partial unique index), same as `findUnique`. Use `updateMany` when the filter can match multiple rows.
|
|
77
77
|
|
|
78
78
|
```ts
|
|
79
79
|
// Returns {} on success, null if no row matched
|
|
@@ -181,7 +181,7 @@ Atomically insert a row or return an existing one when a unique constraint match
|
|
|
181
181
|
|
|
182
182
|
Postgres issues `INSERT … ON CONFLICT DO UPDATE` with a no-op assignment so `RETURNING` always includes the row — including when a concurrent session already inserted it. `created` is `(xmax = 0)` (inserted vs existing). A no-op update can fire `UPDATE` triggers; column values are not changed.
|
|
183
183
|
|
|
184
|
-
Under `SERIALIZABLE` (and sometimes `REPEATABLE READ`), a concurrent insert can still abort the transaction with a serialization failure. Retry the whole `$transaction`. SQLite
|
|
184
|
+
Under `SERIALIZABLE` (and sometimes `REPEATABLE READ`), a concurrent insert can still abort the transaction with a serialization failure. Retry the whole `$transaction`. SQLite, MySQL, and MariaDB use find-then-insert and retry the find after a unique violation.
|
|
185
185
|
|
|
186
186
|
```ts
|
|
187
187
|
const { record, created } = await db.tags.findOrCreate({
|
|
@@ -219,11 +219,11 @@ Unknown column names and operators fail at compile time (`unknown_column`, with
|
|
|
219
219
|
| PostGIS (`geometry`, `geography`, `point`) | `intersects`, `within`, `dWithin` |
|
|
220
220
|
| All nullable | `isNull`, `isNotNull` |
|
|
221
221
|
|
|
222
|
-
`contains`, `startsWith`, `endsWith`, and `equals` compile to `LIKE` / `=`. Pass sibling `mode: "insensitive"` for case-folding (`ILIKE` on Postgres, `LOWER(col) LIKE LOWER($n)` on SQLite). SQLite `LIKE` is ASCII case-insensitive even in default mode.
|
|
222
|
+
`contains`, `startsWith`, `endsWith`, and `equals` compile to `LIKE` / `=`. Pass sibling `mode: "insensitive"` for case-folding (`ILIKE` on Postgres, `LOWER(col) LIKE LOWER($n)` on SQLite/MySQL/MariaDB). SQLite `LIKE` is ASCII case-insensitive even in default mode. `%` and `_` in the search string are matched literally (`ESCAPE '\'` on Postgres/SQLite, `ESCAPE '\\'` on MySQL/MariaDB).
|
|
223
223
|
|
|
224
|
-
`search` is POSIX regex (`~`, or `~*` with `mode: "insensitive"`)
|
|
224
|
+
`search` is POSIX regex on PostgreSQL (`~`, or `~*` with `mode: "insensitive"`), `REGEXP_LIKE` on MySQL 8, `REGEXP` on MariaDB, and JavaScript `RegExp` (`REGEXP` / `regexp_i`) on SQLite.
|
|
225
225
|
|
|
226
|
-
JSON operators on PostgreSQL use `@>`, `?`, and `#>` / `#>>`. On SQLite they compile to `json_patch` (object containment), `json_each` (key existence), and `json_extract` (path).
|
|
226
|
+
JSON operators on PostgreSQL use `@>`, `?`, and `#>` / `#>>`. On SQLite they compile to `json_patch` (object containment), `json_each` (key existence), and `json_extract` (path). On MySQL and MariaDB they compile to `JSON_CONTAINS`, `JSON_CONTAINS_PATH`, and `JSON_EXTRACT`.
|
|
227
227
|
|
|
228
228
|
```ts
|
|
229
229
|
await db.posts.findMany({
|
|
@@ -321,7 +321,7 @@ await db.users.findMany({
|
|
|
321
321
|
});
|
|
322
322
|
```
|
|
323
323
|
|
|
324
|
-
Not supported on
|
|
324
|
+
Not supported on MySQL or MariaDB (`DISTINCT ON` is PostgreSQL-only; SQLite emulates it with `ROW_NUMBER()`). Use `groupBy({ by: [...] })` or a raw `db.sql` query instead.
|
|
325
325
|
|
|
326
326
|
## Eager loading with `with`
|
|
327
327
|
|
|
@@ -524,7 +524,7 @@ const byAuthor = await db.posts.groupBy({
|
|
|
524
524
|
|
|
525
525
|
Star `_count: true` still works with `having: { _count: { gte: 5 } }` and `orderBy: { _count: "desc" }`.
|
|
526
526
|
|
|
527
|
-
`where` filters rows before grouping. `having` filters groups (aggregates only). `by` alone lists distinct groups — useful
|
|
527
|
+
`where` filters rows before grouping. `having` filters groups (aggregates only). `by` alone lists distinct groups — useful when you need grouped aggregates rather than `findMany({ distinct })`.
|
|
528
528
|
|
|
529
529
|
## Count
|
|
530
530
|
|
|
@@ -545,7 +545,7 @@ const taken = await db.users.exists({ where: { email: "a@b.com" } });
|
|
|
545
545
|
|
|
546
546
|
## Raw SQL
|
|
547
547
|
|
|
548
|
-
`db.sql` and `import { sql } from "neoorm/sql"` share one compiler. Interpolate values, `sqlId("table")`, nested `sql\`...\`` fragments, or `sqlBuilder.compile()`. `sqlBuilder`
|
|
548
|
+
`db.sql` and `import { sql } from "neoorm/sql"` share one compiler. Interpolate values, `sqlId("table")`, nested `sql\`...\`` fragments, or `sqlBuilder.compile()`. `sqlBuilder` covers select/join/where/group/order/limit with bound params; use `db.sql` for HAVING and other raw tails.
|
|
549
549
|
|
|
550
550
|
```ts
|
|
551
551
|
import { sql, sqlBuilder, sqlId } from "neoorm/sql";
|
|
@@ -557,12 +557,15 @@ const rows = await db.sql`SELECT * FROM ${ident} WHERE ${filter}`;
|
|
|
557
557
|
const grouped = sqlBuilder
|
|
558
558
|
.selectFrom("users")
|
|
559
559
|
.select(["id", "email"])
|
|
560
|
+
.where("email", "=", "a@b.com")
|
|
561
|
+
.orWhere(sql`role = ${"admin"}`)
|
|
560
562
|
.groupBy("id", "email")
|
|
563
|
+
.limit(10)
|
|
561
564
|
.compile();
|
|
562
|
-
await db.sql`${grouped}
|
|
565
|
+
await db.sql`${grouped} HAVING count(*) > ${0}`;
|
|
563
566
|
```
|
|
564
567
|
|
|
565
|
-
`db.execute({ text, params })` runs already-compiled SQL.
|
|
568
|
+
`db.execute({ text, params })` runs already-compiled SQL. With PostgreSQL tenant `schema`, `db.sql` / `db.execute` qualify unqualified manifest table names (same as `db.sqlId("users")`). Already-qualified `schema.table` identifiers are unchanged.
|
|
566
569
|
|
|
567
570
|
## Logging SQL
|
|
568
571
|
|
package/docs/relations.md
CHANGED
|
@@ -6,13 +6,13 @@
|
|
|
6
6
|
|
|
7
7
|
| Relation kind | Operations |
|
|
8
8
|
|---------------|------------|
|
|
9
|
-
| **To-one** (outgoing FK) | `connect`, `create`, `disconnect` (nullable FK only) |
|
|
9
|
+
| **To-one** (outgoing FK) | `connect`, `connectOrCreate`, `create`, `disconnect` (nullable FK only) |
|
|
10
10
|
| **One-to-many** (inverse) | `create`, `connect`, `disconnect`, `set`, `delete` |
|
|
11
11
|
| **Many-to-many** | `connect`, `connectOrCreate`, `disconnect`, `set`, `delete` |
|
|
12
12
|
|
|
13
|
-
`connect`, `set`, `disconnect`, and `delete` identify related rows by the **target table's
|
|
13
|
+
`connect`, `set`, `disconnect`, and `delete` identify related rows by the **target table's primary key**: `{ id: ... }` or `{ userId: ... }` for a scalar PK, or an object with **every** PK column for a composite key. A single-column `fk()` still stores one referenced value; a composite `foreignKey()` extra assigns **each** local column from the matching target field (never a concatenated PK string).
|
|
14
14
|
|
|
15
|
-
A relation field must be a **pure write bag**: every key is one of those operations. Mixing a write op with extra fields (`{ create: { name: "A" }, foo: 1 }`), passing a scalar, or passing `{}` throws `invalid_nested_write` — the parent row is not written.
|
|
15
|
+
A relation field must be a **pure write bag**: every key is one of those operations. Mixing a write op with extra fields (`{ create: { name: "A" }, foo: 1 }`), passing a scalar, or passing `{}` throws `invalid_nested_write` — the parent row is not written. To-one `connectOrCreate` cannot be mixed with `connect` or `create`.
|
|
16
16
|
|
|
17
17
|
## Examples
|
|
18
18
|
|
|
@@ -21,7 +21,22 @@ A relation field must be a **pure write bag**: every key is one of those operati
|
|
|
21
21
|
```ts
|
|
22
22
|
await db.posts.update({
|
|
23
23
|
where: { id: postId },
|
|
24
|
-
data: {
|
|
24
|
+
data: {
|
|
25
|
+
author: { connect: { id: userId } },
|
|
26
|
+
// or create the author if missing:
|
|
27
|
+
// author: { connectOrCreate: { where: { email }, create: { email, password } } },
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Composite primary key:
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
await db.orders.update({
|
|
36
|
+
where: { id: orderId },
|
|
37
|
+
data: {
|
|
38
|
+
lines: { connect: [{ tenantId: "acme", lineNo: 1 }] },
|
|
39
|
+
},
|
|
25
40
|
});
|
|
26
41
|
```
|
|
27
42
|
|
package/docs/schema.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Schema DSL
|
|
2
2
|
|
|
3
|
-
NeoOrm **0.8.0** uses accessor-based identity: schema keys, `fk("users")`, `many("tags")`, and `through: "postTags"` all refer to **accessors**, not raw SQL names. `schemaToManifest` resolves accessors to SQL before migrations and the typed client are generated.
|
|
3
|
+
NeoOrm **0.8.0** and later uses accessor-based identity: schema keys, `fk("users")`, `many("tags")`, and `through: "postTags"` all refer to **accessors**, not raw SQL names. `schemaToManifest` resolves accessors to SQL before migrations and the typed client are generated.
|
|
4
4
|
|
|
5
5
|
## Tables and accessors
|
|
6
6
|
|
|
@@ -48,10 +48,21 @@ Column field names use camelCase in TypeScript. By default SQL column names are
|
|
|
48
48
|
| `decimal()` / `numeric()` | `NUMERIC` | `string \| null` | Use strings to avoid float loss |
|
|
49
49
|
| `enumType(["a", "b"])` | mode-dependent | union literals | See [Enum columns](#enum-columns) |
|
|
50
50
|
| `bytea()` | `BYTEA` | `Buffer \| null` | |
|
|
51
|
-
| `textArray()` / `intArray()` | arrays | arrays \| null | |
|
|
51
|
+
| `textArray()` / `intArray()` | arrays | arrays \| null | JSON on SQLite/MySQL |
|
|
52
|
+
| `uuidArray()` | `UUID[]` | `string[] \| null` | JSON on SQLite/MySQL |
|
|
53
|
+
| `enumArray(["a", "b"])` | `TEXT[]` or `name[]` | union arrays \| null | Same enum storage mode as `enumType`; JSON on SQLite/MySQL |
|
|
54
|
+
| `real()` / `float()` | `REAL` | `number \| null` | 32-bit. Prisma `Float` is `double()`. MySQL `FLOAT`, SQLite `REAL` |
|
|
55
|
+
| `double()` | `DOUBLE PRECISION` | `number \| null` | MySQL `DOUBLE`, SQLite `REAL` |
|
|
56
|
+
| `date()` | `DATE` | `string \| null` | `YYYY-MM-DD`, not `Date`. SQLite `TEXT`, MySQL `DATE` |
|
|
57
|
+
| `time()` | `TIME` | `string \| null` | SQLite `TEXT`, MySQL `TIME` |
|
|
58
|
+
| `interval()` | `INTERVAL` | `string \| null` | SQLite `TEXT`; rejected on MySQL/MariaDB |
|
|
59
|
+
| `inet()` / `cidr()` | `INET` / `CIDR` | `string \| null` | SQLite `TEXT`; rejected on MySQL/MariaDB |
|
|
60
|
+
| `xml()` | `XML` | `string \| null` | MySQL `LONGTEXT`, SQLite `TEXT` |
|
|
61
|
+
| `money()` | `MONEY` | `string \| null` | MySQL `DECIMAL(19,4)`, SQLite `TEXT` |
|
|
62
|
+
| `int4Range()` / `int8Range()` / `numRange()` / `tsRange()` / `tstzRange()` / `dateRange()` | matching PG ranges | `string \| null` | PostgreSQL only (rejected on SQLite and MySQL/MariaDB) |
|
|
52
63
|
| `citext()` | `CITEXT` | `string \| null` | Requires `citext` extension |
|
|
53
64
|
|
|
54
|
-
All column builders support `.notNull()`, `.unique()`, `.default(value)`, `.primary()`, `.map(name)`, `.hidden()`, `.index()`, and `.check("sql expression")`.
|
|
65
|
+
All column builders support `.notNull()`, `.unique()`, `.default(value)`, `.primary()`, `.map(name)`, `.hidden()`, `.index()`, and `.check("sql expression")`. Text columns add `.maxLength()`, `.minLength()`, `.notEmpty()`, `.email()`, and `.url()`. Numeric columns (`int`, `bigint`, `serial`, `decimal`, `real`, `double`) add `.min()`, `.max()`, and `.positive()`.
|
|
55
66
|
|
|
56
67
|
`.hidden()` marks a column as sensitive. It is omitted from default query output on the root table and on nested `with` includes. Pass `includeHidden: true` when the app needs the value (for example password verification on login). Use `.strip()` to remove any remaining sensitive fields before JSON responses.
|
|
57
68
|
|
|
@@ -100,7 +111,9 @@ Fluent modifiers (no options bag):
|
|
|
100
111
|
|--------|--------|
|
|
101
112
|
| `.as("author")` | override relation name on this table (default: strip `Id` from column name) |
|
|
102
113
|
| `.inverse("articles")` | override relation name on the target table (rare — see defaults below) |
|
|
103
|
-
| `.onDelete("cascade" \| "restrict" \| "set null" \| "no action")` | FK
|
|
114
|
+
| `.onDelete("cascade" \| "restrict" \| "set null" \| "no action")` | FK `ON DELETE` |
|
|
115
|
+
| `.onUpdate("cascade" \| "restrict" \| "set null" \| "no action")` | FK `ON UPDATE` |
|
|
116
|
+
| `.deferrable("immediate" \| "deferred")` | `DEFERRABLE` (Postgres and SQLite; rejected on MySQL/MariaDB) |
|
|
104
117
|
| `.notNull()` | `NOT NULL` |
|
|
105
118
|
| `.unique()` | `UNIQUE` — unique FKs infer a **singular** inverse (`profiles.userId` → `users.profile`) |
|
|
106
119
|
| `.primary()` | part of composite PK |
|
|
@@ -123,6 +136,39 @@ postTags: table("post_tags", {
|
|
|
123
136
|
|
|
124
137
|
Multiple `.primary()` columns infer a composite key. `primaryKey(t.colA, t.colB)` in extras is optional when you want to name the key without marking every column.
|
|
125
138
|
|
|
139
|
+
### Composite foreign keys
|
|
140
|
+
|
|
141
|
+
Use table extras `foreignKey()` when several local columns form one constraint (and one relation). Do not also mark those columns `fk()`.
|
|
142
|
+
|
|
143
|
+
```ts
|
|
144
|
+
orders: table(
|
|
145
|
+
{
|
|
146
|
+
id: id(),
|
|
147
|
+
tenantId: text().notNull(),
|
|
148
|
+
userId: text().notNull(),
|
|
149
|
+
},
|
|
150
|
+
(t) => [
|
|
151
|
+
foreignKey(t.tenantId, t.userId)
|
|
152
|
+
.references("users", "tenantId", "id")
|
|
153
|
+
.as("user")
|
|
154
|
+
.inverse("orders")
|
|
155
|
+
.onDelete("cascade")
|
|
156
|
+
.onUpdate("cascade"),
|
|
157
|
+
],
|
|
158
|
+
),
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
The target column list must match a primary key or unique index. `.as()` and `.inverse()` are required. Connect still uses the target table's full primary key:
|
|
162
|
+
|
|
163
|
+
```ts
|
|
164
|
+
await db.orders.create({
|
|
165
|
+
data: {
|
|
166
|
+
tenantId: "acme",
|
|
167
|
+
user: { connect: { tenantId: "acme", id: userId } },
|
|
168
|
+
},
|
|
169
|
+
});
|
|
170
|
+
```
|
|
171
|
+
|
|
126
172
|
### Self-relations
|
|
127
173
|
|
|
128
174
|
```ts
|
|
@@ -148,7 +194,35 @@ export const schema = defineSchema({
|
|
|
148
194
|
|
|
149
195
|
## Column checks
|
|
150
196
|
|
|
151
|
-
`.check("
|
|
197
|
+
Use `.check("sql expression")` for custom CHECK constraints, or the typed helpers below. Helpers compile to database CHECK constraints (and `VARCHAR(n)` for text length on Postgres). They are not runtime validators by themselves. With [`generate.zod`](zod.md), [`generate.typebox`](typebox.md), or [`generate.elysia`](elysia.md), the same helpers are copied into generated validation schemas at your API boundary.
|
|
198
|
+
|
|
199
|
+
### Typed constraint helpers
|
|
200
|
+
|
|
201
|
+
| Method | Column types | Postgres | SQLite |
|
|
202
|
+
|--------|--------------|----------|--------|
|
|
203
|
+
| `.maxLength(n)` / `text({ maxLength: n })` | `text` | `VARCHAR(n)` | `TEXT` + `CHECK (length(col) <= n)` |
|
|
204
|
+
| `.minLength(n)` | `text`, `citext` | CHECK | CHECK |
|
|
205
|
+
| `.notEmpty()` | `text`, `citext` | CHECK | CHECK |
|
|
206
|
+
| `.email()` | `text`, `citext` | — | — |
|
|
207
|
+
| `.url()` | `text`, `citext` | — | — |
|
|
208
|
+
| `.min(n)` / `.max(n)` | `int`, `bigint`, `serial`, `decimal` | CHECK | CHECK |
|
|
209
|
+
| `.positive()` | same numeric types | CHECK | CHECK |
|
|
210
|
+
|
|
211
|
+
```ts
|
|
212
|
+
email: text({ maxLength: 255 }).notNull().unique()
|
|
213
|
+
title: text().notNull().minLength(1).maxLength(200)
|
|
214
|
+
bio: text().notEmpty()
|
|
215
|
+
handle: text().notNull().email()
|
|
216
|
+
website: text().notNull().url()
|
|
217
|
+
views: int().notNull().min(0)
|
|
218
|
+
price: decimal({ precision: 10, scale: 2 }).notNull().positive()
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
`.email()` and `.url()` are validation-only: they do not emit a SQL CHECK. With [`generate.zod`](zod.md), [`generate.typebox`](typebox.md), or [`generate.elysia`](elysia.md), columns named `email` or ending in `Email` (`contactEmail`) become email-format strings, and columns named `url` or ending in `Url` (`avatarUrl`) become URL-format strings. You can also mark any `text` / `citext` column with `.email()` or `.url()`. Foreign keys follow the **referenced** column (`authorEmail` pointing at a uuid PK stays a uuid).
|
|
222
|
+
|
|
223
|
+
`citext` length helpers use CHECK only (no `VARCHAR`). NULL still bypasses CHECK — pair `.notEmpty()` with `.notNull()` to reject empty strings.
|
|
224
|
+
|
|
225
|
+
Custom `.check()` expressions are combined with helpers and enum `check` mode using AND. Prefer helpers over raw SQL so constraints use the mapped SQL column name after `.map()`.
|
|
152
226
|
|
|
153
227
|
```ts
|
|
154
228
|
price: decimal({ precision: 10, scale: 2 }).notNull().check("price >= 0"),
|
|
@@ -172,7 +246,24 @@ posts: table(
|
|
|
172
246
|
),
|
|
173
247
|
```
|
|
174
248
|
|
|
175
|
-
Helpers: `unique(...cols)`, `index(...cols)`, `primaryKey(...cols)`. `unique()` and `index()`
|
|
249
|
+
Helpers: `unique(...cols)`, `index(...cols)`, `primaryKey(...cols)`, `expr("sql")`. `unique()` and `index()` support `.using()`, `.ops()`, and `.where()` for partial indexes.
|
|
250
|
+
|
|
251
|
+
### Index methods and expressions
|
|
252
|
+
|
|
253
|
+
```ts
|
|
254
|
+
(t) => [
|
|
255
|
+
index(t.tags).using("gin"),
|
|
256
|
+
index(t.metadata).using("gin").ops("jsonb_path_ops"),
|
|
257
|
+
index(t.location).using("gist"),
|
|
258
|
+
index(t.createdAt).using("brin"),
|
|
259
|
+
unique(expr("lower(email)")),
|
|
260
|
+
index(t.authorId, expr("date_trunc('day', created_at)")),
|
|
261
|
+
],
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
Default method is btree (`USING btree` is omitted in SQL). Postgres also emits `gin`, `gist`, `brin`, and `hash`. SQLite allows expression keys and partial `WHERE`, but not those access methods. MySQL/MariaDB allow `USING HASH` and functional `(expr)` keys; they reject GIN/GiST/BRIN and partial `WHERE`.
|
|
265
|
+
|
|
266
|
+
`unique()` cannot use `gin` / `gist` / `brin`. Expression unique indexes are not `findUnique` / `upsert` targets.
|
|
176
267
|
|
|
177
268
|
### Partial indexes
|
|
178
269
|
|
|
@@ -183,7 +274,7 @@ Helpers: `unique(...cols)`, `index(...cols)`, `primaryKey(...cols)`. `unique()`
|
|
|
183
274
|
],
|
|
184
275
|
```
|
|
185
276
|
|
|
186
|
-
Equality map of column refs → values; compiled to `WHERE "published" = true` (or `= 1` on SQLite). Partial uniques emit as `CREATE UNIQUE INDEX ... WHERE ...`, not table-level `UNIQUE (...)`. They are
|
|
277
|
+
Equality map of column refs → values; compiled to `WHERE "published" = true` (or `= 1` on SQLite). Partial uniques emit as `CREATE UNIQUE INDEX ... WHERE ...`, not table-level `UNIQUE (...)`. They are valid `findUnique` / `upsert` / `findOrCreate` targets: `where` uses the indexed columns, and the index predicate is applied automatically (`AND` on lookups, `ON CONFLICT (…) WHERE …` on Postgres and SQLite).
|
|
187
278
|
|
|
188
279
|
## Many-to-many
|
|
189
280
|
|
|
@@ -254,7 +345,7 @@ status: enumType(["draft", "published", "archived"])
|
|
|
254
345
|
.default("draft"),
|
|
255
346
|
```
|
|
256
347
|
|
|
257
|
-
`datasource.enum` in config: `check` (default), `union`, or `native` (PostgreSQL
|
|
348
|
+
`datasource.enum` in config: `check` (default), `union`, or `native` (PostgreSQL `CREATE TYPE` / MySQL and MariaDB column `ENUM`).
|
|
258
349
|
|
|
259
350
|
## PostgreSQL extensions
|
|
260
351
|
|
|
@@ -269,4 +360,4 @@ Column type plugins still register required extensions automatically.
|
|
|
269
360
|
|
|
270
361
|
## SQLite type mapping
|
|
271
362
|
|
|
272
|
-
Same DSL; see [SQLite](sqlite.md) for storage types and limitations.
|
|
363
|
+
Same DSL; see [SQLite](sqlite.md), [MySQL](mysql.md), and [MariaDB](mariadb.md) for storage types and limitations.
|
package/docs/sqlite.md
CHANGED
|
@@ -75,6 +75,14 @@ const db = createNeoOrmClientFromSqlite(manifest, database);
|
|
|
75
75
|
|
|
76
76
|
`$disconnect()` does not close the handle — call `database.close()` yourself. `createNeoOrmClient(manifest, { db: database })` is the same ownership model.
|
|
77
77
|
|
|
78
|
+
`sqliteDialect` is exported from `neoorm` (alongside `postgresDialect`) for `dbPush`, migrate helpers, and custom executor wiring:
|
|
79
|
+
|
|
80
|
+
```ts
|
|
81
|
+
import { dbPush, sqliteDialect } from "neoorm";
|
|
82
|
+
|
|
83
|
+
await dbPush(client, sqliteDialect, manifest);
|
|
84
|
+
```
|
|
85
|
+
|
|
78
86
|
## CLI
|
|
79
87
|
|
|
80
88
|
The full CLI works against SQLite:
|
|
@@ -95,9 +103,11 @@ bunx neoorm migrate reset --force
|
|
|
95
103
|
|
|
96
104
|
| Schema builder | SQLite storage |
|
|
97
105
|
|----------------|----------------|
|
|
98
|
-
| `id`, `text`, `uuid`, `json`, `jsonb`, `decimal`, `textArray`, `intArray`, `citext`, `enumType` | `TEXT` |
|
|
106
|
+
| `id`, `text`, `uuid`, `json`, `jsonb`, `decimal`, `textArray`, `intArray`, `uuidArray`, `enumArray`, `citext`, `enumType`, `date`, `time`, `interval`, `inet`, `cidr`, `xml`, `money` | `TEXT` |
|
|
99
107
|
| `int`, `serial` | `INTEGER` |
|
|
108
|
+
| `real`, `double` | `REAL` |
|
|
100
109
|
| `bigint` | `TEXT` |
|
|
110
|
+
| `int4Range` … `dateRange` | rejected at schema compile |
|
|
101
111
|
| `serial().primary()` | `INTEGER PRIMARY KEY AUTOINCREMENT` |
|
|
102
112
|
| `bool` | `BOOLEAN` (stored as 0/1) |
|
|
103
113
|
| `timestamp` | `TEXT` (ISO-8601) |
|
|
@@ -110,9 +120,11 @@ bunx neoorm migrate reset --force
|
|
|
110
120
|
|
|
111
121
|
| Feature | PostgreSQL | SQLite |
|
|
112
122
|
|---------|-----------|--------|
|
|
113
|
-
| `distinct` (`DISTINCT ON`) | supported |
|
|
123
|
+
| `distinct` (`DISTINCT ON`) | supported | `ROW_NUMBER()` per distinct columns (same `orderBy` prefix rule) |
|
|
124
|
+
| `search` | POSIX `~` / `~*` | JavaScript `RegExp` via `REGEXP` / `regexp_i` |
|
|
114
125
|
| `datasource.schema` | multi-schema | not applicable |
|
|
115
126
|
| `enum: "native"` | `CREATE TYPE ... AS ENUM` | not applicable (TEXT + CHECK) |
|
|
127
|
+
| range types | supported | rejected at schema compile |
|
|
116
128
|
| transaction options (`readOnly`, `isolationLevel`) | `BEGIN READ ONLY` / `ISOLATION LEVEL` | `readOnly` → `PRAGMA query_only`; `RepeatableRead`/`Serializable` → `BEGIN IMMEDIATE`; other isolation → `BEGIN` |
|
|
117
129
|
| JSON operators | `@>`, `?`, `#>` | `json_patch` / `json_each` / `json_extract` |
|
|
118
130
|
|
package/docs/typebox.md
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# TypeBox schemas
|
|
2
|
+
|
|
3
|
+
`neoorm generate` can print Select, Create, and Update **scalar** TypeBox schemas next to the typed client. Codegen maps your schema to a validator-neutral IR first; TypeBox 1.x is one printer (alongside [Zod](zod.md) and [Elysia `t`](elysia.md)).
|
|
4
|
+
|
|
5
|
+
Generated schemas match the TypeScript shapes of row / insert / update scalars — not nested relation writes (`connect`, `create`, `set`). Those stay TypeScript-only.
|
|
6
|
+
|
|
7
|
+
Schemas target **TypeBox 1.x** (`typebox` on npm), not `@sinclair/typebox` 0.x. For Elysia 1.x route `body:`, use [`generate.elysia`](elysia.md) instead.
|
|
8
|
+
|
|
9
|
+
## Enable
|
|
10
|
+
|
|
11
|
+
Install TypeBox 1.x in the app, then opt in:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
bun add typebox
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
// neoorm.config.ts
|
|
19
|
+
import { defineConfig } from "neoorm";
|
|
20
|
+
|
|
21
|
+
export default defineConfig({
|
|
22
|
+
schema: "./schema.ts",
|
|
23
|
+
out: "./neoorm",
|
|
24
|
+
datasource: {
|
|
25
|
+
provider: "postgresql",
|
|
26
|
+
url: process.env.DATABASE_URL!,
|
|
27
|
+
},
|
|
28
|
+
generate: {
|
|
29
|
+
typebox: true,
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Re-run `neoorm generate` (or `neoorm migrate dev`). Output:
|
|
35
|
+
|
|
36
|
+
- `out/typebox.ts` — generated schemas
|
|
37
|
+
- `out/client.ts` re-exports them
|
|
38
|
+
|
|
39
|
+
Disable `generate.typebox` and generate again to remove `typebox.ts`.
|
|
40
|
+
|
|
41
|
+
`typebox` is an optional peer of NeoOrm. The generated file imports `typebox` directly; NeoOrm does not bundle it. If `generate.typebox` is on and `typebox` is not installed, `neoorm generate` still writes `typebox.ts` and prints a warning (`bun add typebox`).
|
|
42
|
+
|
|
43
|
+
You can enable Zod, TypeBox, and Elysia together. When Zod is on, `client.ts` keeps Zod as a flat `export *` and namespaces TypeBox and Elysia (`export * as typebox` / `export * as elysia`). TypeBox + Elysia without Zod keeps TypeBox flat and namespaces Elysia. Import TypeBox from `./typebox.js` (or `typebox.UserCreateSchema` from the client) when it is namespaced. For Elysia 1.x `body:`, use [`generate.elysia`](elysia.md).
|
|
44
|
+
|
|
45
|
+
## Usage
|
|
46
|
+
|
|
47
|
+
```ts
|
|
48
|
+
import Value from "typebox/value";
|
|
49
|
+
import { db, UserCreateSchema, type UserCreate } from "./neoorm/client.js";
|
|
50
|
+
|
|
51
|
+
const data: UserCreate = Value.Decode(UserCreateSchema, body);
|
|
52
|
+
await db.users.create({ data });
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
`Value.Check` returns a boolean and does not run codecs. Use `Value.Decode` when you need timestamp fields as `Date` (the TypeBox equivalent of Zod `parse`). Invalid data throws.
|
|
56
|
+
|
|
57
|
+
Per table (accessor `users` → model `User`):
|
|
58
|
+
|
|
59
|
+
| Export | Shape |
|
|
60
|
+
|--------|--------|
|
|
61
|
+
| `UserSchema` / `UserSelect` | Select row (default query output: no `.hidden()`, includes `timestamps()`) |
|
|
62
|
+
| `UserCreateSchema` / `UserCreate` | Insert scalars (no primary / serial / `timestamps()`; defaults optional; includes `.hidden()`) |
|
|
63
|
+
| `UserUpdateSchema` / `UserUpdate` | Update scalars (no primary / `timestamps()`; all optional; includes `.hidden()`) |
|
|
64
|
+
|
|
65
|
+
`UserSelect` / `UserCreate` / `UserUpdate` are `Type.StaticDecode` aliases. They are named that way so they do not collide with the model type `User` from `models.ts`. Hoisted enums also get a type (`PostStatus` from `PostStatusSchema`).
|
|
66
|
+
|
|
67
|
+
`schemas.users.select` / `.create` / `.update` are the same objects.
|
|
68
|
+
|
|
69
|
+
`.hidden()` columns (for example `password`) are omitted from select schemas so they match default query results. They stay on create and update so login/register and password-change payloads can still be parsed. Use `includeHidden: true` on the query when the app needs those fields internally.
|
|
70
|
+
|
|
71
|
+
`createdAt` and `updatedAt` from `timestamps()` (and any `timestamp().defaultNow()` / `.updatedAt()` column) are ORM-managed. They appear on select schemas only — create and update parsers reject them so API clients cannot stamp those fields. A plain `timestamp()` without `defaultNow` stays on create/update.
|
|
72
|
+
|
|
73
|
+
Create with `author: { connect: { id } }` is not in `PostCreateSchema`. Pass the FK scalar (`authorId`) or keep nested writes in TypeScript.
|
|
74
|
+
|
|
75
|
+
## Junction (M2M) tables
|
|
76
|
+
|
|
77
|
+
Many-to-many through tables (auto `posts_tags` from `tags: many("tags")`, or an explicit `through` table) are not ordinary entities. Both FK columns are usually the composite primary key, so a naive Create/Update export would be `Type.Object({})`.
|
|
78
|
+
|
|
79
|
+
Codegen treats them as **link** tables:
|
|
80
|
+
|
|
81
|
+
| Export | Shape |
|
|
82
|
+
|--------|--------|
|
|
83
|
+
| `{Model}Schema` / `{Model}Select` | Junction row (both FK ids, plus extras like `priority`) |
|
|
84
|
+
| `{Model}LinkCreateSchema` / `{Model}LinkCreate` | Both FK ids required; extra create-allowed columns |
|
|
85
|
+
| `{Model}CreateSchema` / `{Model}Create` | Alias of `LinkCreateSchema` |
|
|
86
|
+
| `{Model}UpdateSchema` | Extra scalar columns only. **Omitted** when the junction has no updatable fields |
|
|
87
|
+
|
|
88
|
+
Generated comments point at nested writes on the parent (`db.posts.update({ data: { tags: { connect: [{ id }] } } })`). Direct `db.posts_tags.create` is rarely needed. `schemas.posts_tags` has `select` and `create`; `update` is present only when the through table has extra columns.
|
|
89
|
+
|
|
90
|
+
Prefer validating parent payloads in TypeScript (`tags: { connect, set, … }`). Junction TypeBox is for the rare case you insert a link row yourself.
|
|
91
|
+
|
|
92
|
+
## Types and constraints
|
|
93
|
+
|
|
94
|
+
Timestamp columns accept both ORM `Date` values and JSON ISO-8601 strings (`2020-01-01T00:00:00.000Z`, including offsets). `Value.Decode` always returns a `Date`, so the same schema works for HTTP bodies and query results. `Value.Check` accepts either form without converting.
|
|
95
|
+
|
|
96
|
+
`Type.BigInt()` and `Buffer` still match generated models, not JSON. Wrap those fields if the payload is a JSON number/string.
|
|
97
|
+
|
|
98
|
+
The generated file registers `uuid`, `email`, `url`, and `date-time` on TypeBox’s `Format` registry when those formats appear, without overwriting a format you already set.
|
|
99
|
+
|
|
100
|
+
Typed schema helpers map into TypeBox:
|
|
101
|
+
|
|
102
|
+
| Schema | TypeBox |
|
|
103
|
+
|--------|---------|
|
|
104
|
+
| `.minLength()` / `.maxLength()` / `.notEmpty()` | `Type.String({ minLength, maxLength })` |
|
|
105
|
+
| `email` / `*Email` column names, or `.email()` on `text` / `citext` | `Type.String({ format: "email" })` |
|
|
106
|
+
| `url` / `*Url` column names, or `.url()` on `text` / `citext` | `Type.String({ format: "url" })` |
|
|
107
|
+
| `.min()` / `.max()` / `.positive()` on int/serial | `Type.Integer({ minimum, maximum, exclusiveMinimum: 0 })` |
|
|
108
|
+
| same helpers on `bigint` | `Type.BigInt({ minimum, maximum, exclusiveMinimum: 0n })` |
|
|
109
|
+
| same helpers on `decimal()` | `Type.Refine(Type.String(), …)` |
|
|
110
|
+
| `enumType([...])` | hoisted `Type.Enum` |
|
|
111
|
+
| `json()` / `jsonb()` with no type arg | `Type.Record(Type.String(), Type.Unknown())` |
|
|
112
|
+
| `jsonb<Record<string, unknown>>()` | `Type.Record(Type.String(), Type.Unknown())` |
|
|
113
|
+
| `jsonb<{ featured: boolean }>()` (inline object type) | `Type.Object({ featured: Type.Boolean(), … })` |
|
|
114
|
+
| `json()` / `jsonb()` with `.schema()` validation IR | same as IR (`Type.Object`, nested `Type.Record`, …); wins over generics |
|
|
115
|
+
| `timestamp()` | `Date` or ISO datetime string → `Date` (`Type.Codec` + `Value.Decode`) |
|
|
116
|
+
| `bytea()` | `Type.Refine` + `Buffer.isBuffer` |
|
|
117
|
+
|
|
118
|
+
Raw `.check("sql")` is not mapped. With `generate.typebox: true` (or `generate.zod` / `generate.elysia`), codegen reads inline `json()` / `jsonb()` type arguments from `schema.ts` and maps object literals to `Type.Object`, `Record<K,V>` to `Type.Record`, and primitives to the matching TypeBox types. Type aliases and imported types are not resolved yet — use an inline type or `.schema()` for those.
|
|
119
|
+
|
|
120
|
+
```ts
|
|
121
|
+
metadata: jsonb<{ featured: boolean; category?: string }>(),
|
|
122
|
+
// → metadata: Type.Union([Type.Object({ featured: Type.Boolean(), category: Type.Optional(Type.String()) }), Type.Null()])
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
For shapes codegen cannot infer from the generic, use `.schema()`:
|
|
126
|
+
|
|
127
|
+
```ts
|
|
128
|
+
metadata: jsonb().schema({
|
|
129
|
+
kind: "object",
|
|
130
|
+
fields: [
|
|
131
|
+
{
|
|
132
|
+
name: "featured",
|
|
133
|
+
type: { kind: "boolean" },
|
|
134
|
+
nullable: false,
|
|
135
|
+
optional: false,
|
|
136
|
+
},
|
|
137
|
+
],
|
|
138
|
+
}),
|
|
139
|
+
```
|
package/docs/zod.md
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# Zod schemas
|
|
2
|
+
|
|
3
|
+
`neoorm generate` can print Select, Create, and Update **scalar** Zod schemas next to the typed client. Codegen maps your schema to a validator-neutral IR first; Zod, [TypeBox](typebox.md), and [Elysia `t`](elysia.md) are the printers shipped today.
|
|
4
|
+
|
|
5
|
+
Generated schemas match the TypeScript shapes of row / insert / update scalars — not nested relation writes (`connect`, `create`, `set`). Those stay TypeScript-only.
|
|
6
|
+
|
|
7
|
+
## Enable
|
|
8
|
+
|
|
9
|
+
Install Zod 4 in the app, then opt in:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
bun add zod
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
// neoorm.config.ts
|
|
17
|
+
import { defineConfig } from "neoorm";
|
|
18
|
+
|
|
19
|
+
export default defineConfig({
|
|
20
|
+
schema: "./schema.ts",
|
|
21
|
+
out: "./neoorm",
|
|
22
|
+
datasource: {
|
|
23
|
+
provider: "postgresql",
|
|
24
|
+
url: process.env.DATABASE_URL!,
|
|
25
|
+
},
|
|
26
|
+
generate: {
|
|
27
|
+
zod: true,
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Re-run `neoorm generate` (or `neoorm migrate dev`). Output:
|
|
33
|
+
|
|
34
|
+
- `out/zod.ts` — generated schemas
|
|
35
|
+
- `out/client.ts` re-exports them
|
|
36
|
+
|
|
37
|
+
Disable `generate.zod` and generate again to remove `zod.ts`.
|
|
38
|
+
|
|
39
|
+
`zod` is an optional peer of NeoOrm. The generated file imports `zod` directly; NeoOrm does not bundle it. If `generate.zod` is on and `zod` is not installed, `neoorm generate` still writes `zod.ts` and prints a warning (`bun add zod`).
|
|
40
|
+
|
|
41
|
+
## Usage
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
import { db, UserCreateSchema, type UserCreate } from "./neoorm/client.js";
|
|
45
|
+
|
|
46
|
+
const data: UserCreate = UserCreateSchema.parse(body);
|
|
47
|
+
await db.users.create({ data });
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Per table (accessor `users` → model `User`):
|
|
51
|
+
|
|
52
|
+
| Export | Shape |
|
|
53
|
+
|--------|--------|
|
|
54
|
+
| `UserSchema` / `UserSelect` | Select row (default query output: no `.hidden()`, includes `timestamps()`) |
|
|
55
|
+
| `UserCreateSchema` / `UserCreate` | Insert scalars (no primary / serial / `timestamps()`; defaults optional; includes `.hidden()`) |
|
|
56
|
+
| `UserUpdateSchema` / `UserUpdate` | Update scalars (no primary / `timestamps()`; all optional; includes `.hidden()`) |
|
|
57
|
+
|
|
58
|
+
`UserSelect` / `UserCreate` / `UserUpdate` are `z.infer` aliases. They are named that way so they do not collide with the model type `User` from `models.ts`. Hoisted enums also get a type (`PostStatus` from `PostStatusSchema`).
|
|
59
|
+
|
|
60
|
+
`schemas.users.select` / `.create` / `.update` are the same objects.
|
|
61
|
+
|
|
62
|
+
`.hidden()` columns (for example `password`) are omitted from select schemas so they match default query results. They stay on create and update so login/register and password-change payloads can still be parsed. Use `includeHidden: true` on the query when the app needs those fields internally.
|
|
63
|
+
|
|
64
|
+
`createdAt` and `updatedAt` from `timestamps()` (and any `timestamp().defaultNow()` / `.updatedAt()` column) are ORM-managed. They appear on select schemas only — create and update parsers reject them so API clients cannot stamp those fields. A plain `timestamp()` without `defaultNow` stays on create/update.
|
|
65
|
+
|
|
66
|
+
Create with `author: { connect: { id } }` is not in `PostCreateSchema`. Pass the FK scalar (`authorId`) or keep nested writes in TypeScript.
|
|
67
|
+
|
|
68
|
+
## Junction (M2M) tables
|
|
69
|
+
|
|
70
|
+
Many-to-many through tables (auto `posts_tags` from `tags: many("tags")`, or an explicit `through` table) are not ordinary entities. Both FK columns are usually the composite primary key, so a naive Create/Update export would be `z.object({})`.
|
|
71
|
+
|
|
72
|
+
Codegen treats them as **link** tables:
|
|
73
|
+
|
|
74
|
+
| Export | Shape |
|
|
75
|
+
|--------|--------|
|
|
76
|
+
| `{Model}Schema` / `{Model}Select` | Junction row (both FK ids, plus extras like `priority`) |
|
|
77
|
+
| `{Model}LinkCreateSchema` / `{Model}LinkCreate` | Both FK ids required; extra create-allowed columns |
|
|
78
|
+
| `{Model}CreateSchema` / `{Model}Create` | Alias of `LinkCreateSchema` |
|
|
79
|
+
| `{Model}UpdateSchema` | Extra scalar columns only. **Omitted** when the junction has no updatable fields |
|
|
80
|
+
|
|
81
|
+
Generated comments point at nested writes on the parent (`db.posts.update({ data: { tags: { connect: [{ id }] } } })`). Direct `db.posts_tags.create` is rarely needed. `schemas.posts_tags` has `select` and `create`; `update` is present only when the through table has extra columns.
|
|
82
|
+
|
|
83
|
+
Prefer validating parent payloads in TypeScript (`tags: { connect, set, … }`). Junction Zod is for the rare case you insert a link row yourself.
|
|
84
|
+
|
|
85
|
+
## Types and constraints
|
|
86
|
+
|
|
87
|
+
Timestamp columns accept both ORM `Date` values and JSON ISO-8601 strings (`2020-01-01T00:00:00.000Z`, including offsets). `parse` always returns a `Date`, so the same schema works for HTTP bodies and query results.
|
|
88
|
+
|
|
89
|
+
`z.bigint()` and `Buffer` still match generated models, not JSON. Wrap those fields if the payload is a JSON number/string.
|
|
90
|
+
|
|
91
|
+
Typed schema helpers map into Zod:
|
|
92
|
+
|
|
93
|
+
| Schema | Zod |
|
|
94
|
+
|--------|-----|
|
|
95
|
+
| `.minLength()` / `.maxLength()` / `.notEmpty()` | `z.string().min()` / `.max()` |
|
|
96
|
+
| `email` / `*Email` column names, or `.email()` on `text` / `citext` | `z.email()` |
|
|
97
|
+
| `url` / `*Url` column names, or `.url()` on `text` / `citext` | `z.url()` |
|
|
98
|
+
| `.min()` / `.max()` / `.positive()` on int/serial/bigint | matching number/bigint checks |
|
|
99
|
+
| same helpers on `decimal()` | `z.string().refine(...)` |
|
|
100
|
+
| `enumType([...])` | hoisted `z.enum` |
|
|
101
|
+
| `json()` / `jsonb()` with no type arg | `z.record(z.string(), z.unknown())` |
|
|
102
|
+
| `jsonb<Record<string, unknown>>()` | `z.record(z.string(), z.unknown())` |
|
|
103
|
+
| `jsonb<{ featured: boolean }>()` (inline object type) | `z.object({ featured: z.boolean(), … })` |
|
|
104
|
+
| `json()` / `jsonb()` with `.schema()` validation IR | same as IR (`z.object`, nested `z.record`, …); wins over generics |
|
|
105
|
+
| `timestamp()` | `Date` or ISO datetime string → `Date` |
|
|
106
|
+
|
|
107
|
+
Raw `.check("sql")` is not mapped. With `generate.zod: true`, `generate.typebox: true`, or `generate.elysia: true`, codegen reads inline `json()` / `jsonb()` type arguments from `schema.ts` and maps object literals to `z.object`, `Record<K,V>` to `z.record`, and primitives to the matching Zod types. Type aliases and imported types are not resolved yet — use an inline type or `.schema()` for those.
|
|
108
|
+
|
|
109
|
+
```ts
|
|
110
|
+
metadata: jsonb<{ featured: boolean; category?: string }>(),
|
|
111
|
+
// → metadata: z.object({ featured: z.boolean(), category: z.string().optional() }).nullable()
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
For shapes codegen cannot infer from the generic, use `.schema()`:
|
|
115
|
+
|
|
116
|
+
```ts
|
|
117
|
+
metadata: jsonb().schema({
|
|
118
|
+
kind: "object",
|
|
119
|
+
fields: [
|
|
120
|
+
{
|
|
121
|
+
name: "featured",
|
|
122
|
+
type: { kind: "boolean" },
|
|
123
|
+
nullable: false,
|
|
124
|
+
optional: false,
|
|
125
|
+
},
|
|
126
|
+
],
|
|
127
|
+
}),
|
|
128
|
+
```
|