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.
- package/CHANGELOG.md +13 -0
- package/README.md +4 -2
- package/dist/access.d.ts +62 -0
- package/dist/access.d.ts.map +1 -0
- package/dist/access.js +258 -0
- package/dist/adapter-errors.d.ts +43 -0
- package/dist/adapter-errors.d.ts.map +1 -0
- package/dist/adapter-errors.js +72 -0
- package/dist/adapter-json.d.ts +20 -0
- package/dist/adapter-json.d.ts.map +1 -0
- package/dist/adapter-json.js +2 -0
- package/dist/adapter-migrations.d.ts +90 -0
- package/dist/adapter-migrations.d.ts.map +1 -0
- package/dist/adapter-migrations.js +1 -0
- package/dist/adapter.d.ts +333 -0
- package/dist/adapter.d.ts.map +1 -0
- package/dist/adapter.js +5 -0
- package/dist/aggregate.d.ts +31 -0
- package/dist/aggregate.d.ts.map +1 -0
- package/dist/aggregate.js +99 -0
- package/dist/app-internals.d.ts +10 -0
- package/dist/app-internals.d.ts.map +1 -0
- package/dist/app-internals.js +12 -0
- package/dist/app-options.d.ts +22 -0
- package/dist/app-options.d.ts.map +1 -0
- package/dist/app-options.js +3 -0
- package/dist/cascade.d.ts +47 -0
- package/dist/cascade.d.ts.map +1 -0
- package/dist/cascade.js +209 -0
- package/dist/cli-config.d.ts +6 -0
- package/dist/cli-config.d.ts.map +1 -0
- package/dist/cli-config.js +45 -0
- package/dist/cli-deploy.d.ts +14 -0
- package/dist/cli-deploy.d.ts.map +1 -0
- package/dist/cli-deploy.js +193 -0
- package/dist/cli-main.d.ts +15 -0
- package/dist/cli-main.d.ts.map +1 -0
- package/dist/cli-main.js +155 -0
- package/dist/cli-migrate.d.ts +20 -0
- package/dist/cli-migrate.d.ts.map +1 -0
- package/dist/cli-migrate.js +105 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +4 -0
- package/dist/complete.d.ts +13 -0
- package/dist/complete.d.ts.map +1 -0
- package/dist/complete.js +60 -0
- package/dist/console-logger.d.ts +3 -0
- package/dist/console-logger.d.ts.map +1 -0
- package/dist/console-logger.js +62 -0
- package/dist/create-many.d.ts +7 -0
- package/dist/create-many.d.ts.map +1 -0
- package/dist/create-many.js +88 -0
- package/dist/decimal.d.ts +23 -0
- package/dist/decimal.d.ts.map +1 -0
- package/dist/decimal.js +50 -0
- package/dist/define-app.d.ts +59 -0
- package/dist/define-app.d.ts.map +1 -0
- package/dist/define-app.js +5 -0
- package/dist/define-enum.d.ts +6 -0
- package/dist/define-enum.d.ts.map +1 -0
- package/dist/define-enum.js +18 -0
- package/dist/define-function.d.ts +25 -0
- package/dist/define-function.d.ts.map +1 -0
- package/dist/define-function.js +111 -0
- package/dist/define-model.d.ts +48 -0
- package/dist/define-model.d.ts.map +1 -0
- package/dist/define-model.js +85 -0
- package/dist/definition.d.ts +179 -0
- package/dist/definition.d.ts.map +1 -0
- package/dist/definition.js +165 -0
- package/dist/delete-many.d.ts +13 -0
- package/dist/delete-many.d.ts.map +1 -0
- package/dist/delete-many.js +58 -0
- package/dist/engine.d.ts +16 -0
- package/dist/engine.d.ts.map +1 -0
- package/dist/engine.js +174 -0
- package/dist/errors.d.ts +50 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +43 -0
- package/dist/exact.d.ts +20 -0
- package/dist/exact.d.ts.map +1 -0
- package/dist/exact.js +1 -0
- package/dist/feed.d.ts +6 -0
- package/dist/feed.d.ts.map +1 -0
- package/dist/feed.js +33 -0
- package/dist/field-codec.d.ts +11 -0
- package/dist/field-codec.d.ts.map +1 -0
- package/dist/field-codec.js +48 -0
- package/dist/field-rules.d.ts +57 -0
- package/dist/field-rules.d.ts.map +1 -0
- package/dist/field-rules.js +1 -0
- package/dist/field-type.d.ts +51 -0
- package/dist/field-type.d.ts.map +1 -0
- package/dist/field-type.js +106 -0
- package/dist/fields.d.ts +295 -0
- package/dist/fields.d.ts.map +1 -0
- package/dist/fields.js +1 -0
- package/dist/function.d.ts +126 -0
- package/dist/function.d.ts.map +1 -0
- package/dist/function.js +1 -0
- package/dist/functions.d.ts +18 -0
- package/dist/functions.d.ts.map +1 -0
- package/dist/functions.js +99 -0
- package/dist/group-by.d.ts +10 -0
- package/dist/group-by.d.ts.map +1 -0
- package/dist/group-by.js +145 -0
- package/dist/group-having.d.ts +8 -0
- package/dist/group-having.d.ts.map +1 -0
- package/dist/group-having.js +97 -0
- package/dist/hooks.d.ts +41 -0
- package/dist/hooks.d.ts.map +1 -0
- package/dist/hooks.js +149 -0
- package/dist/hosts.d.ts +3 -0
- package/dist/hosts.d.ts.map +1 -0
- package/dist/hosts.js +8 -0
- package/dist/identify.d.ts +15 -0
- package/dist/identify.d.ts.map +1 -0
- package/dist/identify.js +71 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- package/dist/internal.d.ts +28 -0
- package/dist/internal.d.ts.map +1 -0
- package/dist/internal.js +39 -0
- package/dist/lifecycle.d.ts +21 -0
- package/dist/lifecycle.d.ts.map +1 -0
- package/dist/lifecycle.js +149 -0
- package/dist/list-update.d.ts +12 -0
- package/dist/list-update.d.ts.map +1 -0
- package/dist/list-update.js +52 -0
- package/dist/live.d.ts +37 -0
- package/dist/live.d.ts.map +1 -0
- package/dist/live.js +139 -0
- package/dist/manifest.d.ts +11 -0
- package/dist/manifest.d.ts.map +1 -0
- package/dist/manifest.js +60 -0
- package/dist/memory-aggregate.d.ts +7 -0
- package/dist/memory-aggregate.d.ts.map +1 -0
- package/dist/memory-aggregate.js +107 -0
- package/dist/memory-conditions.d.ts +8 -0
- package/dist/memory-conditions.d.ts.map +1 -0
- package/dist/memory-conditions.js +82 -0
- package/dist/memory-draft.d.ts +34 -0
- package/dist/memory-draft.d.ts.map +1 -0
- package/dist/memory-draft.js +255 -0
- package/dist/memory-json.d.ts +6 -0
- package/dist/memory-json.d.ts.map +1 -0
- package/dist/memory-json.js +86 -0
- package/dist/memory-read.d.ts +45 -0
- package/dist/memory-read.d.ts.map +1 -0
- package/dist/memory-read.js +244 -0
- package/dist/memory-search.d.ts +16 -0
- package/dist/memory-search.d.ts.map +1 -0
- package/dist/memory-search.js +110 -0
- package/dist/memory-values.d.ts +23 -0
- package/dist/memory-values.d.ts.map +1 -0
- package/dist/memory-values.js +82 -0
- package/dist/memory.d.ts +4 -0
- package/dist/memory.d.ts.map +1 -0
- package/dist/memory.js +157 -0
- package/dist/migrate.d.ts +22 -0
- package/dist/migrate.d.ts.map +1 -0
- package/dist/migrate.js +131 -0
- package/dist/model.d.ts +239 -0
- package/dist/model.d.ts.map +1 -0
- package/dist/model.js +1 -0
- package/dist/nested-write.d.ts +64 -0
- package/dist/nested-write.d.ts.map +1 -0
- package/dist/nested-write.js +274 -0
- package/dist/number-update.d.ts +18 -0
- package/dist/number-update.d.ts.map +1 -0
- package/dist/number-update.js +96 -0
- package/dist/plugin-app.d.ts +48 -0
- package/dist/plugin-app.d.ts.map +1 -0
- package/dist/plugin-app.js +230 -0
- package/dist/plugin-code.d.ts +17 -0
- package/dist/plugin-code.d.ts.map +1 -0
- package/dist/plugin-code.js +125 -0
- package/dist/plugin-use.d.ts +22 -0
- package/dist/plugin-use.d.ts.map +1 -0
- package/dist/plugin-use.js +161 -0
- package/dist/plugin.d.ts +357 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +17 -0
- package/dist/query-aggregate.d.ts +82 -0
- package/dist/query-aggregate.d.ts.map +1 -0
- package/dist/query-aggregate.js +1 -0
- package/dist/query-args.d.ts +41 -0
- package/dist/query-args.d.ts.map +1 -0
- package/dist/query-args.js +263 -0
- package/dist/query-bulk.d.ts +35 -0
- package/dist/query-bulk.d.ts.map +1 -0
- package/dist/query-bulk.js +1 -0
- package/dist/query-conditions.d.ts +9 -0
- package/dist/query-conditions.d.ts.map +1 -0
- package/dist/query-conditions.js +46 -0
- package/dist/query-context.d.ts +32 -0
- package/dist/query-context.d.ts.map +1 -0
- package/dist/query-context.js +55 -0
- package/dist/query-filter.d.ts +46 -0
- package/dist/query-filter.d.ts.map +1 -0
- package/dist/query-filter.js +1 -0
- package/dist/query-group.d.ts +82 -0
- package/dist/query-group.d.ts.map +1 -0
- package/dist/query-group.js +1 -0
- package/dist/query-json.d.ts +9 -0
- package/dist/query-json.d.ts.map +1 -0
- package/dist/query-json.js +66 -0
- package/dist/query-order.d.ts +15 -0
- package/dist/query-order.d.ts.map +1 -0
- package/dist/query-order.js +115 -0
- package/dist/query-project.d.ts +15 -0
- package/dist/query-project.d.ts.map +1 -0
- package/dist/query-project.js +45 -0
- package/dist/query-search.d.ts +24 -0
- package/dist/query-search.d.ts.map +1 -0
- package/dist/query-search.js +2 -0
- package/dist/query-values.d.ts +28 -0
- package/dist/query-values.d.ts.map +1 -0
- package/dist/query-values.js +91 -0
- package/dist/query-where.d.ts +46 -0
- package/dist/query-where.d.ts.map +1 -0
- package/dist/query-where.js +200 -0
- package/dist/query-write.d.ts +14 -0
- package/dist/query-write.d.ts.map +1 -0
- package/dist/query-write.js +260 -0
- package/dist/query.d.ts +254 -0
- package/dist/query.d.ts.map +1 -0
- package/dist/query.js +1 -0
- package/dist/record.d.ts +257 -0
- package/dist/record.d.ts.map +1 -0
- package/dist/record.js +1 -0
- package/dist/recorder.d.ts +32 -0
- package/dist/recorder.d.ts.map +1 -0
- package/dist/recorder.js +152 -0
- package/dist/relation-write.d.ts +41 -0
- package/dist/relation-write.d.ts.map +1 -0
- package/dist/relation-write.js +112 -0
- package/dist/relations.d.ts +42 -0
- package/dist/relations.d.ts.map +1 -0
- package/dist/relations.js +119 -0
- package/dist/rules.d.ts +97 -0
- package/dist/rules.d.ts.map +1 -0
- package/dist/rules.js +71 -0
- package/dist/running.d.ts +29 -0
- package/dist/running.d.ts.map +1 -0
- package/dist/running.js +39 -0
- package/dist/schema-indexes.d.ts +11 -0
- package/dist/schema-indexes.d.ts.map +1 -0
- package/dist/schema-indexes.js +59 -0
- package/dist/schema-virtuals.d.ts +19 -0
- package/dist/schema-virtuals.d.ts.map +1 -0
- package/dist/schema-virtuals.js +100 -0
- package/dist/schema.d.ts +35 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +267 -0
- package/dist/scope.d.ts +46 -0
- package/dist/scope.d.ts.map +1 -0
- package/dist/scope.js +82 -0
- package/dist/scratch.d.ts +14 -0
- package/dist/scratch.d.ts.map +1 -0
- package/dist/scratch.js +10 -0
- package/dist/serve.d.ts +2 -0
- package/dist/serve.d.ts.map +1 -0
- package/dist/serve.js +4 -0
- package/dist/symbols.d.ts +10 -0
- package/dist/symbols.d.ts.map +1 -0
- package/dist/symbols.js +1 -0
- package/dist/transaction.d.ts +28 -0
- package/dist/transaction.d.ts.map +1 -0
- package/dist/transaction.js +156 -0
- package/dist/types.d.ts +58 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +1 -0
- package/dist/update-many.d.ts +7 -0
- package/dist/update-many.d.ts.map +1 -0
- package/dist/update-many.js +182 -0
- package/dist/uuid.d.ts +7 -0
- package/dist/uuid.d.ts.map +1 -0
- package/dist/uuid.js +48 -0
- package/dist/validation.d.ts +24 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +72 -0
- package/dist/virtual-read.d.ts +9 -0
- package/dist/virtual-read.d.ts.map +1 -0
- package/dist/virtual-read.js +145 -0
- package/dist/virtual.d.ts +142 -0
- package/dist/virtual.d.ts.map +1 -0
- package/dist/virtual.js +1 -0
- package/dist/write-context.d.ts +46 -0
- package/dist/write-context.d.ts.map +1 -0
- package/dist/write-context.js +102 -0
- package/dist/write-data.d.ts +26 -0
- package/dist/write-data.d.ts.map +1 -0
- package/dist/write-data.js +144 -0
- package/dist/write-plan.d.ts +107 -0
- package/dist/write-plan.d.ts.map +1 -0
- package/dist/write-plan.js +170 -0
- package/dist/write-target.d.ts +17 -0
- package/dist/write-target.d.ts.map +1 -0
- package/dist/write-target.js +59 -0
- package/dist/writes.d.ts +23 -0
- package/dist/writes.d.ts.map +1 -0
- package/dist/writes.js +246 -0
- package/dist/writing.d.ts +10 -0
- package/dist/writing.d.ts.map +1 -0
- package/dist/writing.js +66 -0
- package/package.json +42 -3
- package/src/access.ts +329 -0
- package/src/adapter-errors.ts +89 -0
- package/src/adapter-json.ts +21 -0
- package/src/adapter-migrations.ts +98 -0
- package/src/adapter.ts +363 -0
- package/src/aggregate.ts +136 -0
- package/src/app-internals.ts +26 -0
- package/src/app-options.ts +24 -0
- package/src/cascade.ts +297 -0
- package/src/cli-config.ts +45 -0
- package/src/cli-deploy.ts +235 -0
- package/src/cli-main.ts +166 -0
- package/src/cli-migrate.ts +143 -0
- package/src/cli.ts +5 -0
- package/src/complete.ts +73 -0
- package/src/console-logger.ts +87 -0
- package/src/create-many.ts +126 -0
- package/src/decimal.ts +74 -0
- package/src/define-app.ts +108 -0
- package/src/define-enum.ts +26 -0
- package/src/define-function.ts +188 -0
- package/src/define-model.ts +220 -0
- package/src/definition.ts +352 -0
- package/src/delete-many.ts +76 -0
- package/src/engine.ts +281 -0
- package/src/errors.ts +97 -0
- package/src/exact.ts +49 -0
- package/src/feed.ts +39 -0
- package/src/field-codec.ts +72 -0
- package/src/field-rules.ts +77 -0
- package/src/field-type.ts +219 -0
- package/src/fields.ts +383 -0
- package/src/function.ts +143 -0
- package/src/functions.ts +154 -0
- package/src/group-by.ts +186 -0
- package/src/group-having.ts +143 -0
- package/src/hooks.ts +215 -0
- package/src/hosts.ts +11 -0
- package/src/identify.ts +95 -0
- package/src/index.ts +125 -0
- package/src/internal.ts +68 -0
- package/src/lifecycle.ts +214 -0
- package/src/list-update.ts +71 -0
- package/src/live.ts +199 -0
- package/src/manifest.ts +71 -0
- package/src/memory-aggregate.ts +162 -0
- package/src/memory-conditions.ts +88 -0
- package/src/memory-draft.ts +319 -0
- package/src/memory-json.ts +91 -0
- package/src/memory-read.ts +311 -0
- package/src/memory-search.ts +149 -0
- package/src/memory-values.ts +106 -0
- package/src/memory.ts +173 -0
- package/src/migrate.ts +152 -0
- package/src/model.ts +312 -0
- package/src/nested-write.ts +390 -0
- package/src/number-update.ts +142 -0
- package/src/plugin-app.ts +308 -0
- package/src/plugin-code.ts +158 -0
- package/src/plugin-use.ts +217 -0
- package/src/plugin.ts +400 -0
- package/src/query-aggregate.ts +96 -0
- package/src/query-args.ts +375 -0
- package/src/query-bulk.ts +53 -0
- package/src/query-conditions.ts +53 -0
- package/src/query-context.ts +104 -0
- package/src/query-filter.ts +54 -0
- package/src/query-group.ts +137 -0
- package/src/query-json.ts +90 -0
- package/src/query-order.ts +158 -0
- package/src/query-project.ts +73 -0
- package/src/query-search.ts +31 -0
- package/src/query-values.ts +131 -0
- package/src/query-where.ts +318 -0
- package/src/query-write.ts +360 -0
- package/src/query.ts +400 -0
- package/src/record.ts +379 -0
- package/src/recorder.ts +212 -0
- package/src/relation-write.ts +176 -0
- package/src/relations.ts +165 -0
- package/src/rules.ts +188 -0
- package/src/running.ts +62 -0
- package/src/schema-indexes.ts +88 -0
- package/src/schema-virtuals.ts +134 -0
- package/src/schema.ts +367 -0
- package/src/scope.ts +123 -0
- package/src/scratch.ts +28 -0
- package/src/serve.ts +5 -0
- package/src/symbols.ts +11 -0
- package/src/transaction.ts +211 -0
- package/src/types.ts +79 -0
- package/src/update-many.ts +268 -0
- package/src/uuid.ts +51 -0
- package/src/validation.ts +107 -0
- package/src/virtual-read.ts +198 -0
- package/src/virtual.ts +187 -0
- package/src/write-context.ts +170 -0
- package/src/write-data.ts +188 -0
- package/src/write-plan.ts +273 -0
- package/src/write-target.ts +92 -0
- package/src/writes.ts +361 -0
- 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
|
+
}
|