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,375 @@
1
+ // A read's arguments: `select`, `include`, `orderBy`, `take`, `skip` and
2
+ // `cursor` checked (D-20), as the adapter's read and the shape of the result.
3
+ import type { Filter, Nested } from './adapter.ts'
4
+ import { and } from './access.ts'
5
+ import { InvalidQuery } from './errors.ts'
6
+ import type { FieldInfo, ModelInfo } from './schema.ts'
7
+ import type { VirtualInfo } from './schema-virtuals.ts'
8
+ import { orderBy } from './query-order.ts'
9
+ import type { Models, Scope } from './query-where.ts'
10
+ import {
11
+ entries,
12
+ isObject,
13
+ modelOf,
14
+ normalizeWhere,
15
+ systemFields,
16
+ unknownKey,
17
+ } from './query-where.ts'
18
+
19
+ /** What a query returns of each row, and of each relation it reads. */
20
+ export type Shape = {
21
+ readonly model: ModelInfo
22
+ readonly fields: readonly string[]
23
+ readonly relations: ReadonlyMap<string, Shape>
24
+ /** The `many` relations `_count` returns (D-134). */
25
+ readonly counts: readonly string[]
26
+ readonly virtuals: ReadonlyMap<string, VirtualRead>
27
+ }
28
+
29
+ /** A virtual a read computes (D-151): its input, and a model's rows. */
30
+ export type VirtualRead = {
31
+ readonly info: VirtualInfo
32
+ readonly input: { readonly [key: string]: unknown }
33
+ readonly path: string
34
+ readonly plan: Plan | undefined
35
+ }
36
+
37
+ type Many = Extract<FieldInfo, { readonly kind: 'many' }>
38
+
39
+ export type Plan = { readonly read: Nested; readonly shape: Shape }
40
+
41
+ /** Without `select`, scalar fields return and relations do not (D-20). */
42
+ function scalars(model: ModelInfo): string[] {
43
+ return [
44
+ ...systemFields,
45
+ ...[...model.fields.values()]
46
+ .filter((field) => field.kind === 'scalar')
47
+ .map((field) => field.name),
48
+ ]
49
+ }
50
+
51
+ export function integer(value: unknown, path: string, least?: number): number {
52
+ if (
53
+ typeof value !== 'number' ||
54
+ !Number.isSafeInteger(value) ||
55
+ (least !== undefined && value < least)
56
+ ) {
57
+ const range = least === undefined ? '' : ` of at least ${least}`
58
+ throw new InvalidQuery(path, `${path} must be an integer${range}.`)
59
+ }
60
+ return value
61
+ }
62
+
63
+ /** `_count`: the `many` relations it names, each with its filter (D-134). */
64
+ function counted(
65
+ models: Models,
66
+ model: ModelInfo,
67
+ value: unknown,
68
+ path: string,
69
+ scope: Scope | undefined,
70
+ ): Map<string, Filter> {
71
+ const filter = (field: Many, where?: Filter): Filter =>
72
+ and(where, scope?.related(model, field)) ?? {}
73
+ const counts = new Map<string, Filter>()
74
+ if (value === true) {
75
+ for (const field of model.fields.values()) {
76
+ if (field.kind === 'many') counts.set(field.name, filter(field))
77
+ }
78
+ return counts
79
+ }
80
+ const given = new Map(entries(value, path))
81
+ for (const key of given.keys()) {
82
+ if (key !== 'select') throw unknownKey(`${path}.${key}`, model.name)
83
+ }
84
+ const select = `${path}.select`
85
+ for (const [name, chosen] of entries(given.get('select'), select)) {
86
+ const at = `${select}.${name}`
87
+ const field = model.fields.get(name)
88
+ if (field?.kind !== 'many') throw unknownKey(at, model.name)
89
+ if (chosen === false) continue
90
+ if (chosen === true) {
91
+ counts.set(name, filter(field))
92
+ continue
93
+ }
94
+ const args = new Map(entries(chosen, at))
95
+ for (const key of args.keys()) {
96
+ if (key !== 'where') throw unknownKey(`${at}.${key}`, model.name)
97
+ }
98
+ const counting = modelOf(models, field.target)
99
+ const where = args.has('where')
100
+ ? normalizeWhere(
101
+ models,
102
+ counting,
103
+ args.get('where'),
104
+ `${at}.where`,
105
+ scope,
106
+ )
107
+ : undefined
108
+ counts.set(name, filter(field, where))
109
+ }
110
+ return counts
111
+ }
112
+
113
+ /** A unique target (D-52): exactly `id`, a unique field or a unique index. */
114
+ export function uniqueWhere(
115
+ models: Models,
116
+ model: ModelInfo,
117
+ where: unknown,
118
+ path: string,
119
+ scope?: Scope,
120
+ ): Filter {
121
+ const given = entries(where, path)
122
+ const keys = given.map(([key]) => key).toSorted()
123
+ const matched = model.uniques.some(
124
+ (fields) =>
125
+ fields.length === keys.length &&
126
+ fields.toSorted().every((field, index) => field === keys[index]),
127
+ )
128
+ if (!matched) {
129
+ throw new InvalidQuery(
130
+ path,
131
+ `${path} must name exactly id, a unique field or a unique index of ${model.name}.`,
132
+ )
133
+ }
134
+ const filter: { [field: string]: unknown } = {}
135
+ for (const [key, value] of given) {
136
+ const at = `${path}.${key}`
137
+ if (value === null || isObject(value)) {
138
+ throw new InvalidQuery(at, `${at} must be a value.`)
139
+ }
140
+ const field = model.fields.get(key)
141
+ filter[key] = field?.kind === 'one' ? { is: { id: value } } : value
142
+ }
143
+ return normalizeWhere(models, model, filter, path, scope)
144
+ }
145
+
146
+ /** A read narrowed to the rows of a read filter, its cursor too (D-86). */
147
+ export function restrict(read: Nested, readable: Filter | undefined): Nested {
148
+ if (readable === undefined) return read
149
+ return {
150
+ ...read,
151
+ where: and(read.where, readable),
152
+ ...(read.cursor === undefined
153
+ ? {}
154
+ : { cursor: and(read.cursor, readable) }),
155
+ }
156
+ }
157
+
158
+ type Kind = 'many' | 'one'
159
+
160
+ /** A virtual's `true` or its input, with a model's `select`, `include`, `omit`. */
161
+ function virtualRead(
162
+ models: Models,
163
+ info: VirtualInfo,
164
+ value: unknown,
165
+ path: string,
166
+ scope: Scope | undefined,
167
+ ): VirtualRead {
168
+ const { output } = info
169
+ const target = output.kind === 'model' ? output.name : undefined
170
+ const shaping: { [key: string]: unknown } = {}
171
+ const input: { [key: string]: unknown } = {}
172
+ for (const [key, given] of value === true ? [] : entries(value, path)) {
173
+ if (target !== undefined && oneKeys.has(key)) shaping[key] = given
174
+ else if (info.input === undefined) {
175
+ throw unknownKey(`${path}.${key}`, info.model)
176
+ } else input[key] = given
177
+ }
178
+ const nested =
179
+ target === undefined
180
+ ? undefined
181
+ : plan(
182
+ models,
183
+ modelOf(models, target),
184
+ shaping,
185
+ path,
186
+ 'one',
187
+ oneKeys,
188
+ scope,
189
+ )
190
+ return { info, input, path, plan: nested }
191
+ }
192
+
193
+ /**
194
+ * The arguments of a relation read, or of a top-level read when `path` is
195
+ * empty. A `scope` checks them and narrows each included relation (D-86).
196
+ */
197
+ export function plan(
198
+ models: Models,
199
+ model: ModelInfo,
200
+ args: unknown,
201
+ path: string,
202
+ kind: Kind,
203
+ keys: ReadonlySet<string>,
204
+ scope?: Scope,
205
+ ): Plan {
206
+ const given = new Map(args === undefined ? [] : entries(args, path || 'args'))
207
+ const at = (key: string): string => (path === '' ? key : `${path}.${key}`)
208
+ for (const key of given.keys()) {
209
+ if (!keys.has(key)) throw unknownKey(at(key), model.name)
210
+ }
211
+ const select = given.get('select')
212
+ const include = given.get('include')
213
+ if (select !== undefined && include !== undefined) {
214
+ throw new InvalidQuery(
215
+ path,
216
+ `${at('select')} and include cannot be used together.`,
217
+ )
218
+ }
219
+ if (select !== undefined && given.has('omit')) {
220
+ throw new InvalidQuery(
221
+ path,
222
+ `${at('select')} and omit cannot be used together.`,
223
+ )
224
+ }
225
+ const read: {
226
+ -readonly [K in keyof Nested]: Nested[K]
227
+ } = {}
228
+ const fields: string[] = select === undefined ? scalars(model) : []
229
+ const relations = new Map<string, Shape>()
230
+ const virtuals = new Map<string, VirtualRead>()
231
+ for (const info of select === undefined ? model.virtuals.values() : []) {
232
+ if (info.default)
233
+ virtuals.set(info.name, virtualRead(models, info, true, '', scope))
234
+ }
235
+ const nested: { [field: string]: Nested } = {}
236
+ let count = new Map<string, Filter>()
237
+ const chosen = select ?? include
238
+ if (chosen !== undefined) {
239
+ const base = select === undefined ? 'include' : 'select'
240
+ for (const [name, value] of entries(chosen, at(base))) {
241
+ const where = `${at(base)}.${name}`
242
+ if (name === '_count') {
243
+ if (value === false) continue
244
+ count = counted(models, model, value, where, scope)
245
+ continue
246
+ }
247
+ const virtual = model.virtuals.get(name)
248
+ if (virtual !== undefined) {
249
+ if (value === false) virtuals.delete(name)
250
+ else
251
+ virtuals.set(name, virtualRead(models, virtual, value, where, scope))
252
+ continue
253
+ }
254
+ const field = model.fields.get(name)
255
+ const scalar =
256
+ field === undefined
257
+ ? systemFields.includes(name)
258
+ : field.kind === 'scalar'
259
+ if ((field === undefined && !scalar) || (base === 'include' && scalar)) {
260
+ throw unknownKey(where, model.name)
261
+ }
262
+ if (value === false) continue
263
+ if (scalar) {
264
+ if (value !== true) {
265
+ throw new InvalidQuery(where, `${where} must be a boolean.`)
266
+ }
267
+ fields.push(name)
268
+ continue
269
+ }
270
+ if (field === undefined || field.kind === 'scalar') continue
271
+ const relation = plan(
272
+ models,
273
+ modelOf(models, field.target),
274
+ value === true ? undefined : value,
275
+ where,
276
+ field.kind,
277
+ field.kind === 'many' ? manyKeys : oneKeys,
278
+ scope,
279
+ )
280
+ const readable =
281
+ field.kind === 'one'
282
+ ? scope?.readable(field.target)
283
+ : scope?.related(model, field)
284
+ nested[name] = restrict(relation.read, readable)
285
+ relations.set(name, relation.shape)
286
+ }
287
+ }
288
+ if (Object.keys(nested).length > 0) read.include = nested
289
+ if (count.size > 0) read.count = Object.fromEntries(count)
290
+ if (given.has('where')) {
291
+ read.where = normalizeWhere(
292
+ models,
293
+ model,
294
+ given.get('where'),
295
+ at('where'),
296
+ scope,
297
+ )
298
+ }
299
+ if (kind === 'many') {
300
+ // `_relevance` ranks by the `_search` at the top of `where` (D-148).
301
+ const search: unknown = read.where?.['_search']
302
+ read.orderBy = orderBy(
303
+ models,
304
+ model,
305
+ given.get('orderBy') ?? [],
306
+ at('orderBy'),
307
+ scope,
308
+ isObject(search) && typeof search['search'] === 'string'
309
+ ? search['search']
310
+ : undefined,
311
+ )
312
+ if (given.has('take')) read.take = integer(given.get('take'), at('take'))
313
+ if (given.has('skip')) read.skip = integer(given.get('skip'), at('skip'), 0)
314
+ if (given.has('cursor')) {
315
+ read.cursor = uniqueWhere(
316
+ models,
317
+ model,
318
+ given.get('cursor'),
319
+ at('cursor'),
320
+ scope,
321
+ )
322
+ }
323
+ }
324
+ const hidden = omitted(model, given.get('omit'), at('omit'))
325
+ const shown = fields.filter((name) => !hidden.has(name))
326
+ for (const name of hidden) virtuals.delete(name)
327
+ const needs = [...virtuals.values()].flatMap(({ info }) => info.needs)
328
+ // A field read rule may read any field of the row (D-136).
329
+ const names = [
330
+ ...shown,
331
+ ...relations.keys(),
332
+ ...count.keys(),
333
+ ...virtuals.keys(),
334
+ ]
335
+ if (!names.some((name) => scope?.ruled(model, name) === true)) {
336
+ read.fields = [...new Set(['id', ...shown, ...needs])]
337
+ }
338
+ const counts = [...count.keys()]
339
+ return { read, shape: { model, fields: shown, relations, counts, virtuals } }
340
+ }
341
+
342
+ /** The scalar and system fields `omit` leaves out of the rows (D-135). */
343
+ function omitted(model: ModelInfo, omit: unknown, path: string): Set<string> {
344
+ const names = new Set<string>()
345
+ if (omit === undefined) return names
346
+ for (const [name, value] of entries(omit, path)) {
347
+ const where = `${path}.${name}`
348
+ const field = model.fields.get(name)
349
+ const scalar =
350
+ field === undefined
351
+ ? systemFields.includes(name) || model.virtuals.get(name)?.default
352
+ : field.kind === 'scalar'
353
+ if (scalar !== true) throw unknownKey(where, model.name)
354
+ if (typeof value !== 'boolean') {
355
+ throw new InvalidQuery(where, `${where} must be a boolean.`)
356
+ }
357
+ if (value) names.add(name)
358
+ }
359
+ return names
360
+ }
361
+
362
+ /** The keys that shape a read's rows. */
363
+ export const oneKeys: ReadonlySet<string> = new Set([
364
+ 'select',
365
+ 'include',
366
+ 'omit',
367
+ ])
368
+ const manyKeys = new Set([
369
+ ...oneKeys,
370
+ 'where',
371
+ 'orderBy',
372
+ 'take',
373
+ 'skip',
374
+ 'cursor',
375
+ ])
@@ -0,0 +1,53 @@
1
+ // The writes of many rows, all or nothing: `createMany` (D-135),
2
+ // `updateMany` (D-133), `deleteMany` (D-111), and the forms that return the
3
+ // rows they write (D-149).
4
+ import type { Exact } from './exact.ts'
5
+ import type { Payload, QueryOptions, Shaping, Where } from './query.ts'
6
+ import type { CreateManyData, UpdateManyData } from './record.ts'
7
+
8
+ /** `createMany`: a row or a list, no nested writes, no rows back (D-135). */
9
+ export type CreateManyArgs<F> = {
10
+ readonly data: CreateManyData<F> | readonly CreateManyData<F>[]
11
+ }
12
+
13
+ /** `updateMany`: no relation writes, no returned rows (D-133). */
14
+ export type UpdateManyArgs<F> = {
15
+ readonly where?: Where<F> | undefined
16
+ readonly data: UpdateManyData<F>
17
+ }
18
+
19
+ export type CreateManyAndReturnArgs<F> = CreateManyArgs<F> & Shaping<F>
20
+ export type UpdateManyAndReturnArgs<F> = UpdateManyArgs<F> & Shaping<F>
21
+
22
+ export interface BulkQueries<F> {
23
+ /** A create of each row of `data`, all or nothing (D-135). */
24
+ createMany(
25
+ this: void,
26
+ args: CreateManyArgs<F>,
27
+ options?: QueryOptions,
28
+ ): Promise<{ readonly count: number }>
29
+ /** An update of each row `where` selects, all or nothing (D-133). */
30
+ updateMany(
31
+ this: void,
32
+ args: UpdateManyArgs<F>,
33
+ options?: QueryOptions,
34
+ ): Promise<{ readonly count: number }>
35
+ /** A delete of each row `where` selects, all or nothing (D-111). */
36
+ deleteMany(
37
+ this: void,
38
+ args?: { readonly where?: Where<F> | undefined },
39
+ options?: QueryOptions,
40
+ ): Promise<{ readonly count: number }>
41
+ /** `createMany`'s rows, in `data`'s order, each as `create` returns it. */
42
+ createManyAndReturn<const A extends CreateManyAndReturnArgs<F>>(
43
+ this: void,
44
+ args: A & NoInfer<Exact<A, CreateManyAndReturnArgs<F>>>,
45
+ options?: QueryOptions,
46
+ ): Promise<Payload<F, A>[]>
47
+ /** `updateMany`'s rows, in `id` order, each as `update` returns it. */
48
+ updateManyAndReturn<const A extends UpdateManyAndReturnArgs<F>>(
49
+ this: void,
50
+ args: A & NoInfer<Exact<A, UpdateManyAndReturnArgs<F>>>,
51
+ options?: QueryOptions,
52
+ ): Promise<Payload<F, A>[]>
53
+ }
@@ -0,0 +1,53 @@
1
+ // The conditions of a list field (D-120, D-127, D-149).
2
+ import type { Condition } from './adapter.ts'
3
+ import { InvalidQuery } from './errors.ts'
4
+ import type { ModelInfo } from './schema.ts'
5
+ import type { Scalar } from './query-values.ts'
6
+ import { entries, integerRange, misfit, unknownKey } from './query-values.ts'
7
+
8
+ /** What an element of `field` must be, for a message. */
9
+ function kindOf(field: Scalar): string {
10
+ if (field.type === 'enum') return `one of ${(field.values ?? []).join(', ')}`
11
+ if (field.type !== 'number') return 'a non-empty clean string'
12
+ return field.integer === true
13
+ ? `a whole number ${integerRange}`
14
+ : 'a finite number'
15
+ }
16
+
17
+ /**
18
+ * A list's `has`, `hasSome` and `hasEvery`, which name its elements, and
19
+ * `isEmpty` (D-120, D-127, D-149).
20
+ */
21
+ export function listCondition(
22
+ model: ModelInfo,
23
+ field: Scalar,
24
+ value: unknown,
25
+ path: string,
26
+ ): Condition | undefined {
27
+ const element = (item: unknown, at: string): unknown => {
28
+ if (item === '' || misfit(item, field) !== undefined) {
29
+ throw new InvalidQuery(at, `${at} must be ${kindOf(field)}.`)
30
+ }
31
+ return item
32
+ }
33
+ const condition: { [operator: string]: unknown } = {}
34
+ for (const [operator, operand] of entries(value, path)) {
35
+ const at = `${path}.${operator}`
36
+ if (operator === 'has') condition[operator] = element(operand, at)
37
+ else if (operator === 'isEmpty') {
38
+ if (typeof operand !== 'boolean') {
39
+ throw new InvalidQuery(at, `${at} must be a boolean.`)
40
+ }
41
+ condition[operator] = operand
42
+ } else if (operator !== 'hasSome' && operator !== 'hasEvery') {
43
+ throw unknownKey(at, model.name)
44
+ } else if (!Array.isArray(operand)) {
45
+ throw new InvalidQuery(at, `${at} must be a list.`)
46
+ } else {
47
+ condition[operator] = operand.map((each: unknown, index) =>
48
+ element(each, `${at}.${index}`),
49
+ )
50
+ }
51
+ }
52
+ return Object.keys(condition).length === 0 ? undefined : condition
53
+ }
@@ -0,0 +1,104 @@
1
+ // What every query of a model shares (D-18): the running application, the
2
+ // caller and the scope it reads through, and the shape it returns.
3
+ import type { Access } from './access.ts'
4
+ import { and } from './access.ts'
5
+ import type { Filter, Row } from './adapter.ts'
6
+ import { InvalidQuery } from './errors.ts'
7
+ import type { FindOperation } from './model.ts'
8
+ import type { Caller } from './rules.ts'
9
+ import type { Running } from './running.ts'
10
+ import type { ModelInfo } from './schema.ts'
11
+ import type { QueryScope } from './scope.ts'
12
+ import { planned } from './scope.ts'
13
+ import type { Shape } from './query-args.ts'
14
+ import { oneKeys, plan } from './query-args.ts'
15
+ import type { Hidden } from './query-project.ts'
16
+ import { project } from './query-project.ts'
17
+ import type { Store } from './transaction.ts'
18
+ import { computeVirtuals } from './virtual-read.ts'
19
+ import type { Models } from './query-where.ts'
20
+ import { entries, unknownKey } from './query-where.ts'
21
+
22
+ export type Context = {
23
+ readonly app: Running
24
+ readonly models: Models
25
+ readonly model: ModelInfo
26
+ readonly caller: Caller
27
+ readonly scope: QueryScope
28
+ }
29
+
30
+ export function given(
31
+ args: unknown,
32
+ model: string,
33
+ keys: readonly string[],
34
+ required: readonly string[],
35
+ ): Map<string, unknown> {
36
+ const found = new Map(args === undefined ? [] : entries(args, 'args'))
37
+ for (const key of found.keys()) {
38
+ if (!keys.includes(key)) throw unknownKey(key, model)
39
+ }
40
+ for (const key of required) {
41
+ if (!found.has(key)) throw new InvalidQuery(key, `${key} is required.`)
42
+ }
43
+ return found
44
+ }
45
+
46
+ /** `select` and `include` of a single-row operation. */
47
+ export function picked(found: ReadonlyMap<string, unknown>): object {
48
+ const args: { [key: string]: unknown } = {}
49
+ for (const key of oneKeys) {
50
+ if (found.has(key)) args[key] = found.get(key)
51
+ }
52
+ return args
53
+ }
54
+
55
+ /**
56
+ * The rows a many-row operation's `where` selects that the caller reads, or
57
+ * deletes (D-124).
58
+ */
59
+ export async function filtered(
60
+ context: Context,
61
+ found: ReadonlyMap<string, unknown>,
62
+ operation: FindOperation,
63
+ ): Promise<Filter | undefined> {
64
+ const { models, model, scope } = context
65
+ const keys = new Set(['where'])
66
+ const args = { where: found.get('where') }
67
+ return planned(scope, () =>
68
+ and(
69
+ plan(models, model, args, '', 'one', keys, scope).read.where,
70
+ scope.readable(model.name, operation),
71
+ ),
72
+ )
73
+ }
74
+
75
+ /** The fields the caller's field read rules hide (D-44). */
76
+ export function hiddenBy(access: Access | undefined): Hidden | undefined {
77
+ return access && ((model, row, fields) => access.hidden(model, row, fields))
78
+ }
79
+
80
+ /** Each row, as `shape` shapes it (D-149), its virtuals computed (D-151). */
81
+ export async function shapedRows(
82
+ context: Context,
83
+ store: Store,
84
+ rows: readonly Row[],
85
+ shape: Shape,
86
+ access: Access | undefined,
87
+ ): Promise<unknown[]> {
88
+ const hidden = hiddenBy(access)
89
+ const computed = await computeVirtuals(context, store, rows, shape, hidden)
90
+ return rows.map((row) => project(row, shape, hidden, computed))
91
+ }
92
+
93
+ export async function first(
94
+ context: Context,
95
+ store: Store,
96
+ rows: readonly Row[],
97
+ shape: Shape,
98
+ access: Access | undefined,
99
+ ): Promise<unknown> {
100
+ const [row] = rows
101
+ if (row === undefined) return null
102
+ const [shaped] = await shapedRows(context, store, [row], shape, access)
103
+ return shaped
104
+ }
@@ -0,0 +1,54 @@
1
+ // A scalar's and a list's filter operators (D-20, D-140, D-149): a decimal
2
+ // (D-145) or a day (D-150) is a string compared in order, without `contains`.
3
+ import type { JsonValue } from './fields.ts'
4
+
5
+ export type ScalarFilter<V, Ordered = false> = {
6
+ readonly equals?: V | null | undefined
7
+ readonly not?: V | null | undefined
8
+ readonly in?: readonly V[] | undefined
9
+ readonly notIn?: readonly V[] | undefined
10
+ } & ([V, Ordered] extends [number | Date, unknown] | [unknown, true]
11
+ ? {
12
+ readonly lt?: V | undefined
13
+ readonly lte?: V | undefined
14
+ readonly gt?: V | undefined
15
+ readonly gte?: V | undefined
16
+ }
17
+ : [V] extends [string]
18
+ ? {
19
+ readonly contains?: string | undefined
20
+ readonly startsWith?: string | undefined
21
+ readonly endsWith?: string | undefined
22
+ } & (string extends V
23
+ ? { readonly mode?: 'insensitive' | undefined }
24
+ : unknown)
25
+ : unknown)
26
+
27
+ /** A list has no shorthand; `NOT` gives the opposite (D-120, D-127, D-149). */
28
+ export type ListFilter<E> = {
29
+ readonly has?: E | undefined
30
+ readonly hasSome?: readonly E[] | undefined
31
+ readonly hasEvery?: readonly E[] | undefined
32
+ readonly isEmpty?: boolean | undefined
33
+ }
34
+
35
+ /**
36
+ * A json field's filter at a path into its value (D-149): the operands are
37
+ * any JSON, whatever the field's schema, and the path is not checked.
38
+ */
39
+ export type JsonPathFilter = {
40
+ readonly path: readonly string[]
41
+ readonly equals?: JsonValue | undefined
42
+ readonly not?: JsonValue | undefined
43
+ readonly lt?: number | string | undefined
44
+ readonly lte?: number | string | undefined
45
+ readonly gt?: number | string | undefined
46
+ readonly gte?: number | string | undefined
47
+ readonly stringContains?: string | undefined
48
+ readonly stringStartsWith?: string | undefined
49
+ readonly stringEndsWith?: string | undefined
50
+ readonly mode?: 'insensitive' | undefined
51
+ readonly arrayContains?: JsonValue | undefined
52
+ readonly arrayStartsWith?: JsonValue | undefined
53
+ readonly arrayEndsWith?: JsonValue | undefined
54
+ }