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,375 @@
|
|
|
1
|
+
// A read's arguments: `select`, `include`, `orderBy`, `take`, `skip` and
|
|
2
|
+
// `cursor` checked (D-20), as the adapter's read and the shape of the result.
|
|
3
|
+
import type { Filter, Nested } from './adapter.ts'
|
|
4
|
+
import { and } from './access.ts'
|
|
5
|
+
import { InvalidQuery } from './errors.ts'
|
|
6
|
+
import type { FieldInfo, ModelInfo } from './schema.ts'
|
|
7
|
+
import type { VirtualInfo } from './schema-virtuals.ts'
|
|
8
|
+
import { orderBy } from './query-order.ts'
|
|
9
|
+
import type { Models, Scope } from './query-where.ts'
|
|
10
|
+
import {
|
|
11
|
+
entries,
|
|
12
|
+
isObject,
|
|
13
|
+
modelOf,
|
|
14
|
+
normalizeWhere,
|
|
15
|
+
systemFields,
|
|
16
|
+
unknownKey,
|
|
17
|
+
} from './query-where.ts'
|
|
18
|
+
|
|
19
|
+
/** What a query returns of each row, and of each relation it reads. */
|
|
20
|
+
export type Shape = {
|
|
21
|
+
readonly model: ModelInfo
|
|
22
|
+
readonly fields: readonly string[]
|
|
23
|
+
readonly relations: ReadonlyMap<string, Shape>
|
|
24
|
+
/** The `many` relations `_count` returns (D-134). */
|
|
25
|
+
readonly counts: readonly string[]
|
|
26
|
+
readonly virtuals: ReadonlyMap<string, VirtualRead>
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** A virtual a read computes (D-151): its input, and a model's rows. */
|
|
30
|
+
export type VirtualRead = {
|
|
31
|
+
readonly info: VirtualInfo
|
|
32
|
+
readonly input: { readonly [key: string]: unknown }
|
|
33
|
+
readonly path: string
|
|
34
|
+
readonly plan: Plan | undefined
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
type Many = Extract<FieldInfo, { readonly kind: 'many' }>
|
|
38
|
+
|
|
39
|
+
export type Plan = { readonly read: Nested; readonly shape: Shape }
|
|
40
|
+
|
|
41
|
+
/** Without `select`, scalar fields return and relations do not (D-20). */
|
|
42
|
+
function scalars(model: ModelInfo): string[] {
|
|
43
|
+
return [
|
|
44
|
+
...systemFields,
|
|
45
|
+
...[...model.fields.values()]
|
|
46
|
+
.filter((field) => field.kind === 'scalar')
|
|
47
|
+
.map((field) => field.name),
|
|
48
|
+
]
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function integer(value: unknown, path: string, least?: number): number {
|
|
52
|
+
if (
|
|
53
|
+
typeof value !== 'number' ||
|
|
54
|
+
!Number.isSafeInteger(value) ||
|
|
55
|
+
(least !== undefined && value < least)
|
|
56
|
+
) {
|
|
57
|
+
const range = least === undefined ? '' : ` of at least ${least}`
|
|
58
|
+
throw new InvalidQuery(path, `${path} must be an integer${range}.`)
|
|
59
|
+
}
|
|
60
|
+
return value
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** `_count`: the `many` relations it names, each with its filter (D-134). */
|
|
64
|
+
function counted(
|
|
65
|
+
models: Models,
|
|
66
|
+
model: ModelInfo,
|
|
67
|
+
value: unknown,
|
|
68
|
+
path: string,
|
|
69
|
+
scope: Scope | undefined,
|
|
70
|
+
): Map<string, Filter> {
|
|
71
|
+
const filter = (field: Many, where?: Filter): Filter =>
|
|
72
|
+
and(where, scope?.related(model, field)) ?? {}
|
|
73
|
+
const counts = new Map<string, Filter>()
|
|
74
|
+
if (value === true) {
|
|
75
|
+
for (const field of model.fields.values()) {
|
|
76
|
+
if (field.kind === 'many') counts.set(field.name, filter(field))
|
|
77
|
+
}
|
|
78
|
+
return counts
|
|
79
|
+
}
|
|
80
|
+
const given = new Map(entries(value, path))
|
|
81
|
+
for (const key of given.keys()) {
|
|
82
|
+
if (key !== 'select') throw unknownKey(`${path}.${key}`, model.name)
|
|
83
|
+
}
|
|
84
|
+
const select = `${path}.select`
|
|
85
|
+
for (const [name, chosen] of entries(given.get('select'), select)) {
|
|
86
|
+
const at = `${select}.${name}`
|
|
87
|
+
const field = model.fields.get(name)
|
|
88
|
+
if (field?.kind !== 'many') throw unknownKey(at, model.name)
|
|
89
|
+
if (chosen === false) continue
|
|
90
|
+
if (chosen === true) {
|
|
91
|
+
counts.set(name, filter(field))
|
|
92
|
+
continue
|
|
93
|
+
}
|
|
94
|
+
const args = new Map(entries(chosen, at))
|
|
95
|
+
for (const key of args.keys()) {
|
|
96
|
+
if (key !== 'where') throw unknownKey(`${at}.${key}`, model.name)
|
|
97
|
+
}
|
|
98
|
+
const counting = modelOf(models, field.target)
|
|
99
|
+
const where = args.has('where')
|
|
100
|
+
? normalizeWhere(
|
|
101
|
+
models,
|
|
102
|
+
counting,
|
|
103
|
+
args.get('where'),
|
|
104
|
+
`${at}.where`,
|
|
105
|
+
scope,
|
|
106
|
+
)
|
|
107
|
+
: undefined
|
|
108
|
+
counts.set(name, filter(field, where))
|
|
109
|
+
}
|
|
110
|
+
return counts
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** A unique target (D-52): exactly `id`, a unique field or a unique index. */
|
|
114
|
+
export function uniqueWhere(
|
|
115
|
+
models: Models,
|
|
116
|
+
model: ModelInfo,
|
|
117
|
+
where: unknown,
|
|
118
|
+
path: string,
|
|
119
|
+
scope?: Scope,
|
|
120
|
+
): Filter {
|
|
121
|
+
const given = entries(where, path)
|
|
122
|
+
const keys = given.map(([key]) => key).toSorted()
|
|
123
|
+
const matched = model.uniques.some(
|
|
124
|
+
(fields) =>
|
|
125
|
+
fields.length === keys.length &&
|
|
126
|
+
fields.toSorted().every((field, index) => field === keys[index]),
|
|
127
|
+
)
|
|
128
|
+
if (!matched) {
|
|
129
|
+
throw new InvalidQuery(
|
|
130
|
+
path,
|
|
131
|
+
`${path} must name exactly id, a unique field or a unique index of ${model.name}.`,
|
|
132
|
+
)
|
|
133
|
+
}
|
|
134
|
+
const filter: { [field: string]: unknown } = {}
|
|
135
|
+
for (const [key, value] of given) {
|
|
136
|
+
const at = `${path}.${key}`
|
|
137
|
+
if (value === null || isObject(value)) {
|
|
138
|
+
throw new InvalidQuery(at, `${at} must be a value.`)
|
|
139
|
+
}
|
|
140
|
+
const field = model.fields.get(key)
|
|
141
|
+
filter[key] = field?.kind === 'one' ? { is: { id: value } } : value
|
|
142
|
+
}
|
|
143
|
+
return normalizeWhere(models, model, filter, path, scope)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** A read narrowed to the rows of a read filter, its cursor too (D-86). */
|
|
147
|
+
export function restrict(read: Nested, readable: Filter | undefined): Nested {
|
|
148
|
+
if (readable === undefined) return read
|
|
149
|
+
return {
|
|
150
|
+
...read,
|
|
151
|
+
where: and(read.where, readable),
|
|
152
|
+
...(read.cursor === undefined
|
|
153
|
+
? {}
|
|
154
|
+
: { cursor: and(read.cursor, readable) }),
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
type Kind = 'many' | 'one'
|
|
159
|
+
|
|
160
|
+
/** A virtual's `true` or its input, with a model's `select`, `include`, `omit`. */
|
|
161
|
+
function virtualRead(
|
|
162
|
+
models: Models,
|
|
163
|
+
info: VirtualInfo,
|
|
164
|
+
value: unknown,
|
|
165
|
+
path: string,
|
|
166
|
+
scope: Scope | undefined,
|
|
167
|
+
): VirtualRead {
|
|
168
|
+
const { output } = info
|
|
169
|
+
const target = output.kind === 'model' ? output.name : undefined
|
|
170
|
+
const shaping: { [key: string]: unknown } = {}
|
|
171
|
+
const input: { [key: string]: unknown } = {}
|
|
172
|
+
for (const [key, given] of value === true ? [] : entries(value, path)) {
|
|
173
|
+
if (target !== undefined && oneKeys.has(key)) shaping[key] = given
|
|
174
|
+
else if (info.input === undefined) {
|
|
175
|
+
throw unknownKey(`${path}.${key}`, info.model)
|
|
176
|
+
} else input[key] = given
|
|
177
|
+
}
|
|
178
|
+
const nested =
|
|
179
|
+
target === undefined
|
|
180
|
+
? undefined
|
|
181
|
+
: plan(
|
|
182
|
+
models,
|
|
183
|
+
modelOf(models, target),
|
|
184
|
+
shaping,
|
|
185
|
+
path,
|
|
186
|
+
'one',
|
|
187
|
+
oneKeys,
|
|
188
|
+
scope,
|
|
189
|
+
)
|
|
190
|
+
return { info, input, path, plan: nested }
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* The arguments of a relation read, or of a top-level read when `path` is
|
|
195
|
+
* empty. A `scope` checks them and narrows each included relation (D-86).
|
|
196
|
+
*/
|
|
197
|
+
export function plan(
|
|
198
|
+
models: Models,
|
|
199
|
+
model: ModelInfo,
|
|
200
|
+
args: unknown,
|
|
201
|
+
path: string,
|
|
202
|
+
kind: Kind,
|
|
203
|
+
keys: ReadonlySet<string>,
|
|
204
|
+
scope?: Scope,
|
|
205
|
+
): Plan {
|
|
206
|
+
const given = new Map(args === undefined ? [] : entries(args, path || 'args'))
|
|
207
|
+
const at = (key: string): string => (path === '' ? key : `${path}.${key}`)
|
|
208
|
+
for (const key of given.keys()) {
|
|
209
|
+
if (!keys.has(key)) throw unknownKey(at(key), model.name)
|
|
210
|
+
}
|
|
211
|
+
const select = given.get('select')
|
|
212
|
+
const include = given.get('include')
|
|
213
|
+
if (select !== undefined && include !== undefined) {
|
|
214
|
+
throw new InvalidQuery(
|
|
215
|
+
path,
|
|
216
|
+
`${at('select')} and include cannot be used together.`,
|
|
217
|
+
)
|
|
218
|
+
}
|
|
219
|
+
if (select !== undefined && given.has('omit')) {
|
|
220
|
+
throw new InvalidQuery(
|
|
221
|
+
path,
|
|
222
|
+
`${at('select')} and omit cannot be used together.`,
|
|
223
|
+
)
|
|
224
|
+
}
|
|
225
|
+
const read: {
|
|
226
|
+
-readonly [K in keyof Nested]: Nested[K]
|
|
227
|
+
} = {}
|
|
228
|
+
const fields: string[] = select === undefined ? scalars(model) : []
|
|
229
|
+
const relations = new Map<string, Shape>()
|
|
230
|
+
const virtuals = new Map<string, VirtualRead>()
|
|
231
|
+
for (const info of select === undefined ? model.virtuals.values() : []) {
|
|
232
|
+
if (info.default)
|
|
233
|
+
virtuals.set(info.name, virtualRead(models, info, true, '', scope))
|
|
234
|
+
}
|
|
235
|
+
const nested: { [field: string]: Nested } = {}
|
|
236
|
+
let count = new Map<string, Filter>()
|
|
237
|
+
const chosen = select ?? include
|
|
238
|
+
if (chosen !== undefined) {
|
|
239
|
+
const base = select === undefined ? 'include' : 'select'
|
|
240
|
+
for (const [name, value] of entries(chosen, at(base))) {
|
|
241
|
+
const where = `${at(base)}.${name}`
|
|
242
|
+
if (name === '_count') {
|
|
243
|
+
if (value === false) continue
|
|
244
|
+
count = counted(models, model, value, where, scope)
|
|
245
|
+
continue
|
|
246
|
+
}
|
|
247
|
+
const virtual = model.virtuals.get(name)
|
|
248
|
+
if (virtual !== undefined) {
|
|
249
|
+
if (value === false) virtuals.delete(name)
|
|
250
|
+
else
|
|
251
|
+
virtuals.set(name, virtualRead(models, virtual, value, where, scope))
|
|
252
|
+
continue
|
|
253
|
+
}
|
|
254
|
+
const field = model.fields.get(name)
|
|
255
|
+
const scalar =
|
|
256
|
+
field === undefined
|
|
257
|
+
? systemFields.includes(name)
|
|
258
|
+
: field.kind === 'scalar'
|
|
259
|
+
if ((field === undefined && !scalar) || (base === 'include' && scalar)) {
|
|
260
|
+
throw unknownKey(where, model.name)
|
|
261
|
+
}
|
|
262
|
+
if (value === false) continue
|
|
263
|
+
if (scalar) {
|
|
264
|
+
if (value !== true) {
|
|
265
|
+
throw new InvalidQuery(where, `${where} must be a boolean.`)
|
|
266
|
+
}
|
|
267
|
+
fields.push(name)
|
|
268
|
+
continue
|
|
269
|
+
}
|
|
270
|
+
if (field === undefined || field.kind === 'scalar') continue
|
|
271
|
+
const relation = plan(
|
|
272
|
+
models,
|
|
273
|
+
modelOf(models, field.target),
|
|
274
|
+
value === true ? undefined : value,
|
|
275
|
+
where,
|
|
276
|
+
field.kind,
|
|
277
|
+
field.kind === 'many' ? manyKeys : oneKeys,
|
|
278
|
+
scope,
|
|
279
|
+
)
|
|
280
|
+
const readable =
|
|
281
|
+
field.kind === 'one'
|
|
282
|
+
? scope?.readable(field.target)
|
|
283
|
+
: scope?.related(model, field)
|
|
284
|
+
nested[name] = restrict(relation.read, readable)
|
|
285
|
+
relations.set(name, relation.shape)
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
if (Object.keys(nested).length > 0) read.include = nested
|
|
289
|
+
if (count.size > 0) read.count = Object.fromEntries(count)
|
|
290
|
+
if (given.has('where')) {
|
|
291
|
+
read.where = normalizeWhere(
|
|
292
|
+
models,
|
|
293
|
+
model,
|
|
294
|
+
given.get('where'),
|
|
295
|
+
at('where'),
|
|
296
|
+
scope,
|
|
297
|
+
)
|
|
298
|
+
}
|
|
299
|
+
if (kind === 'many') {
|
|
300
|
+
// `_relevance` ranks by the `_search` at the top of `where` (D-148).
|
|
301
|
+
const search: unknown = read.where?.['_search']
|
|
302
|
+
read.orderBy = orderBy(
|
|
303
|
+
models,
|
|
304
|
+
model,
|
|
305
|
+
given.get('orderBy') ?? [],
|
|
306
|
+
at('orderBy'),
|
|
307
|
+
scope,
|
|
308
|
+
isObject(search) && typeof search['search'] === 'string'
|
|
309
|
+
? search['search']
|
|
310
|
+
: undefined,
|
|
311
|
+
)
|
|
312
|
+
if (given.has('take')) read.take = integer(given.get('take'), at('take'))
|
|
313
|
+
if (given.has('skip')) read.skip = integer(given.get('skip'), at('skip'), 0)
|
|
314
|
+
if (given.has('cursor')) {
|
|
315
|
+
read.cursor = uniqueWhere(
|
|
316
|
+
models,
|
|
317
|
+
model,
|
|
318
|
+
given.get('cursor'),
|
|
319
|
+
at('cursor'),
|
|
320
|
+
scope,
|
|
321
|
+
)
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
const hidden = omitted(model, given.get('omit'), at('omit'))
|
|
325
|
+
const shown = fields.filter((name) => !hidden.has(name))
|
|
326
|
+
for (const name of hidden) virtuals.delete(name)
|
|
327
|
+
const needs = [...virtuals.values()].flatMap(({ info }) => info.needs)
|
|
328
|
+
// A field read rule may read any field of the row (D-136).
|
|
329
|
+
const names = [
|
|
330
|
+
...shown,
|
|
331
|
+
...relations.keys(),
|
|
332
|
+
...count.keys(),
|
|
333
|
+
...virtuals.keys(),
|
|
334
|
+
]
|
|
335
|
+
if (!names.some((name) => scope?.ruled(model, name) === true)) {
|
|
336
|
+
read.fields = [...new Set(['id', ...shown, ...needs])]
|
|
337
|
+
}
|
|
338
|
+
const counts = [...count.keys()]
|
|
339
|
+
return { read, shape: { model, fields: shown, relations, counts, virtuals } }
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/** The scalar and system fields `omit` leaves out of the rows (D-135). */
|
|
343
|
+
function omitted(model: ModelInfo, omit: unknown, path: string): Set<string> {
|
|
344
|
+
const names = new Set<string>()
|
|
345
|
+
if (omit === undefined) return names
|
|
346
|
+
for (const [name, value] of entries(omit, path)) {
|
|
347
|
+
const where = `${path}.${name}`
|
|
348
|
+
const field = model.fields.get(name)
|
|
349
|
+
const scalar =
|
|
350
|
+
field === undefined
|
|
351
|
+
? systemFields.includes(name) || model.virtuals.get(name)?.default
|
|
352
|
+
: field.kind === 'scalar'
|
|
353
|
+
if (scalar !== true) throw unknownKey(where, model.name)
|
|
354
|
+
if (typeof value !== 'boolean') {
|
|
355
|
+
throw new InvalidQuery(where, `${where} must be a boolean.`)
|
|
356
|
+
}
|
|
357
|
+
if (value) names.add(name)
|
|
358
|
+
}
|
|
359
|
+
return names
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/** The keys that shape a read's rows. */
|
|
363
|
+
export const oneKeys: ReadonlySet<string> = new Set([
|
|
364
|
+
'select',
|
|
365
|
+
'include',
|
|
366
|
+
'omit',
|
|
367
|
+
])
|
|
368
|
+
const manyKeys = new Set([
|
|
369
|
+
...oneKeys,
|
|
370
|
+
'where',
|
|
371
|
+
'orderBy',
|
|
372
|
+
'take',
|
|
373
|
+
'skip',
|
|
374
|
+
'cursor',
|
|
375
|
+
])
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// The writes of many rows, all or nothing: `createMany` (D-135),
|
|
2
|
+
// `updateMany` (D-133), `deleteMany` (D-111), and the forms that return the
|
|
3
|
+
// rows they write (D-149).
|
|
4
|
+
import type { Exact } from './exact.ts'
|
|
5
|
+
import type { Payload, QueryOptions, Shaping, Where } from './query.ts'
|
|
6
|
+
import type { CreateManyData, UpdateManyData } from './record.ts'
|
|
7
|
+
|
|
8
|
+
/** `createMany`: a row or a list, no nested writes, no rows back (D-135). */
|
|
9
|
+
export type CreateManyArgs<F> = {
|
|
10
|
+
readonly data: CreateManyData<F> | readonly CreateManyData<F>[]
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** `updateMany`: no relation writes, no returned rows (D-133). */
|
|
14
|
+
export type UpdateManyArgs<F> = {
|
|
15
|
+
readonly where?: Where<F> | undefined
|
|
16
|
+
readonly data: UpdateManyData<F>
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export type CreateManyAndReturnArgs<F> = CreateManyArgs<F> & Shaping<F>
|
|
20
|
+
export type UpdateManyAndReturnArgs<F> = UpdateManyArgs<F> & Shaping<F>
|
|
21
|
+
|
|
22
|
+
export interface BulkQueries<F> {
|
|
23
|
+
/** A create of each row of `data`, all or nothing (D-135). */
|
|
24
|
+
createMany(
|
|
25
|
+
this: void,
|
|
26
|
+
args: CreateManyArgs<F>,
|
|
27
|
+
options?: QueryOptions,
|
|
28
|
+
): Promise<{ readonly count: number }>
|
|
29
|
+
/** An update of each row `where` selects, all or nothing (D-133). */
|
|
30
|
+
updateMany(
|
|
31
|
+
this: void,
|
|
32
|
+
args: UpdateManyArgs<F>,
|
|
33
|
+
options?: QueryOptions,
|
|
34
|
+
): Promise<{ readonly count: number }>
|
|
35
|
+
/** A delete of each row `where` selects, all or nothing (D-111). */
|
|
36
|
+
deleteMany(
|
|
37
|
+
this: void,
|
|
38
|
+
args?: { readonly where?: Where<F> | undefined },
|
|
39
|
+
options?: QueryOptions,
|
|
40
|
+
): Promise<{ readonly count: number }>
|
|
41
|
+
/** `createMany`'s rows, in `data`'s order, each as `create` returns it. */
|
|
42
|
+
createManyAndReturn<const A extends CreateManyAndReturnArgs<F>>(
|
|
43
|
+
this: void,
|
|
44
|
+
args: A & NoInfer<Exact<A, CreateManyAndReturnArgs<F>>>,
|
|
45
|
+
options?: QueryOptions,
|
|
46
|
+
): Promise<Payload<F, A>[]>
|
|
47
|
+
/** `updateMany`'s rows, in `id` order, each as `update` returns it. */
|
|
48
|
+
updateManyAndReturn<const A extends UpdateManyAndReturnArgs<F>>(
|
|
49
|
+
this: void,
|
|
50
|
+
args: A & NoInfer<Exact<A, UpdateManyAndReturnArgs<F>>>,
|
|
51
|
+
options?: QueryOptions,
|
|
52
|
+
): Promise<Payload<F, A>[]>
|
|
53
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// The conditions of a list field (D-120, D-127, D-149).
|
|
2
|
+
import type { Condition } from './adapter.ts'
|
|
3
|
+
import { InvalidQuery } from './errors.ts'
|
|
4
|
+
import type { ModelInfo } from './schema.ts'
|
|
5
|
+
import type { Scalar } from './query-values.ts'
|
|
6
|
+
import { entries, integerRange, misfit, unknownKey } from './query-values.ts'
|
|
7
|
+
|
|
8
|
+
/** What an element of `field` must be, for a message. */
|
|
9
|
+
function kindOf(field: Scalar): string {
|
|
10
|
+
if (field.type === 'enum') return `one of ${(field.values ?? []).join(', ')}`
|
|
11
|
+
if (field.type !== 'number') return 'a non-empty clean string'
|
|
12
|
+
return field.integer === true
|
|
13
|
+
? `a whole number ${integerRange}`
|
|
14
|
+
: 'a finite number'
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* A list's `has`, `hasSome` and `hasEvery`, which name its elements, and
|
|
19
|
+
* `isEmpty` (D-120, D-127, D-149).
|
|
20
|
+
*/
|
|
21
|
+
export function listCondition(
|
|
22
|
+
model: ModelInfo,
|
|
23
|
+
field: Scalar,
|
|
24
|
+
value: unknown,
|
|
25
|
+
path: string,
|
|
26
|
+
): Condition | undefined {
|
|
27
|
+
const element = (item: unknown, at: string): unknown => {
|
|
28
|
+
if (item === '' || misfit(item, field) !== undefined) {
|
|
29
|
+
throw new InvalidQuery(at, `${at} must be ${kindOf(field)}.`)
|
|
30
|
+
}
|
|
31
|
+
return item
|
|
32
|
+
}
|
|
33
|
+
const condition: { [operator: string]: unknown } = {}
|
|
34
|
+
for (const [operator, operand] of entries(value, path)) {
|
|
35
|
+
const at = `${path}.${operator}`
|
|
36
|
+
if (operator === 'has') condition[operator] = element(operand, at)
|
|
37
|
+
else if (operator === 'isEmpty') {
|
|
38
|
+
if (typeof operand !== 'boolean') {
|
|
39
|
+
throw new InvalidQuery(at, `${at} must be a boolean.`)
|
|
40
|
+
}
|
|
41
|
+
condition[operator] = operand
|
|
42
|
+
} else if (operator !== 'hasSome' && operator !== 'hasEvery') {
|
|
43
|
+
throw unknownKey(at, model.name)
|
|
44
|
+
} else if (!Array.isArray(operand)) {
|
|
45
|
+
throw new InvalidQuery(at, `${at} must be a list.`)
|
|
46
|
+
} else {
|
|
47
|
+
condition[operator] = operand.map((each: unknown, index) =>
|
|
48
|
+
element(each, `${at}.${index}`),
|
|
49
|
+
)
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return Object.keys(condition).length === 0 ? undefined : condition
|
|
53
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
// What every query of a model shares (D-18): the running application, the
|
|
2
|
+
// caller and the scope it reads through, and the shape it returns.
|
|
3
|
+
import type { Access } from './access.ts'
|
|
4
|
+
import { and } from './access.ts'
|
|
5
|
+
import type { Filter, Row } from './adapter.ts'
|
|
6
|
+
import { InvalidQuery } from './errors.ts'
|
|
7
|
+
import type { FindOperation } from './model.ts'
|
|
8
|
+
import type { Caller } from './rules.ts'
|
|
9
|
+
import type { Running } from './running.ts'
|
|
10
|
+
import type { ModelInfo } from './schema.ts'
|
|
11
|
+
import type { QueryScope } from './scope.ts'
|
|
12
|
+
import { planned } from './scope.ts'
|
|
13
|
+
import type { Shape } from './query-args.ts'
|
|
14
|
+
import { oneKeys, plan } from './query-args.ts'
|
|
15
|
+
import type { Hidden } from './query-project.ts'
|
|
16
|
+
import { project } from './query-project.ts'
|
|
17
|
+
import type { Store } from './transaction.ts'
|
|
18
|
+
import { computeVirtuals } from './virtual-read.ts'
|
|
19
|
+
import type { Models } from './query-where.ts'
|
|
20
|
+
import { entries, unknownKey } from './query-where.ts'
|
|
21
|
+
|
|
22
|
+
export type Context = {
|
|
23
|
+
readonly app: Running
|
|
24
|
+
readonly models: Models
|
|
25
|
+
readonly model: ModelInfo
|
|
26
|
+
readonly caller: Caller
|
|
27
|
+
readonly scope: QueryScope
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function given(
|
|
31
|
+
args: unknown,
|
|
32
|
+
model: string,
|
|
33
|
+
keys: readonly string[],
|
|
34
|
+
required: readonly string[],
|
|
35
|
+
): Map<string, unknown> {
|
|
36
|
+
const found = new Map(args === undefined ? [] : entries(args, 'args'))
|
|
37
|
+
for (const key of found.keys()) {
|
|
38
|
+
if (!keys.includes(key)) throw unknownKey(key, model)
|
|
39
|
+
}
|
|
40
|
+
for (const key of required) {
|
|
41
|
+
if (!found.has(key)) throw new InvalidQuery(key, `${key} is required.`)
|
|
42
|
+
}
|
|
43
|
+
return found
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** `select` and `include` of a single-row operation. */
|
|
47
|
+
export function picked(found: ReadonlyMap<string, unknown>): object {
|
|
48
|
+
const args: { [key: string]: unknown } = {}
|
|
49
|
+
for (const key of oneKeys) {
|
|
50
|
+
if (found.has(key)) args[key] = found.get(key)
|
|
51
|
+
}
|
|
52
|
+
return args
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* The rows a many-row operation's `where` selects that the caller reads, or
|
|
57
|
+
* deletes (D-124).
|
|
58
|
+
*/
|
|
59
|
+
export async function filtered(
|
|
60
|
+
context: Context,
|
|
61
|
+
found: ReadonlyMap<string, unknown>,
|
|
62
|
+
operation: FindOperation,
|
|
63
|
+
): Promise<Filter | undefined> {
|
|
64
|
+
const { models, model, scope } = context
|
|
65
|
+
const keys = new Set(['where'])
|
|
66
|
+
const args = { where: found.get('where') }
|
|
67
|
+
return planned(scope, () =>
|
|
68
|
+
and(
|
|
69
|
+
plan(models, model, args, '', 'one', keys, scope).read.where,
|
|
70
|
+
scope.readable(model.name, operation),
|
|
71
|
+
),
|
|
72
|
+
)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** The fields the caller's field read rules hide (D-44). */
|
|
76
|
+
export function hiddenBy(access: Access | undefined): Hidden | undefined {
|
|
77
|
+
return access && ((model, row, fields) => access.hidden(model, row, fields))
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** Each row, as `shape` shapes it (D-149), its virtuals computed (D-151). */
|
|
81
|
+
export async function shapedRows(
|
|
82
|
+
context: Context,
|
|
83
|
+
store: Store,
|
|
84
|
+
rows: readonly Row[],
|
|
85
|
+
shape: Shape,
|
|
86
|
+
access: Access | undefined,
|
|
87
|
+
): Promise<unknown[]> {
|
|
88
|
+
const hidden = hiddenBy(access)
|
|
89
|
+
const computed = await computeVirtuals(context, store, rows, shape, hidden)
|
|
90
|
+
return rows.map((row) => project(row, shape, hidden, computed))
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export async function first(
|
|
94
|
+
context: Context,
|
|
95
|
+
store: Store,
|
|
96
|
+
rows: readonly Row[],
|
|
97
|
+
shape: Shape,
|
|
98
|
+
access: Access | undefined,
|
|
99
|
+
): Promise<unknown> {
|
|
100
|
+
const [row] = rows
|
|
101
|
+
if (row === undefined) return null
|
|
102
|
+
const [shaped] = await shapedRows(context, store, [row], shape, access)
|
|
103
|
+
return shaped
|
|
104
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// A scalar's and a list's filter operators (D-20, D-140, D-149): a decimal
|
|
2
|
+
// (D-145) or a day (D-150) is a string compared in order, without `contains`.
|
|
3
|
+
import type { JsonValue } from './fields.ts'
|
|
4
|
+
|
|
5
|
+
export type ScalarFilter<V, Ordered = false> = {
|
|
6
|
+
readonly equals?: V | null | undefined
|
|
7
|
+
readonly not?: V | null | undefined
|
|
8
|
+
readonly in?: readonly V[] | undefined
|
|
9
|
+
readonly notIn?: readonly V[] | undefined
|
|
10
|
+
} & ([V, Ordered] extends [number | Date, unknown] | [unknown, true]
|
|
11
|
+
? {
|
|
12
|
+
readonly lt?: V | undefined
|
|
13
|
+
readonly lte?: V | undefined
|
|
14
|
+
readonly gt?: V | undefined
|
|
15
|
+
readonly gte?: V | undefined
|
|
16
|
+
}
|
|
17
|
+
: [V] extends [string]
|
|
18
|
+
? {
|
|
19
|
+
readonly contains?: string | undefined
|
|
20
|
+
readonly startsWith?: string | undefined
|
|
21
|
+
readonly endsWith?: string | undefined
|
|
22
|
+
} & (string extends V
|
|
23
|
+
? { readonly mode?: 'insensitive' | undefined }
|
|
24
|
+
: unknown)
|
|
25
|
+
: unknown)
|
|
26
|
+
|
|
27
|
+
/** A list has no shorthand; `NOT` gives the opposite (D-120, D-127, D-149). */
|
|
28
|
+
export type ListFilter<E> = {
|
|
29
|
+
readonly has?: E | undefined
|
|
30
|
+
readonly hasSome?: readonly E[] | undefined
|
|
31
|
+
readonly hasEvery?: readonly E[] | undefined
|
|
32
|
+
readonly isEmpty?: boolean | undefined
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* A json field's filter at a path into its value (D-149): the operands are
|
|
37
|
+
* any JSON, whatever the field's schema, and the path is not checked.
|
|
38
|
+
*/
|
|
39
|
+
export type JsonPathFilter = {
|
|
40
|
+
readonly path: readonly string[]
|
|
41
|
+
readonly equals?: JsonValue | undefined
|
|
42
|
+
readonly not?: JsonValue | undefined
|
|
43
|
+
readonly lt?: number | string | undefined
|
|
44
|
+
readonly lte?: number | string | undefined
|
|
45
|
+
readonly gt?: number | string | undefined
|
|
46
|
+
readonly gte?: number | string | undefined
|
|
47
|
+
readonly stringContains?: string | undefined
|
|
48
|
+
readonly stringStartsWith?: string | undefined
|
|
49
|
+
readonly stringEndsWith?: string | undefined
|
|
50
|
+
readonly mode?: 'insensitive' | undefined
|
|
51
|
+
readonly arrayContains?: JsonValue | undefined
|
|
52
|
+
readonly arrayStartsWith?: JsonValue | undefined
|
|
53
|
+
readonly arrayEndsWith?: JsonValue | undefined
|
|
54
|
+
}
|