neoorm 0.8.1 → 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 +10 -5
- package/dist/bin/neoorm.d.ts +1 -1
- package/dist/bin/neoorm.d.ts.map +1 -1
- package/dist/bin/neoorm.js +131 -57
- 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 +1 -0
- package/dist/codegen/generate-summary.d.ts.map +1 -1
- package/dist/codegen/generate-summary.js +3 -0
- package/dist/codegen/generate-summary.js.map +1 -1
- package/dist/codegen/generate.d.ts +7 -1
- package/dist/codegen/generate.d.ts.map +1 -1
- package/dist/codegen/generate.js +53 -20
- package/dist/codegen/generate.js.map +1 -1
- package/dist/codegen/schema-to-manifest.d.ts.map +1 -1
- package/dist/codegen/schema-to-manifest.js +302 -22
- 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/types.d.ts +1 -1
- package/dist/codegen/validation/types.js +1 -1
- package/dist/config.d.ts +2 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +3 -1
- 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 +5 -1
- package/dist/dialect/index.d.ts.map +1 -1
- package/dist/dialect/index.js +4 -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.map +1 -1
- package/dist/dialect/postgres.js +55 -27
- 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 +46 -3
- package/dist/dialect/types.d.ts.map +1 -1
- package/dist/docs/pages.d.ts.map +1 -1
- package/dist/docs/pages.js +3 -0
- package/dist/docs/pages.js.map +1 -1
- package/dist/docs/render.js +1 -1
- package/dist/index.d.ts +5 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -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 -10
- 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 +179 -6
- package/dist/introspect/pull.js.map +1 -1
- package/dist/introspect/queries.d.ts +10 -1
- package/dist/introspect/queries.d.ts.map +1 -1
- package/dist/introspect/queries.js +41 -11
- 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 +145 -26
- 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 +42 -0
- package/dist/plugins/builtin.d.ts.map +1 -1
- package/dist/plugins/builtin.js +276 -8
- package/dist/plugins/builtin.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/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/runtime/client.d.ts +31 -4
- package/dist/runtime/client.d.ts.map +1 -1
- package/dist/runtime/client.js +129 -4
- 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/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/compile-aggregate.d.ts +1 -1
- package/dist/runtime/query/compile-aggregate.d.ts.map +1 -1
- package/dist/runtime/query/compile-aggregate.js +25 -22
- 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 +87 -66
- 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 +108 -62
- package/dist/runtime/query/find.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/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 +69 -55
- 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 +257 -98
- package/dist/runtime/query/relation-writes.js.map +1 -1
- package/dist/runtime/query/table-index.d.ts.map +1 -1
- package/dist/runtime/query/table-index.js +16 -9
- 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/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 +1 -1
- package/dist/schema/column-constraints.d.ts.map +1 -1
- package/dist/schema/column-constraints.js +6 -3
- package/dist/schema/column-constraints.js.map +1 -1
- package/dist/schema/column.d.ts +2 -2
- package/dist/schema/column.d.ts.map +1 -1
- package/dist/schema/column.js +1 -1
- package/dist/schema/column.js.map +1 -1
- package/dist/schema/index.d.ts +4 -4
- package/dist/schema/index.d.ts.map +1 -1
- package/dist/schema/index.js +2 -2
- package/dist/schema/index.js.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 +73 -12
- 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 +1 -1
- 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/docs/cli.md +4 -4
- package/docs/configuration.md +7 -6
- package/docs/elysia.md +131 -0
- package/docs/errors.md +1 -1
- package/docs/examples.md +4 -2
- package/docs/getting-started.md +6 -4
- package/docs/mariadb.md +149 -0
- package/docs/migrations.md +5 -3
- package/docs/mysql.md +134 -0
- package/docs/plugins.md +1 -1
- package/docs/queries.md +14 -11
- package/docs/relations.md +19 -4
- package/docs/schema.md +73 -10
- package/docs/sqlite.md +6 -2
- package/docs/typebox.md +4 -4
- package/docs/zod.md +2 -2
- package/package.json +18 -2
package/docs/elysia.md
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# Elysia schemas
|
|
2
|
+
|
|
3
|
+
`neoorm generate` can print Select, Create, and Update **scalar** Elysia `t` schemas next to the typed client. Codegen maps your schema to a validator-neutral IR first; this printer emits `import { t } from "elysia"` so the objects drop into route `body:` / `query:` the same way as hand-written `t.Object({ title: t.String() })`.
|
|
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
|
+
This is the printer to use with **Elysia 1.x**. [TypeBox 1.x](typebox.md) (`import Type from "typebox"`) is a different package and will not type-check as Elysia `body:`.
|
|
8
|
+
|
|
9
|
+
## Enable
|
|
10
|
+
|
|
11
|
+
Install Elysia in the app, then opt in:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
bun add elysia
|
|
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
|
+
elysia: true,
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Re-run `neoorm generate` (or `neoorm migrate dev`). Output:
|
|
35
|
+
|
|
36
|
+
- `out/elysia.ts` — generated schemas
|
|
37
|
+
- `out/client.ts` re-exports them
|
|
38
|
+
|
|
39
|
+
Disable `generate.elysia` and generate again to remove `elysia.ts`.
|
|
40
|
+
|
|
41
|
+
`elysia` is an optional peer of NeoOrm. The generated file imports `elysia` directly; NeoOrm does not bundle it. If `generate.elysia` is on and `elysia` is not installed, `neoorm generate` still writes `elysia.ts` and prints a warning (`bun add elysia`).
|
|
42
|
+
|
|
43
|
+
When Zod or TypeBox 1.x is also enabled, `client.ts` keeps those as documented and re-exports Elysia as `export * as elysia from "./elysia.js"` so names do not collide. Import from `./elysia.js` (or `elysia.UserCreateSchema`) in that case.
|
|
44
|
+
|
|
45
|
+
## Usage
|
|
46
|
+
|
|
47
|
+
```ts
|
|
48
|
+
import { Elysia } from "elysia";
|
|
49
|
+
import { db, UserCreateSchema, UserUpdateSchema } from "./neoorm/client.js";
|
|
50
|
+
|
|
51
|
+
new Elysia()
|
|
52
|
+
.post("/users", ({ body }) => db.users.create({ data: body }), {
|
|
53
|
+
body: UserCreateSchema,
|
|
54
|
+
})
|
|
55
|
+
.patch("/users/:id", ({ params, body }) =>
|
|
56
|
+
db.users.update({
|
|
57
|
+
where: { id: params.id },
|
|
58
|
+
data: body,
|
|
59
|
+
}), {
|
|
60
|
+
body: UserUpdateSchema,
|
|
61
|
+
},
|
|
62
|
+
)
|
|
63
|
+
.listen(3000);
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Elysia validates `body` against the `t.Object` schema. `t.Date()` accepts ISO-8601 strings, `Date` values, and epoch numbers, and decodes to `Date`.
|
|
67
|
+
|
|
68
|
+
Per table (accessor `users` → model `User`):
|
|
69
|
+
|
|
70
|
+
| Export | Shape |
|
|
71
|
+
|--------|--------|
|
|
72
|
+
| `UserSchema` / `UserSelect` | Select row (default query output: no `.hidden()`, includes `timestamps()`) |
|
|
73
|
+
| `UserCreateSchema` / `UserCreate` | Insert scalars (no primary / serial / `timestamps()`; defaults optional; includes `.hidden()`) |
|
|
74
|
+
| `UserUpdateSchema` / `UserUpdate` | Update scalars (no primary / `timestamps()`; all optional; includes `.hidden()`) |
|
|
75
|
+
|
|
76
|
+
`UserSelect` / `UserCreate` / `UserUpdate` are `typeof Schema.static` 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`).
|
|
77
|
+
|
|
78
|
+
`schemas.users.select` / `.create` / `.update` are the same objects.
|
|
79
|
+
|
|
80
|
+
`.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.
|
|
81
|
+
|
|
82
|
+
`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.
|
|
83
|
+
|
|
84
|
+
Create with `author: { connect: { id } }` is not in `PostCreateSchema`. Pass the FK scalar (`authorId`) or keep nested writes in TypeScript.
|
|
85
|
+
|
|
86
|
+
## Junction (M2M) tables
|
|
87
|
+
|
|
88
|
+
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 `t.Object({})`.
|
|
89
|
+
|
|
90
|
+
Codegen treats them as **link** tables:
|
|
91
|
+
|
|
92
|
+
| Export | Shape |
|
|
93
|
+
|--------|--------|
|
|
94
|
+
| `{Model}Schema` / `{Model}Select` | Junction row (both FK ids, plus extras like `priority`) |
|
|
95
|
+
| `{Model}LinkCreateSchema` / `{Model}LinkCreate` | Both FK ids required; extra create-allowed columns |
|
|
96
|
+
| `{Model}CreateSchema` / `{Model}Create` | Alias of `LinkCreateSchema` |
|
|
97
|
+
| `{Model}UpdateSchema` | Extra scalar columns only. **Omitted** when the junction has no updatable fields |
|
|
98
|
+
|
|
99
|
+
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.
|
|
100
|
+
|
|
101
|
+
Prefer validating parent payloads in TypeScript (`tags: { connect, set, … }`). Junction Elysia schemas are for the rare case you insert a link row yourself.
|
|
102
|
+
|
|
103
|
+
## Types and constraints
|
|
104
|
+
|
|
105
|
+
`t.BigInt()` and `Buffer` still match generated models, not JSON. Wrap those fields if the payload is a JSON number/string.
|
|
106
|
+
|
|
107
|
+
Typed schema helpers map into Elysia `t`:
|
|
108
|
+
|
|
109
|
+
| Schema | Elysia |
|
|
110
|
+
|--------|--------|
|
|
111
|
+
| `.minLength()` / `.maxLength()` / `.notEmpty()` | `t.String({ minLength, maxLength })` |
|
|
112
|
+
| `email` / `*Email` column names, or `.email()` on `text` / `citext` | `t.String({ format: "email" })` |
|
|
113
|
+
| `url` / `*Url` column names, or `.url()` on `text` / `citext` | `t.String({ format: "uri" })` |
|
|
114
|
+
| `.min()` / `.max()` / `.positive()` on int/serial | `t.Integer({ minimum, maximum, exclusiveMinimum: 0 })` |
|
|
115
|
+
| same helpers on `bigint` | `t.BigInt({ minimum, maximum, exclusiveMinimum: 0n })` |
|
|
116
|
+
| same helpers on `decimal()` | `t.Transform(t.String()).Decode(...)` |
|
|
117
|
+
| `enumType([...])` | hoisted `t.UnionEnum` |
|
|
118
|
+
| `json()` / `jsonb()` with no type arg | `t.Record(t.String(), t.Unknown())` |
|
|
119
|
+
| `jsonb<Record<string, unknown>>()` | `t.Record(t.String(), t.Unknown())` |
|
|
120
|
+
| `jsonb<{ featured: boolean }>()` (inline object type) | `t.Object({ featured: t.Boolean(), … })` |
|
|
121
|
+
| `json()` / `jsonb()` with `.schema()` validation IR | same as IR (`t.Object`, nested `t.Record`, …); wins over generics |
|
|
122
|
+
| `timestamp()` | `t.Date()` (ISO string / Date / epoch → `Date`) |
|
|
123
|
+
| `bytea()` | `t.Transform(t.Any())` + `Buffer.isBuffer` |
|
|
124
|
+
| nullable / optional | `t.Nullable` / `t.Optional` |
|
|
125
|
+
|
|
126
|
+
Raw `.check("sql")` is not mapped. With `generate.elysia: true` (or Zod/TypeBox), codegen reads inline `json()` / `jsonb()` type arguments from `schema.ts`. Type aliases and imported types are not resolved yet — use an inline type or `.schema()` for those.
|
|
127
|
+
|
|
128
|
+
```ts
|
|
129
|
+
metadata: jsonb<{ featured: boolean; category?: string }>(),
|
|
130
|
+
// → metadata: t.Nullable(t.Object({ featured: t.Boolean(), category: t.Optional(t.String()) }))
|
|
131
|
+
```
|
package/docs/errors.md
CHANGED
|
@@ -48,7 +48,7 @@ Type guards (`isUniqueViolation`, `isQueryCompileError`, `isNeoOrmError`, …) a
|
|
|
48
48
|
|
|
49
49
|
## Query error codes
|
|
50
50
|
|
|
51
|
-
Codes are **dialect-agnostic** — the same `unique_violation` code is used for PostgreSQL and
|
|
51
|
+
Codes are **dialect-agnostic** — the same `unique_violation` code is used for PostgreSQL, SQLite, MySQL, and MariaDB.
|
|
52
52
|
|
|
53
53
|
| Code | Subclass | Typical HTTP | When |
|
|
54
54
|
|------|----------|--------------|------|
|
package/docs/examples.md
CHANGED
|
@@ -369,7 +369,7 @@ await db.$transaction(async (tx) => {
|
|
|
369
369
|
|
|
370
370
|
## Raw SQL
|
|
371
371
|
|
|
372
|
-
Use `db.sql` when you need full SQL control. It uses the same compiler as `neoorm/sql` (`sql`, `sqlId`, nested fragments). `sqlBuilder`
|
|
372
|
+
Use `db.sql` when you need full SQL control. It uses the same compiler as `neoorm/sql` (`sql`, `sqlId`, nested fragments). `sqlBuilder` covers select/join/where/group/order/limit with bound params — interpolate `.compile()` into `db.sql` for HAVING or other raw tails.
|
|
373
373
|
|
|
374
374
|
```ts
|
|
375
375
|
import { sql, sqlBuilder, sqlId } from "neoorm/sql";
|
|
@@ -391,7 +391,9 @@ const grouped = sqlBuilder
|
|
|
391
391
|
.selectFrom("users")
|
|
392
392
|
.leftJoin("posts", "posts.author_id", "users.id")
|
|
393
393
|
.select(["users.id", "users.email"])
|
|
394
|
+
.where("users.email", "=", email)
|
|
394
395
|
.groupBy("users.id", "users.email")
|
|
396
|
+
.limit(10)
|
|
395
397
|
.compile();
|
|
396
398
|
const dashboard = await db.sql`${grouped} HAVING count(posts.id) > ${0}`;
|
|
397
399
|
```
|
|
@@ -459,7 +461,7 @@ const db = createNeoOrmClient(manifest, {
|
|
|
459
461
|
});
|
|
460
462
|
```
|
|
461
463
|
|
|
462
|
-
See [SQLite](sqlite.md) for type mapping and limitations.
|
|
464
|
+
See [SQLite](sqlite.md), [MySQL](mysql.md), and [MariaDB](mariadb.md) for type mapping and limitations.
|
|
463
465
|
|
|
464
466
|
Log compiled SQL:
|
|
465
467
|
|
package/docs/getting-started.md
CHANGED
|
@@ -89,11 +89,12 @@ export default defineConfig({
|
|
|
89
89
|
bunx neoorm generate
|
|
90
90
|
```
|
|
91
91
|
|
|
92
|
-
To emit Zod or
|
|
92
|
+
To emit Zod, TypeBox, or Elysia Select/Create/Update schemas, install the library and set `generate.zod` / `generate.typebox` / `generate.elysia` in config:
|
|
93
93
|
|
|
94
94
|
```bash
|
|
95
95
|
bun add zod
|
|
96
96
|
bun add typebox
|
|
97
|
+
bun add elysia
|
|
97
98
|
```
|
|
98
99
|
|
|
99
100
|
```ts
|
|
@@ -107,11 +108,12 @@ export default defineConfig({
|
|
|
107
108
|
generate: {
|
|
108
109
|
zod: true,
|
|
109
110
|
typebox: true,
|
|
111
|
+
elysia: true,
|
|
110
112
|
},
|
|
111
113
|
});
|
|
112
114
|
```
|
|
113
115
|
|
|
114
|
-
`neoorm generate` / `neoorm migrate dev` then writes `out/zod.ts` and/or `out/
|
|
116
|
+
`neoorm generate` / `neoorm migrate dev` then writes `out/zod.ts`, `out/typebox.ts`, and/or `out/elysia.ts` (re-exported from `out/client.ts`). See [Zod schemas](zod.md), [TypeBox schemas](typebox.md), and [Elysia schemas](elysia.md). `neoorm init` leaves this commented in `neoorm.config.ts`.
|
|
115
117
|
|
|
116
118
|
## SQLite
|
|
117
119
|
|
|
@@ -134,7 +136,7 @@ const db = createNeoOrmClient(manifest, {
|
|
|
134
136
|
});
|
|
135
137
|
```
|
|
136
138
|
|
|
137
|
-
See [SQLite](sqlite.md).
|
|
139
|
+
See [SQLite](sqlite.md), [MySQL](mysql.md), and [MariaDB](mariadb.md).
|
|
138
140
|
|
|
139
141
|
## Tenant-specific schemas
|
|
140
142
|
|
|
@@ -145,7 +147,7 @@ const tenantDb = createNeoOrmClient(manifest, {
|
|
|
145
147
|
});
|
|
146
148
|
```
|
|
147
149
|
|
|
148
|
-
Raw `db.sql` uses the same compiler as `neoorm/sql`. `db.sql` / `db.execute`
|
|
150
|
+
Raw `db.sql` uses the same compiler as `neoorm/sql`. With PostgreSQL `schema: "tenant_acme"`, `db.sql` / `db.execute` qualify unqualified table names from your manifest (`FROM users` → `"tenant_acme"."users"`). `db.sqlId("users")` is schema-qualified; column `sqlId`s are not. Explicit `schema.table` refs are left alone.
|
|
149
151
|
|
|
150
152
|
Reuse an existing `pg` pool with `createNeoOrmClientFromPool(manifest, pool)`, or an existing SQLite handle with `createNeoOrmClientFromSqlite(manifest, database)`. `$disconnect()` does not close a borrowed pool or database — close it yourself when every consumer is done.
|
|
151
153
|
|
package/docs/mariadb.md
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# MariaDB
|
|
2
|
+
|
|
3
|
+
MariaDB 10.11 LTS is a first-class dialect via the optional official `mariadb` connector. Schema DSL, codegen, migrations, and the query client work the same way as PostgreSQL, with the storage and SQL differences below.
|
|
4
|
+
|
|
5
|
+
MariaDB versions below 10.11, GIS, Galera, and MaxScale specifics are out of scope. MySQL 8 uses a **separate** dialect and the `mysql2` driver — see [MySQL](mysql.md).
|
|
6
|
+
|
|
7
|
+
## Requirements
|
|
8
|
+
|
|
9
|
+
Install the driver next to NeoOrm:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
bun add neoorm mariadb
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
If `mariadb` is missing, creating a client throws: `mariadb is not installed. Run: bun add mariadb`.
|
|
16
|
+
|
|
17
|
+
## Configuration
|
|
18
|
+
|
|
19
|
+
Set `provider: "mariadb"` and a MariaDB 10.11 connection URL:
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
// neoorm.config.ts
|
|
23
|
+
import { defineConfig } from "neoorm";
|
|
24
|
+
|
|
25
|
+
export default defineConfig({
|
|
26
|
+
schema: "./schema.ts",
|
|
27
|
+
out: "./neoorm",
|
|
28
|
+
datasource: {
|
|
29
|
+
provider: "mariadb",
|
|
30
|
+
url: process.env.DATABASE_URL ?? "mariadb://root@localhost:3306/myapp",
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
`datasource.schema` is ignored (the database comes from the URL). `datasource.enum: "native"` is allowed and emits column `ENUM('a','b')` — there is no `CREATE TYPE`.
|
|
36
|
+
|
|
37
|
+
## Runtime client
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
import { createNeoOrmClient } from "neoorm";
|
|
41
|
+
import { manifest } from "./neoorm/manifest.js";
|
|
42
|
+
|
|
43
|
+
const db = createNeoOrmClient(manifest, {
|
|
44
|
+
provider: "mariadb",
|
|
45
|
+
connectionString: process.env.MARIADB_URL,
|
|
46
|
+
});
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Wrap an existing `mariadb` pool with `createNeoOrmClientFromMariadb(manifest, pool)`. `$disconnect()` does not call `pool.end()` in that case — you own the pool.
|
|
50
|
+
|
|
51
|
+
Data queries use connector `execute()` (prepared statements, `prepareCacheLength` 256). Transaction control stays on `query()`. Pools you wrap without `execute` fall back to `query()`.
|
|
52
|
+
|
|
53
|
+
Owned pools from `createNeoOrmClient` accept the same `pool` object as PostgreSQL for shared fields (`max`, idle timeout, keep-alive). Default `max` is 10. Pipelining is enabled (commands are still awaited in order). PostgreSQL-only keys such as `statement_timeout` are ignored.
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
const db = createNeoOrmClient(manifest, {
|
|
57
|
+
provider: "mariadb",
|
|
58
|
+
connectionString: process.env.MARIADB_URL,
|
|
59
|
+
pool: {
|
|
60
|
+
max: 10,
|
|
61
|
+
idleTimeoutMillis: 10_000,
|
|
62
|
+
keepAlive: true,
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Standalone `neoorm/sql` (`sqlId`) stays ANSI-quoted (`"users"`). Use `db.sql` with `db.sqlId("users")` so identifiers are backticks on MariaDB.
|
|
68
|
+
|
|
69
|
+
`mariadbDialect` is exported from `neoorm` for `dbPush`, migrate helpers, and custom executor wiring.
|
|
70
|
+
|
|
71
|
+
## CLI
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
bunx neoorm init --provider mariadb
|
|
75
|
+
bunx neoorm migrate deploy
|
|
76
|
+
bunx neoorm db push
|
|
77
|
+
bunx neoorm db pull
|
|
78
|
+
bunx neoorm migrate status
|
|
79
|
+
bunx neoorm migrate reset --force
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
- Deploy lock is `GET_LOCK('neoorm.migrate.<db>', timeout)` / `RELEASE_LOCK` on the deploy transaction.
|
|
83
|
+
- `migrate reset` sets `FOREIGN_KEY_CHECKS=0`, drops all tables in the current database, then re-enables checks and re-applies migrations.
|
|
84
|
+
- `db pull` introspects `information_schema`. MariaDB `JSON` columns are often `LONGTEXT` plus a `json_valid()` CHECK; NeoOrm maps those back to `jsonb()`.
|
|
85
|
+
|
|
86
|
+
## Type mapping
|
|
87
|
+
|
|
88
|
+
| Schema builder | MariaDB storage |
|
|
89
|
+
|----------------|-----------------|
|
|
90
|
+
| `id()`, unique/PK `text` | `VARCHAR(191)` (InnoDB index prefix; `TEXT` cannot be a unique key) |
|
|
91
|
+
| `text()` otherwise | `TEXT`, or `VARCHAR(n)` with `.maxLength()` |
|
|
92
|
+
| `uuid()` | `CHAR(36)` |
|
|
93
|
+
| `bool()` | `TINYINT(1)` |
|
|
94
|
+
| `int()` | `INT` |
|
|
95
|
+
| `bigint()` | `BIGINT` |
|
|
96
|
+
| `real()` / `float()` | `FLOAT` |
|
|
97
|
+
| `double()` | `DOUBLE` |
|
|
98
|
+
| `serial()` | `INT NOT NULL AUTO_INCREMENT` |
|
|
99
|
+
| `timestamp()` | `DATETIME(6)` + `CURRENT_TIMESTAMP(6)` |
|
|
100
|
+
| `date()` | `DATE` |
|
|
101
|
+
| `time()` | `TIME` |
|
|
102
|
+
| `json()` / `jsonb()` | `JSON` (LONGTEXT + `json_valid()` CHECK under the hood) |
|
|
103
|
+
| `decimal()` | `DECIMAL(p,s)` |
|
|
104
|
+
| `money()` | `DECIMAL(19,4)` |
|
|
105
|
+
| `xml()` | `LONGTEXT` |
|
|
106
|
+
| `bytea()` | `BLOB` |
|
|
107
|
+
| arrays / `citext()` | `JSON` / `VARCHAR` + `utf8mb4_uca1400_ai_ci` |
|
|
108
|
+
| `interval()`, `inet()`, `cidr()`, ranges | rejected at schema compile |
|
|
109
|
+
| `enum: "check"` | `VARCHAR` + `CHECK` |
|
|
110
|
+
| `enum: "native"` | column `ENUM('a','b')` |
|
|
111
|
+
|
|
112
|
+
## Differences from MySQL 8
|
|
113
|
+
|
|
114
|
+
| Feature | MySQL 8 | MariaDB 10.11 |
|
|
115
|
+
|---------|---------|----------------|
|
|
116
|
+
| Driver | `mysql2` | official `mariadb` package |
|
|
117
|
+
| `upsert` | `INSERT … AS new ON DUPLICATE KEY UPDATE` | `ON DUPLICATE KEY UPDATE col = VALUES(col)` |
|
|
118
|
+
| `search` | `REGEXP_LIKE` | `col REGEXP $n` (`(?i)` for insensitive) |
|
|
119
|
+
| `citext` collation | `utf8mb4_0900_ai_ci` | `utf8mb4_uca1400_ai_ci` |
|
|
120
|
+
| CHECK drop | `DROP CHECK` | `DROP CONSTRAINT` |
|
|
121
|
+
| CHECK errno | 3819 | 4025 (`ER_CONSTRAINT_FAILED`) |
|
|
122
|
+
| JSON storage | native JSON | JSON as LONGTEXT + `json_valid()` |
|
|
123
|
+
| `RETURNING` | none | INSERT/DELETE RETURNING exist, but UPDATE does not; NeoOrm uses follow-up `SELECT` for all writes |
|
|
124
|
+
|
|
125
|
+
## Differences from PostgreSQL
|
|
126
|
+
|
|
127
|
+
| Feature | PostgreSQL | MariaDB |
|
|
128
|
+
|---------|------------|---------|
|
|
129
|
+
| Identifier quoting | `"users"` | `` `users` `` |
|
|
130
|
+
| `RETURNING` | native | follow-up `SELECT` (or `insertId` for serial) |
|
|
131
|
+
| `upsert` | `ON CONFLICT … DO UPDATE` | `ON DUPLICATE KEY UPDATE` + `VALUES(col)` |
|
|
132
|
+
| `skipDuplicates` | `ON CONFLICT DO NOTHING` | `INSERT IGNORE` |
|
|
133
|
+
| `findOrCreate` | `xmax = 0` | SELECT → INSERT → retry on unique violation |
|
|
134
|
+
| `in` / `notIn` | array bind | `JSON_TABLE` |
|
|
135
|
+
| `search` | POSIX `~` | `REGEXP` |
|
|
136
|
+
| `ilike` | `ILIKE` | `LOWER(col) LIKE LOWER(?)` |
|
|
137
|
+
| JSON operators | `@>`, `?`, `#>` | `JSON_CONTAINS` / `JSON_EXTRACT` / `JSON_CONTAINS_PATH` |
|
|
138
|
+
| Nested includes | `json_agg … FILTER` | JOIN `JSON_ARRAYAGG` when possible; otherwise batched `IN` queries (no `LATERAL`) |
|
|
139
|
+
| `distinct` (`DISTINCT ON`) | supported | throws |
|
|
140
|
+
| Partial indexes `index({ where })` | supported | rejected at schema compile |
|
|
141
|
+
| PostGIS | supported | rejected |
|
|
142
|
+
| `interval` / `inet` / `cidr` / range types | supported | rejected at schema compile |
|
|
143
|
+
| `datasource.schema` | multi-schema | ignored (URL database) |
|
|
144
|
+
|
|
145
|
+
MariaDB has no `LATERAL`, so correlated `JSON_ARRAYAGG` derived tables (the MySQL 8 / Postgres inline has-many subquery) cannot see outer columns such as `` `users`.`id` ``. Those includes are loaded with a follow-up `WHERE fk IN (…)` query instead.
|
|
146
|
+
|
|
147
|
+
`createManyAndReturn` for serial primary keys uses `LAST_INSERT_ID()` plus row count inside a transaction and assumes consecutive autoincrement values.
|
|
148
|
+
|
|
149
|
+
Everything else — relations, nested writes, cursor pagination, aggregates, `groupBy`, savepoint-based nested transactions — behaves the same as on PostgreSQL.
|
package/docs/migrations.md
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
| `neoorm migrate deploy` | Apply pending migrations |
|
|
11
11
|
| `neoorm migrate status` | List applied vs pending migrations |
|
|
12
12
|
| `neoorm migrate down [--steps N]` | Roll back the last N applied migrations (default 1) |
|
|
13
|
-
| `neoorm migrate reset --force` | Drop the `public` schema (PostgreSQL) or all tables (SQLite) and re-apply migrations (local dev) |
|
|
13
|
+
| `neoorm migrate reset --force` | Drop the `public` schema (PostgreSQL) or all tables (SQLite / MySQL / MariaDB) and re-apply migrations (local dev) |
|
|
14
14
|
| `neoorm db push` | Push the current `schema.ts` to the database (no migration file) |
|
|
15
15
|
| `neoorm db pull` | Introspect the database into a schema file |
|
|
16
16
|
|
|
@@ -38,7 +38,9 @@ neoorm generate --accept-data-loss
|
|
|
38
38
|
| Dialect | Lock |
|
|
39
39
|
|---------|------|
|
|
40
40
|
| PostgreSQL | `pg_advisory_xact_lock` on the deploy transaction |
|
|
41
|
-
| SQLite | `BEGIN IMMEDIATE` around the whole deploy |
|
|
41
|
+
| SQLite | `BEGIN IMMEDIATE` around the whole deploy (after `PRAGMA foreign_keys = OFF`) |
|
|
42
|
+
| MySQL | `GET_LOCK('neoorm.migrate.<db>', timeout)` / `RELEASE_LOCK` on the deploy connection |
|
|
43
|
+
| MariaDB | `GET_LOCK('neoorm.migrate.<db>', timeout)` / `RELEASE_LOCK` on the deploy connection |
|
|
42
44
|
|
|
43
45
|
The ledger table `_neoorm_migrations` stores `name` and `checksum`. Editing an already-applied `migration.sql` is rejected (`migration_guard`) — restore the original file or add a new migration. Pending migrations in one deploy run apply in that locked transaction; if a later statement fails, none of that run's new ledger rows remain.
|
|
44
46
|
|
|
@@ -56,7 +58,7 @@ Shows applied migrations (with timestamps), pending folders on disk, and warning
|
|
|
56
58
|
neoorm migrate reset --force
|
|
57
59
|
```
|
|
58
60
|
|
|
59
|
-
Drops the `public` schema (PostgreSQL) or all tables (SQLite) and re-applies all migrations from disk. Requires `--force`. Use `--skip-apply` to only drop without re-applying.
|
|
61
|
+
Drops the `public` schema (PostgreSQL) or all tables (SQLite, MySQL, and MariaDB) and re-applies all migrations from disk. Requires `--force`. Use `--skip-apply` to only drop without re-applying.
|
|
60
62
|
|
|
61
63
|
PostgreSQL reset recreates the schema owned by the connecting role. It does not `GRANT ALL ON SCHEMA … TO PUBLIC`.
|
|
62
64
|
|
package/docs/mysql.md
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# MySQL
|
|
2
|
+
|
|
3
|
+
MySQL 8.0+ is a first-class dialect via the optional `mysql2` driver. Schema DSL, codegen, migrations, and the query client work against MySQL the same way they do against PostgreSQL, with the storage and SQL differences below.
|
|
4
|
+
|
|
5
|
+
PlanetScale, MySQL 5.7, and GIS/PostGIS are out of scope.
|
|
6
|
+
|
|
7
|
+
## Requirements
|
|
8
|
+
|
|
9
|
+
Install the driver next to NeoOrm:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
bun add neoorm mysql2
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
If `mysql2` is missing, creating a client throws: `mysql2 is not installed. Run: bun add mysql2`.
|
|
16
|
+
|
|
17
|
+
## Configuration
|
|
18
|
+
|
|
19
|
+
Set `provider: "mysql"` and a MySQL 8 connection URL:
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
// neoorm.config.ts
|
|
23
|
+
import { defineConfig } from "neoorm";
|
|
24
|
+
|
|
25
|
+
export default defineConfig({
|
|
26
|
+
schema: "./schema.ts",
|
|
27
|
+
out: "./neoorm",
|
|
28
|
+
datasource: {
|
|
29
|
+
provider: "mysql",
|
|
30
|
+
url: process.env.DATABASE_URL ?? "mysql://root@localhost:3306/myapp",
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
`datasource.schema` is ignored (the database comes from the URL). `datasource.enum: "native"` is allowed and emits column `ENUM('a','b')` — there is no `CREATE TYPE`.
|
|
36
|
+
|
|
37
|
+
## Runtime client
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
import { createNeoOrmClient } from "neoorm";
|
|
41
|
+
import { manifest } from "./neoorm/manifest.js";
|
|
42
|
+
|
|
43
|
+
const db = createNeoOrmClient(manifest, {
|
|
44
|
+
provider: "mysql",
|
|
45
|
+
connectionString: process.env.MYSQL_URL,
|
|
46
|
+
});
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Wrap an existing `mysql2/promise` pool with `createNeoOrmClientFromMysql(manifest, pool)`. `$disconnect()` does not call `pool.end()` in that case — you own the pool.
|
|
50
|
+
|
|
51
|
+
Data queries use mysql2 `execute()` (binary prepared statements, cached per connection). Transaction control (`START TRANSACTION`, `COMMIT`, `SAVEPOINT`) stays on `query()`. Pools you wrap without `execute` fall back to `query()`.
|
|
52
|
+
|
|
53
|
+
Owned pools from `createNeoOrmClient` accept the same `pool` object as PostgreSQL for shared fields (`max`, idle timeout, keep-alive). Default `max` is 10 (`connectionLimit`). PostgreSQL-only keys such as `statement_timeout` are ignored.
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
const db = createNeoOrmClient(manifest, {
|
|
57
|
+
provider: "mysql",
|
|
58
|
+
connectionString: process.env.MYSQL_URL,
|
|
59
|
+
pool: {
|
|
60
|
+
max: 10,
|
|
61
|
+
idleTimeoutMillis: 10_000,
|
|
62
|
+
keepAlive: true,
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Standalone `neoorm/sql` (`sqlId`) stays ANSI-quoted (`"users"`). Use `db.sql` with `db.sqlId("users")` so identifiers are backticks on MySQL.
|
|
68
|
+
|
|
69
|
+
`mysqlDialect` is exported from `neoorm` for `dbPush`, migrate helpers, and custom executor wiring.
|
|
70
|
+
|
|
71
|
+
## CLI
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
bunx neoorm init --provider mysql
|
|
75
|
+
bunx neoorm migrate deploy
|
|
76
|
+
bunx neoorm db push
|
|
77
|
+
bunx neoorm db pull
|
|
78
|
+
bunx neoorm migrate status
|
|
79
|
+
bunx neoorm migrate reset --force
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
- Deploy lock is `GET_LOCK('neoorm.migrate.<db>', timeout)` / `RELEASE_LOCK` on the deploy transaction.
|
|
83
|
+
- `migrate reset` sets `FOREIGN_KEY_CHECKS=0`, drops all tables in the current database, then re-enables checks and re-applies migrations.
|
|
84
|
+
- `db pull` introspects `information_schema` back into a schema file.
|
|
85
|
+
|
|
86
|
+
## Type mapping
|
|
87
|
+
|
|
88
|
+
| Schema builder | MySQL storage |
|
|
89
|
+
|----------------|---------------|
|
|
90
|
+
| `id()`, unique/PK `text` | `VARCHAR(191)` (InnoDB index prefix; `TEXT` cannot be a unique key) |
|
|
91
|
+
| `text()` otherwise | `TEXT`, or `VARCHAR(n)` with `.maxLength()` |
|
|
92
|
+
| `uuid()` | `CHAR(36)` |
|
|
93
|
+
| `bool()` | `TINYINT(1)` |
|
|
94
|
+
| `int()` | `INT` |
|
|
95
|
+
| `bigint()` | `BIGINT` |
|
|
96
|
+
| `real()` / `float()` | `FLOAT` |
|
|
97
|
+
| `double()` | `DOUBLE` |
|
|
98
|
+
| `serial()` | `INT NOT NULL AUTO_INCREMENT` |
|
|
99
|
+
| `timestamp()` | `DATETIME(6)` + `CURRENT_TIMESTAMP(6)` |
|
|
100
|
+
| `date()` | `DATE` |
|
|
101
|
+
| `time()` | `TIME` |
|
|
102
|
+
| `json()` / `jsonb()` | `JSON` |
|
|
103
|
+
| `decimal()` | `DECIMAL(p,s)` |
|
|
104
|
+
| `money()` | `DECIMAL(19,4)` |
|
|
105
|
+
| `xml()` | `LONGTEXT` |
|
|
106
|
+
| `bytea()` | `BLOB` |
|
|
107
|
+
| arrays / `citext()` | `JSON` / `VARCHAR` + `utf8mb4_0900_ai_ci` |
|
|
108
|
+
| `interval()`, `inet()`, `cidr()`, ranges | rejected at schema compile |
|
|
109
|
+
| `enum: "check"` | `VARCHAR` + `CHECK` (8.0.16+) |
|
|
110
|
+
| `enum: "native"` | column `ENUM('a','b')` |
|
|
111
|
+
|
|
112
|
+
## Differences from PostgreSQL
|
|
113
|
+
|
|
114
|
+
| Feature | PostgreSQL | MySQL |
|
|
115
|
+
|---------|------------|--------|
|
|
116
|
+
| Identifier quoting | `"users"` | `` `users` `` |
|
|
117
|
+
| `RETURNING` | native | follow-up `SELECT` (or `insertId` for serial) |
|
|
118
|
+
| `upsert` | `ON CONFLICT … DO UPDATE` | `INSERT … AS new ON DUPLICATE KEY UPDATE` |
|
|
119
|
+
| `skipDuplicates` | `ON CONFLICT DO NOTHING` | `INSERT IGNORE` |
|
|
120
|
+
| `findOrCreate` | `xmax = 0` | SELECT → INSERT → retry on unique violation |
|
|
121
|
+
| `in` / `notIn` | array bind | `JSON_TABLE` |
|
|
122
|
+
| `search` | POSIX `~` | `REGEXP_LIKE` |
|
|
123
|
+
| `ilike` | `ILIKE` | `LOWER(col) LIKE LOWER(?)` |
|
|
124
|
+
| JSON operators | `@>`, `?`, `#>` | `JSON_CONTAINS` / `JSON_EXTRACT` / `JSON_CONTAINS_PATH` |
|
|
125
|
+
| Nested includes | `json_agg … FILTER` | `JSON_ARRAYAGG(CASE WHEN …)` |
|
|
126
|
+
| `distinct` (`DISTINCT ON`) | supported | throws |
|
|
127
|
+
| Partial indexes `index({ where })` | supported | rejected at schema compile |
|
|
128
|
+
| PostGIS | supported | rejected |
|
|
129
|
+
| `interval` / `inet` / `cidr` / range types | supported | rejected at schema compile |
|
|
130
|
+
| `datasource.schema` | multi-schema | ignored (URL database) |
|
|
131
|
+
|
|
132
|
+
`createManyAndReturn` for serial primary keys uses `LAST_INSERT_ID()` plus row count inside a transaction and assumes consecutive autoincrement values.
|
|
133
|
+
|
|
134
|
+
Everything else — relations, nested writes, cursor pagination, aggregates, `groupBy`, savepoint-based nested transactions — behaves the same as on PostgreSQL.
|
package/docs/plugins.md
CHANGED
|
@@ -48,7 +48,7 @@ users: table({
|
|
|
48
48
|
|
|
49
49
|
See the plugin registry source for `ColumnTypePlugin` and `NeoOrmPlugin` interfaces.
|
|
50
50
|
|
|
51
|
-
Column types may implement `columnValidation(col)` returning a validator-neutral IR node (`{ kind: "string" }`, `{ kind: "enum", values }`, GeoJSON `object` / `union`, and so on). Zod and
|
|
51
|
+
Column types may implement `columnValidation(col)` returning a validator-neutral IR node (`{ kind: "string" }`, `{ kind: "enum", values }`, GeoJSON `object` / `union`, and so on). Zod, TypeBox, and Elysia codegen print that IR. Omit the hook to fall back to `unknown`.
|
|
52
52
|
|
|
53
53
|
To type `where` operators, augment `PluginColumnWhereOperators` on `neoorm/schema` with your column kind:
|
|
54
54
|
|
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. `%` and `_` in the search string are matched literally (`ESCAPE '\'`).
|
|
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
|
|