ultimabase 0.0.0-stage → 0.1.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 (411) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/README.md +4 -2
  3. package/dist/access.d.ts +62 -0
  4. package/dist/access.d.ts.map +1 -0
  5. package/dist/access.js +258 -0
  6. package/dist/adapter-errors.d.ts +43 -0
  7. package/dist/adapter-errors.d.ts.map +1 -0
  8. package/dist/adapter-errors.js +72 -0
  9. package/dist/adapter-json.d.ts +20 -0
  10. package/dist/adapter-json.d.ts.map +1 -0
  11. package/dist/adapter-json.js +2 -0
  12. package/dist/adapter-migrations.d.ts +90 -0
  13. package/dist/adapter-migrations.d.ts.map +1 -0
  14. package/dist/adapter-migrations.js +1 -0
  15. package/dist/adapter.d.ts +333 -0
  16. package/dist/adapter.d.ts.map +1 -0
  17. package/dist/adapter.js +5 -0
  18. package/dist/aggregate.d.ts +31 -0
  19. package/dist/aggregate.d.ts.map +1 -0
  20. package/dist/aggregate.js +99 -0
  21. package/dist/app-internals.d.ts +10 -0
  22. package/dist/app-internals.d.ts.map +1 -0
  23. package/dist/app-internals.js +12 -0
  24. package/dist/app-options.d.ts +22 -0
  25. package/dist/app-options.d.ts.map +1 -0
  26. package/dist/app-options.js +3 -0
  27. package/dist/cascade.d.ts +47 -0
  28. package/dist/cascade.d.ts.map +1 -0
  29. package/dist/cascade.js +209 -0
  30. package/dist/cli-config.d.ts +6 -0
  31. package/dist/cli-config.d.ts.map +1 -0
  32. package/dist/cli-config.js +45 -0
  33. package/dist/cli-deploy.d.ts +14 -0
  34. package/dist/cli-deploy.d.ts.map +1 -0
  35. package/dist/cli-deploy.js +193 -0
  36. package/dist/cli-main.d.ts +16 -0
  37. package/dist/cli-main.d.ts.map +1 -0
  38. package/dist/cli-main.js +151 -0
  39. package/dist/cli-migrate.d.ts +20 -0
  40. package/dist/cli-migrate.d.ts.map +1 -0
  41. package/dist/cli-migrate.js +105 -0
  42. package/dist/cli.d.ts +3 -0
  43. package/dist/cli.d.ts.map +1 -0
  44. package/dist/cli.js +4 -0
  45. package/dist/complete.d.ts +13 -0
  46. package/dist/complete.d.ts.map +1 -0
  47. package/dist/complete.js +60 -0
  48. package/dist/console-logger.d.ts +3 -0
  49. package/dist/console-logger.d.ts.map +1 -0
  50. package/dist/console-logger.js +62 -0
  51. package/dist/create-many.d.ts +7 -0
  52. package/dist/create-many.d.ts.map +1 -0
  53. package/dist/create-many.js +88 -0
  54. package/dist/decimal.d.ts +23 -0
  55. package/dist/decimal.d.ts.map +1 -0
  56. package/dist/decimal.js +50 -0
  57. package/dist/define-app.d.ts +59 -0
  58. package/dist/define-app.d.ts.map +1 -0
  59. package/dist/define-app.js +5 -0
  60. package/dist/define-enum.d.ts +6 -0
  61. package/dist/define-enum.d.ts.map +1 -0
  62. package/dist/define-enum.js +18 -0
  63. package/dist/define-function.d.ts +25 -0
  64. package/dist/define-function.d.ts.map +1 -0
  65. package/dist/define-function.js +111 -0
  66. package/dist/define-model.d.ts +48 -0
  67. package/dist/define-model.d.ts.map +1 -0
  68. package/dist/define-model.js +85 -0
  69. package/dist/definition.d.ts +179 -0
  70. package/dist/definition.d.ts.map +1 -0
  71. package/dist/definition.js +165 -0
  72. package/dist/delete-many.d.ts +13 -0
  73. package/dist/delete-many.d.ts.map +1 -0
  74. package/dist/delete-many.js +58 -0
  75. package/dist/engine.d.ts +16 -0
  76. package/dist/engine.d.ts.map +1 -0
  77. package/dist/engine.js +174 -0
  78. package/dist/errors.d.ts +50 -0
  79. package/dist/errors.d.ts.map +1 -0
  80. package/dist/errors.js +43 -0
  81. package/dist/exact.d.ts +20 -0
  82. package/dist/exact.d.ts.map +1 -0
  83. package/dist/exact.js +1 -0
  84. package/dist/feed.d.ts +6 -0
  85. package/dist/feed.d.ts.map +1 -0
  86. package/dist/feed.js +33 -0
  87. package/dist/field-codec.d.ts +11 -0
  88. package/dist/field-codec.d.ts.map +1 -0
  89. package/dist/field-codec.js +48 -0
  90. package/dist/field-rules.d.ts +57 -0
  91. package/dist/field-rules.d.ts.map +1 -0
  92. package/dist/field-rules.js +1 -0
  93. package/dist/field-type.d.ts +51 -0
  94. package/dist/field-type.d.ts.map +1 -0
  95. package/dist/field-type.js +106 -0
  96. package/dist/fields.d.ts +295 -0
  97. package/dist/fields.d.ts.map +1 -0
  98. package/dist/fields.js +1 -0
  99. package/dist/function.d.ts +126 -0
  100. package/dist/function.d.ts.map +1 -0
  101. package/dist/function.js +1 -0
  102. package/dist/functions.d.ts +18 -0
  103. package/dist/functions.d.ts.map +1 -0
  104. package/dist/functions.js +99 -0
  105. package/dist/group-by.d.ts +10 -0
  106. package/dist/group-by.d.ts.map +1 -0
  107. package/dist/group-by.js +145 -0
  108. package/dist/group-having.d.ts +8 -0
  109. package/dist/group-having.d.ts.map +1 -0
  110. package/dist/group-having.js +97 -0
  111. package/dist/hooks.d.ts +41 -0
  112. package/dist/hooks.d.ts.map +1 -0
  113. package/dist/hooks.js +149 -0
  114. package/dist/hosts.d.ts +3 -0
  115. package/dist/hosts.d.ts.map +1 -0
  116. package/dist/hosts.js +8 -0
  117. package/dist/identify.d.ts +15 -0
  118. package/dist/identify.d.ts.map +1 -0
  119. package/dist/identify.js +71 -0
  120. package/dist/index.d.ts +25 -0
  121. package/dist/index.d.ts.map +1 -0
  122. package/dist/index.js +12 -0
  123. package/dist/internal.d.ts +28 -0
  124. package/dist/internal.d.ts.map +1 -0
  125. package/dist/internal.js +39 -0
  126. package/dist/lifecycle.d.ts +21 -0
  127. package/dist/lifecycle.d.ts.map +1 -0
  128. package/dist/lifecycle.js +149 -0
  129. package/dist/list-update.d.ts +12 -0
  130. package/dist/list-update.d.ts.map +1 -0
  131. package/dist/list-update.js +52 -0
  132. package/dist/live.d.ts +37 -0
  133. package/dist/live.d.ts.map +1 -0
  134. package/dist/live.js +139 -0
  135. package/dist/manifest.d.ts +11 -0
  136. package/dist/manifest.d.ts.map +1 -0
  137. package/dist/manifest.js +60 -0
  138. package/dist/memory-aggregate.d.ts +7 -0
  139. package/dist/memory-aggregate.d.ts.map +1 -0
  140. package/dist/memory-aggregate.js +107 -0
  141. package/dist/memory-conditions.d.ts +8 -0
  142. package/dist/memory-conditions.d.ts.map +1 -0
  143. package/dist/memory-conditions.js +82 -0
  144. package/dist/memory-draft.d.ts +34 -0
  145. package/dist/memory-draft.d.ts.map +1 -0
  146. package/dist/memory-draft.js +255 -0
  147. package/dist/memory-json.d.ts +6 -0
  148. package/dist/memory-json.d.ts.map +1 -0
  149. package/dist/memory-json.js +86 -0
  150. package/dist/memory-read.d.ts +45 -0
  151. package/dist/memory-read.d.ts.map +1 -0
  152. package/dist/memory-read.js +244 -0
  153. package/dist/memory-search.d.ts +16 -0
  154. package/dist/memory-search.d.ts.map +1 -0
  155. package/dist/memory-search.js +110 -0
  156. package/dist/memory-values.d.ts +23 -0
  157. package/dist/memory-values.d.ts.map +1 -0
  158. package/dist/memory-values.js +82 -0
  159. package/dist/memory.d.ts +4 -0
  160. package/dist/memory.d.ts.map +1 -0
  161. package/dist/memory.js +157 -0
  162. package/dist/migrate.d.ts +22 -0
  163. package/dist/migrate.d.ts.map +1 -0
  164. package/dist/migrate.js +131 -0
  165. package/dist/model.d.ts +239 -0
  166. package/dist/model.d.ts.map +1 -0
  167. package/dist/model.js +1 -0
  168. package/dist/nested-write.d.ts +64 -0
  169. package/dist/nested-write.d.ts.map +1 -0
  170. package/dist/nested-write.js +274 -0
  171. package/dist/number-update.d.ts +18 -0
  172. package/dist/number-update.d.ts.map +1 -0
  173. package/dist/number-update.js +96 -0
  174. package/dist/plugin-app.d.ts +48 -0
  175. package/dist/plugin-app.d.ts.map +1 -0
  176. package/dist/plugin-app.js +230 -0
  177. package/dist/plugin-code.d.ts +17 -0
  178. package/dist/plugin-code.d.ts.map +1 -0
  179. package/dist/plugin-code.js +125 -0
  180. package/dist/plugin-use.d.ts +22 -0
  181. package/dist/plugin-use.d.ts.map +1 -0
  182. package/dist/plugin-use.js +161 -0
  183. package/dist/plugin.d.ts +357 -0
  184. package/dist/plugin.d.ts.map +1 -0
  185. package/dist/plugin.js +17 -0
  186. package/dist/query-aggregate.d.ts +82 -0
  187. package/dist/query-aggregate.d.ts.map +1 -0
  188. package/dist/query-aggregate.js +1 -0
  189. package/dist/query-args.d.ts +41 -0
  190. package/dist/query-args.d.ts.map +1 -0
  191. package/dist/query-args.js +263 -0
  192. package/dist/query-bulk.d.ts +35 -0
  193. package/dist/query-bulk.d.ts.map +1 -0
  194. package/dist/query-bulk.js +1 -0
  195. package/dist/query-conditions.d.ts +9 -0
  196. package/dist/query-conditions.d.ts.map +1 -0
  197. package/dist/query-conditions.js +46 -0
  198. package/dist/query-context.d.ts +32 -0
  199. package/dist/query-context.d.ts.map +1 -0
  200. package/dist/query-context.js +55 -0
  201. package/dist/query-filter.d.ts +46 -0
  202. package/dist/query-filter.d.ts.map +1 -0
  203. package/dist/query-filter.js +1 -0
  204. package/dist/query-group.d.ts +82 -0
  205. package/dist/query-group.d.ts.map +1 -0
  206. package/dist/query-group.js +1 -0
  207. package/dist/query-json.d.ts +9 -0
  208. package/dist/query-json.d.ts.map +1 -0
  209. package/dist/query-json.js +66 -0
  210. package/dist/query-order.d.ts +15 -0
  211. package/dist/query-order.d.ts.map +1 -0
  212. package/dist/query-order.js +115 -0
  213. package/dist/query-project.d.ts +15 -0
  214. package/dist/query-project.d.ts.map +1 -0
  215. package/dist/query-project.js +45 -0
  216. package/dist/query-search.d.ts +24 -0
  217. package/dist/query-search.d.ts.map +1 -0
  218. package/dist/query-search.js +2 -0
  219. package/dist/query-values.d.ts +28 -0
  220. package/dist/query-values.d.ts.map +1 -0
  221. package/dist/query-values.js +91 -0
  222. package/dist/query-where.d.ts +46 -0
  223. package/dist/query-where.d.ts.map +1 -0
  224. package/dist/query-where.js +200 -0
  225. package/dist/query-write.d.ts +14 -0
  226. package/dist/query-write.d.ts.map +1 -0
  227. package/dist/query-write.js +260 -0
  228. package/dist/query.d.ts +254 -0
  229. package/dist/query.d.ts.map +1 -0
  230. package/dist/query.js +1 -0
  231. package/dist/record.d.ts +257 -0
  232. package/dist/record.d.ts.map +1 -0
  233. package/dist/record.js +1 -0
  234. package/dist/recorder.d.ts +32 -0
  235. package/dist/recorder.d.ts.map +1 -0
  236. package/dist/recorder.js +152 -0
  237. package/dist/relation-write.d.ts +41 -0
  238. package/dist/relation-write.d.ts.map +1 -0
  239. package/dist/relation-write.js +112 -0
  240. package/dist/relations.d.ts +42 -0
  241. package/dist/relations.d.ts.map +1 -0
  242. package/dist/relations.js +119 -0
  243. package/dist/rules.d.ts +97 -0
  244. package/dist/rules.d.ts.map +1 -0
  245. package/dist/rules.js +71 -0
  246. package/dist/running.d.ts +29 -0
  247. package/dist/running.d.ts.map +1 -0
  248. package/dist/running.js +39 -0
  249. package/dist/schema-indexes.d.ts +11 -0
  250. package/dist/schema-indexes.d.ts.map +1 -0
  251. package/dist/schema-indexes.js +59 -0
  252. package/dist/schema-virtuals.d.ts +19 -0
  253. package/dist/schema-virtuals.d.ts.map +1 -0
  254. package/dist/schema-virtuals.js +100 -0
  255. package/dist/schema.d.ts +35 -0
  256. package/dist/schema.d.ts.map +1 -0
  257. package/dist/schema.js +267 -0
  258. package/dist/scope.d.ts +46 -0
  259. package/dist/scope.d.ts.map +1 -0
  260. package/dist/scope.js +82 -0
  261. package/dist/scratch.d.ts +14 -0
  262. package/dist/scratch.d.ts.map +1 -0
  263. package/dist/scratch.js +10 -0
  264. package/dist/serve.d.ts +2 -0
  265. package/dist/serve.d.ts.map +1 -0
  266. package/dist/serve.js +4 -0
  267. package/dist/symbols.d.ts +10 -0
  268. package/dist/symbols.d.ts.map +1 -0
  269. package/dist/symbols.js +1 -0
  270. package/dist/transaction.d.ts +28 -0
  271. package/dist/transaction.d.ts.map +1 -0
  272. package/dist/transaction.js +156 -0
  273. package/dist/types.d.ts +58 -0
  274. package/dist/types.d.ts.map +1 -0
  275. package/dist/types.js +1 -0
  276. package/dist/update-many.d.ts +7 -0
  277. package/dist/update-many.d.ts.map +1 -0
  278. package/dist/update-many.js +182 -0
  279. package/dist/uuid.d.ts +7 -0
  280. package/dist/uuid.d.ts.map +1 -0
  281. package/dist/uuid.js +48 -0
  282. package/dist/validation.d.ts +24 -0
  283. package/dist/validation.d.ts.map +1 -0
  284. package/dist/validation.js +72 -0
  285. package/dist/virtual-read.d.ts +9 -0
  286. package/dist/virtual-read.d.ts.map +1 -0
  287. package/dist/virtual-read.js +145 -0
  288. package/dist/virtual.d.ts +142 -0
  289. package/dist/virtual.d.ts.map +1 -0
  290. package/dist/virtual.js +1 -0
  291. package/dist/write-context.d.ts +46 -0
  292. package/dist/write-context.d.ts.map +1 -0
  293. package/dist/write-context.js +102 -0
  294. package/dist/write-data.d.ts +26 -0
  295. package/dist/write-data.d.ts.map +1 -0
  296. package/dist/write-data.js +144 -0
  297. package/dist/write-plan.d.ts +107 -0
  298. package/dist/write-plan.d.ts.map +1 -0
  299. package/dist/write-plan.js +170 -0
  300. package/dist/write-target.d.ts +17 -0
  301. package/dist/write-target.d.ts.map +1 -0
  302. package/dist/write-target.js +59 -0
  303. package/dist/writes.d.ts +23 -0
  304. package/dist/writes.d.ts.map +1 -0
  305. package/dist/writes.js +246 -0
  306. package/dist/writing.d.ts +10 -0
  307. package/dist/writing.d.ts.map +1 -0
  308. package/dist/writing.js +66 -0
  309. package/package.json +42 -3
  310. package/src/access.ts +329 -0
  311. package/src/adapter-errors.ts +89 -0
  312. package/src/adapter-json.ts +21 -0
  313. package/src/adapter-migrations.ts +98 -0
  314. package/src/adapter.ts +363 -0
  315. package/src/aggregate.ts +136 -0
  316. package/src/app-internals.ts +26 -0
  317. package/src/app-options.ts +24 -0
  318. package/src/cascade.ts +297 -0
  319. package/src/cli-config.ts +45 -0
  320. package/src/cli-deploy.ts +235 -0
  321. package/src/cli-main.ts +165 -0
  322. package/src/cli-migrate.ts +143 -0
  323. package/src/cli.ts +5 -0
  324. package/src/complete.ts +73 -0
  325. package/src/console-logger.ts +87 -0
  326. package/src/create-many.ts +126 -0
  327. package/src/decimal.ts +74 -0
  328. package/src/define-app.ts +108 -0
  329. package/src/define-enum.ts +26 -0
  330. package/src/define-function.ts +188 -0
  331. package/src/define-model.ts +220 -0
  332. package/src/definition.ts +352 -0
  333. package/src/delete-many.ts +76 -0
  334. package/src/engine.ts +281 -0
  335. package/src/errors.ts +97 -0
  336. package/src/exact.ts +49 -0
  337. package/src/feed.ts +39 -0
  338. package/src/field-codec.ts +72 -0
  339. package/src/field-rules.ts +77 -0
  340. package/src/field-type.ts +219 -0
  341. package/src/fields.ts +383 -0
  342. package/src/function.ts +143 -0
  343. package/src/functions.ts +154 -0
  344. package/src/group-by.ts +186 -0
  345. package/src/group-having.ts +143 -0
  346. package/src/hooks.ts +215 -0
  347. package/src/hosts.ts +11 -0
  348. package/src/identify.ts +95 -0
  349. package/src/index.ts +125 -0
  350. package/src/internal.ts +68 -0
  351. package/src/lifecycle.ts +214 -0
  352. package/src/list-update.ts +71 -0
  353. package/src/live.ts +199 -0
  354. package/src/manifest.ts +71 -0
  355. package/src/memory-aggregate.ts +162 -0
  356. package/src/memory-conditions.ts +88 -0
  357. package/src/memory-draft.ts +319 -0
  358. package/src/memory-json.ts +91 -0
  359. package/src/memory-read.ts +311 -0
  360. package/src/memory-search.ts +149 -0
  361. package/src/memory-values.ts +106 -0
  362. package/src/memory.ts +173 -0
  363. package/src/migrate.ts +152 -0
  364. package/src/model.ts +312 -0
  365. package/src/nested-write.ts +390 -0
  366. package/src/number-update.ts +142 -0
  367. package/src/plugin-app.ts +308 -0
  368. package/src/plugin-code.ts +158 -0
  369. package/src/plugin-use.ts +217 -0
  370. package/src/plugin.ts +400 -0
  371. package/src/query-aggregate.ts +96 -0
  372. package/src/query-args.ts +375 -0
  373. package/src/query-bulk.ts +53 -0
  374. package/src/query-conditions.ts +53 -0
  375. package/src/query-context.ts +104 -0
  376. package/src/query-filter.ts +54 -0
  377. package/src/query-group.ts +137 -0
  378. package/src/query-json.ts +90 -0
  379. package/src/query-order.ts +158 -0
  380. package/src/query-project.ts +73 -0
  381. package/src/query-search.ts +31 -0
  382. package/src/query-values.ts +131 -0
  383. package/src/query-where.ts +318 -0
  384. package/src/query-write.ts +360 -0
  385. package/src/query.ts +400 -0
  386. package/src/record.ts +379 -0
  387. package/src/recorder.ts +212 -0
  388. package/src/relation-write.ts +176 -0
  389. package/src/relations.ts +165 -0
  390. package/src/rules.ts +188 -0
  391. package/src/running.ts +62 -0
  392. package/src/schema-indexes.ts +88 -0
  393. package/src/schema-virtuals.ts +134 -0
  394. package/src/schema.ts +367 -0
  395. package/src/scope.ts +123 -0
  396. package/src/scratch.ts +28 -0
  397. package/src/serve.ts +5 -0
  398. package/src/symbols.ts +11 -0
  399. package/src/transaction.ts +211 -0
  400. package/src/types.ts +79 -0
  401. package/src/update-many.ts +268 -0
  402. package/src/uuid.ts +51 -0
  403. package/src/validation.ts +107 -0
  404. package/src/virtual-read.ts +198 -0
  405. package/src/virtual.ts +187 -0
  406. package/src/write-context.ts +170 -0
  407. package/src/write-data.ts +188 -0
  408. package/src/write-plan.ts +273 -0
  409. package/src/write-target.ts +92 -0
  410. package/src/writes.ts +361 -0
  411. package/src/writing.ts +93 -0
@@ -0,0 +1,211 @@
1
+ // Transactions (D-36, D-88): `AsyncLocalStorage` carries the open one, so a
2
+ // hook's queries join it without a `tx` argument.
3
+ import { AsyncLocalStorage } from 'node:async_hooks'
4
+ import { randomUUID } from 'node:crypto'
5
+ import type { Adapter, Transaction } from './adapter.ts'
6
+ import { RelationViolation } from './adapter.ts'
7
+ import type { Logger } from './app-options.ts'
8
+ import { decoded } from './field-codec.ts'
9
+ import { recordCount, recordFind } from './recorder.ts'
10
+ import type { Running } from './running.ts'
11
+ import { current } from './running.ts'
12
+ import { refused } from './write-target.ts'
13
+
14
+ /** What a query reads and writes through. */
15
+ export type Store = Pick<
16
+ Transaction,
17
+ 'find' | 'count' | 'aggregate' | 'write'
18
+ > & {
19
+ /**
20
+ * The write's changes when nothing follows them: alone, they need no
21
+ * transaction (D-99).
22
+ */
23
+ readonly last: Transaction['write']
24
+ }
25
+
26
+ /** A `*Commit` hook, run once the transaction commits (D-15). */
27
+ export type Commit = () => Promise<void>
28
+
29
+ type Open = {
30
+ readonly app: Running
31
+ /** Begun by the first query (D-99). */
32
+ tx: Promise<Transaction> | undefined
33
+ readonly commits: Commit[]
34
+ /** The first query that failed in it (D-36). */
35
+ failure: { readonly error: unknown } | undefined
36
+ }
37
+
38
+ const opened = new AsyncLocalStorage<Open>()
39
+
40
+ async function begun(open: Open): Promise<Transaction> {
41
+ open.tx ??= open.app.adapter.begin()
42
+ return open.tx
43
+ }
44
+
45
+ /**
46
+ * The store's rows with their plugin fields decoded (D-122), each read kept
47
+ * if a recording runs (D-154).
48
+ */
49
+ function decoding(app: Running, store: Store): Store {
50
+ const { models } = app.resolved
51
+ return {
52
+ find: async (read) => {
53
+ const rows = await store.find(read)
54
+ recordFind(models, read, rows)
55
+ return decoded(models, read, rows)
56
+ },
57
+ count: async (read) => {
58
+ recordCount(models, read)
59
+ return store.count(read)
60
+ },
61
+ // It reads no plugin field (D-143).
62
+ aggregate: async (read) => {
63
+ recordCount(models, read)
64
+ return store.aggregate(read)
65
+ },
66
+ write: async (write) =>
67
+ decoded(models, write.read, await store.write(write)),
68
+ last: async (write) => decoded(models, write.read, await store.last(write)),
69
+ }
70
+ }
71
+
72
+ /** Queries in the transaction; `owner`'s last write may run alone. */
73
+ function storeOf(open: Open, owner: boolean): Store {
74
+ return decoding(open.app, {
75
+ find: async (read) => (await begun(open)).find(read),
76
+ count: async (read) => (await begun(open)).count(read),
77
+ aggregate: async (read) => (await begun(open)).aggregate(read),
78
+ write: async (write) => (await begun(open)).write(write),
79
+ last: async (write) =>
80
+ owner && open.tx === undefined
81
+ ? open.app.adapter.write(write)
82
+ : (await begun(open)).write(write),
83
+ })
84
+ }
85
+
86
+ /** Runs in the open transaction; a failure rolls all of it back (D-36). */
87
+ async function joined<R>(open: Open, run: () => Promise<R>): Promise<R> {
88
+ if (open.failure !== undefined) {
89
+ throw new Error(
90
+ 'A query failed in this transaction, which rolls back: no other query runs in it.',
91
+ { cause: open.failure.error },
92
+ )
93
+ }
94
+ try {
95
+ return await run()
96
+ } catch (error) {
97
+ open.failure ??= { error }
98
+ throw error
99
+ }
100
+ }
101
+
102
+ async function committed(logger: Logger, commits: Commit[]): Promise<void> {
103
+ for (const commit of commits) {
104
+ try {
105
+ await commit()
106
+ } catch (error) {
107
+ logger.error(
108
+ { correlationId: randomUUID(), err: error },
109
+ 'A commit hook failed; the write is kept.',
110
+ )
111
+ }
112
+ }
113
+ }
114
+
115
+ /** A transaction that failed to begin has nothing to roll back. */
116
+ async function rollback(open: Open): Promise<void> {
117
+ const tx = await open.tx?.catch(() => undefined)
118
+ await tx?.rollback()
119
+ }
120
+
121
+ async function atomic<R>(
122
+ app: Running,
123
+ run: (open: Open) => Promise<R>,
124
+ ): Promise<R> {
125
+ const open: Open = { app, tx: undefined, commits: [], failure: undefined }
126
+ let result: R
127
+ try {
128
+ result = await opened.run(open, run, open)
129
+ } catch (error) {
130
+ await rollback(open)
131
+ throw error
132
+ }
133
+ if (open.failure !== undefined) {
134
+ await rollback(open)
135
+ throw open.failure.error
136
+ }
137
+ try {
138
+ await (await open.tx)?.commit()
139
+ } catch (error) {
140
+ // A deferred foreign key checks at the commit (D-99).
141
+ throw error instanceof RelationViolation ? refused(error) : error
142
+ }
143
+ // Outside the transaction: a commit hook's queries open their own.
144
+ await committed(app.logger, open.commits)
145
+ return result
146
+ }
147
+
148
+ /** A read: in the open transaction, else straight on the adapter. */
149
+ export async function reading<R>(
150
+ app: Running,
151
+ run: (store: Store) => Promise<R>,
152
+ ): Promise<R> {
153
+ const open = opened.getStore()
154
+ if (open === undefined) {
155
+ const { adapter } = app
156
+ return run(
157
+ decoding(app, {
158
+ find: async (read) => adapter.find(read),
159
+ count: async (read) => adapter.count(read),
160
+ aggregate: async (read) => adapter.aggregate(read),
161
+ write: async (write) => adapter.write(write),
162
+ last: async (write) => adapter.write(write),
163
+ }),
164
+ )
165
+ }
166
+ return joined(open, async () => run(storeOf(open, false)))
167
+ }
168
+
169
+ /** A write: in the open transaction, else in its own (D-36). */
170
+ export async function writing<R>(
171
+ app: Running,
172
+ run: (store: Store, commits: Commit[]) => Promise<R>,
173
+ ): Promise<R> {
174
+ const open = opened.getStore()
175
+ if (open !== undefined) {
176
+ return joined(open, async () => run(storeOf(open, false), open.commits))
177
+ }
178
+ return atomic(app, async (own) => run(storeOf(own, true), own.commits))
179
+ }
180
+
181
+ /**
182
+ * Runs `run` in one transaction: if it throws, or if a query in it fails,
183
+ * every write rolls back and the `*Commit` hooks never run. Inside another
184
+ * `transaction`, it joins that one (D-36).
185
+ */
186
+ export async function transaction<R>(run: () => Promise<R>): Promise<R> {
187
+ const open = opened.getStore()
188
+ if (open !== undefined) return joined(open, run)
189
+ const app = current()
190
+ if (app === undefined) {
191
+ throw new Error('transaction runs on a started application.')
192
+ }
193
+ return app.work.run(async () => atomic(app, run))
194
+ }
195
+
196
+ /**
197
+ * Runs an adapter's own statement, `database.sql`, in the running
198
+ * application's open transaction, else alone (D-137).
199
+ */
200
+ export async function joining<R>(
201
+ adapter: Adapter,
202
+ run: (tx: Transaction | undefined) => Promise<R>,
203
+ ): Promise<R> {
204
+ const open = opened.getStore()
205
+ if (open?.app.adapter === adapter) {
206
+ return joined(open, async () => run(await begun(open)))
207
+ }
208
+ const app = current()
209
+ if (app?.adapter !== adapter) return run(undefined)
210
+ return app.work.run(async () => run(undefined))
211
+ }
package/src/types.ts ADDED
@@ -0,0 +1,79 @@
1
+ // A model's and a function's types, named from the model or the function
2
+ // (D-74): what application code passes to queries and gets back.
3
+ import type { ResultOf, RunArgs } from './function.ts'
4
+ import type { Model } from './model.ts'
5
+ import type { AggregateArgs } from './query-aggregate.ts'
6
+ import type {
7
+ CreateManyAndReturnArgs,
8
+ CreateManyArgs,
9
+ UpdateManyAndReturnArgs,
10
+ UpdateManyArgs,
11
+ } from './query-bulk.ts'
12
+ import type { GroupByArgs } from './query-group.ts'
13
+ import type {
14
+ CreateArgs,
15
+ FindFirstArgs,
16
+ FindManyArgs,
17
+ FindUniqueArgs,
18
+ Include,
19
+ OrderBy,
20
+ Payload,
21
+ Select,
22
+ UpdateArgs,
23
+ UpsertArgs,
24
+ Where,
25
+ } from './query.ts'
26
+ import type { CreateData, UpdateData } from './record.ts'
27
+
28
+ type Parts<M> =
29
+ M extends Model<infer N, infer F, infer _P, infer _W, infer X>
30
+ ? { readonly name: N; readonly fields: F; readonly unique: X }
31
+ : never
32
+ type FieldsOf<M> = Parts<M>['fields']
33
+ type UniqueOf<M> = Parts<M>['unique']
34
+ type NameOf<M> = Parts<M>['name']
35
+
36
+ type Operations<F, X, N> = {
37
+ readonly findMany: FindManyArgs<F, X, N>
38
+ readonly findFirst: FindFirstArgs<F, X>
39
+ readonly findUnique: FindUniqueArgs<F, X>
40
+ readonly findFirstOrThrow: FindFirstArgs<F, X>
41
+ readonly findUniqueOrThrow: FindUniqueArgs<F, X>
42
+ readonly count: { readonly where?: Where<F> }
43
+ readonly aggregate: AggregateArgs<F>
44
+ readonly groupBy: GroupByArgs<F, N>
45
+ readonly create: CreateArgs<F, N>
46
+ readonly update: UpdateArgs<F, X, N>
47
+ readonly upsert: UpsertArgs<F, X, N>
48
+ readonly delete: FindUniqueArgs<F, X>
49
+ readonly updateMany: UpdateManyArgs<F>
50
+ readonly createMany: CreateManyArgs<F>
51
+ readonly updateManyAndReturn: UpdateManyAndReturnArgs<F>
52
+ readonly createManyAndReturn: CreateManyAndReturnArgs<F>
53
+ readonly deleteMany: { readonly where?: Where<F> | undefined }
54
+ }
55
+
56
+ /** One record, as arguments `A` return it; without them, its scalars. */
57
+ export type RecordOf<M, A = {}> = Payload<FieldsOf<M>, A>
58
+ export type WhereOf<M> = Where<FieldsOf<M>>
59
+ export type IncludeOf<M> = Include<FieldsOf<M>>
60
+ export type SelectOf<M> = Select<FieldsOf<M>>
61
+ export type OrderByOf<M> = OrderBy<FieldsOf<M>>
62
+ export type CreateDataOf<M> = CreateData<FieldsOf<M>, NameOf<M>>
63
+ export type UpdateDataOf<M> = UpdateData<FieldsOf<M>, NameOf<M>>
64
+ /** The arguments of one operation of the model. */
65
+ export type ArgsOf<
66
+ M,
67
+ O extends keyof Operations<FieldsOf<M>, UniqueOf<M>, NameOf<M>>,
68
+ > = Operations<FieldsOf<M>, UniqueOf<M>, NameOf<M>>[O]
69
+
70
+ /** What a caller sends to a function, before validation. */
71
+ export type InputOf<Fn> = Fn extends { readonly input: infer I }
72
+ ? RunArgs<I> extends readonly [input: infer V, options?: unknown]
73
+ ? V
74
+ : undefined
75
+ : never
76
+ /** What a function returns. */
77
+ export type OutputOf<Fn> = Fn extends { readonly output: infer O }
78
+ ? ResultOf<O>
79
+ : never
@@ -0,0 +1,268 @@
1
+ // `updateMany` (D-133): what an `update` runs, on each row its `where`
2
+ // selects, all or nothing; one write when nothing needs the rows (D-99).
3
+ // `updateManyAndReturn` returns them in `id` order, each as `update` does
4
+ // (D-149).
5
+ import { recordOf } from './access.ts'
6
+ import type { Change, Filter, Order, Row } from './adapter.ts'
7
+ import { InvalidQuery } from './errors.ts'
8
+ import type { Event } from './hooks.ts'
9
+ import { rereads, runHook, settle } from './hooks.ts'
10
+ import type { Operations } from './number-update.ts'
11
+ import { computeOn, operationsOf, inRange, validates } from './number-update.ts'
12
+ import type { Plan as Shaped } from './query-args.ts'
13
+ import { oneKeys } from './query-args.ts'
14
+ import type { Context } from './query-context.ts'
15
+ import { filtered, given, shapedRows } from './query-context.ts'
16
+ import { byIds, entries } from './query-where.ts'
17
+ import { updatePlan } from './query-write.ts'
18
+ import type { ModelInfo } from './schema.ts'
19
+ import type { Commit, Store } from './transaction.ts'
20
+ import type { Draft } from './write-data.ts'
21
+ import { prepared, readData } from './write-data.ts'
22
+ import type { Plan } from './write-plan.ts'
23
+ import { updated } from './write-plan.ts'
24
+ import { checkedWrite, guarded } from './write-target.ts'
25
+ import { writingOf } from './writing.ts'
26
+ import { returned } from './writes.ts'
27
+
28
+ // In id order, so two calls on the same rows wait instead of deadlocking.
29
+ const orderBy: readonly Order[] = [{ field: 'id', direction: 'asc' }]
30
+
31
+ /** The scalar data of the rows, as `update` reads it (D-133 point 2). */
32
+ function scalarData(context: Context, data: unknown): Draft {
33
+ const { models, model } = context
34
+ for (const [name] of entries(data, 'data')) {
35
+ if (model.fields.get(name)?.kind === 'scalar') continue
36
+ if (!model.fields.has(name)) continue
37
+ const path = `data.${name}`
38
+ throw new InvalidQuery(
39
+ path,
40
+ `${path} is a relation, which updateMany does not write.`,
41
+ )
42
+ }
43
+ return readData(models, model, data, false)
44
+ }
45
+
46
+ /** The plan's update, spread to every row `where` selects. */
47
+ function spread(plan: Plan, where: Filter): Change[] {
48
+ return plan.steps.flatMap((step) =>
49
+ step.kind === 'update' ? [{ ...step, where }] : [],
50
+ )
51
+ }
52
+
53
+ /** A row, its own copy of the data, and the data the rules saw (D-133). */
54
+ type Item = {
55
+ readonly row: Row
56
+ readonly draft: Draft
57
+ readonly sent: Readonly<Draft>
58
+ }
59
+
60
+ /** Rows whose data came out the same, and the operations the write computes. */
61
+ type Group = {
62
+ readonly draft: Draft
63
+ readonly operations: Operations
64
+ readonly items: [Item, ...Item[]]
65
+ }
66
+
67
+ const same = (a: Group, b: Omit<Group, 'items'>): boolean => {
68
+ const keys = Object.keys(a.draft)
69
+ return (
70
+ keys.length === Object.keys(b.draft).length &&
71
+ keys.every(
72
+ (key) =>
73
+ Object.hasOwn(b.draft, key) && Object.is(a.draft[key], b.draft[key]),
74
+ ) &&
75
+ [...a.operations.keys()].join() === [...b.operations.keys()].join()
76
+ )
77
+ }
78
+
79
+ /**
80
+ * A row's data after its hooks: a number or list they left as computed is
81
+ * computed by the write again, the row being locked, so rows share one
82
+ * statement.
83
+ */
84
+ function settled(item: Item, operations: Operations): Omit<Group, 'items'> {
85
+ const left = new Map(operations)
86
+ const draft: Draft = { ...item.draft }
87
+ for (const name of operations.keys()) {
88
+ if (Object.is(item.draft[name], item.sent[name])) delete draft[name]
89
+ else left.delete(name)
90
+ }
91
+ return { draft, operations: left }
92
+ }
93
+
94
+ /** The rows by data; one a hook gave a relation write is planned alone. */
95
+ function grouped(
96
+ model: ModelInfo,
97
+ items: readonly Item[],
98
+ operations: Operations,
99
+ ) {
100
+ const groups: Group[] = []
101
+ for (const item of items) {
102
+ const each = settled(item, operations)
103
+ const scalars = Object.keys(each.draft).every(
104
+ (name) => model.fields.get(name)?.kind === 'scalar',
105
+ )
106
+ const found = scalars
107
+ ? groups.find((group) => same(group, each))
108
+ : undefined
109
+ if (found === undefined) groups.push({ ...each, items: [item] })
110
+ else found.items.push(item)
111
+ }
112
+ return groups
113
+ }
114
+
115
+ /**
116
+ * Each row through the update rule and hooks, then their writes (D-133):
117
+ * the rows changed, or with `at` as it shapes them.
118
+ */
119
+ async function lockedRows(
120
+ context: Context,
121
+ store: Store,
122
+ commits: Commit[],
123
+ data: { readonly draft: Draft; readonly operations: Operations },
124
+ where: Filter,
125
+ at: Shaped | undefined,
126
+ ): Promise<readonly unknown[]> {
127
+ const { model, caller, scope } = context
128
+ const { operations } = data
129
+ const rows = await store.find({
130
+ model: model.name,
131
+ where,
132
+ orderBy,
133
+ lock: true,
134
+ })
135
+ const items = rows.map((row): Item => {
136
+ const draft: Draft = { ...data.draft }
137
+ computeOn(model, row, draft, operations)
138
+ return { row, draft, sent: { ...draft } }
139
+ })
140
+ // One row refused, none written (D-111).
141
+ for (const { row, sent } of items) {
142
+ scope.access?.allow(model, 'update', { record: row, data: sent })
143
+ }
144
+ if (items.length === 0) return []
145
+ for (const { row, draft } of items) {
146
+ await prepared(model, draft, caller, false)
147
+ const record = recordOf(model, row)
148
+ await runHook(model, 'beforeUpdate', { caller, record, data: draft })
149
+ }
150
+ const events: Event[] = items.map(({ row }) => ({
151
+ model,
152
+ operation: 'update',
153
+ id: row.id,
154
+ before: row,
155
+ }))
156
+ const writing = writingOf(scope, store, events)
157
+ const changes: Change[] = []
158
+ for (const group of grouped(model, items, operations)) {
159
+ const [{ row, sent }] = group.items
160
+ const plan = await updatePlan(
161
+ scope,
162
+ model,
163
+ row.id,
164
+ group.draft,
165
+ sent,
166
+ store,
167
+ group.operations,
168
+ )
169
+ events.push(...plan.events)
170
+ await plan.check(writing)
171
+ const ids = group.items.map((item) => item.row.id)
172
+ const one = ids.length === 1
173
+ changes.push(
174
+ ...(one
175
+ ? await plan.changes(writing)
176
+ : spread(plan, { id: { in: ids } })),
177
+ )
178
+ }
179
+ const ids = items.map((item) => item.row.id)
180
+ const read = at && {
181
+ model: model.name,
182
+ ...at.read,
183
+ where: byIds(ids),
184
+ orderBy,
185
+ }
186
+ // An `after*` hook may change the rows: they are read once it ran (D-88).
187
+ const again = rereads(events)
188
+ const written = await inRange(
189
+ model,
190
+ checkedWrite(store, writing.checks, changes, again ? undefined : read),
191
+ )
192
+ await settle(store, caller, events, commits)
193
+ if (at === undefined || read === undefined) return ids
194
+ const result = again ? await store.find(read) : written
195
+ return shapedRows(context, store, result, at.shape, scope.access)
196
+ }
197
+
198
+ /**
199
+ * Updates the rows `where` selects: their ids, or with `at` the rows as it
200
+ * shapes them, in `id` order. The rows come back even when a read rule
201
+ * would hide them, as `update`'s does (D-86).
202
+ */
203
+ async function updating(
204
+ context: Context,
205
+ store: Store,
206
+ commits: Commit[],
207
+ found: ReadonlyMap<string, unknown>,
208
+ at?: Shaped,
209
+ ): Promise<readonly unknown[]> {
210
+ const { model, caller, scope } = context
211
+ const draft = scalarData(context, found.get('data'))
212
+ const operations = operationsOf(model, draft)
213
+ const where: Filter = (await filtered(context, found, 'update')) ?? {}
214
+ // A relation read beside the UPDATE would see its rows as they were.
215
+ const nests = at?.read.include !== undefined || at?.read.count !== undefined
216
+ if (
217
+ scope.access !== undefined ||
218
+ updated(model) ||
219
+ validates(model, operations) ||
220
+ nests
221
+ ) {
222
+ const data = { draft, operations }
223
+ return lockedRows(context, store, commits, data, where, at)
224
+ }
225
+ // Nothing needs the stored rows: one UPDATE, which computes the numbers
226
+ // and lists and returns the rows it changed (D-104).
227
+ await prepared(model, draft, caller, false)
228
+ const plan = await updatePlan(
229
+ scope,
230
+ model,
231
+ '',
232
+ draft,
233
+ draft,
234
+ store,
235
+ operations,
236
+ )
237
+ const write = {
238
+ changes: spread(plan, where),
239
+ read: { model: model.name, ...at?.read, orderBy },
240
+ before: true,
241
+ }
242
+ const rows = await inRange(model, guarded(store.last(write), []))
243
+ if (at === undefined) return rows.map((row) => row.id)
244
+ return shapedRows(context, store, rows, at.shape, scope.access)
245
+ }
246
+
247
+ export async function updateMany(
248
+ context: Context,
249
+ store: Store,
250
+ commits: Commit[],
251
+ args: unknown,
252
+ ): Promise<{ readonly count: number }> {
253
+ const found = given(args, context.model.name, ['where', 'data'], ['data'])
254
+ const ids = await updating(context, store, commits, found)
255
+ return { count: ids.length }
256
+ }
257
+
258
+ export async function updateManyAndReturn(
259
+ context: Context,
260
+ store: Store,
261
+ commits: Commit[],
262
+ args: unknown,
263
+ ): Promise<readonly unknown[]> {
264
+ const keys = ['where', 'data', ...oneKeys]
265
+ const found = given(args, context.model.name, keys, ['data'])
266
+ const at = await returned(context, found)
267
+ return updating(context, store, commits, found, at)
268
+ }
package/src/uuid.ts ADDED
@@ -0,0 +1,51 @@
1
+ // UUIDv7 ids (D-22, RFC 9562): a millisecond timestamp, then a 12-bit counter
2
+ // in `rand_a` so that ids made in the same millisecond still sort in order.
3
+
4
+ let lastTime = 0
5
+ let counter = 0
6
+
7
+ export function uuidv7(): string {
8
+ let time = Date.now()
9
+ if (time > lastTime) {
10
+ lastTime = time
11
+ counter = 0
12
+ } else {
13
+ counter += 1
14
+ if (counter > 0xfff) {
15
+ lastTime += 1
16
+ counter = 0
17
+ }
18
+ time = lastTime
19
+ }
20
+ const bytes = new Uint8Array(16)
21
+ crypto.getRandomValues(bytes)
22
+ for (let index = 5; index >= 0; index -= 1) {
23
+ bytes[index] = time % 256
24
+ time = Math.floor(time / 256)
25
+ }
26
+ bytes[6] = 0x70 | (counter >> 8)
27
+ bytes[7] = counter & 0xff
28
+ bytes[8] = 0x80 | ((bytes[8] ?? 0) & 0x3f)
29
+ const hex = Array.from(bytes, (byte) => byte.toString(16).padStart(2, '0'))
30
+ return [
31
+ hex.slice(0, 4),
32
+ hex.slice(4, 6),
33
+ hex.slice(6, 8),
34
+ hex.slice(8, 10),
35
+ hex.slice(10, 16),
36
+ ]
37
+ .map((part) => part.join(''))
38
+ .join('-')
39
+ }
40
+
41
+ const made = new WeakMap<object, string>()
42
+
43
+ /**
44
+ * The id of the row a nested `create` makes from `data`: made once, so its
45
+ * relation's field rule sees it before the row is planned (D-128).
46
+ */
47
+ export function madeId(data: object): string {
48
+ const id = made.get(data) ?? uuidv7()
49
+ made.set(data, id)
50
+ return id
51
+ }