ultimabase 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -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 +16 -0
- package/dist/cli-main.d.ts.map +1 -0
- package/dist/cli-main.js +151 -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 +165 -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,352 @@
|
|
|
1
|
+
// What `defineModel`, the field builders and `definePlugin` record at run
|
|
2
|
+
// time: the values behind the types.
|
|
3
|
+
import type { ScalarType } from './adapter.ts'
|
|
4
|
+
import type { Enum, StandardSchema } from './fields.ts'
|
|
5
|
+
import type { Manifest, OutputForm } from './plugin.ts'
|
|
6
|
+
import type { Standard } from './validation.ts'
|
|
7
|
+
import { Refused } from './errors.ts'
|
|
8
|
+
import { isObject } from './query-where.ts'
|
|
9
|
+
import { standardOf } from './validation.ts'
|
|
10
|
+
|
|
11
|
+
export type FieldOptions = { readonly [option: string]: unknown }
|
|
12
|
+
|
|
13
|
+
/** What a plugin field type adds to its stored field (D-122). */
|
|
14
|
+
export type Codec = {
|
|
15
|
+
readonly plugin: string
|
|
16
|
+
/** The field type's own options this field took (D-152). */
|
|
17
|
+
readonly options: { readonly [key: string]: unknown }
|
|
18
|
+
/** The stored value of the rows stored before the field (D-152). */
|
|
19
|
+
readonly filled?: unknown
|
|
20
|
+
readonly decode: (stored: unknown) => unknown
|
|
21
|
+
readonly encode: (value: unknown) => unknown
|
|
22
|
+
readonly schema: Standard
|
|
23
|
+
readonly toJSON: (value: unknown) => unknown
|
|
24
|
+
/** What `toJSON` gives, as a Standard Schema (D-152). */
|
|
25
|
+
readonly shape?: StandardSchema
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** A field as its builder recorded it. */
|
|
29
|
+
export type FieldDefinition =
|
|
30
|
+
| {
|
|
31
|
+
readonly kind: 'scalar'
|
|
32
|
+
readonly type: ScalarType
|
|
33
|
+
readonly values?: readonly string[]
|
|
34
|
+
/** The `defineEnum` its values are, if one (D-150). */
|
|
35
|
+
readonly enum?: Enum
|
|
36
|
+
readonly options: FieldOptions
|
|
37
|
+
readonly rules?: object
|
|
38
|
+
/** A plugin field type's, whose `type` is the stored one (D-122). */
|
|
39
|
+
readonly codec?: Codec
|
|
40
|
+
}
|
|
41
|
+
| {
|
|
42
|
+
readonly kind: 'one' | 'many'
|
|
43
|
+
readonly target: () => unknown
|
|
44
|
+
readonly options: FieldOptions
|
|
45
|
+
readonly rules?: object
|
|
46
|
+
}
|
|
47
|
+
| {
|
|
48
|
+
/** Computed on read (D-151): its schema, or a thunk of its output. */
|
|
49
|
+
readonly kind: 'virtual'
|
|
50
|
+
readonly output: unknown
|
|
51
|
+
readonly options: FieldOptions
|
|
52
|
+
readonly rules?: object
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export type IndexDefinition = {
|
|
56
|
+
readonly fields: readonly string[]
|
|
57
|
+
readonly unique?: boolean
|
|
58
|
+
/** A trigram index, for `contains` and `mode: 'insensitive'` (D-141). */
|
|
59
|
+
readonly contains?: true
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export type ModelDefinition = {
|
|
63
|
+
readonly name: string
|
|
64
|
+
readonly description: string | undefined
|
|
65
|
+
readonly dbName: string | undefined
|
|
66
|
+
readonly fields: ReadonlyMap<string, FieldDefinition>
|
|
67
|
+
readonly indexes: readonly IndexDefinition[]
|
|
68
|
+
/** The plugins of its `use`, in that order (D-89). */
|
|
69
|
+
readonly uses: readonly PluginUse[]
|
|
70
|
+
/** Its operations, which a field type's plugin is given (D-152). */
|
|
71
|
+
readonly queries: object
|
|
72
|
+
/** What `.with` received, once (D-07). */
|
|
73
|
+
rules: object | undefined
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** A plugin's code, as `definePlugin` checked it (D-57, D-89). */
|
|
77
|
+
export type PluginCode = {
|
|
78
|
+
readonly name: string
|
|
79
|
+
readonly manifest: Manifest
|
|
80
|
+
readonly requires: readonly string[]
|
|
81
|
+
readonly optional: readonly string[]
|
|
82
|
+
readonly hooks: { readonly [hook: string]: Function | undefined }
|
|
83
|
+
readonly redirect: Function | undefined
|
|
84
|
+
readonly commands: Function | undefined
|
|
85
|
+
readonly api: Function | undefined
|
|
86
|
+
readonly addedFields: Function | undefined
|
|
87
|
+
/** The indexes it adds to the models that use it (D-122). */
|
|
88
|
+
readonly addedIndexes: readonly IndexDefinition[]
|
|
89
|
+
readonly start: Function | undefined
|
|
90
|
+
readonly stop: Function | undefined
|
|
91
|
+
/** Who sends a request, if the plugin knows it (D-112). */
|
|
92
|
+
readonly identify: Function | undefined
|
|
93
|
+
/** The keys it gives a user caller (D-121). */
|
|
94
|
+
readonly caller: Function | undefined
|
|
95
|
+
/** Takes what each commit changed (D-154). */
|
|
96
|
+
readonly changed: Function | undefined
|
|
97
|
+
readonly missed: Function | undefined
|
|
98
|
+
/** Its own models, which no plugin is given (D-110). */
|
|
99
|
+
readonly models: readonly unknown[]
|
|
100
|
+
/** Builds its functions when the application starts (D-113). */
|
|
101
|
+
readonly functions: Function | undefined
|
|
102
|
+
/** The APIs of the plugins it needs, read when used (D-89). */
|
|
103
|
+
readonly plugins: object
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** A plugin's entry in `use`, or its value in `plugins`, with its options. */
|
|
107
|
+
export type PluginRecord = {
|
|
108
|
+
readonly code: PluginCode
|
|
109
|
+
readonly options: object
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** A plugin in a model's `use`, and what its code gets of the model. */
|
|
113
|
+
export type PluginUse = {
|
|
114
|
+
readonly code: PluginCode
|
|
115
|
+
readonly model: {
|
|
116
|
+
readonly name: string
|
|
117
|
+
readonly queries: object
|
|
118
|
+
readonly options: object
|
|
119
|
+
readonly plugins: object
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** A model or a type, as an output holds it (D-109). */
|
|
124
|
+
export type NamedOutput =
|
|
125
|
+
| {
|
|
126
|
+
readonly kind: 'model'
|
|
127
|
+
/** The `defineModel` value. */
|
|
128
|
+
readonly value: object
|
|
129
|
+
readonly name: string
|
|
130
|
+
}
|
|
131
|
+
| { readonly kind: 'type'; readonly type: TypeDefinition }
|
|
132
|
+
|
|
133
|
+
/** A function's or a type's output, as the kernel reads it (D-109). */
|
|
134
|
+
export type Output =
|
|
135
|
+
| { readonly kind: 'schema'; readonly schema: StandardSchema }
|
|
136
|
+
| (NamedOutput & { readonly form: OutputForm })
|
|
137
|
+
|
|
138
|
+
export type TypeDefinition = {
|
|
139
|
+
readonly name: string
|
|
140
|
+
readonly description: string | undefined
|
|
141
|
+
readonly fields: ReadonlyMap<string, Output>
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export type FunctionDefinition = {
|
|
145
|
+
readonly name: string
|
|
146
|
+
readonly description: string | undefined
|
|
147
|
+
readonly query: boolean
|
|
148
|
+
readonly input: StandardSchema | undefined
|
|
149
|
+
readonly output: Output | undefined
|
|
150
|
+
/** The plugins of its `use` (D-27). */
|
|
151
|
+
readonly uses: readonly PluginRecord[]
|
|
152
|
+
readonly run: Function
|
|
153
|
+
/** What `.with` received, once. */
|
|
154
|
+
rules: object | undefined
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export const fieldDefinitions: WeakMap<object, FieldDefinition> = new WeakMap()
|
|
158
|
+
export const modelDefinitions: WeakMap<object, ModelDefinition> = new WeakMap()
|
|
159
|
+
/** The application values `plugin()` returned. */
|
|
160
|
+
export const pluginValues: WeakMap<object, PluginRecord> = new WeakMap()
|
|
161
|
+
/** The model entries `use()` returned. */
|
|
162
|
+
export const pluginEntries: WeakMap<object, PluginRecord> = new WeakMap()
|
|
163
|
+
export const typeDefinitions: WeakMap<object, TypeDefinition> = new WeakMap()
|
|
164
|
+
export const functionDefinitions: WeakMap<object, FunctionDefinition> =
|
|
165
|
+
new WeakMap()
|
|
166
|
+
/** What each `X.nullable()` returned stands for (D-109). */
|
|
167
|
+
export const nullables: WeakMap<object, NamedOutput> = new WeakMap()
|
|
168
|
+
|
|
169
|
+
/** `X.nullable()`, for a model or a type. */
|
|
170
|
+
export function nullable(named: NamedOutput): object {
|
|
171
|
+
const marker = Object.freeze({})
|
|
172
|
+
nullables.set(marker, named)
|
|
173
|
+
return marker
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** The name a `define*` call takes: a non-empty string. */
|
|
177
|
+
export function nameOf(name: unknown, by: string): string {
|
|
178
|
+
if (typeof name !== 'string' || name === '') {
|
|
179
|
+
throw new Error(`${by} takes a name.`)
|
|
180
|
+
}
|
|
181
|
+
return name
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/** A `description` (D-150): a non-empty string, when given. */
|
|
185
|
+
export function describedBy(
|
|
186
|
+
description: unknown,
|
|
187
|
+
of: string,
|
|
188
|
+
): string | undefined {
|
|
189
|
+
if (description === undefined) return undefined
|
|
190
|
+
if (typeof description !== 'string' || description === '') {
|
|
191
|
+
throw new Error(`${of}: description is a non-empty string.`)
|
|
192
|
+
}
|
|
193
|
+
return description
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* The `.with` of a model or a function: its rules, in one call, read when
|
|
198
|
+
* they apply, so it returns the same value.
|
|
199
|
+
*/
|
|
200
|
+
export const withOnce =
|
|
201
|
+
(name: string, record: { rules: object | undefined }, value: () => object) =>
|
|
202
|
+
(rules: unknown): object => {
|
|
203
|
+
if (record.rules !== undefined) {
|
|
204
|
+
throw new Refused(`${name} takes its rules in one \`.with\`.`)
|
|
205
|
+
}
|
|
206
|
+
if (!isObject(rules)) throw new Refused(`${name}.with takes an object.`)
|
|
207
|
+
record.rules = rules
|
|
208
|
+
return value()
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/** A field value, keyed by its definition; `.with` takes its rules once. */
|
|
212
|
+
export function build(definition: FieldDefinition): object {
|
|
213
|
+
describedBy(definition.options['description'], 'A field')
|
|
214
|
+
const field = {
|
|
215
|
+
with: (rules: object): object => {
|
|
216
|
+
if (definition.rules !== undefined) {
|
|
217
|
+
throw new Error('A field takes its rules in one `.with`.')
|
|
218
|
+
}
|
|
219
|
+
return build({ ...definition, rules })
|
|
220
|
+
},
|
|
221
|
+
}
|
|
222
|
+
fieldDefinitions.set(field, definition)
|
|
223
|
+
return field
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/** A field that `list: true` makes a list (D-120, D-127). */
|
|
227
|
+
function listable(
|
|
228
|
+
type: ScalarType,
|
|
229
|
+
options: FieldOptions = {},
|
|
230
|
+
values?: readonly string[],
|
|
231
|
+
shared?: Enum,
|
|
232
|
+
): object {
|
|
233
|
+
const list = options['list'] === true
|
|
234
|
+
if (list) {
|
|
235
|
+
if (options['optional'] !== undefined) {
|
|
236
|
+
throw new Error(
|
|
237
|
+
'A list is never null: left out, it is []. Remove optional.',
|
|
238
|
+
)
|
|
239
|
+
}
|
|
240
|
+
if (options['unique'] !== undefined) {
|
|
241
|
+
throw new Error('A list cannot be unique.')
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
if (options['search'] !== undefined && (list || type !== 'string')) {
|
|
245
|
+
throw new Error('search takes a string field that is not a list.')
|
|
246
|
+
}
|
|
247
|
+
return build({
|
|
248
|
+
kind: 'scalar',
|
|
249
|
+
type,
|
|
250
|
+
...(values === undefined ? {} : { values }),
|
|
251
|
+
...(shared === undefined ? {} : { enum: shared }),
|
|
252
|
+
options,
|
|
253
|
+
})
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/** A field no list holds yet (D-127). */
|
|
257
|
+
const scalar =
|
|
258
|
+
(type: ScalarType) =>
|
|
259
|
+
(options?: FieldOptions): object => {
|
|
260
|
+
if (options?.['list'] !== undefined) {
|
|
261
|
+
throw new Error(
|
|
262
|
+
`f.${type}() makes no list: a list holds strings, numbers or enum values.`,
|
|
263
|
+
)
|
|
264
|
+
}
|
|
265
|
+
return build({ kind: 'scalar', type, options: options ?? {} })
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const whole = (value: unknown, most: number): value is number =>
|
|
269
|
+
Number.isInteger(value) && Number(value) >= 0 && Number(value) <= most
|
|
270
|
+
|
|
271
|
+
/** Exact numbers of `precision` digits, `scale` after the point (D-145). */
|
|
272
|
+
function decimal(options?: FieldOptions): object {
|
|
273
|
+
const { precision, scale } = options ?? {}
|
|
274
|
+
if (!whole(precision, 1000) || precision === 0) {
|
|
275
|
+
throw new Error('f.decimal() takes a precision from 1 to 1000.')
|
|
276
|
+
}
|
|
277
|
+
if (!whole(scale, precision)) {
|
|
278
|
+
throw new Error(
|
|
279
|
+
`f.decimal() takes a scale from 0 to its precision, ${precision}.`,
|
|
280
|
+
)
|
|
281
|
+
}
|
|
282
|
+
return scalar('decimal')(options)
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const relation =
|
|
286
|
+
(kind: 'one' | 'many') =>
|
|
287
|
+
(target: () => unknown, options?: FieldOptions): object =>
|
|
288
|
+
build({ kind, target, options: options ?? {} })
|
|
289
|
+
|
|
290
|
+
/** The `defineEnum` values, which `enumOf` takes in place of a list. */
|
|
291
|
+
export const sharedEnums: WeakMap<object, Enum> = new WeakMap()
|
|
292
|
+
|
|
293
|
+
/** An enum's values: strings, each once. */
|
|
294
|
+
export function enumValues(values: unknown, of: string): readonly string[] {
|
|
295
|
+
const listed: readonly unknown[] = Array.isArray(values) ? values : []
|
|
296
|
+
const strings = listed.filter((value) => typeof value === 'string')
|
|
297
|
+
if (strings.length === 0 || strings.length !== listed.length) {
|
|
298
|
+
throw new Error(`${of} takes a list of strings.`)
|
|
299
|
+
}
|
|
300
|
+
if (new Set(strings).size !== strings.length) {
|
|
301
|
+
throw new Error(`${of} lists a value twice: ${strings.join(', ')}.`)
|
|
302
|
+
}
|
|
303
|
+
return strings
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function enumOf(values: unknown, options?: FieldOptions): object {
|
|
307
|
+
const shared = isObject(values) ? sharedEnums.get(values) : undefined
|
|
308
|
+
if (shared !== undefined) {
|
|
309
|
+
return listable('enum', options, shared.values, shared)
|
|
310
|
+
}
|
|
311
|
+
return listable('enum', options, enumValues(values, 'enumOf'))
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/** The builders `defineModel`'s `fields` callback receives (D-09, D-12). */
|
|
315
|
+
export const fieldBuilders: object = Object.freeze({
|
|
316
|
+
string: (options?: FieldOptions) => listable('string', options),
|
|
317
|
+
number: (options?: FieldOptions) => listable('number', options),
|
|
318
|
+
decimal,
|
|
319
|
+
boolean: scalar('boolean'),
|
|
320
|
+
dateTime: scalar('dateTime'),
|
|
321
|
+
date: scalar('date'),
|
|
322
|
+
json: scalar('json'),
|
|
323
|
+
enumOf,
|
|
324
|
+
one: relation('one'),
|
|
325
|
+
many: relation('many'),
|
|
326
|
+
virtual: (output: unknown, options?: FieldOptions): object =>
|
|
327
|
+
build({ kind: 'virtual', output, options: options ?? {} }),
|
|
328
|
+
})
|
|
329
|
+
|
|
330
|
+
function namedOf(value: unknown): NamedOutput | undefined {
|
|
331
|
+
if (!isObject(value)) return undefined
|
|
332
|
+
const model = modelDefinitions.get(value)
|
|
333
|
+
if (model !== undefined) return { kind: 'model', value, name: model.name }
|
|
334
|
+
const type = typeDefinitions.get(value)
|
|
335
|
+
return type === undefined ? undefined : { kind: 'type', type }
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/** An output's value, read once at its definition (D-109). */
|
|
339
|
+
export function outputOf(value: unknown, where: string): Output {
|
|
340
|
+
const standard = standardOf(value)
|
|
341
|
+
if (standard !== undefined) return { kind: 'schema', schema: standard.schema }
|
|
342
|
+
const named = namedOf(value)
|
|
343
|
+
if (named !== undefined) return { ...named, form: 'one' }
|
|
344
|
+
const marked = isObject(value) ? nullables.get(value) : undefined
|
|
345
|
+
if (marked !== undefined) return { ...marked, form: 'nullable' }
|
|
346
|
+
const list: readonly unknown[] = Array.isArray(value) ? value : []
|
|
347
|
+
const listed = list.length === 1 ? namedOf(list[0]) : undefined
|
|
348
|
+
if (listed !== undefined) return { ...listed, form: 'list' }
|
|
349
|
+
throw new Refused(
|
|
350
|
+
`${where} is a model, a type, a list of one, X.nullable() or a Standard Schema.`,
|
|
351
|
+
)
|
|
352
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// `deleteMany` (D-111): what a `delete` runs, on each row its `where`
|
|
2
|
+
// selects, all or nothing; one write when nothing runs (D-102).
|
|
3
|
+
import type { Filter, Row } from './adapter.ts'
|
|
4
|
+
import { cascadeOf, cascadeWhere } from './cascade.ts'
|
|
5
|
+
import type { Event } from './hooks.ts'
|
|
6
|
+
import { settle } from './hooks.ts'
|
|
7
|
+
import type { Context } from './query-context.ts'
|
|
8
|
+
import { filtered, given } from './query-context.ts'
|
|
9
|
+
import { byIds } from './query-where.ts'
|
|
10
|
+
import type { Commit, Store } from './transaction.ts'
|
|
11
|
+
import { checkedWrite, guarded } from './write-target.ts'
|
|
12
|
+
import { cascaded, writingOf } from './writing.ts'
|
|
13
|
+
|
|
14
|
+
export async function removeMany(
|
|
15
|
+
context: Context,
|
|
16
|
+
store: Store,
|
|
17
|
+
commits: Commit[],
|
|
18
|
+
args: unknown,
|
|
19
|
+
): Promise<{ readonly count: number }> {
|
|
20
|
+
const { models, model, scope } = context
|
|
21
|
+
const found = given(args, model.name, ['where'], [])
|
|
22
|
+
const where: Filter = (await filtered(context, found, 'delete')) ?? {}
|
|
23
|
+
const reach = cascadeWhere(models, model, where)
|
|
24
|
+
if (reach !== undefined && scope.access === undefined) {
|
|
25
|
+
// The write reads its rows as they were, to count them (D-102).
|
|
26
|
+
const read = { model: model.name, where }
|
|
27
|
+
const { checks } = reach
|
|
28
|
+
const rows = await guarded(
|
|
29
|
+
store.last({
|
|
30
|
+
checks: checks.map(({ check }) => check),
|
|
31
|
+
changes: reach.changes,
|
|
32
|
+
read,
|
|
33
|
+
before: true,
|
|
34
|
+
}),
|
|
35
|
+
checks,
|
|
36
|
+
)
|
|
37
|
+
return { count: rows.length }
|
|
38
|
+
}
|
|
39
|
+
// In id order, so two calls on the same rows wait instead of deadlocking.
|
|
40
|
+
const orderBy = [{ field: 'id', direction: 'asc' }] as const
|
|
41
|
+
const rows = await store.find({
|
|
42
|
+
model: model.name,
|
|
43
|
+
where,
|
|
44
|
+
orderBy,
|
|
45
|
+
lock: true,
|
|
46
|
+
})
|
|
47
|
+
// One row refused, none deleted (D-111).
|
|
48
|
+
for (const row of rows) scope.access?.allow(model, 'delete', { record: row })
|
|
49
|
+
if (rows.length > 0) await deleted(context, store, commits, rows)
|
|
50
|
+
return { count: rows.length }
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Deletes `rows`, which the caller may delete, as `delete` does: in one write
|
|
55
|
+
* when nothing runs (D-102), else with their cascade or redirect, then the
|
|
56
|
+
* hooks.
|
|
57
|
+
*/
|
|
58
|
+
export async function deleted(
|
|
59
|
+
context: Context,
|
|
60
|
+
store: Store,
|
|
61
|
+
commits: Commit[],
|
|
62
|
+
rows: readonly Row[],
|
|
63
|
+
): Promise<void> {
|
|
64
|
+
const { models, model, caller, scope } = context
|
|
65
|
+
const locked = cascadeWhere(models, model, byIds(rows.map(({ id }) => id)))
|
|
66
|
+
if (locked !== undefined) {
|
|
67
|
+
await checkedWrite(store, locked.checks, locked.changes)
|
|
68
|
+
return
|
|
69
|
+
}
|
|
70
|
+
const events: Event[] = []
|
|
71
|
+
const writing = writingOf(scope, store, events)
|
|
72
|
+
const cascade = await cascadeOf(models, model, rows, store)
|
|
73
|
+
const changes = await cascaded(writing, cascade)
|
|
74
|
+
if (changes.length > 0) await checkedWrite(store, writing.checks, changes)
|
|
75
|
+
await settle(store, caller, events, commits)
|
|
76
|
+
}
|
package/src/engine.ts
ADDED
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
// The queries a model sends to the running application (D-18): each one
|
|
2
|
+
// checked, run on the adapter, in the open transaction if any (D-36), and
|
|
3
|
+
// returned in the shape it asked for.
|
|
4
|
+
import { accessFor, and } from './access.ts'
|
|
5
|
+
import { InvalidQuery, PublicError } from './errors.ts'
|
|
6
|
+
import type { Identity } from './rules.ts'
|
|
7
|
+
import { isMaster } from './rules.ts'
|
|
8
|
+
import type { Read, Row } from './adapter.ts'
|
|
9
|
+
import type { Running } from './running.ts'
|
|
10
|
+
import { current } from './running.ts'
|
|
11
|
+
import type { ModelInfo } from './schema.ts'
|
|
12
|
+
import type { Context } from './query-context.ts'
|
|
13
|
+
import { filtered, first, given, picked, shapedRows } from './query-context.ts'
|
|
14
|
+
import { oneKeys, plan, restrict, uniqueWhere } from './query-args.ts'
|
|
15
|
+
import { counted } from './virtual-read.ts'
|
|
16
|
+
import { entries, isObject, unknownKey } from './query-where.ts'
|
|
17
|
+
import { QueryScope, planned } from './scope.ts'
|
|
18
|
+
import type { Commit, Store } from './transaction.ts'
|
|
19
|
+
import { reading, writing } from './transaction.ts'
|
|
20
|
+
import { aggregate } from './aggregate.ts'
|
|
21
|
+
import { distinctOf, groupBy } from './group-by.ts'
|
|
22
|
+
import { removeMany } from './delete-many.ts'
|
|
23
|
+
import { updateMany, updateManyAndReturn } from './update-many.ts'
|
|
24
|
+
import { createMany, createManyAndReturn } from './create-many.ts'
|
|
25
|
+
import { create, remove, update, upsert } from './writes.ts'
|
|
26
|
+
|
|
27
|
+
export type Operation =
|
|
28
|
+
| 'findMany'
|
|
29
|
+
| 'findFirst'
|
|
30
|
+
| 'findUnique'
|
|
31
|
+
| 'findFirstOrThrow'
|
|
32
|
+
| 'findUniqueOrThrow'
|
|
33
|
+
| 'count'
|
|
34
|
+
| 'aggregate'
|
|
35
|
+
| 'groupBy'
|
|
36
|
+
| 'create'
|
|
37
|
+
| 'update'
|
|
38
|
+
| 'upsert'
|
|
39
|
+
| 'delete'
|
|
40
|
+
| 'updateMany'
|
|
41
|
+
| 'createMany'
|
|
42
|
+
| 'updateManyAndReturn'
|
|
43
|
+
| 'createManyAndReturn'
|
|
44
|
+
| 'deleteMany'
|
|
45
|
+
// The transports' upsert, which also says whether it created (D-142).
|
|
46
|
+
| 'upserted'
|
|
47
|
+
|
|
48
|
+
const anonymous: Identity = Object.freeze({ kind: 'anonymous' })
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* `{ as }` only; without it, the caller is anonymous (D-19). The kernel
|
|
52
|
+
* completes a user one (D-121).
|
|
53
|
+
*/
|
|
54
|
+
export function callerOf(options: unknown, model: string): Identity {
|
|
55
|
+
if (options === undefined) return anonymous
|
|
56
|
+
const keys = new Map(entries(options, 'options'))
|
|
57
|
+
for (const key of keys.keys()) {
|
|
58
|
+
if (key !== 'as') throw unknownKey(`options.${key}`, model)
|
|
59
|
+
}
|
|
60
|
+
const caller = keys.get('as')
|
|
61
|
+
if (caller === undefined) return anonymous
|
|
62
|
+
if (!isObject(caller)) {
|
|
63
|
+
throw new InvalidQuery('options.as', 'options.as must be a caller.')
|
|
64
|
+
}
|
|
65
|
+
switch (caller['kind']) {
|
|
66
|
+
case 'anonymous':
|
|
67
|
+
return anonymous
|
|
68
|
+
case 'user':
|
|
69
|
+
if (
|
|
70
|
+
!isObject(caller['user']) ||
|
|
71
|
+
typeof caller['user']['id'] !== 'string'
|
|
72
|
+
) {
|
|
73
|
+
throw new InvalidQuery('options.as', 'options.as.user must be { id }.')
|
|
74
|
+
}
|
|
75
|
+
// oxlint-disable-next-line typescript/no-unsafe-type-assertion -- the kernel reads only the id; the record's other fields are the server code's.
|
|
76
|
+
return caller as Identity
|
|
77
|
+
case 'master':
|
|
78
|
+
// Only the kernel creates Master (D-19): a copy is a forgery.
|
|
79
|
+
if (!isMaster(caller)) {
|
|
80
|
+
throw new Error('options.as is a Master the kernel did not create.')
|
|
81
|
+
}
|
|
82
|
+
return caller
|
|
83
|
+
default:
|
|
84
|
+
throw new InvalidQuery('options.as', 'options.as must be a caller.')
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const manyKeys = new Set([
|
|
89
|
+
'where',
|
|
90
|
+
...oneKeys,
|
|
91
|
+
'orderBy',
|
|
92
|
+
'take',
|
|
93
|
+
'skip',
|
|
94
|
+
'cursor',
|
|
95
|
+
])
|
|
96
|
+
const distinctKeys = new Set([...manyKeys, 'distinct'])
|
|
97
|
+
|
|
98
|
+
/** Finds a read's root rows, decoded; a live read may serve them (D-154). */
|
|
99
|
+
export type RootFind = (store: Store, read: Read) => Promise<Row[]>
|
|
100
|
+
|
|
101
|
+
const onStore: RootFind = async (store, read) => store.find(read)
|
|
102
|
+
|
|
103
|
+
async function find(
|
|
104
|
+
context: Context,
|
|
105
|
+
store: Store,
|
|
106
|
+
args: unknown,
|
|
107
|
+
single: boolean,
|
|
108
|
+
root = onStore,
|
|
109
|
+
): Promise<unknown> {
|
|
110
|
+
const { models, model, scope } = context
|
|
111
|
+
const keys = single ? manyKeys : distinctKeys
|
|
112
|
+
const { read, shape, readable } = await planned(scope, () => ({
|
|
113
|
+
...plan(models, model, args, '', 'many', keys, scope),
|
|
114
|
+
readable: scope.readable(model.name),
|
|
115
|
+
}))
|
|
116
|
+
const take = read.take ?? 1
|
|
117
|
+
const limited = single ? { ...read, take: Math.sign(take) } : read
|
|
118
|
+
const distinct = single ? undefined : await distinctOf(context, args)
|
|
119
|
+
const rows = await root(store, {
|
|
120
|
+
model: model.name,
|
|
121
|
+
...restrict(limited, readable),
|
|
122
|
+
...(distinct && { distinct }),
|
|
123
|
+
})
|
|
124
|
+
if (single) return first(context, store, rows, shape, scope.access)
|
|
125
|
+
return shapedRows(context, store, rows, shape, scope.access)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
async function findUnique(
|
|
129
|
+
context: Context,
|
|
130
|
+
store: Store,
|
|
131
|
+
args: unknown,
|
|
132
|
+
): Promise<unknown> {
|
|
133
|
+
const { models, model, scope } = context
|
|
134
|
+
const found = given(args, model.name, ['where', ...oneKeys], ['where'])
|
|
135
|
+
const { read, shape, where } = await planned(scope, () => ({
|
|
136
|
+
...plan(models, model, picked(found), '', 'one', oneKeys, scope),
|
|
137
|
+
where: and(
|
|
138
|
+
uniqueWhere(models, model, found.get('where'), 'where', scope),
|
|
139
|
+
scope.readable(model.name),
|
|
140
|
+
),
|
|
141
|
+
}))
|
|
142
|
+
const rows = await store.find({ model: model.name, ...read, where })
|
|
143
|
+
return first(context, store, rows, shape, scope.access)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
async function count(
|
|
147
|
+
context: Context,
|
|
148
|
+
store: Store,
|
|
149
|
+
args: unknown,
|
|
150
|
+
): Promise<number> {
|
|
151
|
+
const { model } = context
|
|
152
|
+
const where = await filtered(
|
|
153
|
+
context,
|
|
154
|
+
given(args, model.name, ['where'], []),
|
|
155
|
+
'read',
|
|
156
|
+
)
|
|
157
|
+
return store.count({ model: model.name, ...(where && { where }) })
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
type Run = (context: Context, store: Store, args: unknown) => Promise<unknown>
|
|
161
|
+
type Write = (
|
|
162
|
+
context: Context,
|
|
163
|
+
store: Store,
|
|
164
|
+
commits: Commit[],
|
|
165
|
+
args: unknown,
|
|
166
|
+
) => Promise<unknown>
|
|
167
|
+
|
|
168
|
+
type ReadOperation =
|
|
169
|
+
| 'findMany'
|
|
170
|
+
| 'findFirst'
|
|
171
|
+
| 'findUnique'
|
|
172
|
+
| 'findFirstOrThrow'
|
|
173
|
+
| 'findUniqueOrThrow'
|
|
174
|
+
| 'count'
|
|
175
|
+
| 'aggregate'
|
|
176
|
+
| 'groupBy'
|
|
177
|
+
|
|
178
|
+
/** The read, which fails with `NOT_FOUND` rather than return `null` (D-135). */
|
|
179
|
+
const orThrow =
|
|
180
|
+
(read: Run): Run =>
|
|
181
|
+
async (context, store, args) => {
|
|
182
|
+
const row = await read(context, store, args)
|
|
183
|
+
if (row !== null) return row
|
|
184
|
+
throw new PublicError('NOT_FOUND', `No ${context.model.name} matches.`)
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const reads: { readonly [O in ReadOperation]: Run } = {
|
|
188
|
+
findMany: async (context, store, args) => find(context, store, args, false),
|
|
189
|
+
findFirst: async (context, store, args) => find(context, store, args, true),
|
|
190
|
+
findUnique,
|
|
191
|
+
findFirstOrThrow: orThrow(async (context, store, args) =>
|
|
192
|
+
find(context, store, args, true),
|
|
193
|
+
),
|
|
194
|
+
findUniqueOrThrow: orThrow(findUnique),
|
|
195
|
+
count,
|
|
196
|
+
aggregate,
|
|
197
|
+
groupBy,
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const writes: { readonly [O in Exclude<Operation, ReadOperation>]: Write } = {
|
|
201
|
+
create,
|
|
202
|
+
update,
|
|
203
|
+
upsert: async (context, store, commits, args) =>
|
|
204
|
+
(await upsert(context, store, commits, args)).row,
|
|
205
|
+
upserted: upsert,
|
|
206
|
+
delete: remove,
|
|
207
|
+
updateMany,
|
|
208
|
+
createMany,
|
|
209
|
+
updateManyAndReturn,
|
|
210
|
+
createManyAndReturn,
|
|
211
|
+
deleteMany: removeMany,
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function isRead(operation: Operation): operation is ReadOperation {
|
|
215
|
+
return Object.hasOwn(reads, operation)
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/** The running application and its model `name`. */
|
|
219
|
+
function runningModel(name: string): {
|
|
220
|
+
readonly app: Running
|
|
221
|
+
readonly model: ModelInfo
|
|
222
|
+
} {
|
|
223
|
+
const app = current()
|
|
224
|
+
const model = app?.resolved.models.get(name)
|
|
225
|
+
if (app === undefined || model === undefined) {
|
|
226
|
+
throw new Error(`${name} is not a model of a started application.`)
|
|
227
|
+
}
|
|
228
|
+
return { app, model }
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
async function contextOf(
|
|
232
|
+
app: Running,
|
|
233
|
+
model: ModelInfo,
|
|
234
|
+
identity: Identity,
|
|
235
|
+
): Promise<Context> {
|
|
236
|
+
const caller = await app.complete(identity)
|
|
237
|
+
const { models } = app.resolved
|
|
238
|
+
const access = accessFor(models, caller, app.logger)
|
|
239
|
+
const scope = new QueryScope(models, caller, access)
|
|
240
|
+
return { app, models, model, caller, scope }
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/** Runs one query of a model of the running application. */
|
|
244
|
+
export async function query(
|
|
245
|
+
name: string,
|
|
246
|
+
operation: Operation,
|
|
247
|
+
args: unknown,
|
|
248
|
+
options: unknown,
|
|
249
|
+
): Promise<unknown> {
|
|
250
|
+
const { app, model } = runningModel(name)
|
|
251
|
+
const identity = callerOf(options, name)
|
|
252
|
+
counted()
|
|
253
|
+
return app.work.run(async () => {
|
|
254
|
+
const context = await contextOf(app, model, identity)
|
|
255
|
+
if (isRead(operation)) {
|
|
256
|
+
const read = reads[operation]
|
|
257
|
+
return reading(app, async (store) => read(context, store, args))
|
|
258
|
+
}
|
|
259
|
+
const write = writes[operation]
|
|
260
|
+
return writing(app, async (store, commits) =>
|
|
261
|
+
write(context, store, commits, args),
|
|
262
|
+
)
|
|
263
|
+
})
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/** `findMany(args)` as `options` says, its root rows found by `root` (D-154). */
|
|
267
|
+
export async function rootFind(
|
|
268
|
+
name: string,
|
|
269
|
+
args: unknown,
|
|
270
|
+
options: unknown,
|
|
271
|
+
root: RootFind,
|
|
272
|
+
): Promise<unknown> {
|
|
273
|
+
const { app, model } = runningModel(name)
|
|
274
|
+
const identity = callerOf(options, name)
|
|
275
|
+
return app.work.run(async () => {
|
|
276
|
+
const context = await contextOf(app, model, identity)
|
|
277
|
+
return reading(app, async (store) =>
|
|
278
|
+
find(context, store, args, false, root),
|
|
279
|
+
)
|
|
280
|
+
})
|
|
281
|
+
}
|