ultimabase 0.0.0-stage → 0.1.1

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 +13 -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 +15 -0
  37. package/dist/cli-main.d.ts.map +1 -0
  38. package/dist/cli-main.js +155 -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 +166 -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,71 @@
1
+ // A plugin's manifest (D-57, D-95): checked by `definePlugin`, its ranges
2
+ // matched at `start` against the running kernel and the installed plugins.
3
+ import { satisfies, valid, validRange } from 'semver'
4
+ import kernel from '../package.json' with { type: 'json' }
5
+ import type { Logger } from './app-options.ts'
6
+ import type { PluginRecord } from './definition.ts'
7
+ import type { Manifest } from './plugin.ts'
8
+ import { isObject } from './query-where.ts'
9
+ import { Refused } from './errors.ts'
10
+
11
+ /** Checks a plugin's `manifest`: a name, a version and valid ranges. */
12
+ export function manifestOf(value: unknown, plugin: string): Manifest {
13
+ const path = `${plugin}.manifest`
14
+ if (!isObject(value)) throw new TypeError(`${path} is an object.`)
15
+ const { name, version, peerDependencies } = value
16
+ if (typeof name !== 'string' || name === '') {
17
+ throw new TypeError(`${path}.name is a package name.`)
18
+ }
19
+ if (typeof version !== 'string' || valid(version) === null) {
20
+ throw new TypeError(`${path}.version is a version, not ${String(version)}.`)
21
+ }
22
+ if (peerDependencies === undefined) return Object.freeze({ name, version })
23
+ if (!isObject(peerDependencies)) {
24
+ throw new TypeError(`${path}.peerDependencies is an object.`)
25
+ }
26
+ const ranges: { [name: string]: string } = {}
27
+ for (const [peer, range] of Object.entries(peerDependencies)) {
28
+ if (typeof range !== 'string' || validRange(range) === null) {
29
+ throw new TypeError(
30
+ `${path}.peerDependencies.${peer} is a version range, not ${String(range)}.`,
31
+ )
32
+ }
33
+ ranges[peer] = range
34
+ }
35
+ return Object.freeze({
36
+ name,
37
+ version,
38
+ peerDependencies: Object.freeze(ranges),
39
+ })
40
+ }
41
+
42
+ // A prerelease of the kernel runs the plugins written for its version range.
43
+ const accepts = (version: string, range: string): boolean =>
44
+ satisfies(version, range, { includePrerelease: true })
45
+
46
+ /**
47
+ * Refuses a plugin whose ranges exclude the running kernel or an installed
48
+ * plugin, naming both versions; warns of one that gives the kernel no range.
49
+ */
50
+ export function checkVersions(
51
+ plugins: readonly PluginRecord[],
52
+ logger: Logger,
53
+ ): void {
54
+ const installed = new Map<string, string>([[kernel.name, kernel.version]])
55
+ for (const { code } of plugins) {
56
+ installed.set(code.manifest.name, code.manifest.version)
57
+ }
58
+ for (const { code } of plugins) {
59
+ const { name, version, peerDependencies = {} } = code.manifest
60
+ if (peerDependencies[kernel.name] === undefined) {
61
+ logger.warn(`${name} declares no ${kernel.name} range.`)
62
+ }
63
+ for (const [peer, range] of Object.entries(peerDependencies)) {
64
+ const found = installed.get(peer)
65
+ if (found === undefined || accepts(found, range)) continue
66
+ throw new Refused(
67
+ `${name} ${version} needs ${peer} ${range}; this is ${peer} ${found}.`,
68
+ )
69
+ }
70
+ }
71
+ }
@@ -0,0 +1,162 @@
1
+ // How `memory()` aggregates (D-143), as PostgreSQL: a `null` is skipped, and
2
+ // a sum, an average, a minimum or a maximum of no value is `null`.
3
+ // A decimal's sum is exact, at its field's scale, its average that sum's
4
+ // number divided (D-145). Groups are kept by `having` as rows by a `where`
5
+ // (D-149), and sorted as a read's rows (D-146).
6
+ import type {
7
+ Aggregate,
8
+ AggregateRead,
9
+ Filter,
10
+ GroupKey,
11
+ Read,
12
+ SchemaField,
13
+ } from './adapter.ts'
14
+ import { sumDecimals } from './decimal.ts'
15
+ import type { Truth } from './memory-conditions.ts'
16
+ import { and, isFilters, not, or, scalar } from './memory-conditions.ts'
17
+ import type { Reader, StoredRow } from './memory-read.ts'
18
+ import { compareKeys, compareValues, copy, sortable } from './memory-values.ts'
19
+
20
+ function of(
21
+ values: readonly unknown[],
22
+ operation: Aggregate['operation'],
23
+ field: SchemaField | undefined,
24
+ ): unknown {
25
+ if (operation === 'count') return values.length
26
+ const [head, ...rest] = values
27
+ if (head === undefined) return null
28
+ if (operation === 'min' || operation === 'max') {
29
+ const sign = operation === 'min' ? 1 : -1
30
+ const order = (a: unknown, b: unknown) =>
31
+ compareValues(sortable(a, field), sortable(b, field))
32
+ const found = rest.reduce(
33
+ (best, item) => (sign * order(item, best) < 0 ? item : best),
34
+ head,
35
+ )
36
+ return copy(found)
37
+ }
38
+ const sum =
39
+ field?.kind === 'scalar' && field.type === 'decimal'
40
+ ? sumDecimals(values.map(String), field.scale ?? 0)
41
+ : values.reduce((total: number, item) => total + Number(item), 0)
42
+ return operation === 'sum' ? sum : Number(sum) / values.length
43
+ }
44
+
45
+ /** One value per aggregate of `rows`, in order. */
46
+ function aggregated(
47
+ rows: readonly StoredRow[],
48
+ aggregates: readonly Aggregate[],
49
+ fieldOf: (name: string) => SchemaField | undefined,
50
+ ): unknown[] {
51
+ return aggregates.map(({ operation, field }) => {
52
+ if (field === undefined) return rows.length
53
+ const values = rows.map((row) => row[field] ?? null)
54
+ return of(
55
+ values.filter((item) => item !== null),
56
+ operation,
57
+ fieldOf(field),
58
+ )
59
+ })
60
+ }
61
+
62
+ /** The values of a row's keys, a key through a relation its target's. */
63
+ function keysOf(
64
+ reader: Reader,
65
+ model: string,
66
+ row: StoredRow,
67
+ by: readonly GroupKey[],
68
+ ): unknown[] {
69
+ return by.map(({ field, through }) => {
70
+ const reached = reader.reach(model, row, through)
71
+ return copy(reached?.row[field] ?? null)
72
+ })
73
+ }
74
+
75
+ /** Of the rows with the same keys, the first in `read`'s order (D-147). */
76
+ export function distinct(
77
+ reader: Reader,
78
+ read: Read,
79
+ rows: StoredRow[],
80
+ ): StoredRow[] {
81
+ const { where, orderBy } = read
82
+ const sorted = reader.select(read.model, rows, {
83
+ ...(where && { where }),
84
+ ...(orderBy && { orderBy }),
85
+ })
86
+ const seen = new Set<string>()
87
+ return sorted.filter((row) => {
88
+ const id = JSON.stringify(
89
+ keysOf(reader, read.model, row, read.distinct ?? []),
90
+ )
91
+ if (seen.has(id)) return false
92
+ seen.add(id)
93
+ return true
94
+ })
95
+ }
96
+
97
+ /** Whether a group's values pass `having`, whose keys are positions (D-149). */
98
+ function held(
99
+ filter: Filter,
100
+ values: readonly unknown[],
101
+ fields: readonly (SchemaField | undefined)[],
102
+ ): Truth {
103
+ const truths: Truth[] = []
104
+ for (const [key, part] of Object.entries(filter)) {
105
+ if (part === undefined) continue
106
+ if (!isFilters(part)) {
107
+ const at = Number(key)
108
+ const sorted = (value: unknown) => sortable(value, fields[at])
109
+ truths.push(scalar(values[at] ?? null, part, sorted))
110
+ continue
111
+ }
112
+ const inner = part.map((each) => held(each, values, fields))
113
+ if (key === 'AND') truths.push(and(inner))
114
+ else if (key === 'OR') truths.push(or(inner))
115
+ else truths.push(and(inner.map((truth) => not(truth))))
116
+ }
117
+ return and(truths)
118
+ }
119
+
120
+ /** Each group of `rows`: its keys' values, then its aggregates', in order. */
121
+ export function grouped(
122
+ reader: Reader,
123
+ read: AggregateRead,
124
+ rows: readonly StoredRow[],
125
+ ): unknown[][] {
126
+ const by = read.by ?? []
127
+ const fieldOf = (name: string) => reader.field(read.model, name)
128
+ const groups = new Map<string, { keys: unknown[]; rows: StoredRow[] }>()
129
+ if (by.length === 0) groups.set('[]', { keys: [], rows: [] })
130
+ for (const row of rows) {
131
+ const keys = keysOf(reader, read.model, row, by)
132
+ const id = JSON.stringify(keys)
133
+ const group = groups.get(id) ?? { keys, rows: [] }
134
+ group.rows.push(row)
135
+ groups.set(id, group)
136
+ }
137
+ const lists = [...groups.values()].map((group) => [
138
+ ...group.keys,
139
+ ...aggregated(group.rows, read.aggregates, fieldOf),
140
+ ])
141
+ // A key through a relation is an id; a decimal sorts by value (D-145).
142
+ const fields = [
143
+ ...by.map(({ field, through }) => (through ? undefined : field)),
144
+ ...read.aggregates.map(({ operation, field }) =>
145
+ operation === 'count' ? undefined : field,
146
+ ),
147
+ ].map((name) => (name === undefined ? undefined : fieldOf(name)))
148
+ const { having } = read
149
+ const kept =
150
+ having === undefined
151
+ ? lists
152
+ : lists.filter((list) => held(having, list, fields) === true)
153
+ const order = read.orderBy ?? []
154
+ const sorted = (list: readonly unknown[]) =>
155
+ order.map(({ at }) => sortable(list[at], fields[at]))
156
+ kept.sort((a, b) => compareKeys(sorted(a), sorted(b), order))
157
+ const skip = read.skip ?? 0
158
+ return kept.slice(
159
+ skip,
160
+ read.take === undefined ? undefined : skip + read.take,
161
+ )
162
+ }
@@ -0,0 +1,88 @@
1
+ // How `memory()` tests a condition, with SQL's three-valued logic: `null` is
2
+ // unknown, as PostgreSQL compares it (D-94).
3
+ import type { Condition, Filter } from './adapter.ts'
4
+ import { jsonPath } from './memory-json.ts'
5
+ import { compareValues, lower, same, text } from './memory-values.ts'
6
+
7
+ export type Truth = boolean | null
8
+
9
+ export function and(truths: readonly Truth[]): Truth {
10
+ if (truths.includes(false)) return false
11
+ return truths.includes(null) ? null : true
12
+ }
13
+
14
+ export function or(truths: readonly Truth[]): Truth {
15
+ if (truths.includes(true)) return true
16
+ return truths.includes(null) ? null : false
17
+ }
18
+
19
+ export function not(truth: Truth): Truth {
20
+ return truth === null ? null : !truth
21
+ }
22
+
23
+ export function isFilters(
24
+ value: Condition | readonly Filter[],
25
+ ): value is readonly Filter[] {
26
+ return Array.isArray(value)
27
+ }
28
+
29
+ export function scalar(
30
+ stored: unknown,
31
+ condition: Condition,
32
+ sorted: (value: unknown) => unknown,
33
+ ): Truth {
34
+ if (condition.path !== undefined) return jsonPath(stored, condition)
35
+ const truths: Truth[] = []
36
+ const known = stored !== null
37
+ const fold =
38
+ condition.mode === 'insensitive' ? lower : (item: unknown) => item
39
+ const value = fold(stored)
40
+ for (const [operator, raw] of Object.entries(condition)) {
41
+ if (raw === undefined || operator === 'mode') continue
42
+ const operand: unknown = Array.isArray(raw)
43
+ ? raw.map((item: unknown) => fold(item))
44
+ : fold(raw)
45
+ if (operator === 'equals' || operator === 'not') {
46
+ const equal =
47
+ operand === null ? !known : known ? same(value, operand) : null
48
+ truths.push(operator === 'equals' ? equal : not(equal))
49
+ } else if (operator === 'in' || operator === 'notIn') {
50
+ const list: readonly unknown[] = Array.isArray(operand) ? operand : []
51
+ const found =
52
+ list.length === 0
53
+ ? false
54
+ : known
55
+ ? list.some((item) => same(value, item))
56
+ : null
57
+ truths.push(operator === 'in' ? found : not(found))
58
+ } else if (!known) {
59
+ truths.push(null)
60
+ } else if (operator.startsWith('has') || operator === 'isEmpty') {
61
+ const items: readonly unknown[] = Array.isArray(value) ? value : []
62
+ const wanted: readonly unknown[] = Array.isArray(operand)
63
+ ? operand
64
+ : [operand]
65
+ const holds = (item: unknown): boolean => items.includes(item)
66
+ truths.push(
67
+ operator === 'isEmpty'
68
+ ? (items.length === 0) === operand
69
+ : operator === 'hasEvery'
70
+ ? wanted.every(holds)
71
+ : wanted.some(holds),
72
+ )
73
+ } else if (operator === 'contains') {
74
+ truths.push(text(value, operand, (a, b) => a.includes(b)))
75
+ } else if (operator === 'startsWith') {
76
+ truths.push(text(value, operand, (a, b) => a.startsWith(b)))
77
+ } else if (operator === 'endsWith') {
78
+ truths.push(text(value, operand, (a, b) => a.endsWith(b)))
79
+ } else {
80
+ const order = compareValues(sorted(value), sorted(operand))
81
+ if (operator === 'lt') truths.push(order < 0)
82
+ else if (operator === 'lte') truths.push(order <= 0)
83
+ else if (operator === 'gt') truths.push(order > 0)
84
+ else truths.push(order >= 0)
85
+ }
86
+ }
87
+ return and(truths)
88
+ }
@@ -0,0 +1,319 @@
1
+ // The draft a memory() write changes: a copy of the store, and what the
2
+ // write changed for the feed (D-154).
3
+ import type {
4
+ AggregateRead,
5
+ Arithmetic,
6
+ Change,
7
+ Changed,
8
+ CountRead,
9
+ ListUpdate,
10
+ Read,
11
+ Row,
12
+ ScalarType,
13
+ Schema,
14
+ SchemaField,
15
+ Write,
16
+ } from './adapter.ts'
17
+ import { CheckFailed, OutOfRange, UniqueViolation } from './adapter.ts'
18
+ import type { Fields, Store, StoredRow } from './memory-read.ts'
19
+ import { Reader } from './memory-read.ts'
20
+ import { distinct, grouped } from './memory-aggregate.ts'
21
+ import { copy } from './memory-values.ts'
22
+ import { listed } from './list-update.ts'
23
+ import { computed } from './number-update.ts'
24
+ import { isInteger } from './query-where.ts'
25
+
26
+ /**
27
+ * A value as PostgreSQL gives it back: `-0` is `0`, which `pg` sends for a
28
+ * number, and JSON text for json (D-94).
29
+ */
30
+ function storedAs(type: ScalarType | undefined, value: unknown): unknown {
31
+ if (type === 'json') {
32
+ const parsed: unknown = JSON.parse(JSON.stringify(value))
33
+ return parsed
34
+ }
35
+ if (type === 'number' && value === 0) return 0
36
+ return copy(value)
37
+ }
38
+
39
+ function keyOf(values: readonly unknown[]): string {
40
+ return JSON.stringify(
41
+ values.map((value) =>
42
+ value instanceof Date ? `date:${value.getTime()}` : value,
43
+ ),
44
+ )
45
+ }
46
+
47
+ export class Draft {
48
+ readonly reader: Reader
49
+ readonly schema: Schema
50
+ /** What the write changed, in order, for the feed (D-154). */
51
+ readonly changed: Changed[] = []
52
+
53
+ constructor(store: Store, fields: Fields, schema: Schema) {
54
+ this.reader = new Reader(store, fields)
55
+ this.schema = schema
56
+ }
57
+
58
+ find(read: Read): Row[] {
59
+ const stored = [...this.reader.table(read.model).values()]
60
+ const rows = read.distinct ? distinct(this.reader, read, stored) : stored
61
+ return this.reader
62
+ .select(read.model, rows, read)
63
+ .map((row) => this.reader.output(read.model, row, read))
64
+ }
65
+
66
+ count(read: CountRead): number {
67
+ return this.selected(read).length
68
+ }
69
+
70
+ aggregate(read: AggregateRead): unknown[][] {
71
+ return grouped(this.reader, read, this.selected(read))
72
+ }
73
+
74
+ private selected(read: CountRead): StoredRow[] {
75
+ const rows = [...this.reader.table(read.model).values()]
76
+ return rows.filter(
77
+ (row) => this.reader.matches(read.model, row, read.where) === true,
78
+ )
79
+ }
80
+
81
+ write(write: Write): Row[] {
82
+ for (const [index, check] of (write.checks ?? []).entries()) {
83
+ if (this.count(check) !== check.count) throw new CheckFailed(index)
84
+ }
85
+ const read = write.read
86
+ if (read !== undefined && write.before === true) {
87
+ const { model } = read
88
+ const deletes = write.changes.some(
89
+ (change) => change.kind === 'delete' && change.model === model,
90
+ )
91
+ const update = write.changes.find(
92
+ (change) => change.kind === 'update' && change.model === model,
93
+ )
94
+ if (!deletes) {
95
+ // The rows its update changed, as it left them (D-133).
96
+ const changed = update && this.matching(model, update)
97
+ const ids = new Set(changed?.map((row) => row.id))
98
+ this.change(write.changes)
99
+ return this.find(read).filter((row) => ids.has(row.id))
100
+ }
101
+ const rows = this.find(read)
102
+ this.change(write.changes)
103
+ // The rows the write's delete removed, as they were (D-104).
104
+ const table = this.reader.table(read.model)
105
+ return rows.filter((row) => !table.has(row.id))
106
+ }
107
+ this.change(write.changes)
108
+ return read === undefined ? [] : this.find(read)
109
+ }
110
+
111
+ private change(changes: readonly Change[]): void {
112
+ const touched = new Set<string>()
113
+ this.reader.removed.clear()
114
+ for (const [index, change] of changes.entries()) {
115
+ touched.add(change.model)
116
+ if (change.kind === 'delete') {
117
+ const removed = this.remove(change)
118
+ this.reader.removed.set(index, removed)
119
+ this.note({ kind: 'delete', model: change.model, ids: [...removed] })
120
+ } else this.apply(change)
121
+ }
122
+ for (const model of touched) this.checkUnique(model)
123
+ }
124
+
125
+ private note(change: Changed): void {
126
+ const rows = 'field' in change ? change.pairs : change.ids
127
+ if (rows?.length !== 0) this.changed.push(change)
128
+ }
129
+
130
+ private matching(model: string, change: Change): StoredRow[] {
131
+ if (change.kind !== 'update' && change.kind !== 'delete') return []
132
+ return [...this.reader.table(model).values()].filter(
133
+ (row) => this.reader.matches(model, row, change.where) === true,
134
+ )
135
+ }
136
+
137
+ private stored(
138
+ model: string,
139
+ value: { readonly [field: string]: unknown },
140
+ ): { [field: string]: unknown } {
141
+ return Object.fromEntries(
142
+ Object.entries(value).map(([key, item]) => {
143
+ const field = this.reader.field(model, key)
144
+ const type = field?.kind === 'scalar' ? field.type : undefined
145
+ return [key, storedAs(type, item)]
146
+ }),
147
+ )
148
+ }
149
+
150
+ /** The numbers and lists a change computes on `row` (D-132, D-149). */
151
+ private computed(
152
+ model: string,
153
+ row: StoredRow,
154
+ compute: { readonly [field: string]: Arithmetic | ListUpdate } = {},
155
+ ): { [field: string]: unknown } {
156
+ return Object.fromEntries(
157
+ Object.entries(compute).map(([key, operation]): [string, unknown] => {
158
+ if ('elements' in operation) return [key, listed(row[key], operation)]
159
+ const field = this.reader.field(model, key)
160
+ const integer = field?.kind === 'scalar' && field.integer === true
161
+ const result = computed(row[key], operation, integer)
162
+ if (integer ? !isInteger(result) : !Number.isFinite(result)) {
163
+ throw new OutOfRange(model, key)
164
+ }
165
+ return [key, result]
166
+ }),
167
+ )
168
+ }
169
+
170
+ /** Applies `delete`; returns the ids it removed. */
171
+ private remove(change: Extract<Change, { kind: 'delete' }>): Set<string> {
172
+ const table = this.reader.table(change.model)
173
+ const removed = new Set<string>()
174
+ for (const row of this.matching(change.model, change)) {
175
+ removed.add(row.id)
176
+ table.delete(row.id)
177
+ for (const field of this.joinFields(change.model)) {
178
+ this.unlink(change.model, field, row.id, [
179
+ ...(this.side(change.model, field.name).get(row.id) ?? []),
180
+ ])
181
+ }
182
+ }
183
+ return removed
184
+ }
185
+
186
+ private apply(change: Exclude<Change, { kind: 'delete' }>): void {
187
+ const table = this.reader.table(change.model)
188
+ switch (change.kind) {
189
+ case 'insert': {
190
+ if (table.has(change.row.id)) {
191
+ throw new UniqueViolation(change.model, ['id'])
192
+ }
193
+ table.set(change.row.id, {
194
+ ...this.stored(change.model, change.row),
195
+ id: change.row.id,
196
+ })
197
+ this.note({ kind: 'insert', model: change.model, ids: [change.row.id] })
198
+ return
199
+ }
200
+ case 'update': {
201
+ const rows = this.matching(change.model, change)
202
+ for (const row of rows) {
203
+ const set = {
204
+ ...change.set,
205
+ ...this.computed(change.model, row, change.compute),
206
+ }
207
+ table.set(row.id, {
208
+ ...row,
209
+ ...this.stored(change.model, set),
210
+ id: row.id,
211
+ })
212
+ }
213
+ const fields = [
214
+ ...Object.keys(change.set),
215
+ ...Object.keys(change.compute ?? {}),
216
+ ]
217
+ const ids = rows.map((row) => row.id)
218
+ this.note({ kind: 'update', model: change.model, ids, fields })
219
+ return
220
+ }
221
+ case 'link':
222
+ case 'unlink': {
223
+ const field = this.reader.field(change.model, change.field)
224
+ if (field?.kind !== 'many' || !field.join) {
225
+ throw new Error(`${change.model}.${change.field} has no join.`)
226
+ }
227
+ const { model, id } = change
228
+ const targets =
229
+ change.kind === 'unlink'
230
+ ? this.unlink(model, field, id, change.targets)
231
+ : change.targets.filter((target) =>
232
+ this.link(model, field, id, target),
233
+ )
234
+ const pairs = targets.map((target): [string, string] => [id, target])
235
+ this.note({ kind: change.kind, model, field: field.name, pairs })
236
+ return
237
+ }
238
+ }
239
+ }
240
+
241
+ private joinFields(model: string): SchemaField[] {
242
+ return [...(this.reader.fields.get(model)?.values() ?? [])].filter(
243
+ (field) =>
244
+ field.kind === 'many' && field.join && field.from === undefined,
245
+ )
246
+ }
247
+
248
+ private side(model: string, field: string): Map<string, Set<string>> {
249
+ const name = `${model}.${field}`
250
+ let side = this.reader.store.joins.get(name)
251
+ if (side === undefined) {
252
+ side = new Map()
253
+ this.reader.store.joins.set(name, side)
254
+ }
255
+ return side
256
+ }
257
+
258
+ private ids(model: string, field: string, id: string): Set<string> {
259
+ const side = this.side(model, field)
260
+ let ids = side.get(id)
261
+ if (ids === undefined) {
262
+ ids = new Set()
263
+ side.set(id, ids)
264
+ }
265
+ return ids
266
+ }
267
+
268
+ /** Links `id` to `target` unless they were; tells whether it did. */
269
+ private link(
270
+ model: string,
271
+ field: Extract<SchemaField, { kind: 'many' }>,
272
+ id: string,
273
+ target: string,
274
+ ): boolean {
275
+ const ids = this.ids(model, field.name, id)
276
+ if (ids.has(target)) return false
277
+ ids.add(target)
278
+ this.ids(field.target, field.inverse, target).add(id)
279
+ return true
280
+ }
281
+
282
+ /** Unlinks `id` from `targets`; returns the targets it was linked to. */
283
+ private unlink(
284
+ model: string,
285
+ field: SchemaField,
286
+ id: string,
287
+ targets: readonly string[],
288
+ ): string[] {
289
+ if (field.kind !== 'many') return []
290
+ return targets.filter((target) => {
291
+ const removed = this.side(model, field.name).get(id)?.delete(target)
292
+ this.side(field.target, field.inverse).get(target)?.delete(id)
293
+ return removed === true
294
+ })
295
+ }
296
+
297
+ /** Unique fields, unique indexes and one-to-one keys; `null` never clashes. */
298
+ private checkUnique(model: string): void {
299
+ const definition = this.schema.models.find((each) => each.name === model)
300
+ if (definition === undefined) return
301
+ const sets: (readonly string[])[] = definition.indexes
302
+ .filter((index) => index.unique)
303
+ .map((index) => index.fields)
304
+ for (const field of definition.fields) {
305
+ if (field.kind !== 'many' && field.unique) sets.push([field.name])
306
+ }
307
+ const rows = [...this.reader.table(model).values()]
308
+ for (const fields of sets) {
309
+ const seen = new Set<string>()
310
+ for (const row of rows) {
311
+ const values = fields.map((field) => row[field] ?? null)
312
+ if (values.includes(null)) continue
313
+ const found = keyOf(values)
314
+ if (seen.has(found)) throw new UniqueViolation(model, fields)
315
+ seen.add(found)
316
+ }
317
+ }
318
+ }
319
+ }