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,211 @@
|
|
|
1
|
+
// Transactions (D-36, D-88): `AsyncLocalStorage` carries the open one, so a
|
|
2
|
+
// hook's queries join it without a `tx` argument.
|
|
3
|
+
import { AsyncLocalStorage } from 'node:async_hooks'
|
|
4
|
+
import { randomUUID } from 'node:crypto'
|
|
5
|
+
import type { Adapter, Transaction } from './adapter.ts'
|
|
6
|
+
import { RelationViolation } from './adapter.ts'
|
|
7
|
+
import type { Logger } from './app-options.ts'
|
|
8
|
+
import { decoded } from './field-codec.ts'
|
|
9
|
+
import { recordCount, recordFind } from './recorder.ts'
|
|
10
|
+
import type { Running } from './running.ts'
|
|
11
|
+
import { current } from './running.ts'
|
|
12
|
+
import { refused } from './write-target.ts'
|
|
13
|
+
|
|
14
|
+
/** What a query reads and writes through. */
|
|
15
|
+
export type Store = Pick<
|
|
16
|
+
Transaction,
|
|
17
|
+
'find' | 'count' | 'aggregate' | 'write'
|
|
18
|
+
> & {
|
|
19
|
+
/**
|
|
20
|
+
* The write's changes when nothing follows them: alone, they need no
|
|
21
|
+
* transaction (D-99).
|
|
22
|
+
*/
|
|
23
|
+
readonly last: Transaction['write']
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** A `*Commit` hook, run once the transaction commits (D-15). */
|
|
27
|
+
export type Commit = () => Promise<void>
|
|
28
|
+
|
|
29
|
+
type Open = {
|
|
30
|
+
readonly app: Running
|
|
31
|
+
/** Begun by the first query (D-99). */
|
|
32
|
+
tx: Promise<Transaction> | undefined
|
|
33
|
+
readonly commits: Commit[]
|
|
34
|
+
/** The first query that failed in it (D-36). */
|
|
35
|
+
failure: { readonly error: unknown } | undefined
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const opened = new AsyncLocalStorage<Open>()
|
|
39
|
+
|
|
40
|
+
async function begun(open: Open): Promise<Transaction> {
|
|
41
|
+
open.tx ??= open.app.adapter.begin()
|
|
42
|
+
return open.tx
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* The store's rows with their plugin fields decoded (D-122), each read kept
|
|
47
|
+
* if a recording runs (D-154).
|
|
48
|
+
*/
|
|
49
|
+
function decoding(app: Running, store: Store): Store {
|
|
50
|
+
const { models } = app.resolved
|
|
51
|
+
return {
|
|
52
|
+
find: async (read) => {
|
|
53
|
+
const rows = await store.find(read)
|
|
54
|
+
recordFind(models, read, rows)
|
|
55
|
+
return decoded(models, read, rows)
|
|
56
|
+
},
|
|
57
|
+
count: async (read) => {
|
|
58
|
+
recordCount(models, read)
|
|
59
|
+
return store.count(read)
|
|
60
|
+
},
|
|
61
|
+
// It reads no plugin field (D-143).
|
|
62
|
+
aggregate: async (read) => {
|
|
63
|
+
recordCount(models, read)
|
|
64
|
+
return store.aggregate(read)
|
|
65
|
+
},
|
|
66
|
+
write: async (write) =>
|
|
67
|
+
decoded(models, write.read, await store.write(write)),
|
|
68
|
+
last: async (write) => decoded(models, write.read, await store.last(write)),
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Queries in the transaction; `owner`'s last write may run alone. */
|
|
73
|
+
function storeOf(open: Open, owner: boolean): Store {
|
|
74
|
+
return decoding(open.app, {
|
|
75
|
+
find: async (read) => (await begun(open)).find(read),
|
|
76
|
+
count: async (read) => (await begun(open)).count(read),
|
|
77
|
+
aggregate: async (read) => (await begun(open)).aggregate(read),
|
|
78
|
+
write: async (write) => (await begun(open)).write(write),
|
|
79
|
+
last: async (write) =>
|
|
80
|
+
owner && open.tx === undefined
|
|
81
|
+
? open.app.adapter.write(write)
|
|
82
|
+
: (await begun(open)).write(write),
|
|
83
|
+
})
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Runs in the open transaction; a failure rolls all of it back (D-36). */
|
|
87
|
+
async function joined<R>(open: Open, run: () => Promise<R>): Promise<R> {
|
|
88
|
+
if (open.failure !== undefined) {
|
|
89
|
+
throw new Error(
|
|
90
|
+
'A query failed in this transaction, which rolls back: no other query runs in it.',
|
|
91
|
+
{ cause: open.failure.error },
|
|
92
|
+
)
|
|
93
|
+
}
|
|
94
|
+
try {
|
|
95
|
+
return await run()
|
|
96
|
+
} catch (error) {
|
|
97
|
+
open.failure ??= { error }
|
|
98
|
+
throw error
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async function committed(logger: Logger, commits: Commit[]): Promise<void> {
|
|
103
|
+
for (const commit of commits) {
|
|
104
|
+
try {
|
|
105
|
+
await commit()
|
|
106
|
+
} catch (error) {
|
|
107
|
+
logger.error(
|
|
108
|
+
{ correlationId: randomUUID(), err: error },
|
|
109
|
+
'A commit hook failed; the write is kept.',
|
|
110
|
+
)
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** A transaction that failed to begin has nothing to roll back. */
|
|
116
|
+
async function rollback(open: Open): Promise<void> {
|
|
117
|
+
const tx = await open.tx?.catch(() => undefined)
|
|
118
|
+
await tx?.rollback()
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async function atomic<R>(
|
|
122
|
+
app: Running,
|
|
123
|
+
run: (open: Open) => Promise<R>,
|
|
124
|
+
): Promise<R> {
|
|
125
|
+
const open: Open = { app, tx: undefined, commits: [], failure: undefined }
|
|
126
|
+
let result: R
|
|
127
|
+
try {
|
|
128
|
+
result = await opened.run(open, run, open)
|
|
129
|
+
} catch (error) {
|
|
130
|
+
await rollback(open)
|
|
131
|
+
throw error
|
|
132
|
+
}
|
|
133
|
+
if (open.failure !== undefined) {
|
|
134
|
+
await rollback(open)
|
|
135
|
+
throw open.failure.error
|
|
136
|
+
}
|
|
137
|
+
try {
|
|
138
|
+
await (await open.tx)?.commit()
|
|
139
|
+
} catch (error) {
|
|
140
|
+
// A deferred foreign key checks at the commit (D-99).
|
|
141
|
+
throw error instanceof RelationViolation ? refused(error) : error
|
|
142
|
+
}
|
|
143
|
+
// Outside the transaction: a commit hook's queries open their own.
|
|
144
|
+
await committed(app.logger, open.commits)
|
|
145
|
+
return result
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** A read: in the open transaction, else straight on the adapter. */
|
|
149
|
+
export async function reading<R>(
|
|
150
|
+
app: Running,
|
|
151
|
+
run: (store: Store) => Promise<R>,
|
|
152
|
+
): Promise<R> {
|
|
153
|
+
const open = opened.getStore()
|
|
154
|
+
if (open === undefined) {
|
|
155
|
+
const { adapter } = app
|
|
156
|
+
return run(
|
|
157
|
+
decoding(app, {
|
|
158
|
+
find: async (read) => adapter.find(read),
|
|
159
|
+
count: async (read) => adapter.count(read),
|
|
160
|
+
aggregate: async (read) => adapter.aggregate(read),
|
|
161
|
+
write: async (write) => adapter.write(write),
|
|
162
|
+
last: async (write) => adapter.write(write),
|
|
163
|
+
}),
|
|
164
|
+
)
|
|
165
|
+
}
|
|
166
|
+
return joined(open, async () => run(storeOf(open, false)))
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/** A write: in the open transaction, else in its own (D-36). */
|
|
170
|
+
export async function writing<R>(
|
|
171
|
+
app: Running,
|
|
172
|
+
run: (store: Store, commits: Commit[]) => Promise<R>,
|
|
173
|
+
): Promise<R> {
|
|
174
|
+
const open = opened.getStore()
|
|
175
|
+
if (open !== undefined) {
|
|
176
|
+
return joined(open, async () => run(storeOf(open, false), open.commits))
|
|
177
|
+
}
|
|
178
|
+
return atomic(app, async (own) => run(storeOf(own, true), own.commits))
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Runs `run` in one transaction: if it throws, or if a query in it fails,
|
|
183
|
+
* every write rolls back and the `*Commit` hooks never run. Inside another
|
|
184
|
+
* `transaction`, it joins that one (D-36).
|
|
185
|
+
*/
|
|
186
|
+
export async function transaction<R>(run: () => Promise<R>): Promise<R> {
|
|
187
|
+
const open = opened.getStore()
|
|
188
|
+
if (open !== undefined) return joined(open, run)
|
|
189
|
+
const app = current()
|
|
190
|
+
if (app === undefined) {
|
|
191
|
+
throw new Error('transaction runs on a started application.')
|
|
192
|
+
}
|
|
193
|
+
return app.work.run(async () => atomic(app, run))
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Runs an adapter's own statement, `database.sql`, in the running
|
|
198
|
+
* application's open transaction, else alone (D-137).
|
|
199
|
+
*/
|
|
200
|
+
export async function joining<R>(
|
|
201
|
+
adapter: Adapter,
|
|
202
|
+
run: (tx: Transaction | undefined) => Promise<R>,
|
|
203
|
+
): Promise<R> {
|
|
204
|
+
const open = opened.getStore()
|
|
205
|
+
if (open?.app.adapter === adapter) {
|
|
206
|
+
return joined(open, async () => run(await begun(open)))
|
|
207
|
+
}
|
|
208
|
+
const app = current()
|
|
209
|
+
if (app?.adapter !== adapter) return run(undefined)
|
|
210
|
+
return app.work.run(async () => run(undefined))
|
|
211
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// A model's and a function's types, named from the model or the function
|
|
2
|
+
// (D-74): what application code passes to queries and gets back.
|
|
3
|
+
import type { ResultOf, RunArgs } from './function.ts'
|
|
4
|
+
import type { Model } from './model.ts'
|
|
5
|
+
import type { AggregateArgs } from './query-aggregate.ts'
|
|
6
|
+
import type {
|
|
7
|
+
CreateManyAndReturnArgs,
|
|
8
|
+
CreateManyArgs,
|
|
9
|
+
UpdateManyAndReturnArgs,
|
|
10
|
+
UpdateManyArgs,
|
|
11
|
+
} from './query-bulk.ts'
|
|
12
|
+
import type { GroupByArgs } from './query-group.ts'
|
|
13
|
+
import type {
|
|
14
|
+
CreateArgs,
|
|
15
|
+
FindFirstArgs,
|
|
16
|
+
FindManyArgs,
|
|
17
|
+
FindUniqueArgs,
|
|
18
|
+
Include,
|
|
19
|
+
OrderBy,
|
|
20
|
+
Payload,
|
|
21
|
+
Select,
|
|
22
|
+
UpdateArgs,
|
|
23
|
+
UpsertArgs,
|
|
24
|
+
Where,
|
|
25
|
+
} from './query.ts'
|
|
26
|
+
import type { CreateData, UpdateData } from './record.ts'
|
|
27
|
+
|
|
28
|
+
type Parts<M> =
|
|
29
|
+
M extends Model<infer N, infer F, infer _P, infer _W, infer X>
|
|
30
|
+
? { readonly name: N; readonly fields: F; readonly unique: X }
|
|
31
|
+
: never
|
|
32
|
+
type FieldsOf<M> = Parts<M>['fields']
|
|
33
|
+
type UniqueOf<M> = Parts<M>['unique']
|
|
34
|
+
type NameOf<M> = Parts<M>['name']
|
|
35
|
+
|
|
36
|
+
type Operations<F, X, N> = {
|
|
37
|
+
readonly findMany: FindManyArgs<F, X, N>
|
|
38
|
+
readonly findFirst: FindFirstArgs<F, X>
|
|
39
|
+
readonly findUnique: FindUniqueArgs<F, X>
|
|
40
|
+
readonly findFirstOrThrow: FindFirstArgs<F, X>
|
|
41
|
+
readonly findUniqueOrThrow: FindUniqueArgs<F, X>
|
|
42
|
+
readonly count: { readonly where?: Where<F> }
|
|
43
|
+
readonly aggregate: AggregateArgs<F>
|
|
44
|
+
readonly groupBy: GroupByArgs<F, N>
|
|
45
|
+
readonly create: CreateArgs<F, N>
|
|
46
|
+
readonly update: UpdateArgs<F, X, N>
|
|
47
|
+
readonly upsert: UpsertArgs<F, X, N>
|
|
48
|
+
readonly delete: FindUniqueArgs<F, X>
|
|
49
|
+
readonly updateMany: UpdateManyArgs<F>
|
|
50
|
+
readonly createMany: CreateManyArgs<F>
|
|
51
|
+
readonly updateManyAndReturn: UpdateManyAndReturnArgs<F>
|
|
52
|
+
readonly createManyAndReturn: CreateManyAndReturnArgs<F>
|
|
53
|
+
readonly deleteMany: { readonly where?: Where<F> | undefined }
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** One record, as arguments `A` return it; without them, its scalars. */
|
|
57
|
+
export type RecordOf<M, A = {}> = Payload<FieldsOf<M>, A>
|
|
58
|
+
export type WhereOf<M> = Where<FieldsOf<M>>
|
|
59
|
+
export type IncludeOf<M> = Include<FieldsOf<M>>
|
|
60
|
+
export type SelectOf<M> = Select<FieldsOf<M>>
|
|
61
|
+
export type OrderByOf<M> = OrderBy<FieldsOf<M>>
|
|
62
|
+
export type CreateDataOf<M> = CreateData<FieldsOf<M>, NameOf<M>>
|
|
63
|
+
export type UpdateDataOf<M> = UpdateData<FieldsOf<M>, NameOf<M>>
|
|
64
|
+
/** The arguments of one operation of the model. */
|
|
65
|
+
export type ArgsOf<
|
|
66
|
+
M,
|
|
67
|
+
O extends keyof Operations<FieldsOf<M>, UniqueOf<M>, NameOf<M>>,
|
|
68
|
+
> = Operations<FieldsOf<M>, UniqueOf<M>, NameOf<M>>[O]
|
|
69
|
+
|
|
70
|
+
/** What a caller sends to a function, before validation. */
|
|
71
|
+
export type InputOf<Fn> = Fn extends { readonly input: infer I }
|
|
72
|
+
? RunArgs<I> extends readonly [input: infer V, options?: unknown]
|
|
73
|
+
? V
|
|
74
|
+
: undefined
|
|
75
|
+
: never
|
|
76
|
+
/** What a function returns. */
|
|
77
|
+
export type OutputOf<Fn> = Fn extends { readonly output: infer O }
|
|
78
|
+
? ResultOf<O>
|
|
79
|
+
: never
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
// `updateMany` (D-133): what an `update` runs, on each row its `where`
|
|
2
|
+
// selects, all or nothing; one write when nothing needs the rows (D-99).
|
|
3
|
+
// `updateManyAndReturn` returns them in `id` order, each as `update` does
|
|
4
|
+
// (D-149).
|
|
5
|
+
import { recordOf } from './access.ts'
|
|
6
|
+
import type { Change, Filter, Order, Row } from './adapter.ts'
|
|
7
|
+
import { InvalidQuery } from './errors.ts'
|
|
8
|
+
import type { Event } from './hooks.ts'
|
|
9
|
+
import { rereads, runHook, settle } from './hooks.ts'
|
|
10
|
+
import type { Operations } from './number-update.ts'
|
|
11
|
+
import { computeOn, operationsOf, inRange, validates } from './number-update.ts'
|
|
12
|
+
import type { Plan as Shaped } from './query-args.ts'
|
|
13
|
+
import { oneKeys } from './query-args.ts'
|
|
14
|
+
import type { Context } from './query-context.ts'
|
|
15
|
+
import { filtered, given, shapedRows } from './query-context.ts'
|
|
16
|
+
import { byIds, entries } from './query-where.ts'
|
|
17
|
+
import { updatePlan } from './query-write.ts'
|
|
18
|
+
import type { ModelInfo } from './schema.ts'
|
|
19
|
+
import type { Commit, Store } from './transaction.ts'
|
|
20
|
+
import type { Draft } from './write-data.ts'
|
|
21
|
+
import { prepared, readData } from './write-data.ts'
|
|
22
|
+
import type { Plan } from './write-plan.ts'
|
|
23
|
+
import { updated } from './write-plan.ts'
|
|
24
|
+
import { checkedWrite, guarded } from './write-target.ts'
|
|
25
|
+
import { writingOf } from './writing.ts'
|
|
26
|
+
import { returned } from './writes.ts'
|
|
27
|
+
|
|
28
|
+
// In id order, so two calls on the same rows wait instead of deadlocking.
|
|
29
|
+
const orderBy: readonly Order[] = [{ field: 'id', direction: 'asc' }]
|
|
30
|
+
|
|
31
|
+
/** The scalar data of the rows, as `update` reads it (D-133 point 2). */
|
|
32
|
+
function scalarData(context: Context, data: unknown): Draft {
|
|
33
|
+
const { models, model } = context
|
|
34
|
+
for (const [name] of entries(data, 'data')) {
|
|
35
|
+
if (model.fields.get(name)?.kind === 'scalar') continue
|
|
36
|
+
if (!model.fields.has(name)) continue
|
|
37
|
+
const path = `data.${name}`
|
|
38
|
+
throw new InvalidQuery(
|
|
39
|
+
path,
|
|
40
|
+
`${path} is a relation, which updateMany does not write.`,
|
|
41
|
+
)
|
|
42
|
+
}
|
|
43
|
+
return readData(models, model, data, false)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** The plan's update, spread to every row `where` selects. */
|
|
47
|
+
function spread(plan: Plan, where: Filter): Change[] {
|
|
48
|
+
return plan.steps.flatMap((step) =>
|
|
49
|
+
step.kind === 'update' ? [{ ...step, where }] : [],
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** A row, its own copy of the data, and the data the rules saw (D-133). */
|
|
54
|
+
type Item = {
|
|
55
|
+
readonly row: Row
|
|
56
|
+
readonly draft: Draft
|
|
57
|
+
readonly sent: Readonly<Draft>
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Rows whose data came out the same, and the operations the write computes. */
|
|
61
|
+
type Group = {
|
|
62
|
+
readonly draft: Draft
|
|
63
|
+
readonly operations: Operations
|
|
64
|
+
readonly items: [Item, ...Item[]]
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const same = (a: Group, b: Omit<Group, 'items'>): boolean => {
|
|
68
|
+
const keys = Object.keys(a.draft)
|
|
69
|
+
return (
|
|
70
|
+
keys.length === Object.keys(b.draft).length &&
|
|
71
|
+
keys.every(
|
|
72
|
+
(key) =>
|
|
73
|
+
Object.hasOwn(b.draft, key) && Object.is(a.draft[key], b.draft[key]),
|
|
74
|
+
) &&
|
|
75
|
+
[...a.operations.keys()].join() === [...b.operations.keys()].join()
|
|
76
|
+
)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* A row's data after its hooks: a number or list they left as computed is
|
|
81
|
+
* computed by the write again, the row being locked, so rows share one
|
|
82
|
+
* statement.
|
|
83
|
+
*/
|
|
84
|
+
function settled(item: Item, operations: Operations): Omit<Group, 'items'> {
|
|
85
|
+
const left = new Map(operations)
|
|
86
|
+
const draft: Draft = { ...item.draft }
|
|
87
|
+
for (const name of operations.keys()) {
|
|
88
|
+
if (Object.is(item.draft[name], item.sent[name])) delete draft[name]
|
|
89
|
+
else left.delete(name)
|
|
90
|
+
}
|
|
91
|
+
return { draft, operations: left }
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** The rows by data; one a hook gave a relation write is planned alone. */
|
|
95
|
+
function grouped(
|
|
96
|
+
model: ModelInfo,
|
|
97
|
+
items: readonly Item[],
|
|
98
|
+
operations: Operations,
|
|
99
|
+
) {
|
|
100
|
+
const groups: Group[] = []
|
|
101
|
+
for (const item of items) {
|
|
102
|
+
const each = settled(item, operations)
|
|
103
|
+
const scalars = Object.keys(each.draft).every(
|
|
104
|
+
(name) => model.fields.get(name)?.kind === 'scalar',
|
|
105
|
+
)
|
|
106
|
+
const found = scalars
|
|
107
|
+
? groups.find((group) => same(group, each))
|
|
108
|
+
: undefined
|
|
109
|
+
if (found === undefined) groups.push({ ...each, items: [item] })
|
|
110
|
+
else found.items.push(item)
|
|
111
|
+
}
|
|
112
|
+
return groups
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Each row through the update rule and hooks, then their writes (D-133):
|
|
117
|
+
* the rows changed, or with `at` as it shapes them.
|
|
118
|
+
*/
|
|
119
|
+
async function lockedRows(
|
|
120
|
+
context: Context,
|
|
121
|
+
store: Store,
|
|
122
|
+
commits: Commit[],
|
|
123
|
+
data: { readonly draft: Draft; readonly operations: Operations },
|
|
124
|
+
where: Filter,
|
|
125
|
+
at: Shaped | undefined,
|
|
126
|
+
): Promise<readonly unknown[]> {
|
|
127
|
+
const { model, caller, scope } = context
|
|
128
|
+
const { operations } = data
|
|
129
|
+
const rows = await store.find({
|
|
130
|
+
model: model.name,
|
|
131
|
+
where,
|
|
132
|
+
orderBy,
|
|
133
|
+
lock: true,
|
|
134
|
+
})
|
|
135
|
+
const items = rows.map((row): Item => {
|
|
136
|
+
const draft: Draft = { ...data.draft }
|
|
137
|
+
computeOn(model, row, draft, operations)
|
|
138
|
+
return { row, draft, sent: { ...draft } }
|
|
139
|
+
})
|
|
140
|
+
// One row refused, none written (D-111).
|
|
141
|
+
for (const { row, sent } of items) {
|
|
142
|
+
scope.access?.allow(model, 'update', { record: row, data: sent })
|
|
143
|
+
}
|
|
144
|
+
if (items.length === 0) return []
|
|
145
|
+
for (const { row, draft } of items) {
|
|
146
|
+
await prepared(model, draft, caller, false)
|
|
147
|
+
const record = recordOf(model, row)
|
|
148
|
+
await runHook(model, 'beforeUpdate', { caller, record, data: draft })
|
|
149
|
+
}
|
|
150
|
+
const events: Event[] = items.map(({ row }) => ({
|
|
151
|
+
model,
|
|
152
|
+
operation: 'update',
|
|
153
|
+
id: row.id,
|
|
154
|
+
before: row,
|
|
155
|
+
}))
|
|
156
|
+
const writing = writingOf(scope, store, events)
|
|
157
|
+
const changes: Change[] = []
|
|
158
|
+
for (const group of grouped(model, items, operations)) {
|
|
159
|
+
const [{ row, sent }] = group.items
|
|
160
|
+
const plan = await updatePlan(
|
|
161
|
+
scope,
|
|
162
|
+
model,
|
|
163
|
+
row.id,
|
|
164
|
+
group.draft,
|
|
165
|
+
sent,
|
|
166
|
+
store,
|
|
167
|
+
group.operations,
|
|
168
|
+
)
|
|
169
|
+
events.push(...plan.events)
|
|
170
|
+
await plan.check(writing)
|
|
171
|
+
const ids = group.items.map((item) => item.row.id)
|
|
172
|
+
const one = ids.length === 1
|
|
173
|
+
changes.push(
|
|
174
|
+
...(one
|
|
175
|
+
? await plan.changes(writing)
|
|
176
|
+
: spread(plan, { id: { in: ids } })),
|
|
177
|
+
)
|
|
178
|
+
}
|
|
179
|
+
const ids = items.map((item) => item.row.id)
|
|
180
|
+
const read = at && {
|
|
181
|
+
model: model.name,
|
|
182
|
+
...at.read,
|
|
183
|
+
where: byIds(ids),
|
|
184
|
+
orderBy,
|
|
185
|
+
}
|
|
186
|
+
// An `after*` hook may change the rows: they are read once it ran (D-88).
|
|
187
|
+
const again = rereads(events)
|
|
188
|
+
const written = await inRange(
|
|
189
|
+
model,
|
|
190
|
+
checkedWrite(store, writing.checks, changes, again ? undefined : read),
|
|
191
|
+
)
|
|
192
|
+
await settle(store, caller, events, commits)
|
|
193
|
+
if (at === undefined || read === undefined) return ids
|
|
194
|
+
const result = again ? await store.find(read) : written
|
|
195
|
+
return shapedRows(context, store, result, at.shape, scope.access)
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Updates the rows `where` selects: their ids, or with `at` the rows as it
|
|
200
|
+
* shapes them, in `id` order. The rows come back even when a read rule
|
|
201
|
+
* would hide them, as `update`'s does (D-86).
|
|
202
|
+
*/
|
|
203
|
+
async function updating(
|
|
204
|
+
context: Context,
|
|
205
|
+
store: Store,
|
|
206
|
+
commits: Commit[],
|
|
207
|
+
found: ReadonlyMap<string, unknown>,
|
|
208
|
+
at?: Shaped,
|
|
209
|
+
): Promise<readonly unknown[]> {
|
|
210
|
+
const { model, caller, scope } = context
|
|
211
|
+
const draft = scalarData(context, found.get('data'))
|
|
212
|
+
const operations = operationsOf(model, draft)
|
|
213
|
+
const where: Filter = (await filtered(context, found, 'update')) ?? {}
|
|
214
|
+
// A relation read beside the UPDATE would see its rows as they were.
|
|
215
|
+
const nests = at?.read.include !== undefined || at?.read.count !== undefined
|
|
216
|
+
if (
|
|
217
|
+
scope.access !== undefined ||
|
|
218
|
+
updated(model) ||
|
|
219
|
+
validates(model, operations) ||
|
|
220
|
+
nests
|
|
221
|
+
) {
|
|
222
|
+
const data = { draft, operations }
|
|
223
|
+
return lockedRows(context, store, commits, data, where, at)
|
|
224
|
+
}
|
|
225
|
+
// Nothing needs the stored rows: one UPDATE, which computes the numbers
|
|
226
|
+
// and lists and returns the rows it changed (D-104).
|
|
227
|
+
await prepared(model, draft, caller, false)
|
|
228
|
+
const plan = await updatePlan(
|
|
229
|
+
scope,
|
|
230
|
+
model,
|
|
231
|
+
'',
|
|
232
|
+
draft,
|
|
233
|
+
draft,
|
|
234
|
+
store,
|
|
235
|
+
operations,
|
|
236
|
+
)
|
|
237
|
+
const write = {
|
|
238
|
+
changes: spread(plan, where),
|
|
239
|
+
read: { model: model.name, ...at?.read, orderBy },
|
|
240
|
+
before: true,
|
|
241
|
+
}
|
|
242
|
+
const rows = await inRange(model, guarded(store.last(write), []))
|
|
243
|
+
if (at === undefined) return rows.map((row) => row.id)
|
|
244
|
+
return shapedRows(context, store, rows, at.shape, scope.access)
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export async function updateMany(
|
|
248
|
+
context: Context,
|
|
249
|
+
store: Store,
|
|
250
|
+
commits: Commit[],
|
|
251
|
+
args: unknown,
|
|
252
|
+
): Promise<{ readonly count: number }> {
|
|
253
|
+
const found = given(args, context.model.name, ['where', 'data'], ['data'])
|
|
254
|
+
const ids = await updating(context, store, commits, found)
|
|
255
|
+
return { count: ids.length }
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
export async function updateManyAndReturn(
|
|
259
|
+
context: Context,
|
|
260
|
+
store: Store,
|
|
261
|
+
commits: Commit[],
|
|
262
|
+
args: unknown,
|
|
263
|
+
): Promise<readonly unknown[]> {
|
|
264
|
+
const keys = ['where', 'data', ...oneKeys]
|
|
265
|
+
const found = given(args, context.model.name, keys, ['data'])
|
|
266
|
+
const at = await returned(context, found)
|
|
267
|
+
return updating(context, store, commits, found, at)
|
|
268
|
+
}
|
package/src/uuid.ts
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// UUIDv7 ids (D-22, RFC 9562): a millisecond timestamp, then a 12-bit counter
|
|
2
|
+
// in `rand_a` so that ids made in the same millisecond still sort in order.
|
|
3
|
+
|
|
4
|
+
let lastTime = 0
|
|
5
|
+
let counter = 0
|
|
6
|
+
|
|
7
|
+
export function uuidv7(): string {
|
|
8
|
+
let time = Date.now()
|
|
9
|
+
if (time > lastTime) {
|
|
10
|
+
lastTime = time
|
|
11
|
+
counter = 0
|
|
12
|
+
} else {
|
|
13
|
+
counter += 1
|
|
14
|
+
if (counter > 0xfff) {
|
|
15
|
+
lastTime += 1
|
|
16
|
+
counter = 0
|
|
17
|
+
}
|
|
18
|
+
time = lastTime
|
|
19
|
+
}
|
|
20
|
+
const bytes = new Uint8Array(16)
|
|
21
|
+
crypto.getRandomValues(bytes)
|
|
22
|
+
for (let index = 5; index >= 0; index -= 1) {
|
|
23
|
+
bytes[index] = time % 256
|
|
24
|
+
time = Math.floor(time / 256)
|
|
25
|
+
}
|
|
26
|
+
bytes[6] = 0x70 | (counter >> 8)
|
|
27
|
+
bytes[7] = counter & 0xff
|
|
28
|
+
bytes[8] = 0x80 | ((bytes[8] ?? 0) & 0x3f)
|
|
29
|
+
const hex = Array.from(bytes, (byte) => byte.toString(16).padStart(2, '0'))
|
|
30
|
+
return [
|
|
31
|
+
hex.slice(0, 4),
|
|
32
|
+
hex.slice(4, 6),
|
|
33
|
+
hex.slice(6, 8),
|
|
34
|
+
hex.slice(8, 10),
|
|
35
|
+
hex.slice(10, 16),
|
|
36
|
+
]
|
|
37
|
+
.map((part) => part.join(''))
|
|
38
|
+
.join('-')
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const made = new WeakMap<object, string>()
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* The id of the row a nested `create` makes from `data`: made once, so its
|
|
45
|
+
* relation's field rule sees it before the row is planned (D-128).
|
|
46
|
+
*/
|
|
47
|
+
export function madeId(data: object): string {
|
|
48
|
+
const id = made.get(data) ?? uuidv7()
|
|
49
|
+
made.set(data, id)
|
|
50
|
+
return id
|
|
51
|
+
}
|