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.
Files changed (174) hide show
  1. package/README.md +6 -4
  2. package/dist/bin/neoorm.js +28 -1
  3. package/dist/bin/neoorm.js.map +1 -1
  4. package/dist/codegen/generate.d.ts +7 -0
  5. package/dist/codegen/generate.d.ts.map +1 -1
  6. package/dist/codegen/generate.js +18 -2
  7. package/dist/codegen/generate.js.map +1 -1
  8. package/dist/codegen/schema-to-manifest.d.ts +15 -1
  9. package/dist/codegen/schema-to-manifest.d.ts.map +1 -1
  10. package/dist/codegen/schema-to-manifest.js +87 -25
  11. package/dist/codegen/schema-to-manifest.js.map +1 -1
  12. package/dist/config.d.ts +12 -0
  13. package/dist/config.d.ts.map +1 -1
  14. package/dist/config.js +6 -0
  15. package/dist/config.js.map +1 -1
  16. package/dist/docs/pages.d.ts +9 -0
  17. package/dist/docs/pages.d.ts.map +1 -0
  18. package/dist/docs/pages.js +64 -0
  19. package/dist/docs/pages.js.map +1 -0
  20. package/dist/docs/render.d.ts +4 -0
  21. package/dist/docs/render.d.ts.map +1 -0
  22. package/dist/docs/render.js +245 -0
  23. package/dist/docs/render.js.map +1 -0
  24. package/dist/docs/resolve-docs-dir.d.ts +2 -0
  25. package/dist/docs/resolve-docs-dir.d.ts.map +1 -0
  26. package/dist/docs/resolve-docs-dir.js +27 -0
  27. package/dist/docs/resolve-docs-dir.js.map +1 -0
  28. package/dist/docs/server.d.ts +11 -0
  29. package/dist/docs/server.d.ts.map +1 -0
  30. package/dist/docs/server.js +99 -0
  31. package/dist/docs/server.js.map +1 -0
  32. package/dist/index.d.ts +8 -0
  33. package/dist/index.d.ts.map +1 -1
  34. package/dist/index.js +7 -0
  35. package/dist/index.js.map +1 -1
  36. package/dist/init/templates.js +2 -2
  37. package/dist/introspect/pull.d.ts.map +1 -1
  38. package/dist/introspect/pull.js +16 -2
  39. package/dist/introspect/pull.js.map +1 -1
  40. package/dist/migrate/runner.d.ts +4 -0
  41. package/dist/migrate/runner.d.ts.map +1 -1
  42. package/dist/migrate/runner.js +4 -0
  43. package/dist/migrate/runner.js.map +1 -1
  44. package/dist/plugins/builtin.d.ts +24 -0
  45. package/dist/plugins/builtin.d.ts.map +1 -1
  46. package/dist/plugins/builtin.js +21 -0
  47. package/dist/plugins/builtin.js.map +1 -1
  48. package/dist/plugins/index.d.ts +4 -0
  49. package/dist/plugins/index.d.ts.map +1 -1
  50. package/dist/plugins/index.js +4 -0
  51. package/dist/plugins/index.js.map +1 -1
  52. package/dist/plugins/postgis/columns.d.ts +7 -3
  53. package/dist/plugins/postgis/columns.d.ts.map +1 -1
  54. package/dist/plugins/postgis/columns.js +3 -0
  55. package/dist/plugins/postgis/columns.js.map +1 -1
  56. package/dist/plugins/registry.d.ts +1 -0
  57. package/dist/plugins/registry.d.ts.map +1 -1
  58. package/dist/plugins/registry.js +1 -0
  59. package/dist/plugins/registry.js.map +1 -1
  60. package/dist/plugins/types.d.ts +2 -0
  61. package/dist/plugins/types.d.ts.map +1 -1
  62. package/dist/runtime/client.d.ts +27 -1
  63. package/dist/runtime/client.d.ts.map +1 -1
  64. package/dist/runtime/client.js +15 -5
  65. package/dist/runtime/client.js.map +1 -1
  66. package/dist/runtime/error-builders.d.ts +14 -0
  67. package/dist/runtime/error-builders.d.ts.map +1 -0
  68. package/dist/runtime/error-builders.js +54 -0
  69. package/dist/runtime/error-builders.js.map +1 -0
  70. package/dist/runtime/error-hints.d.ts +19 -0
  71. package/dist/runtime/error-hints.d.ts.map +1 -0
  72. package/dist/runtime/error-hints.js +181 -0
  73. package/dist/runtime/error-hints.js.map +1 -0
  74. package/dist/runtime/errors.d.ts +12 -0
  75. package/dist/runtime/errors.d.ts.map +1 -1
  76. package/dist/runtime/errors.js +24 -2
  77. package/dist/runtime/errors.js.map +1 -1
  78. package/dist/runtime/pg-error.d.ts.map +1 -1
  79. package/dist/runtime/pg-error.js +86 -0
  80. package/dist/runtime/pg-error.js.map +1 -1
  81. package/dist/runtime/query/compile.d.ts.map +1 -1
  82. package/dist/runtime/query/compile.js +5 -12
  83. package/dist/runtime/query/compile.js.map +1 -1
  84. package/dist/runtime/query/cursor-codec.d.ts +11 -0
  85. package/dist/runtime/query/cursor-codec.d.ts.map +1 -1
  86. package/dist/runtime/query/cursor-codec.js +11 -0
  87. package/dist/runtime/query/cursor-codec.js.map +1 -1
  88. package/dist/runtime/query/cursor.d.ts.map +1 -1
  89. package/dist/runtime/query/cursor.js +2 -5
  90. package/dist/runtime/query/cursor.js.map +1 -1
  91. package/dist/runtime/query/execute.d.ts.map +1 -1
  92. package/dist/runtime/query/execute.js +28 -9
  93. package/dist/runtime/query/execute.js.map +1 -1
  94. package/dist/runtime/query/group-by.d.ts.map +1 -1
  95. package/dist/runtime/query/group-by.js +3 -7
  96. package/dist/runtime/query/group-by.js.map +1 -1
  97. package/dist/runtime/query/projection.d.ts.map +1 -1
  98. package/dist/runtime/query/projection.js +2 -4
  99. package/dist/runtime/query/projection.js.map +1 -1
  100. package/dist/runtime/query/relation-planner.d.ts.map +1 -1
  101. package/dist/runtime/query/relation-planner.js +6 -1
  102. package/dist/runtime/query/relation-planner.js.map +1 -1
  103. package/dist/runtime/query/strip.d.ts.map +1 -1
  104. package/dist/runtime/query/strip.js +2 -4
  105. package/dist/runtime/query/strip.js.map +1 -1
  106. package/dist/runtime/query/table-index.d.ts +4 -0
  107. package/dist/runtime/query/table-index.d.ts.map +1 -1
  108. package/dist/runtime/query/table-index.js +45 -0
  109. package/dist/runtime/query/table-index.js.map +1 -1
  110. package/dist/runtime/schema-error.d.ts +1 -1
  111. package/dist/runtime/schema-error.d.ts.map +1 -1
  112. package/dist/runtime/schema-error.js +7 -2
  113. package/dist/runtime/schema-error.js.map +1 -1
  114. package/dist/runtime/sqlite-error.d.ts +12 -0
  115. package/dist/runtime/sqlite-error.d.ts.map +1 -0
  116. package/dist/runtime/sqlite-error.js +169 -0
  117. package/dist/runtime/sqlite-error.js.map +1 -0
  118. package/dist/runtime/types.d.ts +8 -0
  119. package/dist/runtime/types.d.ts.map +1 -1
  120. package/dist/schema/column.d.ts +32 -5
  121. package/dist/schema/column.d.ts.map +1 -1
  122. package/dist/schema/column.js +5 -0
  123. package/dist/schema/column.js.map +1 -1
  124. package/dist/schema/define-schema.d.ts +19 -0
  125. package/dist/schema/define-schema.d.ts.map +1 -1
  126. package/dist/schema/define-schema.js +15 -0
  127. package/dist/schema/define-schema.js.map +1 -1
  128. package/dist/schema/index.d.ts +7 -1
  129. package/dist/schema/index.d.ts.map +1 -1
  130. package/dist/schema/index.js +1 -1
  131. package/dist/schema/index.js.map +1 -1
  132. package/dist/schema/many-to-many.d.ts +23 -1
  133. package/dist/schema/many-to-many.d.ts.map +1 -1
  134. package/dist/schema/many-to-many.js +16 -1
  135. package/dist/schema/many-to-many.js.map +1 -1
  136. package/dist/schema/nested-relation-types.d.ts +2 -1
  137. package/dist/schema/nested-relation-types.d.ts.map +1 -1
  138. package/dist/schema/relation-types.d.ts +14 -9
  139. package/dist/schema/relation-types.d.ts.map +1 -1
  140. package/dist/schema/relation.d.ts +34 -2
  141. package/dist/schema/relation.d.ts.map +1 -1
  142. package/dist/schema/relation.js.map +1 -1
  143. package/dist/schema/table.d.ts +20 -0
  144. package/dist/schema/table.d.ts.map +1 -1
  145. package/dist/schema/table.js +3 -0
  146. package/dist/schema/table.js.map +1 -1
  147. package/dist/schema/types.d.ts +7 -0
  148. package/dist/schema/types.d.ts.map +1 -1
  149. package/dist/sql/builder.d.ts +1 -0
  150. package/dist/sql/builder.d.ts.map +1 -1
  151. package/dist/sql/builder.js +1 -0
  152. package/dist/sql/builder.js.map +1 -1
  153. package/dist/sql/index.d.ts +5 -1
  154. package/dist/sql/index.d.ts.map +1 -1
  155. package/dist/sql/index.js +5 -1
  156. package/dist/sql/index.js.map +1 -1
  157. package/dist/sql/template.d.ts +3 -0
  158. package/dist/sql/template.d.ts.map +1 -1
  159. package/dist/sql/template.js +3 -0
  160. package/dist/sql/template.js.map +1 -1
  161. package/docs/api-docstrings.md +43 -0
  162. package/docs/cli.md +107 -0
  163. package/docs/configuration.md +51 -0
  164. package/docs/errors.md +72 -0
  165. package/docs/examples.md +498 -0
  166. package/docs/getting-started.md +122 -0
  167. package/docs/migrations.md +67 -0
  168. package/docs/plugins.md +47 -0
  169. package/docs/queries.md +465 -0
  170. package/docs/relations.md +75 -0
  171. package/docs/schema.md +265 -0
  172. package/docs/sqlite.md +105 -0
  173. package/docs/transactions.md +54 -0
  174. 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.6.2",
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
  }