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
package/src/cascade.ts
ADDED
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
// What a `delete` does to the rows that reference the row it deletes
|
|
2
|
+
// (D-54, D-90): `cascade` deletes them, `setNull` empties their key,
|
|
3
|
+
// `restrict` refuses the delete.
|
|
4
|
+
import type { Change, Filter, Row } from './adapter.ts'
|
|
5
|
+
import { PublicError } from './errors.ts'
|
|
6
|
+
import { hasHook, redirectOf } from './hooks.ts'
|
|
7
|
+
import type { Models } from './query-where.ts'
|
|
8
|
+
import type { ModelInfo } from './schema.ts'
|
|
9
|
+
import type { Checked, Reads } from './write-plan.ts'
|
|
10
|
+
import { updated } from './write-plan.ts'
|
|
11
|
+
|
|
12
|
+
/** A row the delete removes, or redirects (D-89). */
|
|
13
|
+
export type Doomed = {
|
|
14
|
+
readonly model: ModelInfo
|
|
15
|
+
readonly row: Row
|
|
16
|
+
readonly redirected: boolean
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Rows of `model` whose key `field` the delete empties. */
|
|
20
|
+
export type Nulled = {
|
|
21
|
+
readonly model: ModelInfo
|
|
22
|
+
readonly field: string
|
|
23
|
+
readonly rows: readonly Row[]
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export type Cascade = {
|
|
27
|
+
/** The deepest first, the rows the caller deletes last. */
|
|
28
|
+
readonly deleted: readonly Doomed[]
|
|
29
|
+
readonly nulled: readonly Nulled[]
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
type Reference = { readonly model: ModelInfo; readonly field: string }
|
|
33
|
+
|
|
34
|
+
/** A row's key the cascade leaves: the write empties it itself (D-138). */
|
|
35
|
+
export type Except = {
|
|
36
|
+
readonly model: string
|
|
37
|
+
readonly field: string
|
|
38
|
+
readonly id: string
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** The stored `one` relations to `target`, each with its `onDelete`. */
|
|
42
|
+
function referencesTo(
|
|
43
|
+
models: Models,
|
|
44
|
+
target: string,
|
|
45
|
+
): readonly (Reference & { readonly onDelete: string })[] {
|
|
46
|
+
const references: (Reference & { readonly onDelete: string })[] = []
|
|
47
|
+
for (const model of models.values()) {
|
|
48
|
+
for (const field of model.fields.values()) {
|
|
49
|
+
if (field.kind !== 'one' || !field.stored || field.target !== target) {
|
|
50
|
+
continue
|
|
51
|
+
}
|
|
52
|
+
references.push({ model, field: field.name, onDelete: field.onDelete })
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return references
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const keyOf = (model: ModelInfo, id: string): string => `${model.name}:${id}`
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Loads every row deleting `deleting` reaches, level by level, as stored: no
|
|
62
|
+
* `beforeFind`, no access rules (D-54, D-90). Fails with
|
|
63
|
+
* `RELATION_RESTRICTED` when a row the delete leaves references a deleted one.
|
|
64
|
+
* A redirected row stays: it cascades nothing (D-89, D-90).
|
|
65
|
+
*/
|
|
66
|
+
export async function cascadeOf(
|
|
67
|
+
models: Models,
|
|
68
|
+
model: ModelInfo,
|
|
69
|
+
deleting: readonly Row[],
|
|
70
|
+
store: Reads,
|
|
71
|
+
except?: Except,
|
|
72
|
+
): Promise<Cascade> {
|
|
73
|
+
const redirected = redirectOf(model) !== undefined
|
|
74
|
+
const roots = deleting.map((row) => ({ model, row, redirected }))
|
|
75
|
+
if (redirected) return { deleted: roots, nulled: [] }
|
|
76
|
+
const deleted = new Map<string, Doomed>()
|
|
77
|
+
for (const root of roots) deleted.set(keyOf(model, root.row.id), root)
|
|
78
|
+
const restricted: (Reference & {
|
|
79
|
+
readonly id: string
|
|
80
|
+
readonly to: string
|
|
81
|
+
})[] = []
|
|
82
|
+
const nulled = new Map<string, Reference & { readonly rows: Row[] }>()
|
|
83
|
+
let level: readonly Doomed[] = roots
|
|
84
|
+
while (level.length > 0) {
|
|
85
|
+
const next: Doomed[] = []
|
|
86
|
+
const ids = new Map<ModelInfo, string[]>()
|
|
87
|
+
for (const doomed of level) {
|
|
88
|
+
ids.set(doomed.model, [...(ids.get(doomed.model) ?? []), doomed.row.id])
|
|
89
|
+
}
|
|
90
|
+
for (const [target, targets] of ids) {
|
|
91
|
+
for (const reference of referencesTo(models, target.name)) {
|
|
92
|
+
const { model: from, field, onDelete } = reference
|
|
93
|
+
const rows = await store.find({
|
|
94
|
+
model: from.name,
|
|
95
|
+
where: { [field]: { is: { id: { in: targets } } } },
|
|
96
|
+
lock: true,
|
|
97
|
+
})
|
|
98
|
+
for (const found of rows) {
|
|
99
|
+
const left =
|
|
100
|
+
except?.model === from.name &&
|
|
101
|
+
except.field === field &&
|
|
102
|
+
except.id === found.id
|
|
103
|
+
if (left) continue
|
|
104
|
+
if (onDelete === 'restrict') {
|
|
105
|
+
restricted.push({
|
|
106
|
+
model: from,
|
|
107
|
+
field,
|
|
108
|
+
id: found.id,
|
|
109
|
+
to: target.name,
|
|
110
|
+
})
|
|
111
|
+
} else if (onDelete === 'setNull') {
|
|
112
|
+
const key = `${from.name}.${field}`
|
|
113
|
+
const group = nulled.get(key) ?? { model: from, field, rows: [] }
|
|
114
|
+
// A key holds one target, deleted once: no row comes twice.
|
|
115
|
+
group.rows.push(found)
|
|
116
|
+
nulled.set(key, group)
|
|
117
|
+
} else if (!deleted.has(keyOf(from, found.id))) {
|
|
118
|
+
// Not redirected: a row that stayed would keep a key to a
|
|
119
|
+
// deleted one (D-90).
|
|
120
|
+
const doomed = { model: from, row: found, redirected: false }
|
|
121
|
+
deleted.set(keyOf(from, found.id), doomed)
|
|
122
|
+
next.push(doomed)
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
level = next
|
|
128
|
+
}
|
|
129
|
+
// A row the cascade removes no longer references anything.
|
|
130
|
+
const stays = (from: ModelInfo, id: string): boolean =>
|
|
131
|
+
!deleted.has(keyOf(from, id))
|
|
132
|
+
for (const { model: from, field, id, to } of restricted) {
|
|
133
|
+
if (!stays(from, id)) continue
|
|
134
|
+
throw new PublicError(
|
|
135
|
+
'RELATION_RESTRICTED',
|
|
136
|
+
`${from.name}.${field} references a ${to} the delete removes.`,
|
|
137
|
+
{ details: { model: from.name, field } },
|
|
138
|
+
)
|
|
139
|
+
}
|
|
140
|
+
return {
|
|
141
|
+
// The roots last, in the order they were read.
|
|
142
|
+
deleted: [...deleted.values()]
|
|
143
|
+
.slice(roots.length)
|
|
144
|
+
.toReversed()
|
|
145
|
+
.concat(roots),
|
|
146
|
+
nulled: [...nulled.values()].map((group) => ({
|
|
147
|
+
...group,
|
|
148
|
+
rows: group.rows.filter((found) => stays(group.model, found.id)),
|
|
149
|
+
})),
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** Whether deleting a row of `model` runs code of its own. */
|
|
154
|
+
const deleteRuns = (model: ModelInfo): boolean =>
|
|
155
|
+
redirectOf(model) !== undefined ||
|
|
156
|
+
hasHook(model, 'beforeDelete') ||
|
|
157
|
+
hasHook(model, 'afterDelete') ||
|
|
158
|
+
hasHook(model, 'afterDeleteCommit')
|
|
159
|
+
|
|
160
|
+
const anyOf = (filters: readonly Filter[]): Filter =>
|
|
161
|
+
filters.length === 1 && filters[0] !== undefined
|
|
162
|
+
? filters[0]
|
|
163
|
+
: { OR: filters }
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* The changes and checks of a delete whose reach runs no code, so no row is
|
|
167
|
+
* loaded (D-102): the root goes by `where`, each level by the rows the level
|
|
168
|
+
* above removed, and `setNull` empties the keys naming them. `undefined` when
|
|
169
|
+
* a reached model has delete hooks or a redirect, a `setNull` one update
|
|
170
|
+
* hooks, a cascade comes back to a model it left, or a model is both deleted
|
|
171
|
+
* and emptied, or emptied twice.
|
|
172
|
+
*/
|
|
173
|
+
export function cascadeWhere(
|
|
174
|
+
models: Models,
|
|
175
|
+
model: ModelInfo,
|
|
176
|
+
where: Filter,
|
|
177
|
+
): { readonly changes: Change[]; readonly checks: Checked[] } | undefined {
|
|
178
|
+
/**
|
|
179
|
+
* Each deleted model's filters from the root, its longest path from the
|
|
180
|
+
* root, and the relations to the deleted models that reach it.
|
|
181
|
+
*/
|
|
182
|
+
type Level = {
|
|
183
|
+
readonly filters: Filter[]
|
|
184
|
+
depth: number
|
|
185
|
+
readonly parents: Map<string, Reference>
|
|
186
|
+
}
|
|
187
|
+
const deleted = new Map<string, Level>()
|
|
188
|
+
/** The rows each `setNull` or `restrict` relation reaches, by relation. */
|
|
189
|
+
const reached = new Map<
|
|
190
|
+
string,
|
|
191
|
+
Reference & {
|
|
192
|
+
readonly to: string
|
|
193
|
+
readonly restricts: boolean
|
|
194
|
+
readonly filters: Filter[]
|
|
195
|
+
}
|
|
196
|
+
>()
|
|
197
|
+
const reach = (
|
|
198
|
+
reference: Reference & { readonly to: string; readonly restricts: boolean },
|
|
199
|
+
filter: Filter,
|
|
200
|
+
): void => {
|
|
201
|
+
const key = `${reference.model.name}.${reference.field}`
|
|
202
|
+
const group = reached.get(key) ?? { ...reference, filters: [] }
|
|
203
|
+
group.filters.push(filter)
|
|
204
|
+
reached.set(key, group)
|
|
205
|
+
}
|
|
206
|
+
const visit = (
|
|
207
|
+
at: ModelInfo,
|
|
208
|
+
filter: Filter,
|
|
209
|
+
path: string[],
|
|
210
|
+
parent?: Reference,
|
|
211
|
+
): boolean => {
|
|
212
|
+
if (deleteRuns(at)) return false
|
|
213
|
+
const entry: Level = deleted.get(at.name) ?? {
|
|
214
|
+
filters: [],
|
|
215
|
+
depth: 0,
|
|
216
|
+
parents: new Map(),
|
|
217
|
+
}
|
|
218
|
+
entry.filters.push(filter)
|
|
219
|
+
entry.depth = Math.max(entry.depth, path.length)
|
|
220
|
+
if (parent !== undefined) {
|
|
221
|
+
entry.parents.set(`${parent.model.name}.${parent.field}`, parent)
|
|
222
|
+
}
|
|
223
|
+
deleted.set(at.name, entry)
|
|
224
|
+
const below = [...path, at.name]
|
|
225
|
+
for (const { model: from, field, onDelete } of referencesTo(
|
|
226
|
+
models,
|
|
227
|
+
at.name,
|
|
228
|
+
)) {
|
|
229
|
+
const child = { [field]: { is: filter } }
|
|
230
|
+
if (onDelete === 'cascade') {
|
|
231
|
+
const edge = { model: at, field }
|
|
232
|
+
if (below.includes(from.name) || !visit(from, child, below, edge)) {
|
|
233
|
+
return false
|
|
234
|
+
}
|
|
235
|
+
} else if (onDelete === 'setNull' && updated(from)) {
|
|
236
|
+
return false
|
|
237
|
+
} else {
|
|
238
|
+
const restricts = onDelete === 'restrict'
|
|
239
|
+
reach({ model: from, field, to: at.name, restricts }, child)
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
return true
|
|
243
|
+
}
|
|
244
|
+
if (!visit(model, where, [])) return undefined
|
|
245
|
+
const groups = [...reached.values()]
|
|
246
|
+
const nulls = groups.filter((group) => !group.restricts)
|
|
247
|
+
const emptied = new Set(nulls.map((group) => group.model.name))
|
|
248
|
+
// PostgreSQL gives two changes of the same rows in one statement no order.
|
|
249
|
+
const twice =
|
|
250
|
+
emptied.size < nulls.length ||
|
|
251
|
+
[...emptied].some((name) => deleted.has(name))
|
|
252
|
+
if (twice) return undefined
|
|
253
|
+
// A row the cascade removes is not refusing.
|
|
254
|
+
const left = (from: ModelInfo, filters: readonly Filter[]): Filter => {
|
|
255
|
+
const gone = deleted.get(from.name)
|
|
256
|
+
return gone === undefined
|
|
257
|
+
? anyOf(filters)
|
|
258
|
+
: { AND: [anyOf(filters), { NOT: [anyOf(gone.filters)] }] }
|
|
259
|
+
}
|
|
260
|
+
const checks = groups
|
|
261
|
+
.filter((group) => group.restricts)
|
|
262
|
+
.map((group) => ({
|
|
263
|
+
check: {
|
|
264
|
+
model: group.model.name,
|
|
265
|
+
where: left(group.model, group.filters),
|
|
266
|
+
count: 0,
|
|
267
|
+
},
|
|
268
|
+
message: `${group.model.name}.${group.field} references a ${group.to} the delete removes.`,
|
|
269
|
+
restricted: { model: group.model.name, field: group.field },
|
|
270
|
+
}))
|
|
271
|
+
// A level after the levels above it: its parents have a lower depth.
|
|
272
|
+
const levels = [...deleted].toSorted(([, a], [, b]) => a.depth - b.depth)
|
|
273
|
+
const removedBy = (field: string, name: string): Filter => ({
|
|
274
|
+
[field]: { removedBy: levels.findIndex(([each]) => each === name) },
|
|
275
|
+
})
|
|
276
|
+
const changes: Change[] = [
|
|
277
|
+
...levels.map(([name, { parents }]) => ({
|
|
278
|
+
kind: 'delete' as const,
|
|
279
|
+
model: name,
|
|
280
|
+
where:
|
|
281
|
+
parents.size === 0
|
|
282
|
+
? where
|
|
283
|
+
: anyOf(
|
|
284
|
+
[...parents.values()].map((edge) =>
|
|
285
|
+
removedBy(edge.field, edge.model.name),
|
|
286
|
+
),
|
|
287
|
+
),
|
|
288
|
+
})),
|
|
289
|
+
...nulls.map((group) => ({
|
|
290
|
+
kind: 'update' as const,
|
|
291
|
+
model: group.model.name,
|
|
292
|
+
where: removedBy(group.field, group.to),
|
|
293
|
+
set: { [group.field]: null, updatedAt: new Date(Date.now()) },
|
|
294
|
+
})),
|
|
295
|
+
]
|
|
296
|
+
return { changes, checks }
|
|
297
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// `ultimabase.config.ts` (D-105): where the commands find the application.
|
|
2
|
+
import { existsSync } from 'node:fs'
|
|
3
|
+
import { basename, join, resolve } from 'node:path'
|
|
4
|
+
import { pathToFileURL } from 'node:url'
|
|
5
|
+
import type { Internals } from './app-internals.ts'
|
|
6
|
+
import { internalsOf } from './app-internals.ts'
|
|
7
|
+
import { Refused } from './errors.ts'
|
|
8
|
+
|
|
9
|
+
const names = [
|
|
10
|
+
'ultimabase.config.ts',
|
|
11
|
+
'ultimabase.config.mts',
|
|
12
|
+
'ultimabase.config.js',
|
|
13
|
+
'ultimabase.config.mjs',
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
/** The config file `--config` names, or the first one in `cwd`. */
|
|
17
|
+
export function configPath(cwd: string, given: string | undefined): string {
|
|
18
|
+
if (given !== undefined) {
|
|
19
|
+
const path = resolve(cwd, given)
|
|
20
|
+
if (!existsSync(path)) throw new Refused(`There is no ${path}.`)
|
|
21
|
+
return path
|
|
22
|
+
}
|
|
23
|
+
const found = names.map((name) => join(cwd, name)).find(existsSync)
|
|
24
|
+
if (found === undefined) {
|
|
25
|
+
throw new Refused(
|
|
26
|
+
`There is no ultimabase.config.ts in ${cwd}: write one that default-exports defineApp(...), or pass --config <path>.`,
|
|
27
|
+
)
|
|
28
|
+
}
|
|
29
|
+
return found
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** The application the config file default-exports. */
|
|
33
|
+
export async function load(path: string): Promise<Internals> {
|
|
34
|
+
const module: unknown = await import(pathToFileURL(path).href)
|
|
35
|
+
const app =
|
|
36
|
+
typeof module === 'object' && module !== null
|
|
37
|
+
? internalsOf(Reflect.get(module, 'default'))
|
|
38
|
+
: undefined
|
|
39
|
+
if (app === undefined) {
|
|
40
|
+
throw new Refused(
|
|
41
|
+
`${basename(path)} does not default-export defineApp(...).`,
|
|
42
|
+
)
|
|
43
|
+
}
|
|
44
|
+
return app
|
|
45
|
+
}
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
// `migrate deploy`, `migrate status` and `reset` (D-105): the migration
|
|
2
|
+
// files against those the database applied.
|
|
3
|
+
import type { Migration, Migrations, Stored } from './adapter.ts'
|
|
4
|
+
import { MigrationFailed } from './adapter.ts'
|
|
5
|
+
import type { Internals } from './app-internals.ts'
|
|
6
|
+
import { create, read } from './cli-migrate.ts'
|
|
7
|
+
import { kernelLogger, schemaOf } from './lifecycle.ts'
|
|
8
|
+
import { Refused } from './errors.ts'
|
|
9
|
+
import { ahead, hashOf, held, migrationsOf } from './migrate.ts'
|
|
10
|
+
|
|
11
|
+
const upToDate = 'The database is up to date.'
|
|
12
|
+
|
|
13
|
+
const production = (): boolean => process.env['NODE_ENV'] === 'production'
|
|
14
|
+
|
|
15
|
+
/** How a development database gets back to what the files build. */
|
|
16
|
+
const rebuild = (): readonly string[] =>
|
|
17
|
+
production()
|
|
18
|
+
? []
|
|
19
|
+
: [
|
|
20
|
+
'Restore the dump you test on, or rebuild this database from the migrations with `npx ultimabase reset`.',
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
const refused = (...lines: readonly string[]): Refused =>
|
|
24
|
+
new Refused([...lines, ...rebuild()].join('\n'))
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* The files the database has not applied, and the files it applied after
|
|
28
|
+
* the last one, which newer code wrote; refuses a file edited or removed.
|
|
29
|
+
*/
|
|
30
|
+
function planOf(
|
|
31
|
+
stored: Stored | undefined,
|
|
32
|
+
files: readonly Migration[],
|
|
33
|
+
): { readonly pending: readonly Migration[]; readonly later: string[] } {
|
|
34
|
+
const applied = stored?.applied ?? []
|
|
35
|
+
const last = files.at(-1)?.name ?? ''
|
|
36
|
+
const later: string[] = []
|
|
37
|
+
for (const { name, checksum } of applied) {
|
|
38
|
+
const file = files.find((given) => given.name === name)
|
|
39
|
+
if (file === undefined && name > last) {
|
|
40
|
+
later.push(name)
|
|
41
|
+
} else if (file === undefined) {
|
|
42
|
+
throw refused(
|
|
43
|
+
`${name} was applied to the database, and migrations/ no longer holds it: restore it from version control.`,
|
|
44
|
+
)
|
|
45
|
+
} else if (file.checksum !== checksum) {
|
|
46
|
+
throw refused(
|
|
47
|
+
`${name} was edited after it was applied to the database: restore it, and write the change with ${create}.`,
|
|
48
|
+
)
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
const pending = files.filter(
|
|
52
|
+
(file) => !applied.some((given) => given.name === file.name),
|
|
53
|
+
)
|
|
54
|
+
const [first] = pending
|
|
55
|
+
if (first !== undefined && later.length > 0) {
|
|
56
|
+
throw refused(
|
|
57
|
+
`${first.name} is pending, and the database applied ${later.join(', ')} after it, which migrations/ does not hold: deploy the code that holds them.`,
|
|
58
|
+
)
|
|
59
|
+
}
|
|
60
|
+
return { pending, later }
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Why the files do not lead to the models whose hash is `hash`, if so. */
|
|
64
|
+
function behind(files: readonly Migration[], hash: string): string | undefined {
|
|
65
|
+
const last = files.at(-1)
|
|
66
|
+
if (last === undefined) {
|
|
67
|
+
return `migrations/ holds no migration: run ${create} first.`
|
|
68
|
+
}
|
|
69
|
+
if (last.schema === hash) return undefined
|
|
70
|
+
return `The models changed after ${last.name} was written: run ${create}, then deploy again.`
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const syncedSince = (): Refused =>
|
|
74
|
+
refused(
|
|
75
|
+
'Development sync changed this database since its last deploy: deploy applies migrations only to a database they built.',
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
/** Applies `files`; a refused one names those applied before it. */
|
|
79
|
+
async function deployed(
|
|
80
|
+
migrations: Migrations,
|
|
81
|
+
files: readonly Migration[],
|
|
82
|
+
to: unknown,
|
|
83
|
+
again: string,
|
|
84
|
+
): Promise<readonly string[]> {
|
|
85
|
+
const lines = files.map((file) => `Applied ${file.name}.`)
|
|
86
|
+
try {
|
|
87
|
+
await migrations.apply({ kind: 'deploy', files, to })
|
|
88
|
+
} catch (error) {
|
|
89
|
+
if (!(error instanceof MigrationFailed)) throw error
|
|
90
|
+
const done = files.findIndex((file) => file.name === error.at)
|
|
91
|
+
throw new Refused(
|
|
92
|
+
[
|
|
93
|
+
...lines.slice(0, done),
|
|
94
|
+
`${error.at} was not applied: ${error.reason}`,
|
|
95
|
+
files[done]?.transaction === false
|
|
96
|
+
? `It ran without a transaction, so nothing was rolled back: undo what it left, such as an invalid index, then ${again}.`
|
|
97
|
+
: `The database rolled it back: fix it, then ${again}.`,
|
|
98
|
+
].join('\n'),
|
|
99
|
+
)
|
|
100
|
+
}
|
|
101
|
+
return lines
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** Applies the pending migrations, then stores the models' snapshot. */
|
|
105
|
+
export async function migrateDeploy(
|
|
106
|
+
{ options }: Internals,
|
|
107
|
+
dir: string,
|
|
108
|
+
): Promise<readonly string[]> {
|
|
109
|
+
const { database } = options
|
|
110
|
+
const migrations = migrationsOf(database)
|
|
111
|
+
const schema = schemaOf(options)
|
|
112
|
+
const to = migrations.snapshot(schema)
|
|
113
|
+
const hash = hashOf(to)
|
|
114
|
+
const { files } = await read(dir)
|
|
115
|
+
const late = behind(files, hash)
|
|
116
|
+
if (late !== undefined) throw new Refused(late)
|
|
117
|
+
await database.start(schema, kernelLogger(options))
|
|
118
|
+
try {
|
|
119
|
+
const stored = await migrations.stored()
|
|
120
|
+
if (stored?.by === 'sync') throw syncedSince()
|
|
121
|
+
const { pending, later } = planOf(stored, files)
|
|
122
|
+
// The code was rolled back: its migrations are applied, and more.
|
|
123
|
+
if (later.length > 0) return [ahead(later)]
|
|
124
|
+
const current =
|
|
125
|
+
stored?.snapshot !== undefined && hashOf(stored.snapshot) === hash
|
|
126
|
+
if (pending.length === 0 && current) return [upToDate]
|
|
127
|
+
const lines = await deployed(migrations, pending, to, 'deploy again')
|
|
128
|
+
return lines.length === 0 ? [upToDate] : lines
|
|
129
|
+
} finally {
|
|
130
|
+
await database.stop()
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** What a deploy would do or refuse; `ok` when it has nothing to do. */
|
|
135
|
+
export async function migrateStatus(
|
|
136
|
+
{ options }: Internals,
|
|
137
|
+
dir: string,
|
|
138
|
+
): Promise<{ readonly lines: readonly string[]; readonly ok: boolean }> {
|
|
139
|
+
const { database } = options
|
|
140
|
+
const migrations = migrationsOf(database)
|
|
141
|
+
const schema = schemaOf(options)
|
|
142
|
+
const hash = hashOf(migrations.snapshot(schema))
|
|
143
|
+
const { files } = await read(dir)
|
|
144
|
+
await database.start(schema, kernelLogger(options))
|
|
145
|
+
try {
|
|
146
|
+
const stored = await migrations.stored()
|
|
147
|
+
const late = behind(files, hash)
|
|
148
|
+
const { pending, later } = planOf(stored, files)
|
|
149
|
+
const lines = [
|
|
150
|
+
...(late === undefined ? [] : [late]),
|
|
151
|
+
...(stored?.by === 'sync' ? [syncedSince().message] : []),
|
|
152
|
+
...pending.map((file) => `${file.name} is pending.`),
|
|
153
|
+
]
|
|
154
|
+
if (lines.length > 0) return { lines, ok: false }
|
|
155
|
+
if (later.length > 0) return { lines: [ahead(later)], ok: true }
|
|
156
|
+
if (stored?.snapshot !== undefined && hashOf(stored.snapshot) === hash) {
|
|
157
|
+
return { lines: [upToDate], ok: true }
|
|
158
|
+
}
|
|
159
|
+
return {
|
|
160
|
+
lines: [
|
|
161
|
+
"The database's snapshot is not the models': run `npx ultimabase migrate deploy`.",
|
|
162
|
+
],
|
|
163
|
+
ok: false,
|
|
164
|
+
}
|
|
165
|
+
} finally {
|
|
166
|
+
await database.stop()
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/** Drops every table; one another session holds stops the reset. */
|
|
171
|
+
async function dropAll(migrations: Migrations): Promise<readonly string[]> {
|
|
172
|
+
try {
|
|
173
|
+
return await migrations.reset()
|
|
174
|
+
} catch (error) {
|
|
175
|
+
if (!(error instanceof MigrationFailed && error.locked)) throw error
|
|
176
|
+
throw new Refused(
|
|
177
|
+
[
|
|
178
|
+
'UltimaBase did not reset the database: another session held a table.',
|
|
179
|
+
` ${error.at};`,
|
|
180
|
+
` -- ${error.reason}`,
|
|
181
|
+
held,
|
|
182
|
+
].join('\n'),
|
|
183
|
+
)
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Drops a development database's tables, then applies every migration; one
|
|
189
|
+
* on another machine only with `force`.
|
|
190
|
+
*/
|
|
191
|
+
export async function reset(
|
|
192
|
+
{ options }: Internals,
|
|
193
|
+
dir: string,
|
|
194
|
+
force: boolean,
|
|
195
|
+
): Promise<readonly string[]> {
|
|
196
|
+
if (production()) {
|
|
197
|
+
throw new Refused(
|
|
198
|
+
'reset rebuilds only a development database: it is refused in production.',
|
|
199
|
+
)
|
|
200
|
+
}
|
|
201
|
+
const { database } = options
|
|
202
|
+
const migrations = migrationsOf(database)
|
|
203
|
+
if (migrations.remote !== undefined && !force) {
|
|
204
|
+
throw new Refused(
|
|
205
|
+
`reset drops every table, and ${migrations.remote} is not this machine: pass --force if it holds a development database.`,
|
|
206
|
+
)
|
|
207
|
+
}
|
|
208
|
+
const schema = schemaOf(options)
|
|
209
|
+
const { files, snapshot } = await read(dir)
|
|
210
|
+
await database.start(schema, kernelLogger(options))
|
|
211
|
+
try {
|
|
212
|
+
const dropped = await dropAll(migrations)
|
|
213
|
+
const lines = [
|
|
214
|
+
dropped.length === 0
|
|
215
|
+
? 'The database held no table.'
|
|
216
|
+
: `Dropped ${dropped.join(', ')}.`,
|
|
217
|
+
]
|
|
218
|
+
const last = files.at(-1)
|
|
219
|
+
if (last === undefined) {
|
|
220
|
+
return [
|
|
221
|
+
...lines,
|
|
222
|
+
'migrations/ holds no migration: the next start builds the tables.',
|
|
223
|
+
]
|
|
224
|
+
}
|
|
225
|
+
lines.push(...(await deployed(migrations, files, snapshot, 'reset again')))
|
|
226
|
+
if (behind(files, hashOf(migrations.snapshot(schema))) !== undefined) {
|
|
227
|
+
lines.push(
|
|
228
|
+
`The models changed after ${last.name} was written: the next start syncs them.`,
|
|
229
|
+
)
|
|
230
|
+
}
|
|
231
|
+
return lines
|
|
232
|
+
} finally {
|
|
233
|
+
await database.stop()
|
|
234
|
+
}
|
|
235
|
+
}
|