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,176 @@
1
+ // A relation write's operations (D-20, D-128, D-138), their shape checked:
2
+ // `connect`, `connectOrCreate` and `create`, and in an update `delete`,
3
+ // `disconnect`, `update` and, on a `many`, `set`.
4
+ import { InvalidQuery } from './errors.ts'
5
+ import { clean, entries, isObject, unknownKey } from './query-where.ts'
6
+ import type { Draft } from './write-data.ts'
7
+
8
+ /** `{ id }`, exactly. */
9
+ function ref(value: unknown, path: string): string {
10
+ const given = entries(value, path)
11
+ const [first] = given
12
+ if (
13
+ given.length !== 1 ||
14
+ first?.[0] !== 'id' ||
15
+ typeof first[1] !== 'string' ||
16
+ !clean(first[1])
17
+ ) {
18
+ throw new InvalidQuery(path, `${path} must be { id }.`)
19
+ }
20
+ return first[1]
21
+ }
22
+
23
+ /** The operations a relation write takes (D-128, D-138). */
24
+ function allowed(kind: 'one' | 'many', creating: boolean): readonly string[] {
25
+ if (creating) return ['connect', 'connectOrCreate', 'create']
26
+ const changing = kind === 'one' ? ['update'] : ['set', 'update']
27
+ return [
28
+ 'connect',
29
+ 'connectOrCreate',
30
+ 'create',
31
+ 'delete',
32
+ 'disconnect',
33
+ ...changing,
34
+ ].toSorted()
35
+ }
36
+
37
+ function operations(
38
+ value: unknown,
39
+ path: string,
40
+ model: string,
41
+ kind: 'one' | 'many',
42
+ creating: boolean,
43
+ ): Map<string, unknown> {
44
+ if (value === null) {
45
+ throw new InvalidQuery(path, `${path} cannot be null; use disconnect.`)
46
+ }
47
+ const given = new Map(entries(value, path))
48
+ for (const key of given.keys()) {
49
+ if (!allowed(kind, creating).includes(key))
50
+ throw unknownKey(`${path}.${key}`, model)
51
+ }
52
+ return given
53
+ }
54
+
55
+ /** Data a nested `create` or `update` writes (D-128, D-138). */
56
+ function made(value: unknown, path: string): Draft {
57
+ if (!isObject(value)) {
58
+ throw new InvalidQuery(path, `${path} must be an object.`)
59
+ }
60
+ return value
61
+ }
62
+
63
+ /** An object of exactly `keys`. */
64
+ function exactly(
65
+ value: unknown,
66
+ path: string,
67
+ keys: readonly [string, string],
68
+ ): Map<string, unknown> {
69
+ const given = new Map(entries(value, path))
70
+ if (given.size !== 2 || !keys.every((key) => given.has(key))) {
71
+ throw new InvalidQuery(path, `${path} must be { ${keys.join(', ')} }.`)
72
+ }
73
+ return given
74
+ }
75
+
76
+ /** A `connectOrCreate`: the unique target it looks for, the row it creates. */
77
+ export type OrCreate = { readonly where: unknown; readonly create: Draft }
78
+
79
+ function orCreate(value: unknown, path: string): OrCreate {
80
+ const given = exactly(value, path, ['where', 'create'])
81
+ return {
82
+ where: given.get('where'),
83
+ create: made(given.get('create'), `${path}.create`),
84
+ }
85
+ }
86
+
87
+ /** A `many` relation's `update`: a unique target, and its data. */
88
+ export type Updating = { readonly where: unknown; readonly data: Draft }
89
+
90
+ function updating(value: unknown, path: string): Updating {
91
+ const given = exactly(value, path, ['where', 'data'])
92
+ return {
93
+ where: given.get('where'),
94
+ data: made(given.get('data'), `${path}.data`),
95
+ }
96
+ }
97
+
98
+ function list(value: unknown, path: string): readonly unknown[] {
99
+ if (!Array.isArray(value))
100
+ throw new InvalidQuery(path, `${path} must be a list.`)
101
+ return value
102
+ }
103
+
104
+ /** A `one` relation write. */
105
+ export type OneWrite =
106
+ | { readonly kind: 'connect'; readonly id: string }
107
+ | { readonly kind: 'create'; readonly data: Draft }
108
+ | ({ readonly kind: 'connectOrCreate' } & OrCreate)
109
+ | { readonly kind: 'update'; readonly data: Draft }
110
+ | { readonly kind: 'delete' }
111
+ | { readonly kind: 'disconnect' }
112
+
113
+ export function oneWrite(
114
+ value: unknown,
115
+ path: string,
116
+ model: string,
117
+ creating: boolean,
118
+ ): OneWrite {
119
+ const given = operations(value, path, model, 'one', creating)
120
+ const [entry] = given
121
+ if (entry === undefined || given.size !== 1) {
122
+ throw new InvalidQuery(
123
+ path,
124
+ `${path} takes one of ${allowed('one', creating).join(', ')}.`,
125
+ )
126
+ }
127
+ const [key, operand] = entry
128
+ const at = `${path}.${key}`
129
+ if (key === 'connect') return { kind: key, id: ref(operand, at) }
130
+ if (key === 'create' || key === 'update') {
131
+ return { kind: key, data: made(operand, at) }
132
+ }
133
+ if (key === 'connectOrCreate') return { kind: key, ...orCreate(operand, at) }
134
+ if (operand !== true) throw new InvalidQuery(at, `${at} must be true.`)
135
+ return { kind: key === 'delete' ? 'delete' : 'disconnect' }
136
+ }
137
+
138
+ /** A `many` relation write, each operation's list. */
139
+ export type ManyWrite = {
140
+ readonly connect: string[]
141
+ readonly disconnect: string[]
142
+ readonly create: Draft[]
143
+ readonly set: string[] | undefined
144
+ readonly connectOrCreate: OrCreate[]
145
+ readonly update: Updating[]
146
+ readonly delete: unknown[]
147
+ }
148
+
149
+ export function manyWrite(
150
+ value: unknown,
151
+ path: string,
152
+ model: string,
153
+ creating: boolean,
154
+ ): ManyWrite {
155
+ const given = operations(value, path, model, 'many', creating)
156
+ const each = <T>(key: string, read: (item: unknown, at: string) => T): T[] =>
157
+ list(given.get(key) ?? [], `${path}.${key}`).map((item, index) =>
158
+ read(item, `${path}.${key}.${index}`),
159
+ )
160
+ if (given.has('set') && (given.has('connect') || given.has('disconnect'))) {
161
+ throw new InvalidQuery(
162
+ `${path}.set`,
163
+ `${path}.set cannot be used with connect or disconnect.`,
164
+ )
165
+ }
166
+ return {
167
+ // A row disconnected and connected again stays linked (D-101).
168
+ connect: each('connect', ref),
169
+ disconnect: each('disconnect', ref),
170
+ create: each('create', made),
171
+ set: given.has('set') ? each('set', ref) : undefined,
172
+ connectOrCreate: each('connectOrCreate', orCreate),
173
+ update: each('update', updating),
174
+ delete: each('delete', (item) => item),
175
+ }
176
+ }
@@ -0,0 +1,165 @@
1
+ // The filter of a `many` relation, from its model's `.with({ relations })`
2
+ // (D-151): every read of the relation sees only the rows it keeps, Master's
3
+ // too, and a filtered relation takes no `set`.
4
+ import type { Filter } from './adapter.ts'
5
+ import type { FieldOptions } from './definition.ts'
6
+ import { InvalidQuery, Refused } from './errors.ts'
7
+ import type { Models, Scope } from './query-where.ts'
8
+ import { isObject, modelOf, normalizeWhere } from './query-where.ts'
9
+ import type { Caller } from './rules.ts'
10
+ import { filterOf, isReadFilter, readFilter } from './rules.ts'
11
+ import type { FieldInfo, ModelInfo } from './schema.ts'
12
+
13
+ type Many = Extract<FieldInfo, { readonly kind: 'many' }>
14
+
15
+ function relationsOf(model: ModelInfo): { readonly [field: string]: unknown } {
16
+ const { rules } = model.definition
17
+ const relations = isObject(rules) ? rules['relations'] : undefined
18
+ return isObject(relations) ? relations : {}
19
+ }
20
+
21
+ /**
22
+ * Stops startup on a filter that names no `many` of the model, or on a view
23
+ * without one (D-151).
24
+ */
25
+ export function checkRelations(model: ModelInfo): void {
26
+ for (const [name, rule] of Object.entries(relationsOf(model))) {
27
+ const where = `${model.name}.with.relations.${name}`
28
+ if (model.fields.get(name)?.kind !== 'many') {
29
+ throw new Refused(`${where}: ${name} is not a many relation.`)
30
+ }
31
+ if (!isObject(rule) || typeof rule['where'] !== 'function') {
32
+ throw new Refused(`${where} takes a where function.`)
33
+ }
34
+ }
35
+ for (const field of model.fields.values()) {
36
+ if (field.kind === 'many' && field.from !== undefined) {
37
+ if (!isFiltered(model, field.name)) {
38
+ throw new Refused(
39
+ `${model.name}.${field.name} is a view; filter it in .with({ relations }).`,
40
+ )
41
+ }
42
+ }
43
+ }
44
+ }
45
+
46
+ /**
47
+ * A view (D-151): `source`, the `many` its `from` names, under its own name
48
+ * and options. Stops startup unless `from` names a `many` of the model to the
49
+ * same target that is no view.
50
+ */
51
+ export function viewField(
52
+ view: {
53
+ readonly model: string
54
+ readonly name: string
55
+ readonly target: string
56
+ readonly options: FieldOptions
57
+ },
58
+ from: string,
59
+ source: FieldInfo | 'view' | undefined,
60
+ ): FieldInfo {
61
+ const { model, name, target, options } = view
62
+ const at = `${model}.${name}`
63
+ if (options['inverse'] !== undefined) {
64
+ throw new Refused(`${at} is a view and takes no inverse.`)
65
+ }
66
+ if (source === 'view') {
67
+ throw new Refused(`${at}: from '${from}' is a view; name its relation.`)
68
+ }
69
+ if (source?.kind !== 'many') {
70
+ throw new Refused(`${at}: from '${from}' is not a many relation.`)
71
+ }
72
+ if (source.target !== target) {
73
+ throw new Refused(
74
+ `${at}: from '${from}' relates to ${source.target}, not ${target}.`,
75
+ )
76
+ }
77
+ return { ...source, name, from, options }
78
+ }
79
+
80
+ /** Whether `.with({ relations })` filters `field` of `model`. */
81
+ export function isFiltered(model: ModelInfo, field: string): boolean {
82
+ return Object.hasOwn(relationsOf(model), field)
83
+ }
84
+
85
+ /** Fails on a `set` of a filtered relation, which would detach hidden rows. */
86
+ export function refuseSet(model: ModelInfo, field: string, path: string): void {
87
+ if (!isFiltered(model, field)) return
88
+ throw new InvalidQuery(
89
+ path,
90
+ `${model.name}.${field} is filtered; use connect and disconnect, not set.`,
91
+ )
92
+ }
93
+
94
+ /** No row passes: a filter that returns `false`. */
95
+ const closed: Filter = { OR: [] }
96
+ const pending = Symbol('pending')
97
+
98
+ /** The relation filters of one query, run once each for its caller. */
99
+ export class Relations {
100
+ private readonly filters = new Map<
101
+ string,
102
+ Filter | undefined | typeof pending
103
+ >()
104
+ private readonly models: Models
105
+ private readonly caller: Caller
106
+
107
+ constructor(models: Models, caller: Caller) {
108
+ this.models = models
109
+ this.caller = caller
110
+ }
111
+
112
+ /** The rows of `model.field` its filter keeps; `undefined` for all. */
113
+ filter(model: ModelInfo, field: Many): Filter | undefined {
114
+ const key = `${model.name}.${field.name}`
115
+ const known = this.filters.get(key)
116
+ if (known === pending) {
117
+ throw new TypeError(`${key}: its filter reads the relation itself.`)
118
+ }
119
+ if (this.filters.has(key)) return known
120
+ const rule = relationsOf(model)[field.name]
121
+ this.filters.set(key, pending)
122
+ const filter = isObject(rule)
123
+ ? this.run(model, field, rule['where'])
124
+ : undefined
125
+ this.filters.set(key, filter)
126
+ return filter
127
+ }
128
+
129
+ private run(
130
+ model: ModelInfo,
131
+ field: Many,
132
+ rule: unknown,
133
+ ): Filter | undefined {
134
+ const path = `${model.name}.with.relations.${field.name}.where`
135
+ if (typeof rule !== 'function')
136
+ throw new TypeError(`${path} is a function.`)
137
+ const result: unknown = Reflect.apply(rule, undefined, [
138
+ { caller: this.caller, where: readFilter },
139
+ ])
140
+ if (result === true) return undefined
141
+ if (result === false) return closed
142
+ if (!isReadFilter(result)) {
143
+ throw new TypeError(`${path} returns where({ ... }) or a boolean.`)
144
+ }
145
+ const target = modelOf(this.models, field.target)
146
+ // Trusted server code, as a read rule's own filter (D-86).
147
+ return normalizeWhere(
148
+ this.models,
149
+ target,
150
+ filterOf(result),
151
+ path,
152
+ trusted(this),
153
+ )
154
+ }
155
+ }
156
+
157
+ /** The scope of trusted filters: no access rule, relation filters only. */
158
+ export function trusted(relations: Relations): Scope {
159
+ return {
160
+ check: () => undefined,
161
+ ruled: () => false,
162
+ readable: () => undefined,
163
+ related: (model, field) => relations.filter(model, field),
164
+ }
165
+ }
package/src/rules.ts ADDED
@@ -0,0 +1,188 @@
1
+ // What every access rule receives, and the kernel's ready-made rules (D-10).
2
+ import type { RecordOf } from './record.ts'
3
+ import type { modelFields } from './symbols.ts'
4
+
5
+ export type Operation = 'read' | 'create' | 'update' | 'delete'
6
+
7
+ /**
8
+ * The application declares its user model here, once; without it,
9
+ * `caller.user` is `{ id }` (D-24, D-62).
10
+ */
11
+ export interface Register {}
12
+
13
+ /** The user record a registry gives `caller.user`. */
14
+ export type UserOf<R> = R extends {
15
+ readonly user: { readonly [modelFields]: infer F }
16
+ }
17
+ ? RecordOf<F>
18
+ : { readonly id: string }
19
+
20
+ /**
21
+ * The keys plugins add to a user caller, each plugin declaring its own
22
+ * (D-121): `declare module 'ultimabase' { interface UserCallerKeys { ... } }`.
23
+ */
24
+ export interface UserCallerKeys {}
25
+
26
+ type Master = {
27
+ readonly kind: 'master'
28
+ readonly master: { readonly source: 'code' | 'key' }
29
+ }
30
+
31
+ /**
32
+ * Who code says sends an operation, as `as` and a plugin's `identify` give
33
+ * it: a user caller without the keys plugins add (D-121).
34
+ */
35
+ export type Identity =
36
+ | { readonly kind: 'anonymous' }
37
+ | { readonly kind: 'user'; readonly user: UserOf<Register> }
38
+ | Master
39
+
40
+ /** Who sends an operation, as rules, hooks and functions see it (D-19). */
41
+ export type Caller =
42
+ | { readonly kind: 'anonymous' }
43
+ | ({
44
+ readonly kind: 'user'
45
+ readonly user: UserOf<Register>
46
+ } & UserCallerKeys)
47
+ | Master
48
+
49
+ const masters = new WeakSet<object>()
50
+
51
+ /** A Master caller; the kernel's code and transports only (D-19, D-49). */
52
+ export function createMaster(source: 'code' | 'key'): Master {
53
+ const caller: Master = Object.freeze({
54
+ kind: 'master',
55
+ master: Object.freeze({ source }),
56
+ })
57
+ masters.add(caller)
58
+ return caller
59
+ }
60
+
61
+ /** Whether a caller is a Master the kernel created. */
62
+ export function isMaster(caller: object): caller is Master {
63
+ return masters.has(caller)
64
+ }
65
+
66
+ /** Server code acting with every right; only the kernel creates it. */
67
+ export const master: Master = createMaster('code')
68
+
69
+ /** A rule that reads only the caller and the operation fits any access key. */
70
+ export type AnyRule = (facts: {
71
+ readonly caller: Caller
72
+ readonly operation: Operation
73
+ }) => boolean
74
+
75
+ /** A rule that reads only the caller also fits a function's access (D-27). */
76
+ export type CallerRule = (facts: { readonly caller: Caller }) => boolean
77
+
78
+ type Allow = {
79
+ readonly public: CallerRule
80
+ readonly readOnly: AnyRule
81
+ readonly private: CallerRule
82
+ }
83
+
84
+ /** Open to everyone, open to reads, closed to everyone but Master (D-10). */
85
+ export const allow: Allow = Object.freeze({
86
+ public: () => true,
87
+ readOnly: ({ operation }) => operation === 'read',
88
+ private: () => false,
89
+ })
90
+
91
+ const filters = new WeakMap<object, unknown>()
92
+
93
+ /** The read filter a read rule's `where` returns (D-10). */
94
+ export function readFilter(where: unknown): object {
95
+ const token = Object.freeze({})
96
+ filters.set(token, where)
97
+ return token
98
+ }
99
+
100
+ /** Whether a rule's result is a read filter. */
101
+ export function isReadFilter(value: unknown): value is object {
102
+ return typeof value === 'object' && value !== null && filters.has(value)
103
+ }
104
+
105
+ /** The `where` a read filter holds. */
106
+ export function filterOf(token: object): unknown {
107
+ return filters.get(token)
108
+ }
109
+
110
+ type Results = { readonly decided: boolean; readonly filters: object[] }
111
+
112
+ // A rule returns a boolean, or a filter in a read rule (D-76, D-81).
113
+ function collect(
114
+ rules: readonly Rule[],
115
+ facts: unknown,
116
+ decisive: boolean,
117
+ ): Results {
118
+ const found: object[] = []
119
+ for (const rule of rules) {
120
+ const result: unknown = Reflect.apply(rule, undefined, [facts])
121
+ if (result === decisive) return { decided: true, filters: [] }
122
+ if (isReadFilter(result)) found.push(result)
123
+ else if (typeof result !== 'boolean') {
124
+ throw new TypeError('A rule returns a boolean or a read filter.')
125
+ }
126
+ }
127
+ return { decided: false, filters: found }
128
+ }
129
+
130
+ function combine(
131
+ rules: readonly Rule[],
132
+ decisive: boolean,
133
+ operator: 'AND' | 'OR',
134
+ ): (facts: unknown) => unknown {
135
+ return (facts) => {
136
+ const { decided, filters: found } = collect(rules, facts, decisive)
137
+ if (decided) return decisive
138
+ const [first, ...others] = found
139
+ if (first === undefined) return !decisive
140
+ if (others.length === 0) return first
141
+ return readFilter({ [operator]: found.map(filterOf) })
142
+ }
143
+ }
144
+ type Rule = (facts: never) => unknown
145
+
146
+ /** A rule already typed, such as a `const` holding `signedIn(...)`. */
147
+ type Named = (facts: never) => unknown
148
+ type Both<U> = (U extends unknown ? (u: U) => void : never) extends (
149
+ u: infer I,
150
+ ) => void
151
+ ? I
152
+ : never
153
+ /** The rule combining named rules: it needs every rule's facts. */
154
+ type Combined<Rules extends readonly Named[]> = (
155
+ facts: Both<Parameters<Rules[number]>[0]>,
156
+ ) => ReturnType<Rules[number]>
157
+
158
+ /**
159
+ * True when one of the rules is (D-10). The facts and the result come from
160
+ * where it is used, never from the rules, so each rule is typed by its place.
161
+ * In a read rule, the filters combine with `OR` (D-81): `true` from any rule
162
+ * lets every row through, `false` adds nothing.
163
+ */
164
+ export function anyOf<Facts, R = boolean>(
165
+ ...rules: readonly NoInfer<(facts: Facts) => R>[]
166
+ ): (facts: Facts) => R
167
+ /** Outside any place, the facts are those every rule needs. */
168
+ export function anyOf<const Rules extends readonly Named[]>(
169
+ ...rules: Rules
170
+ ): Combined<Rules>
171
+ export function anyOf(...rules: readonly Rule[]): (facts: never) => unknown {
172
+ return combine(rules, true, 'OR')
173
+ }
174
+
175
+ /**
176
+ * True when every rule is (D-81), typed as `anyOf`. In a read rule, the
177
+ * filters combine with `AND`: `false` from any rule hides every row, `true`
178
+ * adds nothing.
179
+ */
180
+ export function allOf<Facts, R = boolean>(
181
+ ...rules: readonly NoInfer<(facts: Facts) => R>[]
182
+ ): (facts: Facts) => R
183
+ export function allOf<const Rules extends readonly Named[]>(
184
+ ...rules: Rules
185
+ ): Combined<Rules>
186
+ export function allOf(...rules: readonly Rule[]): (facts: never) => unknown {
187
+ return combine(rules, false, 'AND')
188
+ }
package/src/running.ts ADDED
@@ -0,0 +1,62 @@
1
+ // The running application (D-18): its resolved models and functions, its
2
+ // adapter, its logger, its plugins' APIs (D-89) and the operations `stop`
3
+ // waits for (D-91), one per realm.
4
+ import type { Adapter } from './adapter.ts'
5
+ import type { Logger } from './app-options.ts'
6
+ import type { Complete } from './complete.ts'
7
+ import type { FunctionDefinition } from './definition.ts'
8
+ import type { Resolved } from './schema.ts'
9
+
10
+ export type Running = {
11
+ readonly resolved: Resolved
12
+ /** Its functions, by their `defineFunction` value (D-75). */
13
+ readonly functions: ReadonlyMap<object, FunctionDefinition>
14
+ readonly adapter: Adapter
15
+ readonly logger: Logger
16
+ /** Each plugin's API, by its name (D-16). */
17
+ readonly apis: ReadonlyMap<string, unknown>
18
+ readonly work: Work
19
+ /** Completes a user caller with its plugins' keys (D-121). */
20
+ readonly complete: Complete
21
+ }
22
+
23
+ /** The operations running, `*Commit` hooks included, that `stop` waits for. */
24
+ export class Work {
25
+ private count = 0
26
+ private readonly idle: (() => void)[] = []
27
+
28
+ async run<R>(run: () => Promise<R>): Promise<R> {
29
+ this.count += 1
30
+ try {
31
+ return await run()
32
+ } finally {
33
+ this.count -= 1
34
+ if (this.count === 0) for (const done of this.idle.splice(0)) done()
35
+ }
36
+ }
37
+
38
+ async settled(): Promise<void> {
39
+ if (this.count === 0) return
40
+ await new Promise<void>((done) => {
41
+ this.idle.push(done)
42
+ })
43
+ }
44
+ }
45
+
46
+ let running: Running | undefined
47
+
48
+ export function register(app: Running): void {
49
+ if (running !== undefined) {
50
+ throw new Error('An application is already running; stop it first.')
51
+ }
52
+ running = app
53
+ }
54
+
55
+ export function unregister(app: Running): void {
56
+ if (running === app) running = undefined
57
+ }
58
+
59
+ /** The running application, `undefined` before `start`. */
60
+ export function current(): Running | undefined {
61
+ return running
62
+ }
@@ -0,0 +1,88 @@
1
+ // The indexes of a model, checked against its fields (D-52, D-120, D-141),
2
+ // and its searched fields (D-148).
3
+ import type { SchemaField } from './adapter.ts'
4
+ import type { IndexDefinition } from './definition.ts'
5
+ import type { ModelInfo } from './schema.ts'
6
+ import { readRules } from './access.ts'
7
+ import { Refused } from './errors.ts'
8
+
9
+ /** An index on a list is a GIN index of that list alone (D-120). */
10
+ function listIndex(model: string, name: string, index: IndexDefinition): void {
11
+ if (index.fields.length > 1) {
12
+ throw new Refused(
13
+ `${model}: the list '${name}' is indexed alone, in an index of its own.`,
14
+ )
15
+ }
16
+ if (index.unique === true) {
17
+ throw new Refused(`${model}: the list '${name}' cannot be unique.`)
18
+ }
19
+ }
20
+
21
+ /** A `contains` index covers one string field, not a list (D-141). */
22
+ function containsIndex(
23
+ model: string,
24
+ fields: ReadonlyMap<string, SchemaField>,
25
+ index: IndexDefinition,
26
+ ): void {
27
+ const [name] = index.fields
28
+ const field = name === undefined ? undefined : fields.get(name)
29
+ if (
30
+ index.fields.length !== 1 ||
31
+ index.unique === true ||
32
+ field?.kind !== 'scalar' ||
33
+ field.type !== 'string' ||
34
+ field.list
35
+ ) {
36
+ throw new Refused(
37
+ `${model}: a contains index covers one string field that is not a list, and is not unique.`,
38
+ )
39
+ }
40
+ }
41
+
42
+ /** Refuses an index over a field that is not one of the model's columns. */
43
+ export function checkIndexes(
44
+ model: string,
45
+ fields: ReadonlyMap<string, SchemaField>,
46
+ indexes: readonly IndexDefinition[],
47
+ ): void {
48
+ for (const index of indexes) {
49
+ for (const name of index.fields) {
50
+ const field = fields.get(name)
51
+ if (
52
+ field === undefined ||
53
+ field.kind === 'many' ||
54
+ (field.kind === 'one' && !field.stored)
55
+ ) {
56
+ throw new Refused(
57
+ `${model}: index field '${name}' is not a scalar or a stored one relation.`,
58
+ )
59
+ }
60
+ if (field.kind === 'scalar' && field.list) {
61
+ listIndex(model, name, index)
62
+ }
63
+ }
64
+ if (index.contains === true) containsIndex(model, fields, index)
65
+ }
66
+ }
67
+
68
+ /**
69
+ * A searched field is the kernel's string, which every caller may read: a
70
+ * match would reveal a value its read rule hides (D-148).
71
+ */
72
+ export function checkSearch(model: ModelInfo): void {
73
+ const rules = readRules(model)
74
+ for (const field of model.fields.values()) {
75
+ if (field.kind !== 'scalar' || field.search !== true) continue
76
+ const name = `${model.name}.${field.name}`
77
+ if (field.codec !== undefined) {
78
+ throw new Refused(
79
+ `${name}: a ${field.codec.plugin} field is not searched.`,
80
+ )
81
+ }
82
+ if (rules[field.name] !== undefined) {
83
+ throw new Refused(
84
+ `${name} has a read rule, and a search would reveal its value: remove search or the rule.`,
85
+ )
86
+ }
87
+ }
88
+ }