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,390 @@
|
|
|
1
|
+
// A relation write's `set`, `connectOrCreate`, `update` and `delete`,
|
|
2
|
+
// resolved before the rules see it (D-138): the linked rows each one reaches,
|
|
3
|
+
// read locked, and `set` and `connectOrCreate` as the links they stand for.
|
|
4
|
+
import { and } from './access.ts'
|
|
5
|
+
import type { Filter, Row } from './adapter.ts'
|
|
6
|
+
import { InvalidQuery, PublicError } from './errors.ts'
|
|
7
|
+
import { computeOn, operationsOf } from './number-update.ts'
|
|
8
|
+
import { uniqueWhere } from './query-args.ts'
|
|
9
|
+
import { isObject, modelOf } from './query-where.ts'
|
|
10
|
+
import type { FieldInfo, ModelInfo } from './schema.ts'
|
|
11
|
+
import type { QueryScope } from './scope.ts'
|
|
12
|
+
import { planned } from './scope.ts'
|
|
13
|
+
import type { Updating } from './relation-write.ts'
|
|
14
|
+
import { manyWrite, oneWrite } from './relation-write.ts'
|
|
15
|
+
import type { Draft } from './write-data.ts'
|
|
16
|
+
import { invalid, readData } from './write-data.ts'
|
|
17
|
+
import type { Reads } from './write-plan.ts'
|
|
18
|
+
|
|
19
|
+
type At = readonly (string | number)[]
|
|
20
|
+
|
|
21
|
+
type Relation = Extract<FieldInfo, { readonly kind: 'one' | 'many' }>
|
|
22
|
+
|
|
23
|
+
/** The row whose relation is written, and where its data sits (D-128). */
|
|
24
|
+
export type Resolving = {
|
|
25
|
+
readonly scope: QueryScope
|
|
26
|
+
readonly reads: Reads
|
|
27
|
+
readonly model: ModelInfo
|
|
28
|
+
readonly id: string
|
|
29
|
+
readonly creating: boolean
|
|
30
|
+
/** A default's or a hook's value, which skips access rules (D-67). */
|
|
31
|
+
readonly trusted: boolean
|
|
32
|
+
readonly at: At
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** The locked row each resolved `update` data or `delete` reaches. */
|
|
36
|
+
const rows = new WeakMap<object, Row>()
|
|
37
|
+
|
|
38
|
+
/** Where a `connectOrCreate`'s create sits in the write's data. */
|
|
39
|
+
const origins = new WeakMap<object, At>()
|
|
40
|
+
|
|
41
|
+
/** A `one` relation write, resolved. */
|
|
42
|
+
export type One =
|
|
43
|
+
| { readonly kind: 'connect'; readonly id: string }
|
|
44
|
+
| { readonly kind: 'create'; readonly data: Draft; readonly at: At }
|
|
45
|
+
| { readonly kind: 'update'; readonly data: Draft; readonly row: Row }
|
|
46
|
+
| { readonly kind: 'delete'; readonly row: Row }
|
|
47
|
+
| { readonly kind: 'disconnect' }
|
|
48
|
+
|
|
49
|
+
/** A `many` relation write, resolved. */
|
|
50
|
+
export type Many = {
|
|
51
|
+
readonly connect: readonly string[]
|
|
52
|
+
readonly disconnect: readonly string[]
|
|
53
|
+
readonly create: readonly { readonly data: Draft; readonly at: At }[]
|
|
54
|
+
readonly update: readonly { readonly data: Draft; readonly row: Row }[]
|
|
55
|
+
readonly delete: readonly { readonly row: Row; readonly path: string }[]
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const nesting = ['set', 'connectOrCreate', 'update', 'delete']
|
|
59
|
+
|
|
60
|
+
/** Whether a relation value of `draft` needs resolving or reaches a row. */
|
|
61
|
+
export const nests = (model: ModelInfo, draft: Draft): boolean =>
|
|
62
|
+
Object.entries(draft).some(([name, value]) => {
|
|
63
|
+
const kind = model.fields.get(name)?.kind
|
|
64
|
+
return (
|
|
65
|
+
(kind === 'one' || kind === 'many') &&
|
|
66
|
+
isObject(value) &&
|
|
67
|
+
nesting.some((key) => Object.hasOwn(value, key))
|
|
68
|
+
)
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
/** The rows of the other side linked to the row written. */
|
|
72
|
+
function linked(r: Resolving, field: Relation, target: ModelInfo): Filter {
|
|
73
|
+
const inverse = target.fields.get(field.inverse)
|
|
74
|
+
const parent = { id: { equals: r.id } }
|
|
75
|
+
return {
|
|
76
|
+
[field.inverse]:
|
|
77
|
+
inverse?.kind === 'many' ? { some: parent } : { is: parent },
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** The linked row `where` names, as the caller may `operation` it. */
|
|
82
|
+
async function reached(
|
|
83
|
+
r: Resolving,
|
|
84
|
+
field: Relation,
|
|
85
|
+
where: Filter | undefined,
|
|
86
|
+
path: string,
|
|
87
|
+
operation: 'update' | 'delete',
|
|
88
|
+
): Promise<Row> {
|
|
89
|
+
const target = modelOf(r.scope.models, field.target)
|
|
90
|
+
const filter = await r.scope.filter(target.name, r.trusted, operation)
|
|
91
|
+
const [row] = await r.reads.find({
|
|
92
|
+
model: target.name,
|
|
93
|
+
where: and(and(linked(r, field, target), where), filter),
|
|
94
|
+
take: 1,
|
|
95
|
+
lock: true,
|
|
96
|
+
})
|
|
97
|
+
if (row === undefined) {
|
|
98
|
+
throw new PublicError('NOT_FOUND', `No ${target.name} matches ${path}.`)
|
|
99
|
+
}
|
|
100
|
+
return row
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** A unique `where`, its fields' rules applied unless the value is trusted. */
|
|
104
|
+
async function unique(
|
|
105
|
+
r: Resolving,
|
|
106
|
+
target: ModelInfo,
|
|
107
|
+
where: unknown,
|
|
108
|
+
path: string,
|
|
109
|
+
): Promise<Filter> {
|
|
110
|
+
const scope = r.trusted ? undefined : r.scope
|
|
111
|
+
return planned(r.scope, () =>
|
|
112
|
+
uniqueWhere(r.scope.models, target, where, path, scope),
|
|
113
|
+
)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* A nested update's data on its locked row: its shape checked again, as a
|
|
118
|
+
* hook may have written it, and its number updates computed (D-132).
|
|
119
|
+
*/
|
|
120
|
+
function updated(
|
|
121
|
+
r: Resolving,
|
|
122
|
+
field: Relation,
|
|
123
|
+
target: ModelInfo,
|
|
124
|
+
data: Draft,
|
|
125
|
+
row: Row,
|
|
126
|
+
at: At,
|
|
127
|
+
): Draft {
|
|
128
|
+
const path = ['data', ...at].join('.')
|
|
129
|
+
const draft = readData(
|
|
130
|
+
r.scope.models,
|
|
131
|
+
target,
|
|
132
|
+
data,
|
|
133
|
+
false,
|
|
134
|
+
path,
|
|
135
|
+
field.inverse,
|
|
136
|
+
)
|
|
137
|
+
computeOn(target, row, draft, operationsOf(target, draft, at))
|
|
138
|
+
rows.set(draft, row)
|
|
139
|
+
return draft
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** The row a `connectOrCreate` finds as stored, without rules (D-138). */
|
|
143
|
+
async function found(
|
|
144
|
+
r: Resolving,
|
|
145
|
+
target: ModelInfo,
|
|
146
|
+
where: unknown,
|
|
147
|
+
path: string,
|
|
148
|
+
): Promise<string | undefined> {
|
|
149
|
+
const filter = uniqueWhere(r.scope.models, target, where, path)
|
|
150
|
+
const [row] = await r.reads.find({
|
|
151
|
+
model: target.name,
|
|
152
|
+
where: filter,
|
|
153
|
+
take: 1,
|
|
154
|
+
lock: true,
|
|
155
|
+
})
|
|
156
|
+
return row?.id
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export async function resolveOne(
|
|
160
|
+
r: Resolving,
|
|
161
|
+
field: Extract<FieldInfo, { readonly kind: 'one' }>,
|
|
162
|
+
value: unknown,
|
|
163
|
+
): Promise<One> {
|
|
164
|
+
const name = field.name
|
|
165
|
+
const path = ['data', ...r.at, name].join('.')
|
|
166
|
+
const write = oneWrite(value, path, r.model.name, r.creating)
|
|
167
|
+
const target = modelOf(r.scope.models, field.target)
|
|
168
|
+
if (write.kind === 'connect' || write.kind === 'disconnect') return write
|
|
169
|
+
if (write.kind === 'create') {
|
|
170
|
+
const at = origins.get(write.data) ?? [...r.at, name, 'create']
|
|
171
|
+
return { kind: 'create', data: write.data, at }
|
|
172
|
+
}
|
|
173
|
+
if (write.kind === 'connectOrCreate') {
|
|
174
|
+
const id = await found(
|
|
175
|
+
r,
|
|
176
|
+
target,
|
|
177
|
+
write.where,
|
|
178
|
+
`${path}.connectOrCreate.where`,
|
|
179
|
+
)
|
|
180
|
+
if (id !== undefined) return { kind: 'connect', id }
|
|
181
|
+
const at = [...r.at, name, 'connectOrCreate', 'create']
|
|
182
|
+
origins.set(write.create, at)
|
|
183
|
+
return { kind: 'create', data: write.create, at }
|
|
184
|
+
}
|
|
185
|
+
if (write.kind === 'update') {
|
|
186
|
+
const known = rows.get(write.data)
|
|
187
|
+
if (known !== undefined) return { ...write, row: known }
|
|
188
|
+
const row = await reached(r, field, undefined, `${path}.update`, 'update')
|
|
189
|
+
const at = [...r.at, name, 'update']
|
|
190
|
+
return {
|
|
191
|
+
kind: 'update',
|
|
192
|
+
data: updated(r, field, target, write.data, row, at),
|
|
193
|
+
row,
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
// Refused as `disconnect` is, before any row is read.
|
|
197
|
+
if (field.stored && !field.optional) {
|
|
198
|
+
throw invalid([{ path: [...r.at, name], message: 'Required.' }])
|
|
199
|
+
}
|
|
200
|
+
const known = isObject(value) ? rows.get(value) : undefined
|
|
201
|
+
if (known !== undefined) return { kind: 'delete', row: known }
|
|
202
|
+
const row = await reached(r, field, undefined, `${path}.delete`, 'delete')
|
|
203
|
+
return { kind: 'delete', row }
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/** The linked rows `update` and `delete` name, each once (D-138). */
|
|
207
|
+
async function changing(
|
|
208
|
+
r: Resolving,
|
|
209
|
+
field: Relation,
|
|
210
|
+
target: ModelInfo,
|
|
211
|
+
given: {
|
|
212
|
+
readonly update: readonly Updating[]
|
|
213
|
+
readonly delete: readonly unknown[]
|
|
214
|
+
},
|
|
215
|
+
path: string,
|
|
216
|
+
): Promise<Pick<Many, 'update' | 'delete'>> {
|
|
217
|
+
const update: { data: Draft; row: Row }[] = []
|
|
218
|
+
for (const [index, { where, data }] of given.update.entries()) {
|
|
219
|
+
const at = `${path}.update.${index}`
|
|
220
|
+
const known = rows.get(data)
|
|
221
|
+
if (known !== undefined) {
|
|
222
|
+
update.push({ data, row: known })
|
|
223
|
+
continue
|
|
224
|
+
}
|
|
225
|
+
const filter = await unique(r, target, where, `${at}.where`)
|
|
226
|
+
const row = await reached(r, field, filter, `${at}.where`, 'update')
|
|
227
|
+
const draft = updated(r, field, target, data, row, [
|
|
228
|
+
...r.at,
|
|
229
|
+
field.name,
|
|
230
|
+
'update',
|
|
231
|
+
index,
|
|
232
|
+
'data',
|
|
233
|
+
])
|
|
234
|
+
update.push({ data: draft, row })
|
|
235
|
+
}
|
|
236
|
+
const deleted: { row: Row; path: string }[] = []
|
|
237
|
+
for (const [index, where] of given.delete.entries()) {
|
|
238
|
+
const at = `${path}.delete.${index}`
|
|
239
|
+
const known = isObject(where) ? rows.get(where) : undefined
|
|
240
|
+
if (known !== undefined) {
|
|
241
|
+
deleted.push({ row: known, path: at })
|
|
242
|
+
continue
|
|
243
|
+
}
|
|
244
|
+
const filter = await unique(r, target, where, at)
|
|
245
|
+
deleted.push({
|
|
246
|
+
row: await reached(r, field, filter, at, 'delete'),
|
|
247
|
+
path: at,
|
|
248
|
+
})
|
|
249
|
+
}
|
|
250
|
+
return { update, delete: deleted }
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/** `set` as the links it changes, from the linked rows the caller reads. */
|
|
254
|
+
async function setting(
|
|
255
|
+
r: Resolving,
|
|
256
|
+
field: Relation,
|
|
257
|
+
target: ModelInfo,
|
|
258
|
+
set: readonly string[],
|
|
259
|
+
leaving: ReadonlySet<string>,
|
|
260
|
+
): Promise<Pick<Many, 'connect' | 'disconnect'>> {
|
|
261
|
+
const inverse = target.fields.get(field.inverse)
|
|
262
|
+
if (inverse?.kind === 'one' && !inverse.optional) {
|
|
263
|
+
throw invalid([
|
|
264
|
+
{
|
|
265
|
+
path: [...r.at, field.name, 'set'],
|
|
266
|
+
message: `A ${target.name} cannot be left without its ${field.inverse}.`,
|
|
267
|
+
},
|
|
268
|
+
])
|
|
269
|
+
}
|
|
270
|
+
const filter = await r.scope.filter(target.name, r.trusted)
|
|
271
|
+
const current = await r.reads.find({
|
|
272
|
+
model: target.name,
|
|
273
|
+
where: and(linked(r, field, target), filter),
|
|
274
|
+
lock: true,
|
|
275
|
+
})
|
|
276
|
+
const kept = new Set(set)
|
|
277
|
+
const ids = new Set(current.map((row) => row.id))
|
|
278
|
+
return {
|
|
279
|
+
connect: [...kept].filter((id) => !ids.has(id)),
|
|
280
|
+
disconnect: [...ids].filter((id) => !kept.has(id) && !leaving.has(id)),
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/** Fails when a row is named by two operations of the relation (D-138). */
|
|
285
|
+
function once(many: Many, path: string): void {
|
|
286
|
+
const seen = new Set<string>()
|
|
287
|
+
const links = new Set([...many.connect, ...many.disconnect])
|
|
288
|
+
const named = [
|
|
289
|
+
...many.update.map(({ row }) => row.id),
|
|
290
|
+
...many.delete.map(({ row }) => row.id),
|
|
291
|
+
]
|
|
292
|
+
for (const id of named) {
|
|
293
|
+
if (seen.has(id) || links.has(id)) {
|
|
294
|
+
throw new InvalidQuery(path, `${path} names the same row twice.`)
|
|
295
|
+
}
|
|
296
|
+
seen.add(id)
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
export async function resolveMany(
|
|
301
|
+
r: Resolving,
|
|
302
|
+
field: Extract<FieldInfo, { readonly kind: 'many' }>,
|
|
303
|
+
value: unknown,
|
|
304
|
+
): Promise<Many> {
|
|
305
|
+
const path = ['data', ...r.at, field.name].join('.')
|
|
306
|
+
const write = manyWrite(value, path, r.model.name, r.creating)
|
|
307
|
+
const target = modelOf(r.scope.models, field.target)
|
|
308
|
+
const { update, delete: deleted } = await changing(
|
|
309
|
+
r,
|
|
310
|
+
field,
|
|
311
|
+
target,
|
|
312
|
+
write,
|
|
313
|
+
path,
|
|
314
|
+
)
|
|
315
|
+
const connect = [...write.connect]
|
|
316
|
+
const disconnect = [...write.disconnect]
|
|
317
|
+
if (write.set !== undefined) {
|
|
318
|
+
const leaving = new Set(deleted.map(({ row }) => row.id))
|
|
319
|
+
const links = await setting(r, field, target, write.set, leaving)
|
|
320
|
+
connect.push(...links.connect)
|
|
321
|
+
disconnect.push(...links.disconnect)
|
|
322
|
+
}
|
|
323
|
+
const create = write.create.map((data, index) => ({
|
|
324
|
+
data,
|
|
325
|
+
at: origins.get(data) ?? [...r.at, field.name, 'create', index],
|
|
326
|
+
}))
|
|
327
|
+
for (const [index, item] of write.connectOrCreate.entries()) {
|
|
328
|
+
const at = `${path}.connectOrCreate.${index}`
|
|
329
|
+
const id = await found(r, target, item.where, `${at}.where`)
|
|
330
|
+
if (id !== undefined) {
|
|
331
|
+
connect.push(id)
|
|
332
|
+
continue
|
|
333
|
+
}
|
|
334
|
+
const origin = [...r.at, field.name, 'connectOrCreate', index, 'create']
|
|
335
|
+
origins.set(item.create, origin)
|
|
336
|
+
create.push({ data: item.create, at: origin })
|
|
337
|
+
}
|
|
338
|
+
const many = { connect, disconnect, create, update, delete: deleted }
|
|
339
|
+
once(many, path)
|
|
340
|
+
return many
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/** A resolved `one` as the relation value hooks and rules see. */
|
|
344
|
+
function oneValue(one: One): unknown {
|
|
345
|
+
if (one.kind === 'connect') return { connect: { id: one.id } }
|
|
346
|
+
if (one.kind === 'create') return { create: one.data }
|
|
347
|
+
if (one.kind === 'update') return { update: one.data }
|
|
348
|
+
if (one.kind === 'disconnect') return { disconnect: true }
|
|
349
|
+
const value = { delete: true }
|
|
350
|
+
rows.set(value, one.row)
|
|
351
|
+
return value
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
const ref = (id: string) => ({ id })
|
|
355
|
+
|
|
356
|
+
/** A resolved `many` as the relation value hooks and rules see. */
|
|
357
|
+
function manyValue(many: Many): Draft {
|
|
358
|
+
const value: Draft = {}
|
|
359
|
+
if (many.connect.length > 0) value['connect'] = many.connect.map(ref)
|
|
360
|
+
if (many.disconnect.length > 0) value['disconnect'] = many.disconnect.map(ref)
|
|
361
|
+
if (many.create.length > 0) {
|
|
362
|
+
value['create'] = many.create.map(({ data }) => data)
|
|
363
|
+
}
|
|
364
|
+
if (many.update.length > 0) {
|
|
365
|
+
value['update'] = many.update.map(({ data, row }) => ({
|
|
366
|
+
where: { id: row.id },
|
|
367
|
+
data,
|
|
368
|
+
}))
|
|
369
|
+
}
|
|
370
|
+
if (many.delete.length > 0) {
|
|
371
|
+
value['delete'] = many.delete.map(({ row }) => {
|
|
372
|
+
const where = { id: row.id }
|
|
373
|
+
rows.set(where, row)
|
|
374
|
+
return where
|
|
375
|
+
})
|
|
376
|
+
}
|
|
377
|
+
return value
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/** Resolves each relation value of `draft` in place, before the rules. */
|
|
381
|
+
export async function resolveData(r: Resolving, draft: Draft): Promise<void> {
|
|
382
|
+
for (const [name, value] of Object.entries(draft)) {
|
|
383
|
+
const field = r.model.fields.get(name)
|
|
384
|
+
if (field?.kind === 'one') {
|
|
385
|
+
draft[name] = oneValue(await resolveOne(r, field, value))
|
|
386
|
+
} else if (field?.kind === 'many') {
|
|
387
|
+
draft[name] = manyValue(await resolveMany(r, field, value))
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
// Number updates (D-132): `{ set }`, `{ increment }`, `{ decrement }`,
|
|
2
|
+
// `{ multiply }` or `{ divide }` on a number field, computed on the row as
|
|
3
|
+
// the write finds it, as a list update is (D-149).
|
|
4
|
+
import type { Arithmetic, ListUpdate } from './adapter.ts'
|
|
5
|
+
import { OutOfRange } from './adapter.ts'
|
|
6
|
+
import { InvalidQuery } from './errors.ts'
|
|
7
|
+
import { listed, listUpdates } from './list-update.ts'
|
|
8
|
+
import { isObject } from './query-where.ts'
|
|
9
|
+
import type { FieldInfo, ModelInfo } from './schema.ts'
|
|
10
|
+
import type { Draft } from './write-data.ts'
|
|
11
|
+
import { invalid, issueOf } from './write-data.ts'
|
|
12
|
+
|
|
13
|
+
const arithmetic = [
|
|
14
|
+
'set',
|
|
15
|
+
'increment',
|
|
16
|
+
'decrement',
|
|
17
|
+
'multiply',
|
|
18
|
+
'divide',
|
|
19
|
+
] as const
|
|
20
|
+
type Operation = (typeof arithmetic)[number]
|
|
21
|
+
|
|
22
|
+
const isOperation = (key: unknown): key is Operation =>
|
|
23
|
+
arithmetic.some((operation) => operation === key)
|
|
24
|
+
|
|
25
|
+
/** The number and list updates an update computes on its row, by field. */
|
|
26
|
+
export type Operations = ReadonlyMap<string, Arithmetic | ListUpdate>
|
|
27
|
+
|
|
28
|
+
type Scalar = Extract<FieldInfo, { readonly kind: 'scalar' }>
|
|
29
|
+
|
|
30
|
+
/** A number field written as a value or an operation: no list, no codec. */
|
|
31
|
+
const operable = (field: FieldInfo | undefined): field is Scalar =>
|
|
32
|
+
field?.kind === 'scalar' &&
|
|
33
|
+
field.type === 'number' &&
|
|
34
|
+
!field.list &&
|
|
35
|
+
field.codec === undefined
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Takes the operations out of an update's draft: `{ set: x }` becomes `x`,
|
|
39
|
+
* an operation by `undefined` leaves its field as it is (D-84), and each
|
|
40
|
+
* other one comes back to be computed on the row.
|
|
41
|
+
*/
|
|
42
|
+
function numberUpdates(
|
|
43
|
+
model: ModelInfo,
|
|
44
|
+
draft: Draft,
|
|
45
|
+
at: readonly (string | number)[] = [],
|
|
46
|
+
): Map<string, Arithmetic> {
|
|
47
|
+
const numbers = new Map<string, Arithmetic>()
|
|
48
|
+
for (const [name, value] of Object.entries(draft)) {
|
|
49
|
+
const field = model.fields.get(name)
|
|
50
|
+
if (!operable(field) || !isObject(value)) continue
|
|
51
|
+
// `undefined` is no operation (D-84); one by `undefined` changes nothing.
|
|
52
|
+
const keys = Object.keys(value)
|
|
53
|
+
const used = arithmetic.filter((key) => value[key] !== undefined)
|
|
54
|
+
if (keys.length === 0 || used.length > 1 || !keys.every(isOperation)) {
|
|
55
|
+
const path = ['data', ...at, name].join('.')
|
|
56
|
+
throw new InvalidQuery(
|
|
57
|
+
path,
|
|
58
|
+
`${path} takes a number or one of ${arithmetic.join(', ')}.`,
|
|
59
|
+
)
|
|
60
|
+
}
|
|
61
|
+
delete draft[name]
|
|
62
|
+
const [operation] = used
|
|
63
|
+
if (operation === undefined) continue
|
|
64
|
+
const by = value[operation]
|
|
65
|
+
if (operation === 'set') {
|
|
66
|
+
draft[name] = by
|
|
67
|
+
continue
|
|
68
|
+
}
|
|
69
|
+
const issue =
|
|
70
|
+
issueOf(by, field) ??
|
|
71
|
+
(operation === 'divide' && by === 0 ? 'Cannot divide by 0.' : undefined)
|
|
72
|
+
if (issue !== undefined)
|
|
73
|
+
throw invalid([{ path: [...at, name], message: issue }])
|
|
74
|
+
numbers.set(name, { operation, by: Number(by) })
|
|
75
|
+
}
|
|
76
|
+
return numbers
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Takes an update's number and list operations out of its draft. */
|
|
80
|
+
export const operationsOf = (
|
|
81
|
+
model: ModelInfo,
|
|
82
|
+
draft: Draft,
|
|
83
|
+
at: readonly (string | number)[] = [],
|
|
84
|
+
): Operations =>
|
|
85
|
+
new Map<string, Arithmetic | ListUpdate>([
|
|
86
|
+
...numberUpdates(model, draft, at),
|
|
87
|
+
...listUpdates(model, draft, at),
|
|
88
|
+
])
|
|
89
|
+
|
|
90
|
+
/** An update's result on `current`, `null` counting as 0. */
|
|
91
|
+
export function computed(
|
|
92
|
+
current: unknown,
|
|
93
|
+
{ operation, by }: Arithmetic,
|
|
94
|
+
integer: boolean,
|
|
95
|
+
): number {
|
|
96
|
+
const from = typeof current === 'number' ? current : 0
|
|
97
|
+
if (operation === 'increment') return from + by
|
|
98
|
+
if (operation === 'decrement') return from - by
|
|
99
|
+
if (operation === 'multiply') return from * by
|
|
100
|
+
return integer ? Math.trunc(from / by) : from / by
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** Whether the row must be read first: a field's `validate` sees the result. */
|
|
104
|
+
export function validates(model: ModelInfo, operations: Operations): boolean {
|
|
105
|
+
return [...operations.keys()].some(
|
|
106
|
+
(name) => model.fields.get(name)?.options['validate'] !== undefined,
|
|
107
|
+
)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** Writes each computed number and list into the draft, from the locked `row`. */
|
|
111
|
+
export function computeOn(
|
|
112
|
+
model: ModelInfo,
|
|
113
|
+
row: { readonly [field: string]: unknown },
|
|
114
|
+
draft: Draft,
|
|
115
|
+
operations: Operations,
|
|
116
|
+
): void {
|
|
117
|
+
for (const [name, operation] of operations) {
|
|
118
|
+
const field = model.fields.get(name)
|
|
119
|
+
const integer = field?.kind === 'scalar' && field.integer === true
|
|
120
|
+
draft[name] =
|
|
121
|
+
'elements' in operation
|
|
122
|
+
? listed(row[name], operation)
|
|
123
|
+
: computed(row[name], operation, integer)
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** A result the write refused, worded as the kernel's own check (D-132). */
|
|
128
|
+
function outOfRange(model: ModelInfo, error: unknown): unknown {
|
|
129
|
+
if (!(error instanceof OutOfRange)) return error
|
|
130
|
+
const field = model.fields.get(error.field)
|
|
131
|
+
const issue = field?.kind === 'scalar' ? issueOf(Infinity, field) : undefined
|
|
132
|
+
return invalid([{ path: [error.field], message: issue ?? error.message }])
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** `run`, a result it computed out of range refused as `outOfRange` words it. */
|
|
136
|
+
export const inRange = async <T>(
|
|
137
|
+
model: ModelInfo,
|
|
138
|
+
run: Promise<T>,
|
|
139
|
+
): Promise<T> =>
|
|
140
|
+
run.catch((error: unknown) => {
|
|
141
|
+
throw outOfRange(model, error)
|
|
142
|
+
})
|