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,318 @@
1
+ // A caller's `where`, checked and normalized for the adapter (D-20, D-73):
2
+ // shorthands become `{ equals }`, `undefined` goes (D-84), and an unknown
3
+ // key fails with its path.
4
+ import type { Condition, Filter, ScalarType } from './adapter.ts'
5
+ import { storedValue } from './decimal.ts'
6
+ import { InvalidQuery } from './errors.ts'
7
+ import type { FieldInfo, ModelInfo } from './schema.ts'
8
+ import { listCondition } from './query-conditions.ts'
9
+ import { isPathFilter, jsonCondition } from './query-json.ts'
10
+ import { searchedFields } from './query-search.ts'
11
+ import {
12
+ clean,
13
+ cleanText,
14
+ entries,
15
+ isObject,
16
+ misfit,
17
+ storedDate,
18
+ unknownKey,
19
+ } from './query-values.ts'
20
+
21
+ export type { Scalar, ScalarCheck } from './query-values.ts'
22
+ export {
23
+ clean,
24
+ entries,
25
+ integerRange,
26
+ isInteger,
27
+ isJson,
28
+ isObject,
29
+ misfit,
30
+ storedDate,
31
+ unknownKey,
32
+ } from './query-values.ts'
33
+
34
+ export type Models = ReadonlyMap<string, ModelInfo>
35
+
36
+ /** The model, or the table, of `name`; another name is a kernel bug. */
37
+ export function modelOf<T>(models: ReadonlyMap<string, T>, name: string): T {
38
+ const model = models.get(name)
39
+ if (model === undefined) throw new Error(`Unknown model ${name}.`)
40
+ return model
41
+ }
42
+
43
+ export const byId = (id: string): Filter => ({ id: { equals: id } })
44
+ export const byIds = (ids: readonly string[]): Filter => ({ id: { in: ids } })
45
+
46
+ /** What a query does with a field: a read rule refuses each (D-44, D-143, D-147). */
47
+ export type Verb = 'filters' | 'sorts' | 'aggregates' | 'compares'
48
+
49
+ /** What a caller other than Master may filter on and see (D-86). */
50
+ export type Scope = {
51
+ /** Fails on a field with a read rule (D-44). */
52
+ readonly check: (
53
+ model: ModelInfo,
54
+ field: string,
55
+ path: string,
56
+ verb: Verb,
57
+ ) => void
58
+ /** Whether the caller runs a field read rule on it (D-44). */
59
+ readonly ruled: (model: ModelInfo, field: string) => boolean
60
+ /** A model's read filter; `undefined` when every row passes. */
61
+ readonly readable: (model: string) => Filter | undefined
62
+ /**
63
+ * The rows a `many` relation reads: its filter (D-151) and its target's
64
+ * read filter; `undefined` when every row passes.
65
+ */
66
+ readonly related: (
67
+ model: ModelInfo,
68
+ field: Extract<FieldInfo, { readonly kind: 'many' }>,
69
+ ) => Filter | undefined
70
+ }
71
+
72
+ /** `id`, `createdAt` and `updatedAt`, which every model has (D-22). */
73
+ const system: ReadonlyMap<string, ScalarType | 'id'> = new Map([
74
+ ['id', 'id'],
75
+ ['createdAt', 'dateTime'],
76
+ ['updatedAt', 'dateTime'],
77
+ ])
78
+ export const systemFields: readonly string[] = [...system.keys()]
79
+
80
+ type Single = ScalarType | 'id'
81
+
82
+ function typeOf(model: ModelInfo, name: string): ScalarType | 'id' | undefined {
83
+ const field = model.fields.get(name)
84
+ return field?.kind === 'scalar' ? field.type : system.get(name)
85
+ }
86
+
87
+ /** Whether `value` fits a field, or a system field, which has no info (D-22). */
88
+ const fits = (type: Single, value: unknown, field: FieldInfo | undefined) =>
89
+ field?.kind === 'scalar'
90
+ ? misfit(value, field) === undefined
91
+ : type === 'dateTime'
92
+ ? storedDate(value)
93
+ : typeof value === 'string' && clean(value)
94
+
95
+ const base = new Set(['equals', 'not', 'in', 'notIn'])
96
+ const ordered = new Set(['lt', 'lte', 'gt', 'gte'])
97
+ const text = new Set(['contains', 'startsWith', 'endsWith'])
98
+
99
+ function operators(type: Single): ReadonlySet<string> {
100
+ if (
101
+ type === 'number' ||
102
+ type === 'decimal' ||
103
+ type === 'dateTime' ||
104
+ type === 'date'
105
+ )
106
+ return new Set([...base, ...ordered])
107
+ if (type === 'string') return new Set([...base, ...text, 'mode'])
108
+ if (type === 'id' || type === 'enum') return new Set([...base, ...text])
109
+ return base
110
+ }
111
+
112
+ /**
113
+ * The operands of a value other than the field's, as an aggregate's in a
114
+ * `having` (D-149): their type, and their check, which stores each.
115
+ */
116
+ export type Operand = {
117
+ readonly type: ScalarType
118
+ readonly check: (item: unknown, at: string) => unknown
119
+ }
120
+
121
+ function scalarCondition(
122
+ model: ModelInfo,
123
+ name: string,
124
+ type: Single,
125
+ value: unknown,
126
+ path: string,
127
+ other?: Operand['check'],
128
+ ): Condition | undefined {
129
+ const field = model.fields.get(name)
130
+ const fit = (item: unknown, at: string): unknown => {
131
+ if (!fits(type, item, field)) {
132
+ throw new InvalidQuery(at, `${at} does not fit ${model.name}.${name}.`)
133
+ }
134
+ return field?.kind === 'scalar' ? storedValue(item, field) : item
135
+ }
136
+ const check = (item: unknown, at: string, nullable: boolean): unknown =>
137
+ item === null && nullable ? null : (other ?? fit)(item, at)
138
+ // A primitive, a `Date` or a list is `equals`; an object is a filter, on
139
+ // `json` too, which compares whole values with `equals` as in Prisma.
140
+ if (value === null || !isObject(value)) {
141
+ return { equals: check(value, path, true) }
142
+ }
143
+ const allowed = operators(type)
144
+ const condition: { [operator: string]: unknown } = {}
145
+ let insensitive = false
146
+ for (const [operator, operand] of entries(value, path)) {
147
+ const at = `${path}.${operator}`
148
+ if (!allowed.has(operator)) throw unknownKey(at, model.name)
149
+ if (operator === 'in' || operator === 'notIn') {
150
+ if (!Array.isArray(operand)) {
151
+ throw new InvalidQuery(at, `${at} must be a list.`)
152
+ }
153
+ condition[operator] = operand.map((item: unknown, index) =>
154
+ check(item, `${at}.${index}`, false),
155
+ )
156
+ } else if (operator === 'mode') {
157
+ if (operand !== 'insensitive') {
158
+ throw new InvalidQuery(at, `${at} must be 'insensitive'.`)
159
+ }
160
+ insensitive = true
161
+ } else if (text.has(operator)) {
162
+ condition[operator] = cleanText(operand, at)
163
+ } else {
164
+ condition[operator] = check(operand, at, base.has(operator))
165
+ }
166
+ }
167
+ // `mode` alone filters nothing, as in Prisma (D-140).
168
+ if (Object.keys(condition).length === 0) return undefined
169
+ return insensitive ? { ...condition, mode: 'insensitive' } : condition
170
+ }
171
+
172
+ /** A scalar field's condition, or with `operand` another value's of it. */
173
+ export function fieldCondition(
174
+ model: ModelInfo,
175
+ name: string,
176
+ value: unknown,
177
+ path: string,
178
+ operand?: Operand,
179
+ ): Condition | undefined {
180
+ const type = operand?.type ?? typeOf(model, name)
181
+ if (type === undefined) throw unknownKey(path, model.name)
182
+ return scalarCondition(model, name, type, value, path, operand?.check)
183
+ }
184
+
185
+ /**
186
+ * A relation operator that sees readable targets only (D-86): an unreadable
187
+ * row is absent, so `is: null` holds when the target is unreadable.
188
+ */
189
+ function restricted(
190
+ operator: string,
191
+ inner: Filter | null,
192
+ readable: Filter,
193
+ ): Condition {
194
+ if (inner === null) {
195
+ return operator === 'is' ? { isNot: readable } : { is: readable }
196
+ }
197
+ if (operator === 'every') {
198
+ return { every: { OR: [{ NOT: [readable] }, inner] } }
199
+ }
200
+ return { [operator]: { AND: [readable, inner] } }
201
+ }
202
+
203
+ function relationFilters(
204
+ models: Models,
205
+ model: ModelInfo,
206
+ field: Extract<FieldInfo, { readonly kind: 'one' | 'many' }>,
207
+ value: unknown,
208
+ path: string,
209
+ scope: Scope | undefined,
210
+ ): Filter[] {
211
+ const target = modelOf(models, field.target)
212
+ const readable =
213
+ field.kind === 'one'
214
+ ? scope?.readable(field.target)
215
+ : scope?.related(model, field)
216
+ const allowed =
217
+ field.kind === 'one' ? ['is', 'isNot'] : ['some', 'every', 'none']
218
+ const filters: Filter[] = []
219
+ for (const [operator, operand] of entries(value, path)) {
220
+ const at = `${path}.${operator}`
221
+ if (!allowed.includes(operator)) throw unknownKey(at, model.name)
222
+ const inner =
223
+ operand === null && field.kind === 'one'
224
+ ? null
225
+ : normalizeWhere(models, target, operand, at, scope)
226
+ filters.push({
227
+ [field.name]:
228
+ readable === undefined
229
+ ? { [operator]: inner }
230
+ : restricted(operator, inner, readable),
231
+ })
232
+ }
233
+ return filters
234
+ }
235
+
236
+ function list(
237
+ models: Models,
238
+ model: ModelInfo,
239
+ value: unknown,
240
+ path: string,
241
+ single: boolean,
242
+ scope: Scope | undefined,
243
+ stored: boolean,
244
+ ): Filter[] {
245
+ if (Array.isArray(value)) {
246
+ return value.map((item: unknown, index) =>
247
+ normalizeWhere(models, model, item, `${path}.${index}`, scope, stored),
248
+ )
249
+ }
250
+ if (single) return [normalizeWhere(models, model, value, path, scope, stored)]
251
+ throw new InvalidQuery(path, `${path} must be a list.`)
252
+ }
253
+
254
+ /**
255
+ * A `where` of `model`, normalized; `path` names it in errors. With a
256
+ * `scope`, relation filters see readable rows only (D-86). Only a
257
+ * `beforeFind`'s, `stored`, filters a plugin field, in its stored form
258
+ * (D-122).
259
+ */
260
+ export function normalizeWhere(
261
+ models: Models,
262
+ model: ModelInfo,
263
+ where: unknown,
264
+ path: string,
265
+ scope?: Scope,
266
+ stored = false,
267
+ ): Filter {
268
+ const filter: { [key: string]: Condition | Filter[] } = {}
269
+ const relations: Filter[] = []
270
+ for (const [key, value] of entries(where, path)) {
271
+ const at = `${path}.${key}`
272
+ if (key === 'AND' || key === 'NOT' || key === 'OR') {
273
+ filter[key] = list(models, model, value, at, key !== 'OR', scope, stored)
274
+ continue
275
+ }
276
+ if (key === '_search' && searchedFields(model.fields.values()).length > 0) {
277
+ if (value !== undefined) filter[key] = { search: cleanText(value, at) }
278
+ continue
279
+ }
280
+ const field = model.fields.get(key)
281
+ const type = typeOf(model, key)
282
+ if (field === undefined && type === undefined) {
283
+ throw unknownKey(at, model.name)
284
+ }
285
+ const codec = field?.kind === 'scalar' ? field.codec : undefined
286
+ if (codec !== undefined && !stored) {
287
+ throw new InvalidQuery(
288
+ at,
289
+ `${at}: ${model.name}.${key} is a ${codec.plugin} field, which only a beforeFind filters.`,
290
+ )
291
+ }
292
+ scope?.check(model, key, at, 'filters')
293
+ if (field !== undefined && field.kind !== 'scalar') {
294
+ relations.push(...relationFilters(models, model, field, value, at, scope))
295
+ continue
296
+ }
297
+ const condition =
298
+ type === undefined
299
+ ? undefined
300
+ : field?.kind === 'scalar' && field.list
301
+ ? listCondition(model, field, value, at)
302
+ : type === 'json' && isPathFilter(value)
303
+ ? jsonCondition(model.name, value, at)
304
+ : scalarCondition(model, key, type, value, at)
305
+ if (condition !== undefined) filter[key] = condition
306
+ }
307
+ const [only, ...others] = relations
308
+ if (only === undefined) return filter
309
+ // One relation filter stays in place; more join `AND`.
310
+ if (others.length === 0 && filter['AND'] === undefined) {
311
+ return { ...filter, ...only }
312
+ }
313
+ const and = filter['AND']
314
+ return {
315
+ ...filter,
316
+ AND: [...(Array.isArray(and) ? and : []), ...relations],
317
+ }
318
+ }
@@ -0,0 +1,360 @@
1
+ // A write's data after its `before*` hooks, turned into the adapter's
2
+ // changes: values and relation writes by id, from either side (D-20, D-78),
3
+ // and the rows its relation writes create, update and delete (D-128, D-138).
4
+ // The caller's data was checked before the hooks (write-data.ts): what fails
5
+ // here is a hook's or a default's value (D-88).
6
+ import { recordOf } from './access.ts'
7
+ import type { Row } from './adapter.ts'
8
+ import { InvalidQuery } from './errors.ts'
9
+ import { runHook } from './hooks.ts'
10
+ import { resolveData, resolveMany, resolveOne } from './nested-write.ts'
11
+ import type { Operations } from './number-update.ts'
12
+ import type { FieldInfo, ModelInfo } from './schema.ts'
13
+ import type { QueryScope } from './scope.ts'
14
+ import { madeId, uuidv7 } from './uuid.ts'
15
+ import { byId, entries, modelOf, unknownKey } from './query-where.ts'
16
+ import type { Context, Relation } from './write-context.ts'
17
+ import {
18
+ fail,
19
+ holds,
20
+ linking,
21
+ pathOf,
22
+ resolving,
23
+ scalar,
24
+ trusted,
25
+ viaOf,
26
+ } from './write-context.ts'
27
+ import type { Draft } from './write-data.ts'
28
+ import { prepared, readData } from './write-data.ts'
29
+ import type { Reads, Step } from './write-plan.ts'
30
+ import { Plan } from './write-plan.ts'
31
+
32
+ /**
33
+ * A row a relation write creates, as `Target.create` creates it (D-128):
34
+ * its access rules unless a hook or a default wrote it, its defaults, its
35
+ * validation and `beforeCreate`, then its own relation writes.
36
+ */
37
+ async function created(
38
+ context: Context,
39
+ field: Relation,
40
+ data: Draft,
41
+ at: readonly (string | number)[],
42
+ skips: boolean,
43
+ ): Promise<{ readonly id: string; readonly steps: Step[] }> {
44
+ const { scope, plan } = context
45
+ const model = modelOf(context.scope.models, field.target)
46
+ const made = madeId(data)
47
+ // A hook's data may come back in another write: it gets a fresh id.
48
+ const id = skips || plan.made.has(made) ? uuidv7() : made
49
+ const path = ['data', ...at].join('.')
50
+ const draft = readData(scope.models, model, data, true, path, field.inverse)
51
+ const inverse = model.fields.get(field.inverse)
52
+ if (inverse?.kind === 'one' && inverse.stored) {
53
+ draft[field.inverse] = { connect: { id: context.id } }
54
+ }
55
+ const child = { ...context, model, id, set: {}, at }
56
+ await resolveData(resolving(child, skips, true), draft)
57
+ const sent: Readonly<Draft> = skips ? {} : { ...draft }
58
+ if (!skips) scope.access?.allow(model, 'create', { data: sent })
59
+ await prepared(model, draft, scope.caller, true, at)
60
+ await runHook(model, 'beforeCreate', { caller: scope.caller, data: draft })
61
+ plan.events.push({ model, operation: 'create', id })
62
+ const steps = await inserted({ ...child, sent, before: [], after: [] }, draft)
63
+ return { id, steps }
64
+ }
65
+
66
+ /**
67
+ * A linked row a relation write updates, as `Target.update` updates it
68
+ * (D-138): its rules unless a hook or a default wrote it, its validation,
69
+ * `beforeUpdate`, then its own relation writes.
70
+ */
71
+ async function changed(
72
+ context: Context,
73
+ field: Relation,
74
+ update: { readonly data: Draft; readonly row: Row },
75
+ at: readonly (string | number)[],
76
+ skips: boolean,
77
+ ): Promise<Step[]> {
78
+ const { scope, plan } = context
79
+ const { data, row } = update
80
+ const model = modelOf(context.scope.models, field.target)
81
+ const child = { ...context, model, id: row.id, set: {}, at }
82
+ await resolveData(resolving(child, skips, false), data)
83
+ const sent: Readonly<Draft> = skips ? {} : { ...data }
84
+ const via = viaOf(context, field)
85
+ if (!skips)
86
+ scope.access?.allow(model, 'update', { record: row, data: sent, via })
87
+ await prepared(model, data, scope.caller, false, at)
88
+ const record = recordOf(model, row)
89
+ await runHook(model, 'beforeUpdate', { caller: scope.caller, record, data })
90
+ plan.events.push({ model, operation: 'update', id: row.id, before: row })
91
+ return updateSteps({ ...child, sent, before: [], after: [] }, data)
92
+ }
93
+
94
+ /** A linked row a relation write deletes, as `Target.delete` (D-138). */
95
+ function dropped(
96
+ context: Context,
97
+ field: Relation,
98
+ row: Row,
99
+ path: string,
100
+ skips: boolean,
101
+ ): Step {
102
+ const model = modelOf(context.scope.models, field.target)
103
+ const via = viaOf(context, field)
104
+ if (!skips) context.scope.access?.allow(model, 'delete', { record: row, via })
105
+ // The parent empties its own key to the row: the cascade leaves it.
106
+ const except =
107
+ field.kind === 'one' && field.stored
108
+ ? { model: context.model.name, field: field.name, id: context.id }
109
+ : undefined
110
+ return {
111
+ kind: 'drop',
112
+ model: model.name,
113
+ rows: [row],
114
+ path,
115
+ ...(except === undefined ? {} : { except }),
116
+ written: context.plan.written,
117
+ }
118
+ }
119
+
120
+ async function one(
121
+ context: Context,
122
+ field: Extract<FieldInfo, { readonly kind: 'one' }>,
123
+ value: unknown,
124
+ creating: boolean,
125
+ ): Promise<void> {
126
+ const skips = trusted(context, field, value)
127
+ const write = await resolveOne(
128
+ resolving(context, skips, creating),
129
+ field,
130
+ value,
131
+ )
132
+ const at = [...context.at, field.name, write.kind]
133
+ if (write.kind === 'update') {
134
+ context.after.push(...(await changed(context, field, write, at, skips)))
135
+ return
136
+ }
137
+ if (write.kind === 'delete') {
138
+ context.after.push(
139
+ dropped(
140
+ context,
141
+ field,
142
+ write.row,
143
+ `${pathOf(context, field.name)}.delete`,
144
+ skips,
145
+ ),
146
+ )
147
+ }
148
+ // A key the other side holds changes its row: it is updated (D-124).
149
+ const operation = field.stored ? 'read' : 'update'
150
+ let target = write.kind === 'connect' ? write.id : undefined
151
+ if (target !== undefined) {
152
+ context.plan.connect(field.target, [target], skips, operation)
153
+ }
154
+ if (write.kind === 'create') {
155
+ const made = await created(context, field, write.data, write.at, skips)
156
+ target = made.id
157
+ // The row that stores the key is written after the row it points to.
158
+ ;(field.stored ? context.before : context.after).push(...made.steps)
159
+ }
160
+ if (field.stored) {
161
+ if (target === undefined && !field.optional) {
162
+ fail(context, [field.name], 'Required.')
163
+ return
164
+ }
165
+ context.set[field.name] = target ?? null
166
+ return
167
+ }
168
+ // A deleted row holds no key; another one may not be left empty (D-37).
169
+ if (write.kind === 'delete') return
170
+ if (!creating && (await holds(context, field, target))) {
171
+ fail(
172
+ context,
173
+ [field.name],
174
+ `The ${field.target} it points to cannot be left without one.`,
175
+ )
176
+ return
177
+ }
178
+ if (write.kind === 'connect') {
179
+ context.after.push({
180
+ kind: 'move',
181
+ model: field.target,
182
+ field: field.inverse,
183
+ via: viaOf(context, field),
184
+ where: byId(write.id),
185
+ to: context.id,
186
+ trusted: skips,
187
+ })
188
+ }
189
+ }
190
+
191
+ /** Deletes, then creates, disconnects and connects, then updates (D-138). */
192
+ async function many(
193
+ context: Context,
194
+ field: Extract<FieldInfo, { readonly kind: 'many' }>,
195
+ value: unknown,
196
+ creating: boolean,
197
+ ): Promise<void> {
198
+ const skips = trusted(context, field, value)
199
+ const write = await resolveMany(
200
+ resolving(context, skips, creating),
201
+ field,
202
+ value,
203
+ )
204
+ const operation = field.join ? 'read' : 'update'
205
+ context.plan.connect(field.target, write.connect, skips, operation)
206
+ for (const { row, path } of write.delete) {
207
+ context.after.push(dropped(context, field, row, path, skips))
208
+ }
209
+ const made: string[] = []
210
+ for (const { data, at } of write.create) {
211
+ const child = await created(context, field, data, at, skips)
212
+ made.push(child.id)
213
+ context.after.push(...child.steps)
214
+ }
215
+ linking(context, field, write, made, skips)
216
+ for (const [index, update] of write.update.entries()) {
217
+ const at = [...context.at, field.name, 'update', index, 'data']
218
+ context.after.push(...(await changed(context, field, update, at, skips)))
219
+ }
220
+ }
221
+
222
+ /** A hook's value the plan refuses is the hook's error, not the caller's. */
223
+ function hookError(context: Context, error: unknown): unknown {
224
+ if (!(error instanceof InvalidQuery)) return error
225
+ return new TypeError(
226
+ `${context.model.name}: a before hook wrote invalid data. ${error.message}`,
227
+ { cause: error },
228
+ )
229
+ }
230
+
231
+ async function fields(
232
+ context: Context,
233
+ data: Draft,
234
+ creating: boolean,
235
+ ): Promise<Set<string>> {
236
+ const given = new Set<string>()
237
+ const at = ['data', ...context.at].join('.')
238
+ for (const [name, value] of entries(data, at)) {
239
+ const field = context.model.fields.get(name)
240
+ // The caller's data was read before the hooks: a hook wrote the key.
241
+ if (field === undefined) {
242
+ throw hookError(
243
+ context,
244
+ unknownKey(pathOf(context, name), context.model.name),
245
+ )
246
+ }
247
+ given.add(name)
248
+ try {
249
+ if (field.kind === 'scalar') scalar(context, field, value)
250
+ else if (field.kind === 'one') await one(context, field, value, creating)
251
+ else await many(context, field, value, creating)
252
+ } catch (error) {
253
+ // The caller's relation value reaches rows only now (D-138).
254
+ throw trusted(context, field, value) ? hookError(context, error) : error
255
+ }
256
+ }
257
+ return given
258
+ }
259
+
260
+ function contextOf(
261
+ scope: QueryScope,
262
+ model: ModelInfo,
263
+ id: string,
264
+ sent: Readonly<Draft>,
265
+ reads: Reads,
266
+ ): Context {
267
+ const plan = new Plan()
268
+ const set = {}
269
+ return {
270
+ scope,
271
+ model,
272
+ id,
273
+ plan,
274
+ reads,
275
+ sent,
276
+ set,
277
+ before: [],
278
+ after: [],
279
+ at: [],
280
+ }
281
+ }
282
+
283
+ /** A created row's steps: the rows it points to, its insert, then the rest. */
284
+ async function inserted(context: Context, draft: Draft): Promise<Step[]> {
285
+ const { model, id, plan } = context
286
+ plan.made.add(id)
287
+ const given = await fields(context, draft, true)
288
+ for (const field of model.fields.values()) {
289
+ if (given.has(field.name) || field.kind === 'many') continue
290
+ if (field.kind === 'one' && !field.stored) continue
291
+ // A required field may be left to a hook: checked after it (D-88).
292
+ // A list left unwritten is empty (D-120).
293
+ if (field.kind === 'scalar' && field.list) {
294
+ context.set[field.name] = []
295
+ } else if (field.optional) context.set[field.name] = null
296
+ else fail(context, [field.name], 'Required.')
297
+ }
298
+ const now = Date.now()
299
+ const row: Row = {
300
+ ...context.set,
301
+ id,
302
+ createdAt: new Date(now),
303
+ updatedAt: new Date(now),
304
+ }
305
+ const insert = { kind: 'insert', model: model.name, row } as const
306
+ return [...context.before, insert, ...context.after]
307
+ }
308
+
309
+ /** An updated row's steps; `updatedAt` always moves. */
310
+ async function updateSteps(
311
+ context: Context,
312
+ draft: Draft,
313
+ operations: Operations = new Map(),
314
+ ): Promise<Step[]> {
315
+ const { model, id, plan } = context
316
+ plan.written.add(`${model.name}:${id}`)
317
+ await fields(context, draft, false)
318
+ return [
319
+ ...context.before,
320
+ {
321
+ kind: 'update',
322
+ model: model.name,
323
+ where: byId(id),
324
+ set: { ...context.set, updatedAt: new Date(Date.now()) },
325
+ ...(operations.size === 0
326
+ ? {}
327
+ : { compute: Object.fromEntries(operations) }),
328
+ },
329
+ ...context.after,
330
+ ]
331
+ }
332
+
333
+ /** `create`'s changes: the row with its id and timestamps (D-22). */
334
+ export async function createPlan(
335
+ scope: QueryScope,
336
+ model: ModelInfo,
337
+ draft: Draft,
338
+ sent: Readonly<Draft>,
339
+ reads: Reads,
340
+ id: string = uuidv7(),
341
+ ): Promise<{ readonly id: string; readonly plan: Plan }> {
342
+ const context = contextOf(scope, model, id, sent, reads)
343
+ context.plan.steps.push(...(await inserted(context, draft)))
344
+ return { id: context.id, plan: context.plan }
345
+ }
346
+
347
+ /** `update`'s changes on the stored row. */
348
+ export async function updatePlan(
349
+ scope: QueryScope,
350
+ model: ModelInfo,
351
+ id: string,
352
+ draft: Draft,
353
+ sent: Readonly<Draft>,
354
+ reads: Reads,
355
+ operations: Operations = new Map(),
356
+ ): Promise<Plan> {
357
+ const context = contextOf(scope, model, id, sent, reads)
358
+ context.plan.steps.push(...(await updateSteps(context, draft, operations)))
359
+ return context.plan
360
+ }