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
package/src/writes.ts ADDED
@@ -0,0 +1,361 @@
1
+ // `create`, `update` and `delete` in the order of D-67: access rules on the
2
+ // data sent, defaults, validation, `before*` hooks, the required check, the
3
+ // write, which a plugin's `redirect` may replace (D-89), `after*` hooks; the
4
+ // `*Commit` hooks once it commits (D-71, D-88). The row a write decides from
5
+ // is locked; a write that loads none answers from the rows it changed (D-99,
6
+ // D-104).
7
+ import { and, recordOf } from './access.ts'
8
+ import type { Filter, Nested, Row } from './adapter.ts'
9
+ import { cascadeWhere } from './cascade.ts'
10
+ import { deleted } from './delete-many.ts'
11
+ import { PublicError } from './errors.ts'
12
+ import type { Event } from './hooks.ts'
13
+ import { redirectOf, rereads, runHook, settle, settles } from './hooks.ts'
14
+ import type { Context } from './query-context.ts'
15
+ import { first, given, hiddenBy, picked } from './query-context.ts'
16
+ import { project } from './query-project.ts'
17
+ import { computeVirtuals } from './virtual-read.ts'
18
+ import type { Shape } from './query-args.ts'
19
+ import { oneKeys, plan, uniqueWhere } from './query-args.ts'
20
+ import type { Operations } from './number-update.ts'
21
+ import { computeOn, operationsOf, inRange, validates } from './number-update.ts'
22
+ import { nests, resolveData } from './nested-write.ts'
23
+ import { createPlan, updatePlan } from './query-write.ts'
24
+ import { planned } from './scope.ts'
25
+ import type { Commit, Store } from './transaction.ts'
26
+ import type { Draft } from './write-data.ts'
27
+ import { prepared, readData } from './write-data.ts'
28
+ import type { Checked, Plan } from './write-plan.ts'
29
+ import { updated } from './write-plan.ts'
30
+ import { uuidv7 } from './uuid.ts'
31
+ import { guarded, target } from './write-target.ts'
32
+ import { writingOf } from './writing.ts'
33
+ import { byId } from './query-where.ts'
34
+
35
+ /** The shape a single-row write returns, and each row of a bulk one's (D-149). */
36
+ export async function returned(
37
+ context: Context,
38
+ found: ReadonlyMap<string, unknown>,
39
+ ): Promise<{ readonly read: Nested; readonly shape: Shape }> {
40
+ const { models, model, scope } = context
41
+ return planned(scope, () =>
42
+ plan(models, model, picked(found), '', 'one', oneKeys, scope),
43
+ )
44
+ }
45
+
46
+ /**
47
+ * Runs the plan, then the `after*` hooks. The written row comes back even
48
+ * when a read rule or `beforeFind` would hide it (D-86, D-88).
49
+ */
50
+ async function written(
51
+ context: Context,
52
+ store: Store,
53
+ commits: Commit[],
54
+ steps: Plan,
55
+ events: Event[],
56
+ at: { readonly id: string; readonly read: Nested; readonly shape: Shape },
57
+ checks: Checked[] = [],
58
+ ): Promise<unknown> {
59
+ const { model, scope } = context
60
+ const writing = writingOf(scope, store, events, checks)
61
+ // The rows it creates, after the row written, before the rows it moves.
62
+ events.push(...steps.events)
63
+ await steps.check(writing)
64
+ const changes = await steps.changes(writing)
65
+ const read = { model: model.name, ...at.read, where: byId(at.id) }
66
+ const again = rereads(events)
67
+ const write = {
68
+ checks: checks.map(({ check }) => check),
69
+ changes,
70
+ ...(again ? {} : { read }),
71
+ }
72
+ // Nothing follows a write without hooks: it may need no transaction.
73
+ const run = settles(events) ? store.write(write) : store.last(write)
74
+ const rows = await guarded(run, checks)
75
+ await settle(store, scope.caller, events, commits)
76
+ // An `after*` hook may have changed the rows: read them again (D-88).
77
+ const result = again ? await store.find(read) : rows
78
+ return first(context, store, result, at.shape, scope.access)
79
+ }
80
+
81
+ /** Creates the row of `draft` as the rules allow. */
82
+ async function creating(
83
+ context: Context,
84
+ store: Store,
85
+ commits: Commit[],
86
+ draft: Draft,
87
+ at: { readonly read: Nested; readonly shape: Shape },
88
+ ): Promise<unknown> {
89
+ const { model, caller, scope } = context
90
+ const { read, shape } = at
91
+ const made = uuidv7()
92
+ const reach = { scope, reads: store, model, id: made, at: [] }
93
+ await resolveData({ ...reach, creating: true, trusted: false }, draft)
94
+ const sent: Readonly<Draft> = { ...draft }
95
+ scope.access?.allow(model, 'create', { data: sent })
96
+ await prepared(model, draft, caller, true)
97
+ await runHook(model, 'beforeCreate', { caller, data: draft })
98
+ const { id, plan: steps } = await createPlan(
99
+ scope,
100
+ model,
101
+ draft,
102
+ sent,
103
+ store,
104
+ made,
105
+ )
106
+ const events: Event[] = [{ model, operation: 'create', id }]
107
+ return written(context, store, commits, steps, events, { id, read, shape })
108
+ }
109
+
110
+ export async function create(
111
+ context: Context,
112
+ store: Store,
113
+ commits: Commit[],
114
+ args: unknown,
115
+ ): Promise<unknown> {
116
+ const { models, model } = context
117
+ const found = given(args, model.name, ['data', ...oneKeys], ['data'])
118
+ const at = await returned(context, found)
119
+ const draft = readData(models, model, found.get('data'), true)
120
+ return creating(context, store, commits, draft, at)
121
+ }
122
+
123
+ /**
124
+ * The rows a single-row write may target: the caller reads them (D-72), and
125
+ * the `beforeFind` hooks let it `operation` them (D-124).
126
+ */
127
+ async function targets(
128
+ context: Context,
129
+ found: ReadonlyMap<string, unknown>,
130
+ operation: 'update' | 'delete',
131
+ ): Promise<Filter> {
132
+ const { models, model, scope } = context
133
+ return planned(scope, () =>
134
+ and(
135
+ uniqueWhere(models, model, found.get('where'), 'where', scope),
136
+ scope.readable(model.name, operation),
137
+ ),
138
+ )
139
+ }
140
+
141
+ const missing = (model: string): string => `No ${model} matches.`
142
+
143
+ /** The `id` a unique `where` gives, if any. */
144
+ function idOf(where: unknown): string | undefined {
145
+ if (typeof where !== 'object' || where === null || !('id' in where)) {
146
+ return undefined
147
+ }
148
+ return typeof where.id === 'string' ? where.id : undefined
149
+ }
150
+
151
+ /**
152
+ * Updates the locked `row` as the rules allow; its number and list updates
153
+ * are computed on it first, so the rules and hooks see the results (D-132,
154
+ * D-149).
155
+ */
156
+ async function updating(
157
+ context: Context,
158
+ store: Store,
159
+ commits: Commit[],
160
+ data: { readonly draft: Draft; readonly operations: Operations },
161
+ at: { readonly row: Row; readonly read: Nested; readonly shape: Shape },
162
+ ): Promise<unknown> {
163
+ const { model, caller, scope } = context
164
+ const { row, read, shape } = at
165
+ const { draft, operations } = data
166
+ computeOn(model, row, draft, operations)
167
+ const reach = { scope, reads: store, model, id: row.id, at: [] }
168
+ await resolveData({ ...reach, creating: false, trusted: false }, draft)
169
+ const sent: Readonly<Draft> = { ...draft }
170
+ scope.access?.allow(model, 'update', { record: row, data: sent })
171
+ await prepared(model, draft, caller, false)
172
+ const record = recordOf(model, row)
173
+ await runHook(model, 'beforeUpdate', { caller, record, data: draft })
174
+ const steps = await updatePlan(scope, model, row.id, draft, sent, store)
175
+ const events: Event[] = [
176
+ { model, operation: 'update', id: row.id, before: row },
177
+ ]
178
+ const to = { id: row.id, read, shape }
179
+ return written(context, store, commits, steps, events, to)
180
+ }
181
+
182
+ export async function update(
183
+ context: Context,
184
+ store: Store,
185
+ commits: Commit[],
186
+ args: unknown,
187
+ ): Promise<unknown> {
188
+ const { models, model, caller, scope } = context
189
+ const found = given(
190
+ args,
191
+ model.name,
192
+ ['where', 'data', ...oneKeys],
193
+ ['where', 'data'],
194
+ )
195
+ const { read, shape } = await returned(context, found)
196
+ // The data's shape is checked whether or not the row is found.
197
+ const draft = readData(models, model, found.get('data'), false)
198
+ const operations = operationsOf(model, draft)
199
+ const id = idOf(found.get('where'))
200
+ const where = await targets(context, found, 'update')
201
+ if (
202
+ scope.access === undefined &&
203
+ id !== undefined &&
204
+ !updated(model) &&
205
+ !nests(model, draft) &&
206
+ !validates(model, operations)
207
+ ) {
208
+ // Nothing needs the stored row: a check stands for its read (D-99),
209
+ // and the write computes its numbers and lists (D-132, D-149).
210
+ const sent: Readonly<Draft> = { ...draft }
211
+ await prepared(model, draft, caller, false)
212
+ const steps = await updatePlan(
213
+ scope,
214
+ model,
215
+ id,
216
+ draft,
217
+ sent,
218
+ store,
219
+ operations,
220
+ )
221
+ const check = { model: model.name, where, count: 1, lock: true }
222
+ const message = missing(model.name)
223
+ const at = { id, read, shape }
224
+ const checks = [{ check, message }]
225
+ const result = await inRange(
226
+ model,
227
+ written(context, store, commits, steps, [], at, checks),
228
+ )
229
+ // The row went between the write's check and its change (D-104).
230
+ if (result === null) throw new PublicError('NOT_FOUND', message)
231
+ return result
232
+ }
233
+ const row = await target(context, store, where)
234
+ const data = { draft, operations }
235
+ return updating(context, store, commits, data, { row, read, shape })
236
+ }
237
+
238
+ export async function remove(
239
+ context: Context,
240
+ store: Store,
241
+ commits: Commit[],
242
+ args: unknown,
243
+ ): Promise<unknown> {
244
+ const { models, model, scope } = context
245
+ const found = given(args, model.name, ['where', ...oneKeys], ['where'])
246
+ const { read, shape } = await returned(context, found)
247
+ const where = await targets(context, found, 'delete')
248
+ // Its relations, counts and virtuals, as the caller reads them, before
249
+ // the delete removes any: one read in its transaction (D-135, D-151).
250
+ const reaches =
251
+ shape.relations.size > 0 ||
252
+ shape.counts.length > 0 ||
253
+ shape.virtuals.size > 0
254
+ const shaped = async (by: Filter, lock: boolean) => {
255
+ if (!reaches) return undefined
256
+ const rows = await store.find({
257
+ model: model.name,
258
+ ...read,
259
+ where: by,
260
+ take: 1,
261
+ lock,
262
+ })
263
+ return rows[0]
264
+ }
265
+ const reach = cascadeWhere(models, model, where)
266
+ if (reach !== undefined && scope.access === undefined) {
267
+ const kept = await shaped(where, true)
268
+ const computed = await computeVirtuals(
269
+ context,
270
+ store,
271
+ kept === undefined ? [] : [kept],
272
+ shape,
273
+ undefined,
274
+ )
275
+ // Nothing needs the stored row: a check stands for its read, and the
276
+ // write returns it as its delete removed it (D-102, D-104).
277
+ const check = { model: model.name, where, count: 1, lock: true }
278
+ const checks: Checked[] = [{ check, message: missing(model.name) }]
279
+ checks.push(...reach.checks)
280
+ const rows = await guarded(
281
+ store.last({
282
+ checks: checks.map((each) => each.check),
283
+ changes: reach.changes,
284
+ read: { model: model.name, where, take: 1 },
285
+ before: true,
286
+ }),
287
+ checks,
288
+ )
289
+ // Another transaction removed it meanwhile (D-104).
290
+ if (rows.length === 0)
291
+ throw new PublicError('NOT_FOUND', missing(model.name))
292
+ return project(kept ?? rows[0], shape, undefined, computed)
293
+ }
294
+ const row = await target(context, store, where)
295
+ scope.access?.allow(model, 'delete', { record: row })
296
+ const redirected = redirectOf(model) !== undefined
297
+ // A redirected row stays: it is read as the redirect left it (D-89).
298
+ const kept = redirected ? undefined : await shaped(byId(row.id), false)
299
+ const hidden = hiddenBy(scope.access)
300
+ const before = redirected
301
+ ? undefined
302
+ : await computeVirtuals(context, store, [kept ?? row], shape, hidden)
303
+ await deleted(context, store, commits, [row])
304
+ // The row as the redirect left it, or as it was once gone (D-89).
305
+ const [left] = redirected
306
+ ? await store.find({
307
+ model: model.name,
308
+ ...read,
309
+ where: byId(row.id),
310
+ take: 1,
311
+ })
312
+ : [kept]
313
+ const result = left ?? row
314
+ const computed =
315
+ before ?? (await computeVirtuals(context, store, [result], shape, hidden))
316
+ return project(result, shape, hidden, computed)
317
+ }
318
+
319
+ /** The row an upsert returns, and whether it created it (D-142). */
320
+ export type Upserted = { readonly row: unknown; readonly created: boolean }
321
+
322
+ /**
323
+ * Updates the row `where` targets, or creates it when the caller finds
324
+ * none: the branch that runs is a plain update or create (D-142).
325
+ */
326
+ export async function upsert(
327
+ context: Context,
328
+ store: Store,
329
+ commits: Commit[],
330
+ args: unknown,
331
+ ): Promise<Upserted> {
332
+ const { models, model } = context
333
+ const found = given(
334
+ args,
335
+ model.name,
336
+ ['where', 'create', 'update', ...oneKeys],
337
+ ['where', 'create', 'update'],
338
+ )
339
+ const at = await returned(context, found)
340
+ // Both data are checked, whichever runs.
341
+ const creation = readData(models, model, found.get('create'), true)
342
+ const draft = readData(models, model, found.get('update'), false)
343
+ const operations = operationsOf(model, draft)
344
+ const where = await targets(context, found, 'update')
345
+ const [row] = await store.find({
346
+ model: model.name,
347
+ where,
348
+ take: 1,
349
+ lock: true,
350
+ })
351
+ // Another create of the row meanwhile fails on its unique index.
352
+ if (row === undefined) {
353
+ return {
354
+ row: await creating(context, store, commits, creation, at),
355
+ created: true,
356
+ }
357
+ }
358
+ const data = { draft, operations }
359
+ const changed = await updating(context, store, commits, data, { row, ...at })
360
+ return { row: changed, created: false }
361
+ }
package/src/writing.ts ADDED
@@ -0,0 +1,93 @@
1
+ // What one write's changes are made in (write-plan.ts): the rows a move's
2
+ // hooks update planned again, and deletes run with their cascade and hooks
3
+ // (D-88, D-90, D-138).
4
+ import { recordOf } from './access.ts'
5
+ import type { Change } from './adapter.ts'
6
+ import type { Cascade } from './cascade.ts'
7
+ import { cascadeOf } from './cascade.ts'
8
+ import { InvalidQuery } from './errors.ts'
9
+ import type { Event } from './hooks.ts'
10
+ import { redirectOf, runHook } from './hooks.ts'
11
+ import { updatePlan } from './query-write.ts'
12
+ import type { QueryScope } from './scope.ts'
13
+ import type { Store } from './transaction.ts'
14
+ import type { Checked, Drop, Writing } from './write-plan.ts'
15
+ import { moved } from './write-plan.ts'
16
+ import { modelOf } from './query-where.ts'
17
+
18
+ /** The changes and events of a delete's cascade, its hooks run (D-90). */
19
+ export async function cascaded(
20
+ writing: Writing,
21
+ cascade: Cascade,
22
+ ): Promise<Change[]> {
23
+ const { caller } = writing.scope
24
+ const changes: Change[] = []
25
+ for (const { model, field, rows } of cascade.nulled) {
26
+ const step = { model: model.name, field, to: null }
27
+ changes.push(...(await moved(step, model, rows, writing)))
28
+ }
29
+ for (const { model, row } of cascade.deleted) {
30
+ await runHook(model, 'beforeDelete', {
31
+ caller,
32
+ record: recordOf(model, row),
33
+ })
34
+ }
35
+ const ids = new Map<string, string[]>()
36
+ for (const { model, row, redirected } of cascade.deleted) {
37
+ const redirect = redirected ? redirectOf(model) : undefined
38
+ // Its queries run their own pipeline, in this transaction (D-89).
39
+ if (redirect !== undefined) await redirect(caller, row.id)
40
+ else ids.set(model.name, [...(ids.get(model.name) ?? []), row.id])
41
+ }
42
+ for (const [model, deleted] of ids) {
43
+ changes.push({ kind: 'delete', model, where: { id: { in: deleted } } })
44
+ }
45
+ for (const { model, row } of cascade.deleted) {
46
+ writing.events.push({ model, operation: 'delete', id: row.id, before: row })
47
+ }
48
+ return changes
49
+ }
50
+
51
+ /**
52
+ * A nested delete as `Target.delete` runs it: a plugin's redirect, or the
53
+ * cascade, which may not reach a row the write updates (D-138).
54
+ */
55
+ async function dropped(writing: Writing, step: Drop): Promise<Change[]> {
56
+ const { scope, store } = writing
57
+ const model = modelOf(scope.models, step.model)
58
+ const cascade = await cascadeOf(
59
+ scope.models,
60
+ model,
61
+ step.rows,
62
+ store,
63
+ step.except,
64
+ )
65
+ const reached = cascade.deleted.some(({ model: from, row }) =>
66
+ step.written.has(`${from.name}:${row.id}`),
67
+ )
68
+ if (reached) {
69
+ throw new InvalidQuery(
70
+ step.path,
71
+ `${step.path} deletes a row the write updates.`,
72
+ )
73
+ }
74
+ return cascaded(writing, cascade)
75
+ }
76
+
77
+ export function writingOf(
78
+ scope: QueryScope,
79
+ store: Store,
80
+ events: Event[],
81
+ checks: Checked[] = [],
82
+ ): Writing {
83
+ const writing: Writing = {
84
+ store,
85
+ scope,
86
+ events,
87
+ checks,
88
+ replan: async (model, id, draft) =>
89
+ updatePlan(scope, model, id, draft, {}, store),
90
+ drop: async (step) => dropped(writing, step),
91
+ }
92
+ return writing
93
+ }