neoorm 0.6.2 → 0.7.0
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 +6 -4
- package/dist/bin/neoorm.js +28 -1
- package/dist/bin/neoorm.js.map +1 -1
- package/dist/codegen/generate.d.ts +7 -0
- package/dist/codegen/generate.d.ts.map +1 -1
- package/dist/codegen/generate.js +18 -2
- package/dist/codegen/generate.js.map +1 -1
- package/dist/codegen/schema-to-manifest.d.ts +15 -1
- package/dist/codegen/schema-to-manifest.d.ts.map +1 -1
- package/dist/codegen/schema-to-manifest.js +87 -25
- package/dist/codegen/schema-to-manifest.js.map +1 -1
- package/dist/config.d.ts +12 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +6 -0
- package/dist/config.js.map +1 -1
- package/dist/docs/pages.d.ts +9 -0
- package/dist/docs/pages.d.ts.map +1 -0
- package/dist/docs/pages.js +64 -0
- package/dist/docs/pages.js.map +1 -0
- package/dist/docs/render.d.ts +4 -0
- package/dist/docs/render.d.ts.map +1 -0
- package/dist/docs/render.js +245 -0
- package/dist/docs/render.js.map +1 -0
- package/dist/docs/resolve-docs-dir.d.ts +2 -0
- package/dist/docs/resolve-docs-dir.d.ts.map +1 -0
- package/dist/docs/resolve-docs-dir.js +27 -0
- package/dist/docs/resolve-docs-dir.js.map +1 -0
- package/dist/docs/server.d.ts +11 -0
- package/dist/docs/server.d.ts.map +1 -0
- package/dist/docs/server.js +99 -0
- package/dist/docs/server.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -1
- package/dist/init/templates.js +2 -2
- package/dist/introspect/pull.d.ts.map +1 -1
- package/dist/introspect/pull.js +16 -2
- package/dist/introspect/pull.js.map +1 -1
- package/dist/migrate/runner.d.ts +4 -0
- package/dist/migrate/runner.d.ts.map +1 -1
- package/dist/migrate/runner.js +4 -0
- package/dist/migrate/runner.js.map +1 -1
- package/dist/plugins/builtin.d.ts +24 -0
- package/dist/plugins/builtin.d.ts.map +1 -1
- package/dist/plugins/builtin.js +21 -0
- package/dist/plugins/builtin.js.map +1 -1
- package/dist/plugins/index.d.ts +4 -0
- package/dist/plugins/index.d.ts.map +1 -1
- package/dist/plugins/index.js +4 -0
- package/dist/plugins/index.js.map +1 -1
- package/dist/plugins/postgis/columns.d.ts +7 -3
- package/dist/plugins/postgis/columns.d.ts.map +1 -1
- package/dist/plugins/postgis/columns.js +3 -0
- package/dist/plugins/postgis/columns.js.map +1 -1
- package/dist/plugins/registry.d.ts +1 -0
- package/dist/plugins/registry.d.ts.map +1 -1
- package/dist/plugins/registry.js +1 -0
- package/dist/plugins/registry.js.map +1 -1
- package/dist/plugins/types.d.ts +2 -0
- package/dist/plugins/types.d.ts.map +1 -1
- package/dist/runtime/client.d.ts +27 -1
- package/dist/runtime/client.d.ts.map +1 -1
- package/dist/runtime/client.js +15 -5
- package/dist/runtime/client.js.map +1 -1
- package/dist/runtime/error-builders.d.ts +14 -0
- package/dist/runtime/error-builders.d.ts.map +1 -0
- package/dist/runtime/error-builders.js +54 -0
- package/dist/runtime/error-builders.js.map +1 -0
- package/dist/runtime/error-hints.d.ts +19 -0
- package/dist/runtime/error-hints.d.ts.map +1 -0
- package/dist/runtime/error-hints.js +181 -0
- package/dist/runtime/error-hints.js.map +1 -0
- package/dist/runtime/errors.d.ts +12 -0
- package/dist/runtime/errors.d.ts.map +1 -1
- package/dist/runtime/errors.js +24 -2
- package/dist/runtime/errors.js.map +1 -1
- package/dist/runtime/pg-error.d.ts.map +1 -1
- package/dist/runtime/pg-error.js +86 -0
- package/dist/runtime/pg-error.js.map +1 -1
- package/dist/runtime/query/compile.d.ts.map +1 -1
- package/dist/runtime/query/compile.js +5 -12
- package/dist/runtime/query/compile.js.map +1 -1
- package/dist/runtime/query/cursor-codec.d.ts +11 -0
- package/dist/runtime/query/cursor-codec.d.ts.map +1 -1
- package/dist/runtime/query/cursor-codec.js +11 -0
- package/dist/runtime/query/cursor-codec.js.map +1 -1
- package/dist/runtime/query/cursor.d.ts.map +1 -1
- package/dist/runtime/query/cursor.js +2 -5
- package/dist/runtime/query/cursor.js.map +1 -1
- package/dist/runtime/query/execute.d.ts.map +1 -1
- package/dist/runtime/query/execute.js +28 -9
- package/dist/runtime/query/execute.js.map +1 -1
- package/dist/runtime/query/group-by.d.ts.map +1 -1
- package/dist/runtime/query/group-by.js +3 -7
- package/dist/runtime/query/group-by.js.map +1 -1
- package/dist/runtime/query/projection.d.ts.map +1 -1
- package/dist/runtime/query/projection.js +2 -4
- package/dist/runtime/query/projection.js.map +1 -1
- package/dist/runtime/query/relation-planner.d.ts.map +1 -1
- package/dist/runtime/query/relation-planner.js +6 -1
- package/dist/runtime/query/relation-planner.js.map +1 -1
- package/dist/runtime/query/strip.d.ts.map +1 -1
- package/dist/runtime/query/strip.js +2 -4
- package/dist/runtime/query/strip.js.map +1 -1
- package/dist/runtime/query/table-index.d.ts +4 -0
- package/dist/runtime/query/table-index.d.ts.map +1 -1
- package/dist/runtime/query/table-index.js +45 -0
- package/dist/runtime/query/table-index.js.map +1 -1
- package/dist/runtime/schema-error.d.ts +1 -1
- package/dist/runtime/schema-error.d.ts.map +1 -1
- package/dist/runtime/schema-error.js +7 -2
- package/dist/runtime/schema-error.js.map +1 -1
- package/dist/runtime/sqlite-error.d.ts +12 -0
- package/dist/runtime/sqlite-error.d.ts.map +1 -0
- package/dist/runtime/sqlite-error.js +169 -0
- package/dist/runtime/sqlite-error.js.map +1 -0
- package/dist/runtime/types.d.ts +8 -0
- package/dist/runtime/types.d.ts.map +1 -1
- package/dist/schema/column.d.ts +32 -5
- package/dist/schema/column.d.ts.map +1 -1
- package/dist/schema/column.js +5 -0
- package/dist/schema/column.js.map +1 -1
- package/dist/schema/define-schema.d.ts +19 -0
- package/dist/schema/define-schema.d.ts.map +1 -1
- package/dist/schema/define-schema.js +15 -0
- package/dist/schema/define-schema.js.map +1 -1
- package/dist/schema/index.d.ts +7 -1
- package/dist/schema/index.d.ts.map +1 -1
- package/dist/schema/index.js +1 -1
- package/dist/schema/index.js.map +1 -1
- package/dist/schema/many-to-many.d.ts +23 -1
- package/dist/schema/many-to-many.d.ts.map +1 -1
- package/dist/schema/many-to-many.js +16 -1
- package/dist/schema/many-to-many.js.map +1 -1
- package/dist/schema/nested-relation-types.d.ts +2 -1
- package/dist/schema/nested-relation-types.d.ts.map +1 -1
- package/dist/schema/relation-types.d.ts +14 -9
- package/dist/schema/relation-types.d.ts.map +1 -1
- package/dist/schema/relation.d.ts +34 -2
- package/dist/schema/relation.d.ts.map +1 -1
- package/dist/schema/relation.js.map +1 -1
- package/dist/schema/table.d.ts +20 -0
- package/dist/schema/table.d.ts.map +1 -1
- package/dist/schema/table.js +3 -0
- package/dist/schema/table.js.map +1 -1
- package/dist/schema/types.d.ts +7 -0
- package/dist/schema/types.d.ts.map +1 -1
- package/dist/sql/builder.d.ts +1 -0
- package/dist/sql/builder.d.ts.map +1 -1
- package/dist/sql/builder.js +1 -0
- package/dist/sql/builder.js.map +1 -1
- package/dist/sql/index.d.ts +5 -1
- package/dist/sql/index.d.ts.map +1 -1
- package/dist/sql/index.js +5 -1
- package/dist/sql/index.js.map +1 -1
- package/dist/sql/template.d.ts +3 -0
- package/dist/sql/template.d.ts.map +1 -1
- package/dist/sql/template.js +3 -0
- package/dist/sql/template.js.map +1 -1
- package/docs/api-docstrings.md +43 -0
- package/docs/cli.md +107 -0
- package/docs/configuration.md +51 -0
- package/docs/errors.md +72 -0
- package/docs/examples.md +498 -0
- package/docs/getting-started.md +122 -0
- package/docs/migrations.md +67 -0
- package/docs/plugins.md +47 -0
- package/docs/queries.md +465 -0
- package/docs/relations.md +75 -0
- package/docs/schema.md +265 -0
- package/docs/sqlite.md +105 -0
- package/docs/transactions.md +54 -0
- package/package.json +3 -1
package/docs/schema.md
ADDED
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
# Schema DSL
|
|
2
|
+
|
|
3
|
+
NeoOrm **0.6** 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
|
+
|
|
5
|
+
## Tables and accessors
|
|
6
|
+
|
|
7
|
+
`defineSchema` keys are TypeScript accessors. By default the SQL table name equals the accessor:
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import { defineSchema, fk, id, table, text, timestamps, uuid } from "neoorm/schema";
|
|
11
|
+
|
|
12
|
+
export const schema = defineSchema({
|
|
13
|
+
users: table({
|
|
14
|
+
id: uuid().primary(),
|
|
15
|
+
email: text().notNull().unique(),
|
|
16
|
+
...timestamps(),
|
|
17
|
+
}),
|
|
18
|
+
|
|
19
|
+
postTags: table("post_tags", {
|
|
20
|
+
postId: fk("posts").primary(),
|
|
21
|
+
tagId: fk("tags").primary(),
|
|
22
|
+
}),
|
|
23
|
+
});
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
- `table({ columns, extras? })` — SQL name is filled in from the accessor (`users` → `"users"`).
|
|
27
|
+
- `table("sql_name", { columns }, extras?)` — override the SQL name (`postTags` → `"post_tags"`).
|
|
28
|
+
|
|
29
|
+
Column field names use camelCase in TypeScript. By default SQL column names are snake_case (`createdAt` → `created_at`). See [column naming](#column-naming-strategy).
|
|
30
|
+
|
|
31
|
+
## Column types
|
|
32
|
+
|
|
33
|
+
| Builder | SQL type | TypeScript | Notes |
|
|
34
|
+
|---------|----------|------------|-------|
|
|
35
|
+
| `id()` | `TEXT` | `string` | App-generated `{prefix}_{uuid}` primary key |
|
|
36
|
+
| `uuid()` | `UUID` | `string` | Defaults to UUID v7; `{ version: 4 }` for v4 |
|
|
37
|
+
| `uuid().primary()` | `UUID` | `string` | Primary key; auto-generated on create if omitted |
|
|
38
|
+
| `serial()` | `INTEGER GENERATED BY DEFAULT AS IDENTITY` | `number` | Auto-increment |
|
|
39
|
+
| `serial().primary()` | same | `number` | Integer PK |
|
|
40
|
+
| `text()` | `TEXT` | `string \| null` | |
|
|
41
|
+
| `bool()` | `BOOLEAN` | `boolean \| null` | |
|
|
42
|
+
| `int()` | `INTEGER` | `number \| null` | |
|
|
43
|
+
| `bigint()` | `BIGINT` | `bigint \| null` | Maps to `TEXT` on SQLite |
|
|
44
|
+
| `timestamp()` | `TIMESTAMPTZ` | `Date \| null` | `.defaultNow()` / `.updatedAt()` on timestamp builders only |
|
|
45
|
+
| `json()` / `jsonb()` | `JSON` / `JSONB` | `unknown \| null` | Generic: `jsonb<MyType>()` |
|
|
46
|
+
| `decimal()` / `numeric()` | `NUMERIC` | `string \| null` | Use strings to avoid float loss |
|
|
47
|
+
| `enumType(["a", "b"])` | mode-dependent | union literals | See [Enum columns](#enum-columns) |
|
|
48
|
+
| `bytea()` | `BYTEA` | `Buffer \| null` | |
|
|
49
|
+
| `textArray()` / `intArray()` | arrays | arrays \| null | |
|
|
50
|
+
| `citext()` | `CITEXT` | `string \| null` | Requires `citext` extension |
|
|
51
|
+
|
|
52
|
+
All column builders support `.notNull()`, `.unique()`, `.default(value)`, `.primary()`, `.map(name)`, `.hidden()`, `.index()`, and `.check("sql expression")`.
|
|
53
|
+
|
|
54
|
+
`.hidden()` marks a column as sensitive for API responses. Query results expose `.strip()` on each row to remove hidden fields before returning data.
|
|
55
|
+
|
|
56
|
+
### Audit timestamps
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
...timestamps(),
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
`timestamps()` expands to `createdAt` and `updatedAt` — both `notNull`, `defaultNow`, with `updatedAt` auto-updated by the ORM on writes. Or define columns manually:
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
createdAt: timestamp().notNull().defaultNow(),
|
|
66
|
+
updatedAt: timestamp().notNull().defaultNow().updatedAt(),
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Foreign keys
|
|
70
|
+
|
|
71
|
+
`fk()` targets are **schema accessors**, never SQL table names.
|
|
72
|
+
|
|
73
|
+
| Target | Example | Resolves to |
|
|
74
|
+
|--------|---------|-------------|
|
|
75
|
+
| accessor | `fk("users")` | target table PK |
|
|
76
|
+
| accessor + column | `fk("users.id")` | that column |
|
|
77
|
+
| hoisted table | `fk(users)` | target PK |
|
|
78
|
+
| hoisted column | `fk(users.id)` | that column |
|
|
79
|
+
|
|
80
|
+
```ts
|
|
81
|
+
posts: table({
|
|
82
|
+
id: id(),
|
|
83
|
+
authorId: fk("users")
|
|
84
|
+
.notNull()
|
|
85
|
+
.index()
|
|
86
|
+
.onDelete("restrict"),
|
|
87
|
+
}),
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Relation names are inferred — no `.inverse()` needed on the happy path:
|
|
91
|
+
|
|
92
|
+
- `authorId` → `author` on `posts` (`with: { author: true }`)
|
|
93
|
+
- inverse on `users` → `posts` (`with: { posts: true }`)
|
|
94
|
+
|
|
95
|
+
Fluent modifiers (no options bag):
|
|
96
|
+
|
|
97
|
+
| Method | Effect |
|
|
98
|
+
|--------|--------|
|
|
99
|
+
| `.as("author")` | override relation name on this table (default: strip `Id` from column name) |
|
|
100
|
+
| `.inverse("articles")` | override relation name on the target table (rare — see defaults below) |
|
|
101
|
+
| `.onDelete("cascade" \| "restrict" \| "set null" \| "no action")` | FK action |
|
|
102
|
+
| `.notNull()` | `NOT NULL` |
|
|
103
|
+
| `.unique()` | `UNIQUE` — unique FKs infer a **singular** inverse (`profiles.userId` → `users.profile`) |
|
|
104
|
+
| `.primary()` | part of composite PK |
|
|
105
|
+
| `.index()` | single-column index |
|
|
106
|
+
|
|
107
|
+
Default inference when `.as()` / `.inverse()` are omitted:
|
|
108
|
+
|
|
109
|
+
- **this table:** `authorId` → `author` (strip trailing `Id`)
|
|
110
|
+
- **target table (to-many):** source accessor (`posts.authorId` → `users.posts`)
|
|
111
|
+
- **target table (unique to-one):** singular of source accessor (`profiles.userId` → `users.profile`)
|
|
112
|
+
|
|
113
|
+
### Composite primary keys with FKs
|
|
114
|
+
|
|
115
|
+
```ts
|
|
116
|
+
postTags: table("post_tags", {
|
|
117
|
+
postId: fk("posts").primary(),
|
|
118
|
+
tagId: fk("tags").primary(),
|
|
119
|
+
}),
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### Self-relations
|
|
123
|
+
|
|
124
|
+
```ts
|
|
125
|
+
comments: table({
|
|
126
|
+
id: id(),
|
|
127
|
+
parentId: fk("comments").as("parent").inverse("children"),
|
|
128
|
+
body: text().notNull(),
|
|
129
|
+
}),
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### Hoisted table references
|
|
133
|
+
|
|
134
|
+
```ts
|
|
135
|
+
const users = table({ id: uuid().primary() });
|
|
136
|
+
|
|
137
|
+
export const schema = defineSchema({
|
|
138
|
+
users,
|
|
139
|
+
posts: table({
|
|
140
|
+
authorId: fk(users).notNull().index(),
|
|
141
|
+
}),
|
|
142
|
+
});
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Column checks
|
|
146
|
+
|
|
147
|
+
`.check("price >= 0")` stores a SQL expression on the column (combined with enum `check` mode when both apply):
|
|
148
|
+
|
|
149
|
+
```ts
|
|
150
|
+
price: decimal({ precision: 10, scale: 2 }).notNull().check("price >= 0"),
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## Indexes and composite keys
|
|
154
|
+
|
|
155
|
+
Single-column indexes: `.index()` on a column.
|
|
156
|
+
|
|
157
|
+
Composite constraints use an **array** extras callback:
|
|
158
|
+
|
|
159
|
+
```ts
|
|
160
|
+
posts: table(
|
|
161
|
+
{
|
|
162
|
+
id: id(),
|
|
163
|
+
authorId: fk("users").notNull(),
|
|
164
|
+
title: text().notNull(),
|
|
165
|
+
views: int().notNull().default(0),
|
|
166
|
+
},
|
|
167
|
+
(t) => [unique(t.authorId, t.title), index(t.views)],
|
|
168
|
+
),
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
Helpers: `unique(...cols)`, `index(...cols)`, `primaryKey(...cols)`.
|
|
172
|
+
|
|
173
|
+
### Partial indexes
|
|
174
|
+
|
|
175
|
+
```ts
|
|
176
|
+
(t) => [index(t.title).where({ published: true })],
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
Equality map of column refs → values; compiled to `WHERE "published" = true` (or `= 1` on SQLite).
|
|
180
|
+
|
|
181
|
+
## Many-to-many
|
|
182
|
+
|
|
183
|
+
Declare a virtual column on the source table:
|
|
184
|
+
|
|
185
|
+
```ts
|
|
186
|
+
posts: table({
|
|
187
|
+
id: id(),
|
|
188
|
+
title: text().notNull(),
|
|
189
|
+
tags: many("tags"),
|
|
190
|
+
}),
|
|
191
|
+
tags: table({ id: id(), slug: text().notNull().unique() }),
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
- `"tags"` is the **target accessor**.
|
|
195
|
+
- Junction table is auto-generated when `through` is omitted.
|
|
196
|
+
- SQL name: alpha-sorted SQL table names joined with `_` (e.g. `posts_tags` — no leading `_`).
|
|
197
|
+
- Accessor equals that identifier (`posts_tags`).
|
|
198
|
+
|
|
199
|
+
Reuse an existing junction by **accessor**:
|
|
200
|
+
|
|
201
|
+
```ts
|
|
202
|
+
posts: table({
|
|
203
|
+
tags: many("tags", {
|
|
204
|
+
through: "postTags",
|
|
205
|
+
leftKey: "postId",
|
|
206
|
+
rightKey: "tagId",
|
|
207
|
+
}),
|
|
208
|
+
}),
|
|
209
|
+
postTags: table("post_tags", {
|
|
210
|
+
postId: fk("posts").primary(),
|
|
211
|
+
tagId: fk("tags").primary(),
|
|
212
|
+
}),
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
`as` / `inverse` override relation names on source and target tables.
|
|
216
|
+
|
|
217
|
+
Virtual M2M columns are relations only — they appear in `with`, relation filters, and relation writes, not in scalar `select`/`where`/`create` data.
|
|
218
|
+
|
|
219
|
+
## Column naming strategy
|
|
220
|
+
|
|
221
|
+
```ts
|
|
222
|
+
export const schema = defineSchema(
|
|
223
|
+
{
|
|
224
|
+
users: table({ emailAddress: text().notNull() }), // SQL: email_address
|
|
225
|
+
legacyUsers: table(
|
|
226
|
+
"legacy_users",
|
|
227
|
+
{ emailAddress: text().notNull() },
|
|
228
|
+
{ columnNaming: "camelCase" },
|
|
229
|
+
),
|
|
230
|
+
},
|
|
231
|
+
{ columnNaming: "snakeCase" },
|
|
232
|
+
);
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
## Custom SQL column names (`.map()`)
|
|
236
|
+
|
|
237
|
+
```ts
|
|
238
|
+
emailAddress: text().notNull().map("email"),
|
|
239
|
+
authorId: fk("users").as("author").map("author_ref"),
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
## Enum columns
|
|
243
|
+
|
|
244
|
+
```ts
|
|
245
|
+
status: enumType(["draft", "published", "archived"])
|
|
246
|
+
.notNull()
|
|
247
|
+
.default("draft"),
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
`datasource.enum` in config: `check` (default), `union`, or `native` (PostgreSQL only).
|
|
251
|
+
|
|
252
|
+
## PostgreSQL extensions
|
|
253
|
+
|
|
254
|
+
```ts
|
|
255
|
+
export const schema = defineSchema(
|
|
256
|
+
{ /* tables */ },
|
|
257
|
+
{ extensions: ["uuid-ossp", "pg_trgm"] },
|
|
258
|
+
);
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
Column type plugins still register required extensions automatically.
|
|
262
|
+
|
|
263
|
+
## SQLite type mapping
|
|
264
|
+
|
|
265
|
+
Same DSL; see [SQLite](sqlite.md) for storage types and limitations.
|
package/docs/sqlite.md
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# SQLite
|
|
2
|
+
|
|
3
|
+
SQLite is a first-class dialect for local development, testing, and single-file deployments. Everything in the schema DSL, codegen, migrations, and query client works against SQLite with no extra database server.
|
|
4
|
+
|
|
5
|
+
## Requirements
|
|
6
|
+
|
|
7
|
+
No SQLite package is required — NeoOrm uses the driver bundled with your runtime:
|
|
8
|
+
|
|
9
|
+
| Runtime | Driver |
|
|
10
|
+
|---------|--------|
|
|
11
|
+
| Node.js 22.5+ | built-in `node:sqlite` (`DatabaseSync`) |
|
|
12
|
+
| Bun | `bun:sqlite` |
|
|
13
|
+
| Anything else | pass your own `db` instance (see [Custom driver](#custom-driver)) |
|
|
14
|
+
|
|
15
|
+
If none is available, creating a client throws: `No SQLite driver available. Provide a db instance, or run on Bun or Node.js 22.5+`.
|
|
16
|
+
|
|
17
|
+
## Configuration
|
|
18
|
+
|
|
19
|
+
Set `provider: "sqlite"` and point `url` at a file path or `:memory:`:
|
|
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: "sqlite",
|
|
30
|
+
url: "./dev.db", // or ":memory:"
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
`datasource.schema` and `datasource.enum: "native"` are PostgreSQL-only and ignored/unsupported on SQLite. Enums always use the `check` (or `union`) TEXT storage mode.
|
|
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: "sqlite",
|
|
45
|
+
databasePath: "./dev.db",
|
|
46
|
+
});
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Use `databasePath: ":memory:"` for an in-memory database (one connection, no persistence).
|
|
50
|
+
|
|
51
|
+
### Custom driver
|
|
52
|
+
|
|
53
|
+
Pass any object implementing `prepare(sql)`, `exec(sql)`, and `close()` — the `node:sqlite` and `bun:sqlite` APIs both match. Useful for sandboxed runtimes or existing database handles:
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
import { DatabaseSync } from "node:sqlite";
|
|
57
|
+
import { createNeoOrmClient } from "neoorm";
|
|
58
|
+
import { manifest } from "./neoorm/manifest.js";
|
|
59
|
+
|
|
60
|
+
const database = new DatabaseSync("./dev.db");
|
|
61
|
+
const db = createNeoOrmClient(manifest, { db: database });
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## CLI
|
|
65
|
+
|
|
66
|
+
The full CLI works against SQLite:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
bunx neoorm migrate deploy
|
|
70
|
+
bunx neoorm db push
|
|
71
|
+
bunx neoorm db pull
|
|
72
|
+
bunx neoorm migrate status
|
|
73
|
+
bunx neoorm migrate reset --force
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
- The migration ledger table is `_neoorm_migrations` (`id INTEGER PRIMARY KEY AUTOINCREMENT`).
|
|
77
|
+
- `migrate reset` drops all non-`sqlite_` tables (there is no schema concept).
|
|
78
|
+
- `db pull` introspects `sqlite_master` back into a schema file.
|
|
79
|
+
|
|
80
|
+
## Type mapping
|
|
81
|
+
|
|
82
|
+
| Schema builder | SQLite storage |
|
|
83
|
+
|----------------|----------------|
|
|
84
|
+
| `id`, `text`, `uuid`, `json`, `jsonb`, `decimal`, `textArray`, `intArray`, `citext`, `enumType` | `TEXT` |
|
|
85
|
+
| `int`, `serial` | `INTEGER` |
|
|
86
|
+
| `bigint` | `TEXT` |
|
|
87
|
+
| `serial().primary()` | `INTEGER PRIMARY KEY AUTOINCREMENT` |
|
|
88
|
+
| `bool` | `BOOLEAN` (stored as 0/1) |
|
|
89
|
+
| `timestamp` | `TIMESTAMPTZ` (stored as ISO-8601 text) |
|
|
90
|
+
| `bytea` | `BLOB` |
|
|
91
|
+
| `fk` | the target column's type (default `TEXT`) |
|
|
92
|
+
|
|
93
|
+
`defaultNow()` compiles to `CURRENT_TIMESTAMP`. Structural table changes that SQLite cannot do in place (column type changes, FK changes) are applied via a table-rebuild strategy: create `__neoorm_<table>_new`, copy rows, drop the old table, rename.
|
|
94
|
+
|
|
95
|
+
## Differences from PostgreSQL
|
|
96
|
+
|
|
97
|
+
| Feature | PostgreSQL | SQLite |
|
|
98
|
+
|---------|-----------|--------|
|
|
99
|
+
| `distinct` (`DISTINCT ON`) | supported | throws `distinct is not supported on SQLite` |
|
|
100
|
+
| `datasource.schema` | multi-schema | not applicable |
|
|
101
|
+
| `enum: "native"` | `CREATE TYPE ... AS ENUM` | not applicable (TEXT + CHECK) |
|
|
102
|
+
| transaction options (`readOnly`, `isolationLevel`) | full | outer `BEGIN` only; nested transactions use savepoints |
|
|
103
|
+
| JSON operators | `@>`, `?`, path ops | implemented with `json_each` / `json_object` |
|
|
104
|
+
|
|
105
|
+
Everything else — relations, nested writes, cursor pagination, aggregates, `groupBy`, upsert, `findOrCreate`, savepoint-based nested transactions — behaves identically.
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Transactions
|
|
2
|
+
|
|
3
|
+
## Interactive callback
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
await db.$transaction(async (tx) => {
|
|
7
|
+
const user = await tx.users.create({ data: { email: "a@b.com" } });
|
|
8
|
+
await tx.posts.create({
|
|
9
|
+
data: { title: "Hello", authorId: user.id },
|
|
10
|
+
});
|
|
11
|
+
});
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Batch steps
|
|
15
|
+
|
|
16
|
+
Sequential, one transaction:
|
|
17
|
+
|
|
18
|
+
```ts
|
|
19
|
+
const [user, post] = await db.$transaction([
|
|
20
|
+
(tx) => tx.users.create({ data: { email: "a@b.com" } }),
|
|
21
|
+
(tx) => tx.posts.create({ data: { title: "Hello" } }),
|
|
22
|
+
]);
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Options
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
await db.$transaction(fn, {
|
|
29
|
+
isolationLevel: "Serializable", // ReadUncommitted | ReadCommitted | RepeatableRead | Serializable
|
|
30
|
+
readOnly: true,
|
|
31
|
+
});
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Nested transactions
|
|
35
|
+
|
|
36
|
+
Uses database savepoints on the same connection. A nested failure rolls back only that block; the outer transaction can continue.
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
await db.$transaction(async (tx) => {
|
|
40
|
+
await tx.users.create({ data: { email: "outer@example.com" } });
|
|
41
|
+
|
|
42
|
+
await tx.$transaction(async (nested) => {
|
|
43
|
+
await nested.posts.create({
|
|
44
|
+
data: { title: "Nested", body: "...", authorId: "user_1" },
|
|
45
|
+
});
|
|
46
|
+
}).catch(() => undefined);
|
|
47
|
+
|
|
48
|
+
// Outer writes are kept even if the nested block failed.
|
|
49
|
+
});
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Nested `create` calls inside a transaction do not start a separate transaction. `readOnly` and `isolationLevel` apply only to the outermost `BEGIN`.
|
|
53
|
+
|
|
54
|
+
On SQLite, nested transactions use `SAVEPOINT`/`RELEASE`/`ROLLBACK TO SAVEPOINT`; `readOnly` and `isolationLevel` are rejected on nested transactions (mapped to `BEGIN`/`BEGIN IMMEDIATE`/`BEGIN DEFERRED` on the outer transaction).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "neoorm",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "TypeScript-first PostgreSQL and SQLite ORM with schema DSL, codegen, and typed relations",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -58,6 +58,7 @@
|
|
|
58
58
|
},
|
|
59
59
|
"files": [
|
|
60
60
|
"dist",
|
|
61
|
+
"docs",
|
|
61
62
|
"README.md",
|
|
62
63
|
"LICENSE"
|
|
63
64
|
],
|
|
@@ -90,6 +91,7 @@
|
|
|
90
91
|
},
|
|
91
92
|
"dependencies": {
|
|
92
93
|
"commander": "^15.0.0",
|
|
94
|
+
"marked": "^15.0.12",
|
|
93
95
|
"pg": "^8.22.0",
|
|
94
96
|
"tsx": "^4.23.0"
|
|
95
97
|
}
|