neoorm 0.7.4 → 0.8.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 (361) hide show
  1. package/README.md +4 -4
  2. package/dist/bin/neoorm.js +36 -27
  3. package/dist/bin/neoorm.js.map +1 -1
  4. package/dist/codegen/diff-manifest.d.ts.map +1 -1
  5. package/dist/codegen/diff-manifest.js +55 -30
  6. package/dist/codegen/diff-manifest.js.map +1 -1
  7. package/dist/codegen/emit-includes.d.ts.map +1 -1
  8. package/dist/codegen/emit-includes.js +6 -6
  9. package/dist/codegen/emit-includes.js.map +1 -1
  10. package/dist/codegen/emit-models.d.ts.map +1 -1
  11. package/dist/codegen/emit-models.js +14 -14
  12. package/dist/codegen/emit-models.js.map +1 -1
  13. package/dist/codegen/generate.d.ts +11 -1
  14. package/dist/codegen/generate.d.ts.map +1 -1
  15. package/dist/codegen/generate.js +30 -11
  16. package/dist/codegen/generate.js.map +1 -1
  17. package/dist/codegen/manifest-relations.d.ts +2 -1
  18. package/dist/codegen/manifest-relations.d.ts.map +1 -1
  19. package/dist/codegen/manifest-relations.js +4 -27
  20. package/dist/codegen/manifest-relations.js.map +1 -1
  21. package/dist/codegen/schema-to-manifest.d.ts +3 -2
  22. package/dist/codegen/schema-to-manifest.d.ts.map +1 -1
  23. package/dist/codegen/schema-to-manifest.js +9 -38
  24. package/dist/codegen/schema-to-manifest.js.map +1 -1
  25. package/dist/config.d.ts +6 -2
  26. package/dist/config.d.ts.map +1 -1
  27. package/dist/config.js +21 -13
  28. package/dist/config.js.map +1 -1
  29. package/dist/datasource-provider.d.ts +7 -0
  30. package/dist/datasource-provider.d.ts.map +1 -0
  31. package/dist/datasource-provider.js +11 -0
  32. package/dist/datasource-provider.js.map +1 -0
  33. package/dist/dialect/fk.d.ts.map +1 -1
  34. package/dist/dialect/fk.js +4 -2
  35. package/dist/dialect/fk.js.map +1 -1
  36. package/dist/dialect/postgres.d.ts.map +1 -1
  37. package/dist/dialect/postgres.js +11 -15
  38. package/dist/dialect/postgres.js.map +1 -1
  39. package/dist/dialect/shared.d.ts +3 -1
  40. package/dist/dialect/shared.d.ts.map +1 -1
  41. package/dist/dialect/shared.js +4 -0
  42. package/dist/dialect/shared.js.map +1 -1
  43. package/dist/dialect/sqlite.d.ts.map +1 -1
  44. package/dist/dialect/sqlite.js +15 -18
  45. package/dist/dialect/sqlite.js.map +1 -1
  46. package/dist/dialect/sqlite.test.d.ts +2 -0
  47. package/dist/dialect/sqlite.test.d.ts.map +1 -0
  48. package/dist/dialect/sqlite.test.js +122 -0
  49. package/dist/dialect/sqlite.test.js.map +1 -0
  50. package/dist/dialect/types.d.ts +2 -1
  51. package/dist/dialect/types.d.ts.map +1 -1
  52. package/dist/docs/render.d.ts.map +1 -1
  53. package/dist/docs/render.js.map +1 -1
  54. package/dist/docs/search.d.ts.map +1 -1
  55. package/dist/docs/search.js +4 -4
  56. package/dist/docs/search.js.map +1 -1
  57. package/dist/docs/server.d.ts.map +1 -1
  58. package/dist/docs/server.js +3 -3
  59. package/dist/docs/server.js.map +1 -1
  60. package/dist/index.d.ts +12 -10
  61. package/dist/index.d.ts.map +1 -1
  62. package/dist/index.js +5 -4
  63. package/dist/index.js.map +1 -1
  64. package/dist/init/scaffold.d.ts.map +1 -1
  65. package/dist/init/scaffold.js +3 -1
  66. package/dist/init/scaffold.js.map +1 -1
  67. package/dist/introspect/pull.d.ts.map +1 -1
  68. package/dist/introspect/pull.js +209 -154
  69. package/dist/introspect/pull.js.map +1 -1
  70. package/dist/introspect/queries.d.ts +6 -0
  71. package/dist/introspect/queries.d.ts.map +1 -1
  72. package/dist/introspect/queries.js +19 -0
  73. package/dist/introspect/queries.js.map +1 -1
  74. package/dist/introspect/sqlite/to-manifest.d.ts.map +1 -1
  75. package/dist/introspect/sqlite/to-manifest.js +6 -9
  76. package/dist/introspect/sqlite/to-manifest.js.map +1 -1
  77. package/dist/introspect/to-manifest.d.ts +8 -1
  78. package/dist/introspect/to-manifest.d.ts.map +1 -1
  79. package/dist/introspect/to-manifest.js +96 -21
  80. package/dist/introspect/to-manifest.js.map +1 -1
  81. package/dist/migrate/runner.d.ts +16 -0
  82. package/dist/migrate/runner.d.ts.map +1 -1
  83. package/dist/migrate/runner.js +147 -24
  84. package/dist/migrate/runner.js.map +1 -1
  85. package/dist/plugins/builtin.d.ts.map +1 -1
  86. package/dist/plugins/builtin.js +15 -16
  87. package/dist/plugins/builtin.js.map +1 -1
  88. package/dist/plugins/json/operators.d.ts.map +1 -1
  89. package/dist/plugins/json/operators.js +100 -27
  90. package/dist/plugins/json/operators.js.map +1 -1
  91. package/dist/plugins/postgis/columns.d.ts +18 -4
  92. package/dist/plugins/postgis/columns.d.ts.map +1 -1
  93. package/dist/plugins/postgis/columns.js +69 -10
  94. package/dist/plugins/postgis/columns.js.map +1 -1
  95. package/dist/plugins/postgis/columns.test.d.ts +2 -0
  96. package/dist/plugins/postgis/columns.test.d.ts.map +1 -0
  97. package/dist/plugins/postgis/columns.test.js +67 -0
  98. package/dist/plugins/postgis/columns.test.js.map +1 -0
  99. package/dist/plugins/postgis/index.d.ts +4 -2
  100. package/dist/plugins/postgis/index.d.ts.map +1 -1
  101. package/dist/plugins/postgis/index.js +2 -1
  102. package/dist/plugins/postgis/index.js.map +1 -1
  103. package/dist/plugins/postgis/where-types.d.ts +18 -0
  104. package/dist/plugins/postgis/where-types.d.ts.map +1 -0
  105. package/dist/plugins/postgis/where-types.js +2 -0
  106. package/dist/plugins/postgis/where-types.js.map +1 -0
  107. package/dist/plugins/registry.d.ts.map +1 -1
  108. package/dist/plugins/registry.js +20 -5
  109. package/dist/plugins/registry.js.map +1 -1
  110. package/dist/plugins/types.d.ts +1 -1
  111. package/dist/plugins/types.d.ts.map +1 -1
  112. package/dist/runtime/client.d.ts +60 -11
  113. package/dist/runtime/client.d.ts.map +1 -1
  114. package/dist/runtime/client.js +85 -39
  115. package/dist/runtime/client.js.map +1 -1
  116. package/dist/runtime/client.test.d.ts +2 -0
  117. package/dist/runtime/client.test.d.ts.map +1 -0
  118. package/dist/runtime/client.test.js +170 -0
  119. package/dist/runtime/client.test.js.map +1 -0
  120. package/dist/runtime/compile-error.d.ts +14 -0
  121. package/dist/runtime/compile-error.d.ts.map +1 -0
  122. package/dist/runtime/compile-error.js +14 -0
  123. package/dist/runtime/compile-error.js.map +1 -0
  124. package/dist/runtime/driver-sqlite.test.d.ts +2 -0
  125. package/dist/runtime/driver-sqlite.test.d.ts.map +1 -0
  126. package/dist/runtime/driver-sqlite.test.js +195 -0
  127. package/dist/runtime/driver-sqlite.test.js.map +1 -0
  128. package/dist/runtime/driver.d.ts +16 -1
  129. package/dist/runtime/driver.d.ts.map +1 -1
  130. package/dist/runtime/driver.js +118 -96
  131. package/dist/runtime/driver.js.map +1 -1
  132. package/dist/runtime/error-builders.d.ts +7 -6
  133. package/dist/runtime/error-builders.d.ts.map +1 -1
  134. package/dist/runtime/error-builders.js +2 -2
  135. package/dist/runtime/error-builders.js.map +1 -1
  136. package/dist/runtime/error-codes.d.ts +50 -0
  137. package/dist/runtime/error-codes.d.ts.map +1 -0
  138. package/dist/runtime/error-codes.js +54 -0
  139. package/dist/runtime/error-codes.js.map +1 -0
  140. package/dist/runtime/error-hints.d.ts +1 -0
  141. package/dist/runtime/error-hints.d.ts.map +1 -1
  142. package/dist/runtime/error-hints.js +13 -1
  143. package/dist/runtime/error-hints.js.map +1 -1
  144. package/dist/runtime/errors.d.ts +55 -8
  145. package/dist/runtime/errors.d.ts.map +1 -1
  146. package/dist/runtime/errors.js +143 -12
  147. package/dist/runtime/errors.js.map +1 -1
  148. package/dist/runtime/errors.test.d.ts +2 -0
  149. package/dist/runtime/errors.test.d.ts.map +1 -0
  150. package/dist/runtime/errors.test.js +115 -0
  151. package/dist/runtime/errors.test.js.map +1 -0
  152. package/dist/runtime/executor.d.ts +22 -3
  153. package/dist/runtime/executor.d.ts.map +1 -1
  154. package/dist/runtime/executor.js +93 -39
  155. package/dist/runtime/executor.js.map +1 -1
  156. package/dist/runtime/parse-db-json.d.ts +8 -0
  157. package/dist/runtime/parse-db-json.d.ts.map +1 -0
  158. package/dist/runtime/parse-db-json.js +33 -0
  159. package/dist/runtime/parse-db-json.js.map +1 -0
  160. package/dist/runtime/pg-error.d.ts.map +1 -1
  161. package/dist/runtime/pg-error.js +16 -16
  162. package/dist/runtime/pg-error.js.map +1 -1
  163. package/dist/runtime/pg-rollback.test.d.ts +2 -0
  164. package/dist/runtime/pg-rollback.test.d.ts.map +1 -0
  165. package/dist/runtime/pg-rollback.test.js +39 -0
  166. package/dist/runtime/pg-rollback.test.js.map +1 -0
  167. package/dist/runtime/query/aggregate.d.ts.map +1 -1
  168. package/dist/runtime/query/aggregate.js +15 -4
  169. package/dist/runtime/query/aggregate.js.map +1 -1
  170. package/dist/runtime/query/compile-aggregate.d.ts +39 -0
  171. package/dist/runtime/query/compile-aggregate.d.ts.map +1 -0
  172. package/dist/runtime/query/compile-aggregate.js +456 -0
  173. package/dist/runtime/query/compile-aggregate.js.map +1 -0
  174. package/dist/runtime/query/compile-where.d.ts +32 -0
  175. package/dist/runtime/query/compile-where.d.ts.map +1 -0
  176. package/dist/runtime/query/compile-where.js +708 -0
  177. package/dist/runtime/query/compile-where.js.map +1 -0
  178. package/dist/runtime/query/compile-write.d.ts +53 -0
  179. package/dist/runtime/query/compile-write.d.ts.map +1 -0
  180. package/dist/runtime/query/compile-write.js +467 -0
  181. package/dist/runtime/query/compile-write.js.map +1 -0
  182. package/dist/runtime/query/compile.d.ts +3 -114
  183. package/dist/runtime/query/compile.d.ts.map +1 -1
  184. package/dist/runtime/query/compile.js +3 -1536
  185. package/dist/runtime/query/compile.js.map +1 -1
  186. package/dist/runtime/query/count.d.ts +1 -0
  187. package/dist/runtime/query/count.d.ts.map +1 -1
  188. package/dist/runtime/query/count.js +12 -14
  189. package/dist/runtime/query/count.js.map +1 -1
  190. package/dist/runtime/query/create.d.ts.map +1 -1
  191. package/dist/runtime/query/create.js +20 -49
  192. package/dist/runtime/query/create.js.map +1 -1
  193. package/dist/runtime/query/cursor-codec.d.ts.map +1 -1
  194. package/dist/runtime/query/cursor-codec.js +8 -2
  195. package/dist/runtime/query/cursor-codec.js.map +1 -1
  196. package/dist/runtime/query/cursor.d.ts.map +1 -1
  197. package/dist/runtime/query/cursor.js +31 -7
  198. package/dist/runtime/query/cursor.js.map +1 -1
  199. package/dist/runtime/query/delete.d.ts.map +1 -1
  200. package/dist/runtime/query/delete.js +17 -17
  201. package/dist/runtime/query/delete.js.map +1 -1
  202. package/dist/runtime/query/execute.d.ts.map +1 -1
  203. package/dist/runtime/query/execute.js +11 -12
  204. package/dist/runtime/query/execute.js.map +1 -1
  205. package/dist/runtime/query/find-or-create.d.ts +4 -2
  206. package/dist/runtime/query/find-or-create.d.ts.map +1 -1
  207. package/dist/runtime/query/find-or-create.js +53 -43
  208. package/dist/runtime/query/find-or-create.js.map +1 -1
  209. package/dist/runtime/query/find-or-create.test.d.ts +2 -0
  210. package/dist/runtime/query/find-or-create.test.d.ts.map +1 -0
  211. package/dist/runtime/query/find-or-create.test.js +189 -0
  212. package/dist/runtime/query/find-or-create.test.js.map +1 -0
  213. package/dist/runtime/query/find.d.ts.map +1 -1
  214. package/dist/runtime/query/find.js +86 -58
  215. package/dist/runtime/query/find.js.map +1 -1
  216. package/dist/runtime/query/group-by.d.ts.map +1 -1
  217. package/dist/runtime/query/group-by.js +3 -2
  218. package/dist/runtime/query/group-by.js.map +1 -1
  219. package/dist/runtime/query/manifest-lookup.d.ts.map +1 -1
  220. package/dist/runtime/query/manifest-lookup.js +1 -1
  221. package/dist/runtime/query/manifest-lookup.js.map +1 -1
  222. package/dist/runtime/query/map-row.d.ts.map +1 -1
  223. package/dist/runtime/query/map-row.js +1 -1
  224. package/dist/runtime/query/map-row.js.map +1 -1
  225. package/dist/runtime/query/paginate.d.ts +3 -0
  226. package/dist/runtime/query/paginate.d.ts.map +1 -1
  227. package/dist/runtime/query/paginate.js +49 -11
  228. package/dist/runtime/query/paginate.js.map +1 -1
  229. package/dist/runtime/query/primary-key.d.ts +1 -1
  230. package/dist/runtime/query/primary-key.d.ts.map +1 -1
  231. package/dist/runtime/query/primary-key.js +45 -8
  232. package/dist/runtime/query/primary-key.js.map +1 -1
  233. package/dist/runtime/query/projection.d.ts +1 -0
  234. package/dist/runtime/query/projection.d.ts.map +1 -1
  235. package/dist/runtime/query/projection.js +23 -7
  236. package/dist/runtime/query/projection.js.map +1 -1
  237. package/dist/runtime/query/relation-planner.d.ts.map +1 -1
  238. package/dist/runtime/query/relation-planner.js +82 -53
  239. package/dist/runtime/query/relation-planner.js.map +1 -1
  240. package/dist/runtime/query/relation-writes.d.ts +3 -1
  241. package/dist/runtime/query/relation-writes.d.ts.map +1 -1
  242. package/dist/runtime/query/relation-writes.js +145 -54
  243. package/dist/runtime/query/relation-writes.js.map +1 -1
  244. package/dist/runtime/query/serialize-dialect.test.d.ts +2 -0
  245. package/dist/runtime/query/serialize-dialect.test.d.ts.map +1 -0
  246. package/dist/runtime/query/serialize-dialect.test.js +79 -0
  247. package/dist/runtime/query/serialize-dialect.test.js.map +1 -0
  248. package/dist/runtime/query/sqlite-constraint-errors.test.d.ts +2 -0
  249. package/dist/runtime/query/sqlite-constraint-errors.test.d.ts.map +1 -0
  250. package/dist/runtime/query/sqlite-constraint-errors.test.js +60 -0
  251. package/dist/runtime/query/sqlite-constraint-errors.test.js.map +1 -0
  252. package/dist/runtime/query/strip.d.ts.map +1 -1
  253. package/dist/runtime/query/strip.js.map +1 -1
  254. package/dist/runtime/query/table-index.d.ts.map +1 -1
  255. package/dist/runtime/query/table-index.js +4 -3
  256. package/dist/runtime/query/table-index.js.map +1 -1
  257. package/dist/runtime/query/unique.d.ts +8 -1
  258. package/dist/runtime/query/unique.d.ts.map +1 -1
  259. package/dist/runtime/query/unique.js +89 -4
  260. package/dist/runtime/query/unique.js.map +1 -1
  261. package/dist/runtime/query/unique.test.d.ts +2 -0
  262. package/dist/runtime/query/unique.test.d.ts.map +1 -0
  263. package/dist/runtime/query/unique.test.js +231 -0
  264. package/dist/runtime/query/unique.test.js.map +1 -0
  265. package/dist/runtime/query/update.d.ts.map +1 -1
  266. package/dist/runtime/query/update.js +30 -35
  267. package/dist/runtime/query/update.js.map +1 -1
  268. package/dist/runtime/query/update.test.d.ts +2 -0
  269. package/dist/runtime/query/update.test.d.ts.map +1 -0
  270. package/dist/runtime/query/update.test.js +186 -0
  271. package/dist/runtime/query/update.test.js.map +1 -0
  272. package/dist/runtime/query/updated-at.d.ts.map +1 -1
  273. package/dist/runtime/query/updated-at.js.map +1 -1
  274. package/dist/runtime/query/upsert.d.ts.map +1 -1
  275. package/dist/runtime/query/upsert.js +5 -7
  276. package/dist/runtime/query/upsert.js.map +1 -1
  277. package/dist/runtime/schema-error.d.ts.map +1 -1
  278. package/dist/runtime/schema-error.js +6 -2
  279. package/dist/runtime/schema-error.js.map +1 -1
  280. package/dist/runtime/sqlite-error.d.ts.map +1 -1
  281. package/dist/runtime/sqlite-error.js +11 -12
  282. package/dist/runtime/sqlite-error.js.map +1 -1
  283. package/dist/runtime/sqlite-open.d.ts +2 -0
  284. package/dist/runtime/sqlite-open.d.ts.map +1 -1
  285. package/dist/runtime/sqlite-open.js +18 -1
  286. package/dist/runtime/sqlite-open.js.map +1 -1
  287. package/dist/runtime/transaction.d.ts +4 -0
  288. package/dist/runtime/transaction.d.ts.map +1 -1
  289. package/dist/runtime/transaction.js +31 -1
  290. package/dist/runtime/transaction.js.map +1 -1
  291. package/dist/runtime/transaction.test.d.ts +2 -0
  292. package/dist/runtime/transaction.test.d.ts.map +1 -0
  293. package/dist/runtime/transaction.test.js +43 -0
  294. package/dist/runtime/transaction.test.js.map +1 -0
  295. package/dist/runtime/types.d.ts +50 -17
  296. package/dist/runtime/types.d.ts.map +1 -1
  297. package/dist/schema/column-where.d.ts +11 -1
  298. package/dist/schema/column-where.d.ts.map +1 -1
  299. package/dist/schema/column.d.ts +1 -1
  300. package/dist/schema/column.d.ts.map +1 -1
  301. package/dist/schema/column.js +1 -1
  302. package/dist/schema/column.js.map +1 -1
  303. package/dist/schema/define-schema.d.ts +1 -1
  304. package/dist/schema/define-schema.d.ts.map +1 -1
  305. package/dist/schema/define-schema.js +5 -23
  306. package/dist/schema/define-schema.js.map +1 -1
  307. package/dist/schema/index.d.ts +2 -2
  308. package/dist/schema/index.d.ts.map +1 -1
  309. package/dist/schema/index.js +1 -1
  310. package/dist/schema/index.js.map +1 -1
  311. package/dist/schema/relation-types.d.ts +13 -17
  312. package/dist/schema/relation-types.d.ts.map +1 -1
  313. package/dist/schema/relation.d.ts.map +1 -1
  314. package/dist/schema/relation.js +6 -4
  315. package/dist/schema/relation.js.map +1 -1
  316. package/dist/schema/table-registry.d.ts.map +1 -1
  317. package/dist/schema/table-registry.js +8 -2
  318. package/dist/schema/table-registry.js.map +1 -1
  319. package/dist/schema/table.d.ts +6 -3
  320. package/dist/schema/table.d.ts.map +1 -1
  321. package/dist/schema/table.js +30 -31
  322. package/dist/schema/table.js.map +1 -1
  323. package/dist/schema/types.d.ts +33 -25
  324. package/dist/schema/types.d.ts.map +1 -1
  325. package/dist/sql/builder.d.ts +5 -1
  326. package/dist/sql/builder.d.ts.map +1 -1
  327. package/dist/sql/builder.js +23 -3
  328. package/dist/sql/builder.js.map +1 -1
  329. package/dist/sql/index.d.ts +5 -2
  330. package/dist/sql/index.d.ts.map +1 -1
  331. package/dist/sql/index.js +5 -2
  332. package/dist/sql/index.js.map +1 -1
  333. package/dist/utils/case.d.ts +4 -0
  334. package/dist/utils/case.d.ts.map +1 -1
  335. package/dist/utils/case.js +11 -0
  336. package/dist/utils/case.js.map +1 -1
  337. package/dist/utils/dotenv.d.ts +8 -0
  338. package/dist/utils/dotenv.d.ts.map +1 -0
  339. package/dist/utils/dotenv.js +70 -0
  340. package/dist/utils/dotenv.js.map +1 -0
  341. package/dist/utils/dotenv.test.d.ts +2 -0
  342. package/dist/utils/dotenv.test.d.ts.map +1 -0
  343. package/dist/utils/dotenv.test.js +32 -0
  344. package/dist/utils/dotenv.test.js.map +1 -0
  345. package/dist/utils/inflect.d.ts +6 -0
  346. package/dist/utils/inflect.d.ts.map +1 -0
  347. package/dist/utils/inflect.js +80 -0
  348. package/dist/utils/inflect.js.map +1 -0
  349. package/docs/cli.md +6 -2
  350. package/docs/configuration.md +3 -1
  351. package/docs/errors.md +61 -6
  352. package/docs/examples.md +63 -13
  353. package/docs/getting-started.md +24 -1
  354. package/docs/migrations.md +15 -2
  355. package/docs/plugins.md +13 -1
  356. package/docs/queries.md +109 -10
  357. package/docs/relations.md +5 -1
  358. package/docs/schema.md +12 -5
  359. package/docs/sqlite.md +24 -10
  360. package/docs/transactions.md +13 -2
  361. package/package.json +4 -2
package/docs/queries.md CHANGED
@@ -19,14 +19,36 @@ const users = await db.users.findMany({
19
19
  skip: 40,
20
20
  });
21
21
 
22
+ // First match after skip, or with DISTINCT ON (null if not found)
23
+ await db.users.findFirst({ orderBy: { email: "asc" }, skip: 1 });
24
+ await db.users.findFirst({
25
+ distinct: ["email"],
26
+ orderBy: { email: "asc" },
27
+ });
28
+
22
29
  // Unique constraint lookup
23
30
  const user = await db.users.findUnique({ where: { slug: "hello" } });
24
31
  ```
25
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`.
34
+
26
35
  ## Create
27
36
 
28
37
  ```ts
29
- const user = await db.users.create({ data: { email: "a@b.com" } });
38
+ const created = await db.users.create({ data: { email: "a@b.com" } });
39
+ created.id; // primary key is always returned
40
+
41
+ // Pass returnCreated: true to get the full row (defaults, timestamps, …)
42
+ const user = await db.users.create({
43
+ data: { email: "a@b.com" },
44
+ returnCreated: true,
45
+ });
46
+
47
+ // `with` also returns the full row (plus relations)
48
+ const post = await db.posts.create({
49
+ data: { title: "Hello", body: "…", authorId: user.id },
50
+ with: { author: true },
51
+ });
30
52
 
31
53
  // Bulk insert (returns count)
32
54
  const count = await db.users.createMany({
@@ -47,13 +69,24 @@ const users = await db.users.createManyAndReturn({
47
69
  });
48
70
  ```
49
71
 
72
+ Unknown keys in `data` fail at compile time (`unknown_column`), same as `where`.
73
+
50
74
  ## Update
51
75
 
76
+ Singular `update` requires a unique `where` (primary key, `@unique` column, or composite unique index), same as `findUnique`. Use `updateMany` when the filter can match multiple rows.
77
+
52
78
  ```ts
53
- // Single record
79
+ // Returns {} on success, null if no row matched
80
+ const updated = await db.users.update({
81
+ where: { id: userId },
82
+ data: { email: "new@b.com" },
83
+ });
84
+
85
+ // Pass returnUpdated: true to get the updated row back
54
86
  const user = await db.users.update({
55
87
  where: { id: userId },
56
88
  data: { email: "new@b.com" },
89
+ returnUpdated: true,
57
90
  });
58
91
 
59
92
  // By ID (scalar PK — string; composite PK — object)
@@ -73,6 +106,12 @@ const updated = await db.users.updateManyAndReturn({
73
106
  where: { email: { contains: "@old" } },
74
107
  data: { status: "archived" },
75
108
  });
109
+
110
+ const post = await db.posts.update({
111
+ where: { id: postId },
112
+ data: { title: "New" },
113
+ with: { author: true }, // full row plus relations; no returnUpdated needed
114
+ });
76
115
  ```
77
116
 
78
117
  Numeric columns (`int`, `serial`, `decimal`, `bigint`) also accept `{ increment }`, `{ decrement }`, `{ multiply }`, and `{ set }`. These compile to `col = col ±/* $n` so concurrent updates do not clobber each other. `{ set: value }` is the same assignment as a plain value. `NULL + 1` stays SQL NULL.
@@ -91,6 +130,8 @@ await db.posts.updateMany({
91
130
 
92
131
  ## Delete
93
132
 
133
+ Singular `delete` requires a unique `where`. Use `deleteMany` for bulk deletes.
134
+
94
135
  ```ts
95
136
  // Returns {} on success, null if no row matched
96
137
  await db.users.delete({ where: { id: userId } });
@@ -101,6 +142,12 @@ const deleted = await db.users.delete({
101
142
  returnDeleted: true,
102
143
  });
103
144
 
145
+ // `with` also returns the full row (plus relations), even without returnDeleted
146
+ const post = await db.posts.delete({
147
+ where: { id: postId },
148
+ with: { author: true },
149
+ });
150
+
104
151
  // Scalar PK — pass a string
105
152
  await db.users.deleteById(userId);
106
153
 
@@ -132,6 +179,10 @@ Atomically insert a row or return an existing one when a unique constraint match
132
179
 
133
180
  `where` must identify a unique constraint (same rules as `findUnique` and `upsert`).
134
181
 
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
+
184
+ Under `SERIALIZABLE` (and sometimes `REPEATABLE READ`), a concurrent insert can still abort the transaction with a serialization failure. Retry the whole `$transaction`. SQLite uses find-then-insert and retries the find after a unique violation.
185
+
135
186
  ```ts
136
187
  const { record, created } = await db.tags.findOrCreate({
137
188
  where: { slug: "orm" },
@@ -158,22 +209,31 @@ await db.posts.findFirst({
158
209
  });
159
210
  ```
160
211
 
212
+ Unknown column names and operators fail at compile time (`unknown_column`, with “did you mean” hints). JSON columns may still use a plain object as equality (`{ metadata: { featured: true } }`).
213
+
161
214
  | Type | Operators |
162
215
  |------|-----------|
163
216
  | String | `equals`, `contains`, `startsWith`, `endsWith`, `search`, `in`, `notIn`, `mode` |
164
217
  | Numeric / Date | `equals`, `gt`, `gte`, `lt`, `lte`, `in`, `notIn` |
165
218
  | JSON | `jsonContains`, `hasKey`, `hasAnyKeys`, `hasAllKeys`, `path` |
219
+ | PostGIS (`geometry`, `geography`, `point`) | `intersects`, `within`, `dWithin` |
166
220
  | All nullable | `isNull`, `isNotNull` |
167
221
 
168
- `contains`, `startsWith`, and `endsWith` compile to `LIKE`. Pass sibling `mode: "insensitive"` for case-folding (`ILIKE` on Postgres, `LOWER(col) LIKE LOWER($n)` on SQLite). SQLite `LIKE` is ASCII case-insensitive even in default mode.
222
+ `contains`, `startsWith`, `endsWith`, and `equals` compile to `LIKE` / `=`. Pass sibling `mode: "insensitive"` for case-folding (`ILIKE` on Postgres, `LOWER(col) LIKE LOWER($n)` on SQLite). SQLite `LIKE` is ASCII case-insensitive even in default mode.
169
223
 
170
224
  `search` is POSIX regex (`~`, or `~*` with `mode: "insensitive"`). It is PostgreSQL-only; SQLite throws.
171
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).
227
+
172
228
  ```ts
173
229
  await db.posts.findMany({
174
230
  where: { title: { contains: "orm", mode: "insensitive" } },
175
231
  });
176
232
 
233
+ await db.users.findMany({
234
+ where: { email: { equals: "Ada@Example.com", mode: "insensitive" } },
235
+ });
236
+
177
237
  await db.posts.findMany({
178
238
  where: { title: { search: "^Neo", mode: "insensitive" } },
179
239
  });
@@ -194,6 +254,8 @@ await db.users.findMany({
194
254
  });
195
255
  ```
196
256
 
257
+ Empty `OR: []` matches no rows. Empty `AND: []` matches every row. Empty `in: []` is false; `{ OR: [{ id: { in: [] } }, { name: "Ada" }] }` still matches the other branch.
258
+
197
259
  ### Relation filters
198
260
 
199
261
  ```ts
@@ -218,7 +280,7 @@ await db.posts.findMany({
218
280
  | `authorId: "user_1"` | Filter by FK column value |
219
281
  | `author: { email: "a@b.c" }` | Filter by related record (to-one) |
220
282
  | `posts: { some: { ... } }` | At least one related record matches (to-many) |
221
- | `posts: { every: { ... } }` | All related records match (to-many) |
283
+ | `posts: { every: { ... } }` | All related records match (to-many). Compiled as `NOT EXISTS` of related rows that fail the nested filter. |
222
284
  | `posts: { none: { ... } }` | No related records match (to-many) |
223
285
 
224
286
  Relation filters compile to SQL `EXISTS` subqueries, so they work with `findMany`, `count`, `updateMany`, and `deleteMany` without duplicate rows.
@@ -226,7 +288,7 @@ Relation filters compile to SQL `EXISTS` subqueries, so they work with `findMany
226
288
  ### JSON and decimal filters
227
289
 
228
290
  ```ts
229
- // jsonb — partial / subset match (@> containment)
291
+ // jsonb — partial / subset match (`@>` on PostgreSQL, `json_patch` on SQLite)
230
292
  await db.posts.findMany({
231
293
  where: { metadata: { jsonContains: { featured: true } } },
232
294
  });
@@ -250,7 +312,7 @@ await db.posts.findMany({
250
312
 
251
313
  ### `distinct`
252
314
 
253
- PostgreSQL `DISTINCT ON` — `orderBy` must lead with the same columns:
315
+ PostgreSQL `DISTINCT ON` — `orderBy` must lead with the same columns (key order is the ORDER BY prefix):
254
316
 
255
317
  ```ts
256
318
  await db.users.findMany({
@@ -270,9 +332,10 @@ Relations are eagerly loaded in `findMany`, `findFirst`, and `findById` via the
270
332
  NeoORM uses a relation-loading planner that picks the fewest round-trips per `with` tree:
271
333
 
272
334
  - **LEFT JOIN** — to-one relations where the parent table owns the foreign key are resolved in the main query. No additional round-trips.
335
+ - **JOIN + `json_agg`** — a single inverse has-many include may use one collection `LEFT JOIN` with `GROUP BY`. A second collection is a correlated subquery so sizes N and M do not become N×M rows.
273
336
  - **Correlated `json_agg` subquery** — inverse has-many relations (and simple linear nested chains) are embedded in the main `SELECT` as correlated subqueries. Multiple top-level relations can be inlined in the same query alongside JOINs.
274
- - **Inline `COUNT` subquery** — simple `_count` on has-many relations is embedded in the main `SELECT`.
275
- - **Batch query** — many-to-many, nested `with` on to-one, sibling-heavy trees, and other complex shapes fall back to batched `WHERE IN (...)` queries (one per relation level). Sibling batch queries at the same level run in parallel.
337
+ - **Inline `COUNT` subquery** — simple `_count` on has-many relations is embedded in the main `SELECT`. At most one `_count` collection uses a `LEFT JOIN`; additional counts are correlated `COUNT(*)` subqueries.
338
+ - **Batch query** — many-to-many, nested `with` on to-one, sibling-heavy trees, and other complex shapes fall back to batched `WHERE IN (...)` queries (one per relation level). Sibling batch queries at the same level run in parallel. Nested `take`/`skip` on those batches apply **per parent** (window ranking), not to the whole `IN` list.
276
339
 
277
340
  Typical query counts:
278
341
 
@@ -395,7 +458,8 @@ for (;;) {
395
458
  ```
396
459
 
397
460
  - `orderBy` is required; scalar `id` is appended automatically when omitted.
398
- - `take` is the page size; `hasMore` / `hasPrevious` use a `take + 1` probe row (plus whether `after` / `before` was passed).
461
+ - `select`, `omit`, and `includeHidden` work like `findMany`. Cursor fields used by `orderBy` stay in the SQL (and in `nextCursor` / `prevCursor`) even when omitted from `items`.
462
+ - `take` is the page size; `hasMore` / `hasPrevious` use a `take + 1` probe in the query direction. A `before` page also probes once forward so `hasMore` is not assumed true. After an `after` page, `hasPrevious` is true because `after` was passed.
399
463
  - `after` is a typed cursor (`nextCursor` from the previous page). `before` walks the other way (`prevCursor`).
400
464
  - `after` and `before` can be combined as exclusive window bounds; pass both on every windowed call.
401
465
  - For HTTP APIs, encode cursors with `encodeCursor` / `decodeCursor` from `neoorm`.
@@ -441,7 +505,7 @@ const stats = await db.posts.aggregate({
441
505
 
442
506
  `_count: true` is still `COUNT(*)` and returns `_count: number`. A field map uses `COUNT(col)` (nulls are skipped) — not `DISTINCT`.
443
507
 
444
- Single-table grouped stats use `groupBy()`. Cross-table dashboards still use `db.sql` or `sqlBuilder`.
508
+ Single-table grouped stats use `groupBy()`. Cross-table dashboards still use `db.sql` (same compiler as `neoorm/sql`) or `sqlBuilder` composed into `db.sql`.
445
509
 
446
510
  ### `groupBy()`
447
511
 
@@ -478,3 +542,38 @@ const taken = await db.users.exists({ where: { email: "a@b.com" } });
478
542
  `count()` returns a number unless `select` is set. `distinct` is `COUNT(DISTINCT col)` on one column and cannot be combined with `select`. Field `select` / `_count` maps are non-null `COUNT(col)`, not distinct.
479
543
 
480
544
  `exists()` runs `SELECT 1 … LIMIT 1` and returns a boolean. Use `count()` when you need the actual number of matches.
545
+
546
+ ## Raw SQL
547
+
548
+ `db.sql` and `import { sql } from "neoorm/sql"` share one compiler. Interpolate values, `sqlId("table")`, nested `sql\`...\`` fragments, or `sqlBuilder.compile()`. `sqlBuilder` itself has no WHERE, LIMIT, or params — add those in the `db.sql` tag.
549
+
550
+ ```ts
551
+ import { sql, sqlBuilder, sqlId } from "neoorm/sql";
552
+
553
+ const ident = sqlId("users");
554
+ const filter = sql`email = ${"a@b.com"}`;
555
+ const rows = await db.sql`SELECT * FROM ${ident} WHERE ${filter}`;
556
+
557
+ const grouped = sqlBuilder
558
+ .selectFrom("users")
559
+ .select(["id", "email"])
560
+ .groupBy("id", "email")
561
+ .compile();
562
+ await db.sql`${grouped} LIMIT ${10}`;
563
+ ```
564
+
565
+ `db.execute({ text, params })` runs already-compiled SQL. Raw SQL is not rewritten for tenant `schema` — qualify identifiers yourself.
566
+
567
+ ## Logging SQL
568
+
569
+ Pass `beforeQuery` / `afterQuery` when creating the client. They run for repository methods and `db.sql` / `db.execute`, including queries inside `$transaction`. BEGIN/COMMIT/SAVEPOINT are not included.
570
+
571
+ ```ts
572
+ const db = createNeoOrmClient(manifest, {
573
+ afterQuery: ({ sql, params, durationMs, error }) => {
574
+ if (error || durationMs > 100) {
575
+ console.warn({ sql, params, durationMs, error });
576
+ }
577
+ },
578
+ });
579
+ ```
package/docs/relations.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Relation writes
2
2
 
3
- `create` and `update` accept nested relation writes alongside scalar fields. Relation-only updates are supported (no scalar `SET` required).
3
+ `create` and `update` accept nested relation writes alongside scalar fields. Relation-only updates are supported (no scalar `SET` required). Nested to-one `create` on `update` (and `create`) runs in the same transaction as the parent row so a failed parent write does not leave an orphan.
4
4
 
5
5
  ## Supported operations
6
6
 
@@ -10,6 +10,10 @@
10
10
  | **One-to-many** (inverse) | `create`, `connect`, `disconnect`, `set`, `delete` |
11
11
  | **Many-to-many** | `connect`, `connectOrCreate`, `disconnect`, `set`, `delete` |
12
12
 
13
+ `connect`, `set`, `disconnect`, and `delete` identify related rows by the **target table's scalar primary key** (`{ id: ... }` when the PK is `id`, `{ userId: ... }` when it is `userId`). Composite-PK connect is not supported.
14
+
15
+ A relation field must be a **pure write bag**: every key is one of those operations. Mixing a write op with extra fields (`{ create: { name: "A" }, foo: 1 }`), passing a scalar, or passing `{}` throws `invalid_nested_write` — the parent row is not written.
16
+
13
17
  ## Examples
14
18
 
15
19
  ### To-one on update
package/docs/schema.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Schema DSL
2
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.
3
+ NeoOrm **0.8.0** uses accessor-based identity: schema keys, `fk("users")`, `many("tags")`, and `through: "postTags"` all refer to **accessors**, not raw SQL names. `schemaToManifest` resolves accessors to SQL before migrations and the typed client are generated.
4
4
 
5
5
  ## Tables and accessors
6
6
 
@@ -26,6 +26,8 @@ export const schema = defineSchema({
26
26
  - `table({ columns, extras? })` — SQL name is filled in from the accessor (`users` → `"users"`).
27
27
  - `table("sql_name", { columns }, extras?)` — override the SQL name (`postTags` → `"post_tags"`).
28
28
 
29
+ Generated row types are the English singular of the accessor: `users` → `User`, `status` → `Status` (not `Statu`).
30
+
29
31
  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
32
 
31
33
  ## Column types
@@ -41,7 +43,7 @@ Column field names use camelCase in TypeScript. By default SQL column names are
41
43
  | `bool()` | `BOOLEAN` | `boolean \| null` | |
42
44
  | `int()` | `INTEGER` | `number \| null` | |
43
45
  | `bigint()` | `BIGINT` | `bigint \| null` | Maps to `TEXT` on SQLite |
44
- | `timestamp()` | `TIMESTAMPTZ` | `Date \| null` | `.defaultNow()` / `.updatedAt()` on timestamp builders only |
46
+ | `timestamp()` | `TIMESTAMPTZ` | `Date \| null` | `.defaultNow()` / `.updatedAt()` on timestamp builders only. Maps to `TEXT` on SQLite |
45
47
  | `json()` / `jsonb()` | `JSON` / `JSONB` | `unknown \| null` | Generic: `jsonb<MyType>()` |
46
48
  | `decimal()` / `numeric()` | `NUMERIC` | `string \| null` | Use strings to avoid float loss |
47
49
  | `enumType(["a", "b"])` | mode-dependent | union literals | See [Enum columns](#enum-columns) |
@@ -119,6 +121,8 @@ postTags: table("post_tags", {
119
121
  }),
120
122
  ```
121
123
 
124
+ Multiple `.primary()` columns infer a composite key. `primaryKey(t.colA, t.colB)` in extras is optional when you want to name the key without marking every column.
125
+
122
126
  ### Self-relations
123
127
 
124
128
  ```ts
@@ -168,15 +172,18 @@ posts: table(
168
172
  ),
169
173
  ```
170
174
 
171
- Helpers: `unique(...cols)`, `index(...cols)`, `primaryKey(...cols)`.
175
+ Helpers: `unique(...cols)`, `index(...cols)`, `primaryKey(...cols)`. `unique()` and `index()` both support `.where()` for partial indexes.
172
176
 
173
177
  ### Partial indexes
174
178
 
175
179
  ```ts
176
- (t) => [index(t.title).where({ published: true })],
180
+ (t) => [
181
+ index(t.title).where({ published: true }),
182
+ unique(t.email).where({ deleted: false }),
183
+ ],
177
184
  ```
178
185
 
179
- Equality map of column refs → values; compiled to `WHERE "published" = true` (or `= 1` on SQLite).
186
+ Equality map of column refs → values; compiled to `WHERE "published" = true` (or `= 1` on SQLite). Partial uniques emit as `CREATE UNIQUE INDEX ... WHERE ...`, not table-level `UNIQUE (...)`. They are not usable as `findUnique` / `upsert` targets.
180
187
 
181
188
  ## Many-to-many
182
189
 
package/docs/sqlite.md CHANGED
@@ -4,13 +4,13 @@ SQLite is a first-class dialect for local development, testing, and single-file
4
4
 
5
5
  ## Requirements
6
6
 
7
- No SQLite package is required — NeoOrm uses the driver bundled with your runtime:
7
+ NeoOrm requires **Node.js 22.5+** or **Bun**. No SQLite package is required — auto-open uses the driver bundled with your runtime:
8
8
 
9
9
  | Runtime | Driver |
10
10
  |---------|--------|
11
11
  | Node.js 22.5+ | built-in `node:sqlite` (`DatabaseSync`) |
12
12
  | Bun | `bun:sqlite` |
13
- | Anything else | pass your own `db` instance (see [Custom driver](#custom-driver)) |
13
+ | Custom | pass your own `db` instance (see [Custom driver](#custom-driver)) |
14
14
 
15
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
16
 
@@ -48,19 +48,33 @@ const db = createNeoOrmClient(manifest, {
48
48
 
49
49
  Use `databasePath: ":memory:"` for an in-memory database (one connection, no persistence).
50
50
 
51
+ Wrapping a connection sets `PRAGMA foreign_keys = ON`, `busy_timeout = 5000`, and `journal_mode = WAL` (WAL is a no-op for `:memory:`). Opt out per client:
52
+
53
+ ```ts
54
+ const db = createNeoOrmClient(manifest, {
55
+ provider: "sqlite",
56
+ databasePath: "./dev.db",
57
+ sqlite: { wal: false, busyTimeout: false },
58
+ });
59
+ ```
60
+
61
+ `busyTimeout` can also be a millisecond wait (`sqlite: { busyTimeout: 10_000 }`).
62
+
51
63
  ### Custom driver
52
64
 
53
65
  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
66
 
55
67
  ```ts
56
68
  import { DatabaseSync } from "node:sqlite";
57
- import { createNeoOrmClient } from "neoorm";
69
+ import { createNeoOrmClientFromSqlite } from "neoorm";
58
70
  import { manifest } from "./neoorm/manifest.js";
59
71
 
60
72
  const database = new DatabaseSync("./dev.db");
61
- const db = createNeoOrmClient(manifest, { db: database });
73
+ const db = createNeoOrmClientFromSqlite(manifest, database);
62
74
  ```
63
75
 
76
+ `$disconnect()` does not close the handle — call `database.close()` yourself. `createNeoOrmClient(manifest, { db: database })` is the same ownership model.
77
+
64
78
  ## CLI
65
79
 
66
80
  The full CLI works against SQLite:
@@ -73,7 +87,7 @@ bunx neoorm migrate status
73
87
  bunx neoorm migrate reset --force
74
88
  ```
75
89
 
76
- - The migration ledger table is `_neoorm_migrations` (`id INTEGER PRIMARY KEY AUTOINCREMENT`).
90
+ - The migration ledger table is `_neoorm_migrations` (`id INTEGER PRIMARY KEY AUTOINCREMENT`, `name`, `checksum`, `applied_at`). `migrate deploy` wraps the run in `BEGIN IMMEDIATE` and refuses an edited `migration.sql` whose checksum no longer matches the ledger.
77
91
  - `migrate reset` drops all non-`sqlite_` tables (there is no schema concept).
78
92
  - `db pull` introspects `sqlite_master` back into a schema file.
79
93
 
@@ -86,11 +100,11 @@ bunx neoorm migrate reset --force
86
100
  | `bigint` | `TEXT` |
87
101
  | `serial().primary()` | `INTEGER PRIMARY KEY AUTOINCREMENT` |
88
102
  | `bool` | `BOOLEAN` (stored as 0/1) |
89
- | `timestamp` | `TIMESTAMPTZ` (stored as ISO-8601 text) |
103
+ | `timestamp` | `TEXT` (ISO-8601) |
90
104
  | `bytea` | `BLOB` |
91
105
  | `fk` | the target column's type (default `TEXT`) |
92
106
 
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.
107
+ `defaultNow()` compiles to `CURRENT_TIMESTAMP`. Structural table changes that SQLite cannot do in place (column type changes, FK changes, nullability) are applied via a table-rebuild strategy: `PRAGMA foreign_keys = OFF`, then in one transaction create `__neoorm_<table>_new`, copy rows, drop the old table, rename, and `PRAGMA foreign_keys = ON`. Foreign keys must be disabled *before* `BEGIN` — SQLite ignores that pragma inside a transaction. The client always enables `foreign_keys` at connect time, so inbound FKs (e.g. `posts.user_id` → `users`) would otherwise block `DROP TABLE` of the parent.
94
108
 
95
109
  ## Differences from PostgreSQL
96
110
 
@@ -99,7 +113,7 @@ bunx neoorm migrate reset --force
99
113
  | `distinct` (`DISTINCT ON`) | supported | throws `distinct is not supported on SQLite` |
100
114
  | `datasource.schema` | multi-schema | not applicable |
101
115
  | `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` |
116
+ | transaction options (`readOnly`, `isolationLevel`) | `BEGIN READ ONLY` / `ISOLATION LEVEL` | `readOnly` → `PRAGMA query_only`; `RepeatableRead`/`Serializable` → `BEGIN IMMEDIATE`; other isolation → `BEGIN` |
117
+ | JSON operators | `@>`, `?`, `#>` | `json_patch` / `json_each` / `json_extract` |
104
118
 
105
- Everything else — relations, nested writes, cursor pagination, aggregates, `groupBy`, upsert, `findOrCreate`, savepoint-based nested transactions — behaves identically.
119
+ Everything else — relations, nested writes, cursor pagination, aggregates, `groupBy`, upsert, `findOrCreate`, savepoint-based nested transactions — behaves identically.
@@ -49,6 +49,17 @@ await db.$transaction(async (tx) => {
49
49
  });
50
50
  ```
51
51
 
52
- Nested `create` calls inside a transaction do not start a separate transaction. `readOnly` and `isolationLevel` apply only to the outermost `BEGIN`.
52
+ Nested `$transaction` uses a savepoint on the same connection, not a new `BEGIN`. `readOnly` and `isolationLevel` are only valid on the outermost `$transaction`; passing them on a nested call throws.
53
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).
54
+ ### SQLite
55
+
56
+ SQLite has no `BEGIN READ ONLY` and no SQL isolation levels. Outer `$transaction` options map as follows:
57
+
58
+ | Option | SQLite |
59
+ |--------|--------|
60
+ | `readOnly: true` | `PRAGMA query_only = ON` for the duration of the transaction (writes fail) |
61
+ | `isolationLevel: "RepeatableRead"` or `"Serializable"` | `BEGIN IMMEDIATE` (reserved lock before the first statement) |
62
+ | `isolationLevel: "ReadUncommitted"` or `"ReadCommitted"` | `BEGIN` (deferred; default) |
63
+ | nested `$transaction` | `SAVEPOINT` — `readOnly` / `isolationLevel` are rejected |
64
+
65
+ After the transaction commits or rolls back, `query_only` is turned off so later writes succeed.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neoorm",
3
- "version": "0.7.4",
3
+ "version": "0.8.0",
4
4
  "description": "TypeScript-first PostgreSQL and SQLite ORM with schema DSL, codegen, and typed relations",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -73,12 +73,14 @@
73
73
  "database"
74
74
  ],
75
75
  "engines": {
76
- "node": ">=20"
76
+ "node": ">=22.5.0"
77
77
  },
78
78
  "scripts": {
79
79
  "build": "bunx tsc -p tsconfig.build.json",
80
80
  "typecheck": "bunx tsc -p tsconfig.json --noEmit",
81
81
  "dev": "bunx tsc -w -p tsconfig.build.json",
82
+ "lint": "biome check .",
83
+ "format": "biome format --write .",
82
84
  "test": "bunx vitest run",
83
85
  "prepublishOnly": "bun run build && bun run test"
84
86
  },