neoorm 0.8.0 → 0.8.2

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