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,308 @@
1
+ // The application's plugins at `start` (D-16, D-89): their values checked,
2
+ // their requirements met, then their APIs built, each after those it needs.
3
+ import type { Row, SchemaModel } from './adapter.ts'
4
+ import { readRules } from './access.ts'
5
+ import type { Logger } from './app-options.ts'
6
+ import type { PluginRecord } from './definition.ts'
7
+ import type { FunctionSource, Functions } from './functions.ts'
8
+ import type { Identify, PluginIdentify } from './identify.ts'
9
+ import type { PluginCaller } from './complete.ts'
10
+ import type { Identity } from './rules.ts'
11
+ import type { ExposedFunction, ExposedModel, ModelQueries } from './plugin.ts'
12
+ import type { ModelShape } from './model.ts'
13
+ import type { ModelInfo, Resolved } from './schema.ts'
14
+ import { pluginValues } from './definition.ts'
15
+ import { query } from './engine.ts'
16
+ import { recordExpiry } from './recorder.ts'
17
+ import { exposedFunctions, resolveFunctions } from './functions.ts'
18
+ import { isObject } from './query-where.ts'
19
+ import { Refused } from './errors.ts'
20
+ import type { Enum, StandardSchema } from './fields.ts'
21
+ import { standardOf } from './validation.ts'
22
+
23
+ /** Each plugin after the plugins it needs; a cycle stops startup. */
24
+ function needsFirst(plugins: readonly PluginRecord[]): readonly PluginRecord[] {
25
+ const byName = new Map(plugins.map((plugin) => [plugin.code.name, plugin]))
26
+ const ordered: PluginRecord[] = []
27
+ const visit = (plugin: PluginRecord, path: readonly string[]): void => {
28
+ if (ordered.includes(plugin)) return
29
+ const { name, requires, optional } = plugin.code
30
+ if (path.includes(name)) {
31
+ const cycle = [...path.slice(path.indexOf(name)), name].join(' → ')
32
+ throw new Refused(`plugins need each other: ${cycle}.`)
33
+ }
34
+ for (const needed of [...requires, ...optional]) {
35
+ const other = byName.get(needed)
36
+ if (other !== undefined) visit(other, [...path, name])
37
+ }
38
+ ordered.push(plugin)
39
+ }
40
+ for (const plugin of plugins) visit(plugin, [])
41
+ return ordered
42
+ }
43
+
44
+ /**
45
+ * The application's `plugins`, in their order (D-29), and the order their
46
+ * APIs are built in, or startup stops with a message.
47
+ */
48
+ export function installed(values: readonly unknown[]): {
49
+ readonly plugins: readonly PluginRecord[]
50
+ readonly apiOrder: readonly PluginRecord[]
51
+ } {
52
+ const plugins: PluginRecord[] = []
53
+ for (const value of values) {
54
+ const plugin = isObject(value) ? pluginValues.get(value) : undefined
55
+ if (plugin === undefined) {
56
+ throw new Refused('plugins lists a value that no plugin returned.')
57
+ }
58
+ const { name } = plugin.code
59
+ if (plugins.some((other) => other.code.name === name)) {
60
+ throw new Refused(`plugins lists two plugins named ${name}.`)
61
+ }
62
+ plugins.push(plugin)
63
+ }
64
+ for (const { code } of plugins) {
65
+ for (const needed of code.requires) {
66
+ if (plugins.some((other) => other.code.name === needed)) continue
67
+ throw new Refused(
68
+ `${code.name} requires ${needed}, which is not in plugins.`,
69
+ )
70
+ }
71
+ }
72
+ return { plugins, apiOrder: needsFirst(plugins) }
73
+ }
74
+
75
+ /**
76
+ * A plugin and what its `api`, `start` and `stop` receive (D-58, D-91, D-106,
77
+ * D-109).
78
+ */
79
+ export type Installed = {
80
+ readonly code: PluginRecord['code']
81
+ readonly context: {
82
+ readonly options: object
83
+ readonly plugins: object
84
+ readonly logger: Logger
85
+ readonly identify: Identify
86
+ readonly expires: (at: Date) => void
87
+ readonly models: readonly ExposedModel[]
88
+ readonly functions: readonly ExposedFunction[]
89
+ }
90
+ }
91
+
92
+ /** The models as the plugin `name` reads them, with its `.with` key. */
93
+ function exposed(name: string, resolved: Resolved): readonly ExposedModel[] {
94
+ const own = [...resolved.byValue].filter(
95
+ ([, info]) => info.owner === undefined,
96
+ )
97
+ return own.map(([value, info]) => {
98
+ const rules = info.definition.rules
99
+ // oxlint-disable-next-line typescript/no-unsafe-type-assertion -- a model value holds its queries; transports pass client arguments, which the kernel checks (D-20).
100
+ const queries = value as ModelQueries
101
+ const key: unknown =
102
+ rules === undefined ? undefined : Reflect.get(rules, name)
103
+ return Object.freeze({
104
+ schema: schemaOf(resolved, info.name),
105
+ description: info.definition.description,
106
+ descriptions: descriptions(info),
107
+ enums: enums(info),
108
+ uniques: info.uniques,
109
+ readRules: readRules(info),
110
+ required: required(info),
111
+ validators: validators(info),
112
+ fieldTypes: fieldTypes(info),
113
+ virtuals: [...info.virtuals.values()].map((virtual) =>
114
+ Object.freeze({
115
+ name: virtual.name,
116
+ description: virtual.description,
117
+ input: virtual.input?.schema,
118
+ output: virtual.output,
119
+ default: virtual.default,
120
+ }),
121
+ ),
122
+ with: key,
123
+ used: info.plugins.some((use) => use.code.name === name),
124
+ queries,
125
+ upsert: async (args, options) =>
126
+ // oxlint-disable-next-line typescript/no-unsafe-type-assertion -- the kernel's upsert answers its row and its branch (D-142).
127
+ (await query(info.name, 'upserted', args, options)) as {
128
+ readonly row: Row
129
+ readonly created: boolean
130
+ },
131
+ // oxlint-disable-next-line typescript/no-unsafe-type-assertion -- a `defineModel` value, keyed by it.
132
+ model: value as ModelShape,
133
+ })
134
+ })
135
+ }
136
+
137
+ /** The fields `create` needs, as its type says (D-53): a list is `[]`. */
138
+ function required(info: ModelInfo): readonly string[] {
139
+ return [...info.fields.values()]
140
+ .filter(
141
+ (field) =>
142
+ field.kind !== 'many' &&
143
+ !field.optional &&
144
+ field.options['default'] === undefined &&
145
+ field.options['list'] !== true,
146
+ )
147
+ .map((field) => field.name)
148
+ }
149
+
150
+ /** Each field's `description` (D-150). */
151
+ function descriptions(info: ModelInfo): ExposedModel['descriptions'] {
152
+ const found: { [field: string]: string } = {}
153
+ for (const field of info.fields.values()) {
154
+ const description = field.options['description']
155
+ if (typeof description === 'string') found[field.name] = description
156
+ }
157
+ return found
158
+ }
159
+
160
+ /** Each field's `defineEnum` (D-150). */
161
+ function enums(info: ModelInfo): ExposedModel['enums'] {
162
+ const found: { [field: string]: Enum } = {}
163
+ for (const field of info.fields.values()) {
164
+ if (field.kind === 'scalar' && field.enum !== undefined) {
165
+ found[field.name] = field.enum
166
+ }
167
+ }
168
+ return found
169
+ }
170
+
171
+ /** Each `json` field's schema (D-125); a function validate has no shape. */
172
+ function validators(info: ModelInfo): {
173
+ readonly [field: string]: StandardSchema
174
+ } {
175
+ const found: { [field: string]: StandardSchema } = {}
176
+ for (const field of info.fields.values()) {
177
+ const standard =
178
+ field.kind === 'scalar' && field.type === 'json'
179
+ ? standardOf(field.options['validate'])
180
+ : undefined
181
+ if (standard !== undefined) found[field.name] = standard.schema
182
+ }
183
+ return found
184
+ }
185
+
186
+ /** Each plugin field type's field: its plugin, options and `toJSON` (D-122). */
187
+ function fieldTypes(info: ModelInfo): ExposedModel['fieldTypes'] {
188
+ const found: { [field: string]: ExposedModel['fieldTypes'][string] } = {}
189
+ for (const field of info.fields.values()) {
190
+ if (field.kind !== 'scalar' || field.codec === undefined) continue
191
+ const { plugin, options, toJSON, shape } = field.codec
192
+ found[field.name] = { plugin, options, toJSON, shape }
193
+ }
194
+ return found
195
+ }
196
+
197
+ function schemaOf(resolved: Resolved, name: string): SchemaModel {
198
+ const found = resolved.schema.models.find((model) => model.name === name)
199
+ if (found === undefined) throw new Error(`${name} has no schema.`)
200
+ return found
201
+ }
202
+
203
+ /**
204
+ * Each plugin with its context, its logger naming it (D-91), and the
205
+ * functions, the application's then each plugin's (D-113).
206
+ */
207
+ export function withContexts(
208
+ apiOrder: readonly PluginRecord[],
209
+ logger: Logger,
210
+ identify: Identify,
211
+ resolved: Resolved,
212
+ appFunctions: readonly unknown[],
213
+ ): { readonly plugins: readonly Installed[]; readonly functions: Functions } {
214
+ const bases = apiOrder.map(({ code, options }) => ({
215
+ code,
216
+ base: {
217
+ options,
218
+ plugins: code.plugins,
219
+ logger: logger.child({ name: code.name }),
220
+ identify,
221
+ expires: recordExpiry,
222
+ models: exposed(code.name, resolved),
223
+ },
224
+ }))
225
+ const sources: FunctionSource[] = [
226
+ { plugin: undefined, values: appFunctions },
227
+ ]
228
+ for (const { code, base } of bases) {
229
+ if (code.functions === undefined) continue
230
+ const values: unknown = Reflect.apply(code.functions, undefined, [base])
231
+ if (!Array.isArray(values)) {
232
+ throw new TypeError(`${code.name}.functions returns an array.`)
233
+ }
234
+ sources.push({ plugin: code.name, values })
235
+ }
236
+ const functions = resolveFunctions(
237
+ sources,
238
+ resolved,
239
+ apiOrder.map((plugin) => plugin.code),
240
+ )
241
+ const plugins = bases.map(({ code, base }) => ({
242
+ code,
243
+ context: { ...base, functions: exposedFunctions(code.name, functions) },
244
+ }))
245
+ return { plugins, functions }
246
+ }
247
+
248
+ /** Builds each plugin's API into `apis`, in their order (D-16, D-89). */
249
+ export function buildApis(
250
+ plugins: readonly Installed[],
251
+ apis: Map<string, unknown>,
252
+ ): void {
253
+ for (const { code, context } of plugins) {
254
+ if (code.api === undefined) continue
255
+ const api: unknown = Reflect.apply(code.api, undefined, [context])
256
+ apis.set(code.name, api)
257
+ }
258
+ }
259
+
260
+ /** `plugins` in the order they were declared. */
261
+ export const inOrder = (
262
+ records: readonly PluginRecord[],
263
+ plugins: readonly Installed[],
264
+ ): readonly Installed[] =>
265
+ plugins.toSorted(
266
+ (a, b) =>
267
+ records.findIndex((record) => record.code === a.code) -
268
+ records.findIndex((record) => record.code === b.code),
269
+ )
270
+
271
+ /** Each plugin's `identify` with its context, in the order of `plugins`. */
272
+ export function pluginIdentifiers(
273
+ records: readonly PluginRecord[],
274
+ plugins: readonly Installed[],
275
+ ): readonly PluginIdentify[] {
276
+ return inOrder(records, plugins).flatMap(({ code, context }) => {
277
+ const identify = code.identify
278
+ if (identify === undefined) return []
279
+ return [
280
+ async (request: Request) => {
281
+ const caller: unknown = await Reflect.apply(identify, undefined, [
282
+ request,
283
+ context,
284
+ ])
285
+ // oxlint-disable-next-line typescript/no-unsafe-type-assertion -- its type is PluginDefinition's; each query checks its caller (D-19).
286
+ return caller as Identity | undefined
287
+ },
288
+ ]
289
+ })
290
+ }
291
+
292
+ /** Each plugin's `caller` with its context, in the order of `plugins`. */
293
+ export function pluginCallers(
294
+ records: readonly PluginRecord[],
295
+ plugins: readonly Installed[],
296
+ ): readonly PluginCaller[] {
297
+ return inOrder(records, plugins).flatMap(({ code, context }) => {
298
+ const caller = code.caller
299
+ if (caller === undefined) return []
300
+ return [
301
+ {
302
+ plugin: code.name,
303
+ caller: async (identity: Identity): Promise<unknown> =>
304
+ Reflect.apply(caller, undefined, [identity, context]),
305
+ },
306
+ ]
307
+ })
308
+ }
@@ -0,0 +1,158 @@
1
+ // A plugin's code at run time (D-57, D-89): what `definePlugin` checks, the
2
+ // APIs it reads once the application starts, and the command it runs in.
3
+ import { AsyncLocalStorage } from 'node:async_hooks'
4
+ import type { IndexDefinition, PluginCode } from './definition.ts'
5
+ import { nameOf } from './definition.ts'
6
+ import { manifestOf } from './manifest.ts'
7
+ import { isObject } from './query-where.ts'
8
+ import { current } from './running.ts'
9
+
10
+ type Holder = { readonly [key: string]: unknown }
11
+
12
+ function functionAt(
13
+ holder: Holder,
14
+ key: string,
15
+ path: string,
16
+ ): Function | undefined {
17
+ const value = holder[key]
18
+ if (value === undefined || typeof value === 'function') return value
19
+ throw new TypeError(`${path} is a function.`)
20
+ }
21
+
22
+ function objectAt(holder: Holder, key: string, path: string): Holder {
23
+ const value = holder[key]
24
+ if (value === undefined) return {}
25
+ if (!isObject(value)) throw new TypeError(`${path} is an object.`)
26
+ return value
27
+ }
28
+
29
+ /** The fixed hooks of D-15. */
30
+ export const hookNames = [
31
+ 'beforeFind',
32
+ 'beforeCreate',
33
+ 'beforeUpdate',
34
+ 'beforeDelete',
35
+ 'afterCreate',
36
+ 'afterUpdate',
37
+ 'afterDelete',
38
+ 'afterCreateCommit',
39
+ 'afterUpdateCommit',
40
+ 'afterDeleteCommit',
41
+ ] as const
42
+
43
+ /** The APIs of `names`, each read from the running application. */
44
+ function apisOf(plugin: string, names: readonly string[]): object {
45
+ const plugins = {}
46
+ for (const name of names) {
47
+ Object.defineProperty(plugins, name, {
48
+ enumerable: true,
49
+ get: () => {
50
+ const app = current()
51
+ if (app === undefined) {
52
+ throw new Error(
53
+ `${plugin}: plugins.${name} is read once the application starts.`,
54
+ )
55
+ }
56
+ return app.apis.get(name)
57
+ },
58
+ })
59
+ }
60
+ return Object.freeze(plugins)
61
+ }
62
+
63
+ const isList = (value: unknown): value is readonly unknown[] =>
64
+ Array.isArray(value)
65
+
66
+ /** `addedIndexes`, each `{ fields, unique?, contains? }`; startup checks the fields. */
67
+ function indexesOf(value: unknown, path: string): readonly IndexDefinition[] {
68
+ const indexes = value ?? []
69
+ const valid = (index: unknown): index is IndexDefinition =>
70
+ isObject(index) &&
71
+ isList(index['fields']) &&
72
+ index['fields'].every((field) => typeof field === 'string') &&
73
+ (index['unique'] === undefined || typeof index['unique'] === 'boolean') &&
74
+ (index['contains'] === undefined || index['contains'] === true)
75
+ if (!isList(indexes) || !indexes.every(valid)) {
76
+ throw new TypeError(
77
+ `${path} is a list of { fields, unique?, contains?: true }.`,
78
+ )
79
+ }
80
+ return Object.freeze(
81
+ indexes.map(({ fields, unique, contains }) => ({
82
+ fields: [...fields],
83
+ ...(unique === undefined ? {} : { unique }),
84
+ ...(contains === undefined ? {} : { contains }),
85
+ })),
86
+ )
87
+ }
88
+
89
+ /** Checks what a plugin's author wrote; the types refuse the rest. */
90
+ export function pluginCode(definition: unknown): PluginCode {
91
+ if (!isObject(definition))
92
+ throw new TypeError('definePlugin takes an object.')
93
+ const name = nameOf(definition['name'], 'definePlugin')
94
+ const requires = Object.keys(
95
+ objectAt(definition, 'requires', `${name}.requires`),
96
+ )
97
+ const optional = Object.keys(
98
+ objectAt(definition, 'optional', `${name}.optional`),
99
+ )
100
+ const given = objectAt(definition, 'hooks', `${name}.hooks`)
101
+ const hooks: { [hook: string]: Function | undefined } = {}
102
+ for (const hook of hookNames) {
103
+ hooks[hook] = functionAt(given, hook, `${name}.hooks.${hook}`)
104
+ }
105
+ const redirect = objectAt(definition, 'redirect', `${name}.redirect`)
106
+ const part = (key: string) => functionAt(definition, key, `${name}.${key}`)
107
+ const models: unknown = definition['models'] ?? []
108
+ if (!isList(models)) {
109
+ throw new TypeError(`${name}.models is an array.`)
110
+ }
111
+ return Object.freeze({
112
+ name,
113
+ manifest: manifestOf(definition['manifest'], name),
114
+ requires,
115
+ optional,
116
+ hooks: Object.freeze(hooks),
117
+ redirect: functionAt(redirect, 'delete', `${name}.redirect.delete`),
118
+ commands: part('commands'),
119
+ api: part('api'),
120
+ addedFields: part('addedFields'),
121
+ addedIndexes: indexesOf(definition['addedIndexes'], `${name}.addedIndexes`),
122
+ start: part('start'),
123
+ stop: part('stop'),
124
+ identify: part('identify'),
125
+ caller: part('caller'),
126
+ changed: part('changed'),
127
+ missed: part('missed'),
128
+ functions: part('functions'),
129
+ models: Object.freeze([...models]),
130
+ plugins: apisOf(name, [...requires, ...optional]),
131
+ })
132
+ }
133
+
134
+ export type Command = {
135
+ readonly model: string
136
+ readonly plugin: string
137
+ readonly command: string
138
+ }
139
+
140
+ const commands = new AsyncLocalStorage<Command>()
141
+
142
+ /** The command of `plugin` running on `model`, if any (D-89). */
143
+ export function commandOf(model: string, plugin: string): string | undefined {
144
+ const running = commands.getStore()
145
+ return running?.model === model && running.plugin === plugin
146
+ ? running.command
147
+ : undefined
148
+ }
149
+
150
+ /** Runs `run` as `plugin`'s command `command` on `model`. */
151
+ export function inCommand<R>(command: Command, run: () => R): R {
152
+ return commands.run(command, run)
153
+ }
154
+
155
+ /** Runs a hook's body: the queries it makes run outside any command. */
156
+ export function outsideCommands<R>(run: () => R): R {
157
+ return commands.exit(run)
158
+ }
@@ -0,0 +1,217 @@
1
+ // A model's plugins at `defineModel` (D-89): the entries of its `use`, the
2
+ // fields they add and the commands they build, which run in their command.
3
+ import type {
4
+ FieldDefinition,
5
+ IndexDefinition,
6
+ ModelDefinition,
7
+ PluginCode,
8
+ PluginRecord,
9
+ PluginUse,
10
+ } from './definition.ts'
11
+ import { fieldBuilders, fieldDefinitions, pluginEntries } from './definition.ts'
12
+ import type { Command } from './plugin-code.ts'
13
+ import { inCommand } from './plugin-code.ts'
14
+ import { isObject, systemFields } from './query-where.ts'
15
+ import { Refused } from './errors.ts'
16
+
17
+ /** The kernel's names, which a model's field cannot take (D-22, D-20, D-134, D-148). */
18
+ const reserved = new Set([
19
+ ...systemFields,
20
+ 'AND',
21
+ 'OR',
22
+ 'NOT',
23
+ '_count',
24
+ '_search',
25
+ '_relevance',
26
+ ])
27
+
28
+ /**
29
+ * Adds the fields a `fields` callback, or a plugin's `addedFields`, built.
30
+ * `owners` names the plugin that added each field.
31
+ */
32
+ export function addFields(
33
+ model: string,
34
+ built: unknown,
35
+ fields: Map<string, FieldDefinition>,
36
+ owners: Map<string, string>,
37
+ plugin?: string,
38
+ ): void {
39
+ if (!isObject(built)) {
40
+ const part = plugin === undefined ? 'fields' : `${plugin}.addedFields`
41
+ throw new Refused(`${model}: ${part} must return an object of fields.`)
42
+ }
43
+ for (const [field, value] of Object.entries(built)) {
44
+ const recorded =
45
+ typeof value === 'object' && value !== null
46
+ ? fieldDefinitions.get(value)
47
+ : undefined
48
+ if (recorded === undefined) {
49
+ throw new Refused(`${model}.${field} is not a field a builder returned.`)
50
+ }
51
+ if (reserved.has(field)) {
52
+ throw new Refused(`${model}.${field}: the kernel owns this name.`)
53
+ }
54
+ if (plugin !== undefined && fields.has(field)) {
55
+ const owner = owners.get(field) ?? model
56
+ throw new Refused(
57
+ `${model}.${field}: ${plugin} adds a field ${owner} already has.`,
58
+ )
59
+ }
60
+ fields.set(field, recorded)
61
+ if (plugin !== undefined) owners.set(field, plugin)
62
+ }
63
+ }
64
+
65
+ /** Runs a command's function, or each function of its object, as it. */
66
+ function wrapped(value: unknown, command: Command, path: string): unknown {
67
+ const run =
68
+ (fn: Function) =>
69
+ (...args: unknown[]): unknown =>
70
+ inCommand(command, () => {
71
+ const result: unknown = Reflect.apply(fn, undefined, args)
72
+ return result
73
+ })
74
+ if (typeof value === 'function') return run(value)
75
+ const refused = new TypeError(
76
+ `${path} is a function or an object of functions.`,
77
+ )
78
+ if (!isObject(value)) throw refused
79
+ const functions: { [key: string]: unknown } = {}
80
+ for (const [key, fn] of Object.entries(value)) {
81
+ if (typeof fn !== 'function') throw refused
82
+ functions[key] = run(fn)
83
+ }
84
+ return Object.freeze(functions)
85
+ }
86
+
87
+ function commandsOf(
88
+ model: string,
89
+ uses: readonly PluginUse[],
90
+ methods: ReadonlySet<string>,
91
+ ): ReadonlyMap<string, unknown> {
92
+ const commands = new Map<string, unknown>()
93
+ const owners = new Map<string, string>()
94
+ for (const { code, model: given } of uses) {
95
+ if (code.commands === undefined) continue
96
+ const built: unknown = Reflect.apply(code.commands, undefined, [given])
97
+ if (!isObject(built)) {
98
+ throw new TypeError(`${code.name}.commands returns an object.`)
99
+ }
100
+ for (const [command, value] of Object.entries(built)) {
101
+ const path = `${model}.${command}`
102
+ if (methods.has(command)) {
103
+ throw new Refused(
104
+ `${path}: ${code.name} adds a command named after a model method.`,
105
+ )
106
+ }
107
+ const other = owners.get(command)
108
+ if (other !== undefined) {
109
+ throw new Refused(
110
+ `${path}: ${other} and ${code.name} both add this command.`,
111
+ )
112
+ }
113
+ owners.set(command, code.name)
114
+ const running = { model, plugin: code.name, command }
115
+ commands.set(command, wrapped(value, running, path))
116
+ }
117
+ }
118
+ return commands
119
+ }
120
+
121
+ /**
122
+ * Reads a model's `use`: each entry once, the fields its plugins add into
123
+ * `fields`, and their commands, built with `queries`.
124
+ */
125
+ /** The plugin entries `use` lists, each once (D-27). */
126
+ export function entriesOf(name: string, use: unknown): readonly PluginRecord[] {
127
+ if (!Array.isArray(use)) throw new Refused(`${name}.use is a list.`)
128
+ const entries: PluginRecord[] = []
129
+ for (const value of use) {
130
+ const entry = isObject(value) ? pluginEntries.get(value) : undefined
131
+ if (entry === undefined) {
132
+ throw new Refused(`${name}.use lists a value no plugin's entry returned.`)
133
+ }
134
+ if (entries.some((other) => other.code.name === entry.code.name)) {
135
+ throw new Refused(`${name}.use lists ${entry.code.name} twice.`)
136
+ }
137
+ entries.push(entry)
138
+ }
139
+ return entries
140
+ }
141
+
142
+ export function usedBy(
143
+ model: string,
144
+ use: readonly unknown[],
145
+ fields: Map<string, FieldDefinition>,
146
+ queries: object,
147
+ ): {
148
+ readonly uses: readonly PluginUse[]
149
+ readonly commands: ReadonlyMap<string, unknown>
150
+ readonly indexes: readonly IndexDefinition[]
151
+ } {
152
+ const uses: PluginUse[] = []
153
+ const owners = new Map<string, string>()
154
+ for (const { code, options } of entriesOf(model, use)) {
155
+ if (code.addedFields !== undefined) {
156
+ const built: unknown = Reflect.apply(code.addedFields, undefined, [
157
+ fieldBuilders,
158
+ options,
159
+ ])
160
+ addFields(model, built, fields, owners, code.name)
161
+ }
162
+ const given = { name: model, queries, options, plugins: code.plugins }
163
+ uses.push({ code, model: Object.freeze(given) })
164
+ }
165
+ const redirecting = uses.filter((each) => each.code.redirect !== undefined)
166
+ if (redirecting.length > 1) {
167
+ const names = redirecting.map((each) => each.code.name).join(' and ')
168
+ throw new Refused(`${model}: ${names} both redirect delete.`)
169
+ }
170
+ const methods = new Set(['name', 'with', 'nullable', ...Object.keys(queries)])
171
+ return {
172
+ uses,
173
+ commands: commandsOf(model, uses, methods),
174
+ indexes: uses.flatMap(({ code }) => code.addedIndexes),
175
+ }
176
+ }
177
+
178
+ /** Stops startup when `owner` uses a plugin that `plugins` does not hold. */
179
+ export function checkInstalled(
180
+ owner: string,
181
+ code: PluginCode,
182
+ plugins: readonly PluginCode[],
183
+ ): void {
184
+ if (plugins.includes(code)) return
185
+ const { name } = code
186
+ const other = plugins.some((each) => each.name === name)
187
+ throw new Refused(
188
+ other
189
+ ? `${owner} uses a ${name} from another definePlugin than the one in plugins.`
190
+ : `${owner} uses ${name}, which is not in plugins.`,
191
+ )
192
+ }
193
+
194
+ /** A model's plugins in the order of `plugins`, each one installed. */
195
+ export function installedUses(
196
+ definition: ModelDefinition,
197
+ plugins: readonly PluginCode[],
198
+ ): readonly PluginUse[] {
199
+ const at = (use: PluginUse) => plugins.indexOf(use.code)
200
+ for (const use of definition.uses) {
201
+ checkInstalled(definition.name, use.code, plugins)
202
+ }
203
+ // A field type's plugin runs its hooks on the model, as if used (D-152).
204
+ const { name, queries } = definition
205
+ const implicit = plugins
206
+ .filter((code) => !definition.uses.some((use) => use.code === code))
207
+ .filter((code) =>
208
+ [...definition.fields.values()].some(
209
+ (field) => field.kind === 'scalar' && field.codec?.plugin === code.name,
210
+ ),
211
+ )
212
+ .map((code) => {
213
+ const model = { name, queries, options: {}, plugins: code.plugins }
214
+ return { code, model: Object.freeze(model) }
215
+ })
216
+ return [...definition.uses, ...implicit].toSorted((a, b) => at(a) - at(b))
217
+ }