ultimabase 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/README.md +4 -2
- package/dist/access.d.ts +62 -0
- package/dist/access.d.ts.map +1 -0
- package/dist/access.js +258 -0
- package/dist/adapter-errors.d.ts +43 -0
- package/dist/adapter-errors.d.ts.map +1 -0
- package/dist/adapter-errors.js +72 -0
- package/dist/adapter-json.d.ts +20 -0
- package/dist/adapter-json.d.ts.map +1 -0
- package/dist/adapter-json.js +2 -0
- package/dist/adapter-migrations.d.ts +90 -0
- package/dist/adapter-migrations.d.ts.map +1 -0
- package/dist/adapter-migrations.js +1 -0
- package/dist/adapter.d.ts +333 -0
- package/dist/adapter.d.ts.map +1 -0
- package/dist/adapter.js +5 -0
- package/dist/aggregate.d.ts +31 -0
- package/dist/aggregate.d.ts.map +1 -0
- package/dist/aggregate.js +99 -0
- package/dist/app-internals.d.ts +10 -0
- package/dist/app-internals.d.ts.map +1 -0
- package/dist/app-internals.js +12 -0
- package/dist/app-options.d.ts +22 -0
- package/dist/app-options.d.ts.map +1 -0
- package/dist/app-options.js +3 -0
- package/dist/cascade.d.ts +47 -0
- package/dist/cascade.d.ts.map +1 -0
- package/dist/cascade.js +209 -0
- package/dist/cli-config.d.ts +6 -0
- package/dist/cli-config.d.ts.map +1 -0
- package/dist/cli-config.js +45 -0
- package/dist/cli-deploy.d.ts +14 -0
- package/dist/cli-deploy.d.ts.map +1 -0
- package/dist/cli-deploy.js +193 -0
- package/dist/cli-main.d.ts +16 -0
- package/dist/cli-main.d.ts.map +1 -0
- package/dist/cli-main.js +151 -0
- package/dist/cli-migrate.d.ts +20 -0
- package/dist/cli-migrate.d.ts.map +1 -0
- package/dist/cli-migrate.js +105 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +4 -0
- package/dist/complete.d.ts +13 -0
- package/dist/complete.d.ts.map +1 -0
- package/dist/complete.js +60 -0
- package/dist/console-logger.d.ts +3 -0
- package/dist/console-logger.d.ts.map +1 -0
- package/dist/console-logger.js +62 -0
- package/dist/create-many.d.ts +7 -0
- package/dist/create-many.d.ts.map +1 -0
- package/dist/create-many.js +88 -0
- package/dist/decimal.d.ts +23 -0
- package/dist/decimal.d.ts.map +1 -0
- package/dist/decimal.js +50 -0
- package/dist/define-app.d.ts +59 -0
- package/dist/define-app.d.ts.map +1 -0
- package/dist/define-app.js +5 -0
- package/dist/define-enum.d.ts +6 -0
- package/dist/define-enum.d.ts.map +1 -0
- package/dist/define-enum.js +18 -0
- package/dist/define-function.d.ts +25 -0
- package/dist/define-function.d.ts.map +1 -0
- package/dist/define-function.js +111 -0
- package/dist/define-model.d.ts +48 -0
- package/dist/define-model.d.ts.map +1 -0
- package/dist/define-model.js +85 -0
- package/dist/definition.d.ts +179 -0
- package/dist/definition.d.ts.map +1 -0
- package/dist/definition.js +165 -0
- package/dist/delete-many.d.ts +13 -0
- package/dist/delete-many.d.ts.map +1 -0
- package/dist/delete-many.js +58 -0
- package/dist/engine.d.ts +16 -0
- package/dist/engine.d.ts.map +1 -0
- package/dist/engine.js +174 -0
- package/dist/errors.d.ts +50 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +43 -0
- package/dist/exact.d.ts +20 -0
- package/dist/exact.d.ts.map +1 -0
- package/dist/exact.js +1 -0
- package/dist/feed.d.ts +6 -0
- package/dist/feed.d.ts.map +1 -0
- package/dist/feed.js +33 -0
- package/dist/field-codec.d.ts +11 -0
- package/dist/field-codec.d.ts.map +1 -0
- package/dist/field-codec.js +48 -0
- package/dist/field-rules.d.ts +57 -0
- package/dist/field-rules.d.ts.map +1 -0
- package/dist/field-rules.js +1 -0
- package/dist/field-type.d.ts +51 -0
- package/dist/field-type.d.ts.map +1 -0
- package/dist/field-type.js +106 -0
- package/dist/fields.d.ts +295 -0
- package/dist/fields.d.ts.map +1 -0
- package/dist/fields.js +1 -0
- package/dist/function.d.ts +126 -0
- package/dist/function.d.ts.map +1 -0
- package/dist/function.js +1 -0
- package/dist/functions.d.ts +18 -0
- package/dist/functions.d.ts.map +1 -0
- package/dist/functions.js +99 -0
- package/dist/group-by.d.ts +10 -0
- package/dist/group-by.d.ts.map +1 -0
- package/dist/group-by.js +145 -0
- package/dist/group-having.d.ts +8 -0
- package/dist/group-having.d.ts.map +1 -0
- package/dist/group-having.js +97 -0
- package/dist/hooks.d.ts +41 -0
- package/dist/hooks.d.ts.map +1 -0
- package/dist/hooks.js +149 -0
- package/dist/hosts.d.ts +3 -0
- package/dist/hosts.d.ts.map +1 -0
- package/dist/hosts.js +8 -0
- package/dist/identify.d.ts +15 -0
- package/dist/identify.d.ts.map +1 -0
- package/dist/identify.js +71 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- package/dist/internal.d.ts +28 -0
- package/dist/internal.d.ts.map +1 -0
- package/dist/internal.js +39 -0
- package/dist/lifecycle.d.ts +21 -0
- package/dist/lifecycle.d.ts.map +1 -0
- package/dist/lifecycle.js +149 -0
- package/dist/list-update.d.ts +12 -0
- package/dist/list-update.d.ts.map +1 -0
- package/dist/list-update.js +52 -0
- package/dist/live.d.ts +37 -0
- package/dist/live.d.ts.map +1 -0
- package/dist/live.js +139 -0
- package/dist/manifest.d.ts +11 -0
- package/dist/manifest.d.ts.map +1 -0
- package/dist/manifest.js +60 -0
- package/dist/memory-aggregate.d.ts +7 -0
- package/dist/memory-aggregate.d.ts.map +1 -0
- package/dist/memory-aggregate.js +107 -0
- package/dist/memory-conditions.d.ts +8 -0
- package/dist/memory-conditions.d.ts.map +1 -0
- package/dist/memory-conditions.js +82 -0
- package/dist/memory-draft.d.ts +34 -0
- package/dist/memory-draft.d.ts.map +1 -0
- package/dist/memory-draft.js +255 -0
- package/dist/memory-json.d.ts +6 -0
- package/dist/memory-json.d.ts.map +1 -0
- package/dist/memory-json.js +86 -0
- package/dist/memory-read.d.ts +45 -0
- package/dist/memory-read.d.ts.map +1 -0
- package/dist/memory-read.js +244 -0
- package/dist/memory-search.d.ts +16 -0
- package/dist/memory-search.d.ts.map +1 -0
- package/dist/memory-search.js +110 -0
- package/dist/memory-values.d.ts +23 -0
- package/dist/memory-values.d.ts.map +1 -0
- package/dist/memory-values.js +82 -0
- package/dist/memory.d.ts +4 -0
- package/dist/memory.d.ts.map +1 -0
- package/dist/memory.js +157 -0
- package/dist/migrate.d.ts +22 -0
- package/dist/migrate.d.ts.map +1 -0
- package/dist/migrate.js +131 -0
- package/dist/model.d.ts +239 -0
- package/dist/model.d.ts.map +1 -0
- package/dist/model.js +1 -0
- package/dist/nested-write.d.ts +64 -0
- package/dist/nested-write.d.ts.map +1 -0
- package/dist/nested-write.js +274 -0
- package/dist/number-update.d.ts +18 -0
- package/dist/number-update.d.ts.map +1 -0
- package/dist/number-update.js +96 -0
- package/dist/plugin-app.d.ts +48 -0
- package/dist/plugin-app.d.ts.map +1 -0
- package/dist/plugin-app.js +230 -0
- package/dist/plugin-code.d.ts +17 -0
- package/dist/plugin-code.d.ts.map +1 -0
- package/dist/plugin-code.js +125 -0
- package/dist/plugin-use.d.ts +22 -0
- package/dist/plugin-use.d.ts.map +1 -0
- package/dist/plugin-use.js +161 -0
- package/dist/plugin.d.ts +357 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +17 -0
- package/dist/query-aggregate.d.ts +82 -0
- package/dist/query-aggregate.d.ts.map +1 -0
- package/dist/query-aggregate.js +1 -0
- package/dist/query-args.d.ts +41 -0
- package/dist/query-args.d.ts.map +1 -0
- package/dist/query-args.js +263 -0
- package/dist/query-bulk.d.ts +35 -0
- package/dist/query-bulk.d.ts.map +1 -0
- package/dist/query-bulk.js +1 -0
- package/dist/query-conditions.d.ts +9 -0
- package/dist/query-conditions.d.ts.map +1 -0
- package/dist/query-conditions.js +46 -0
- package/dist/query-context.d.ts +32 -0
- package/dist/query-context.d.ts.map +1 -0
- package/dist/query-context.js +55 -0
- package/dist/query-filter.d.ts +46 -0
- package/dist/query-filter.d.ts.map +1 -0
- package/dist/query-filter.js +1 -0
- package/dist/query-group.d.ts +82 -0
- package/dist/query-group.d.ts.map +1 -0
- package/dist/query-group.js +1 -0
- package/dist/query-json.d.ts +9 -0
- package/dist/query-json.d.ts.map +1 -0
- package/dist/query-json.js +66 -0
- package/dist/query-order.d.ts +15 -0
- package/dist/query-order.d.ts.map +1 -0
- package/dist/query-order.js +115 -0
- package/dist/query-project.d.ts +15 -0
- package/dist/query-project.d.ts.map +1 -0
- package/dist/query-project.js +45 -0
- package/dist/query-search.d.ts +24 -0
- package/dist/query-search.d.ts.map +1 -0
- package/dist/query-search.js +2 -0
- package/dist/query-values.d.ts +28 -0
- package/dist/query-values.d.ts.map +1 -0
- package/dist/query-values.js +91 -0
- package/dist/query-where.d.ts +46 -0
- package/dist/query-where.d.ts.map +1 -0
- package/dist/query-where.js +200 -0
- package/dist/query-write.d.ts +14 -0
- package/dist/query-write.d.ts.map +1 -0
- package/dist/query-write.js +260 -0
- package/dist/query.d.ts +254 -0
- package/dist/query.d.ts.map +1 -0
- package/dist/query.js +1 -0
- package/dist/record.d.ts +257 -0
- package/dist/record.d.ts.map +1 -0
- package/dist/record.js +1 -0
- package/dist/recorder.d.ts +32 -0
- package/dist/recorder.d.ts.map +1 -0
- package/dist/recorder.js +152 -0
- package/dist/relation-write.d.ts +41 -0
- package/dist/relation-write.d.ts.map +1 -0
- package/dist/relation-write.js +112 -0
- package/dist/relations.d.ts +42 -0
- package/dist/relations.d.ts.map +1 -0
- package/dist/relations.js +119 -0
- package/dist/rules.d.ts +97 -0
- package/dist/rules.d.ts.map +1 -0
- package/dist/rules.js +71 -0
- package/dist/running.d.ts +29 -0
- package/dist/running.d.ts.map +1 -0
- package/dist/running.js +39 -0
- package/dist/schema-indexes.d.ts +11 -0
- package/dist/schema-indexes.d.ts.map +1 -0
- package/dist/schema-indexes.js +59 -0
- package/dist/schema-virtuals.d.ts +19 -0
- package/dist/schema-virtuals.d.ts.map +1 -0
- package/dist/schema-virtuals.js +100 -0
- package/dist/schema.d.ts +35 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +267 -0
- package/dist/scope.d.ts +46 -0
- package/dist/scope.d.ts.map +1 -0
- package/dist/scope.js +82 -0
- package/dist/scratch.d.ts +14 -0
- package/dist/scratch.d.ts.map +1 -0
- package/dist/scratch.js +10 -0
- package/dist/serve.d.ts +2 -0
- package/dist/serve.d.ts.map +1 -0
- package/dist/serve.js +4 -0
- package/dist/symbols.d.ts +10 -0
- package/dist/symbols.d.ts.map +1 -0
- package/dist/symbols.js +1 -0
- package/dist/transaction.d.ts +28 -0
- package/dist/transaction.d.ts.map +1 -0
- package/dist/transaction.js +156 -0
- package/dist/types.d.ts +58 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +1 -0
- package/dist/update-many.d.ts +7 -0
- package/dist/update-many.d.ts.map +1 -0
- package/dist/update-many.js +182 -0
- package/dist/uuid.d.ts +7 -0
- package/dist/uuid.d.ts.map +1 -0
- package/dist/uuid.js +48 -0
- package/dist/validation.d.ts +24 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +72 -0
- package/dist/virtual-read.d.ts +9 -0
- package/dist/virtual-read.d.ts.map +1 -0
- package/dist/virtual-read.js +145 -0
- package/dist/virtual.d.ts +142 -0
- package/dist/virtual.d.ts.map +1 -0
- package/dist/virtual.js +1 -0
- package/dist/write-context.d.ts +46 -0
- package/dist/write-context.d.ts.map +1 -0
- package/dist/write-context.js +102 -0
- package/dist/write-data.d.ts +26 -0
- package/dist/write-data.d.ts.map +1 -0
- package/dist/write-data.js +144 -0
- package/dist/write-plan.d.ts +107 -0
- package/dist/write-plan.d.ts.map +1 -0
- package/dist/write-plan.js +170 -0
- package/dist/write-target.d.ts +17 -0
- package/dist/write-target.d.ts.map +1 -0
- package/dist/write-target.js +59 -0
- package/dist/writes.d.ts +23 -0
- package/dist/writes.d.ts.map +1 -0
- package/dist/writes.js +246 -0
- package/dist/writing.d.ts +10 -0
- package/dist/writing.d.ts.map +1 -0
- package/dist/writing.js +66 -0
- package/package.json +42 -3
- package/src/access.ts +329 -0
- package/src/adapter-errors.ts +89 -0
- package/src/adapter-json.ts +21 -0
- package/src/adapter-migrations.ts +98 -0
- package/src/adapter.ts +363 -0
- package/src/aggregate.ts +136 -0
- package/src/app-internals.ts +26 -0
- package/src/app-options.ts +24 -0
- package/src/cascade.ts +297 -0
- package/src/cli-config.ts +45 -0
- package/src/cli-deploy.ts +235 -0
- package/src/cli-main.ts +165 -0
- package/src/cli-migrate.ts +143 -0
- package/src/cli.ts +5 -0
- package/src/complete.ts +73 -0
- package/src/console-logger.ts +87 -0
- package/src/create-many.ts +126 -0
- package/src/decimal.ts +74 -0
- package/src/define-app.ts +108 -0
- package/src/define-enum.ts +26 -0
- package/src/define-function.ts +188 -0
- package/src/define-model.ts +220 -0
- package/src/definition.ts +352 -0
- package/src/delete-many.ts +76 -0
- package/src/engine.ts +281 -0
- package/src/errors.ts +97 -0
- package/src/exact.ts +49 -0
- package/src/feed.ts +39 -0
- package/src/field-codec.ts +72 -0
- package/src/field-rules.ts +77 -0
- package/src/field-type.ts +219 -0
- package/src/fields.ts +383 -0
- package/src/function.ts +143 -0
- package/src/functions.ts +154 -0
- package/src/group-by.ts +186 -0
- package/src/group-having.ts +143 -0
- package/src/hooks.ts +215 -0
- package/src/hosts.ts +11 -0
- package/src/identify.ts +95 -0
- package/src/index.ts +125 -0
- package/src/internal.ts +68 -0
- package/src/lifecycle.ts +214 -0
- package/src/list-update.ts +71 -0
- package/src/live.ts +199 -0
- package/src/manifest.ts +71 -0
- package/src/memory-aggregate.ts +162 -0
- package/src/memory-conditions.ts +88 -0
- package/src/memory-draft.ts +319 -0
- package/src/memory-json.ts +91 -0
- package/src/memory-read.ts +311 -0
- package/src/memory-search.ts +149 -0
- package/src/memory-values.ts +106 -0
- package/src/memory.ts +173 -0
- package/src/migrate.ts +152 -0
- package/src/model.ts +312 -0
- package/src/nested-write.ts +390 -0
- package/src/number-update.ts +142 -0
- package/src/plugin-app.ts +308 -0
- package/src/plugin-code.ts +158 -0
- package/src/plugin-use.ts +217 -0
- package/src/plugin.ts +400 -0
- package/src/query-aggregate.ts +96 -0
- package/src/query-args.ts +375 -0
- package/src/query-bulk.ts +53 -0
- package/src/query-conditions.ts +53 -0
- package/src/query-context.ts +104 -0
- package/src/query-filter.ts +54 -0
- package/src/query-group.ts +137 -0
- package/src/query-json.ts +90 -0
- package/src/query-order.ts +158 -0
- package/src/query-project.ts +73 -0
- package/src/query-search.ts +31 -0
- package/src/query-values.ts +131 -0
- package/src/query-where.ts +318 -0
- package/src/query-write.ts +360 -0
- package/src/query.ts +400 -0
- package/src/record.ts +379 -0
- package/src/recorder.ts +212 -0
- package/src/relation-write.ts +176 -0
- package/src/relations.ts +165 -0
- package/src/rules.ts +188 -0
- package/src/running.ts +62 -0
- package/src/schema-indexes.ts +88 -0
- package/src/schema-virtuals.ts +134 -0
- package/src/schema.ts +367 -0
- package/src/scope.ts +123 -0
- package/src/scratch.ts +28 -0
- package/src/serve.ts +5 -0
- package/src/symbols.ts +11 -0
- package/src/transaction.ts +211 -0
- package/src/types.ts +79 -0
- package/src/update-many.ts +268 -0
- package/src/uuid.ts +51 -0
- package/src/validation.ts +107 -0
- package/src/virtual-read.ts +198 -0
- package/src/virtual.ts +187 -0
- package/src/write-context.ts +170 -0
- package/src/write-data.ts +188 -0
- package/src/write-plan.ts +273 -0
- package/src/write-target.ts +92 -0
- package/src/writes.ts +361 -0
- package/src/writing.ts +93 -0
package/dist/recorder.js
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
// The reads a caller's construction makes (D-154): `identify`, the plugins'
|
|
2
|
+
// `caller` and `beforeFind` run inside `recording`, which keeps what each
|
|
3
|
+
// read reached, so a write to one of them rebuilds the caller, and the
|
|
4
|
+
// earliest time a plugin said the caller stops holding.
|
|
5
|
+
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
6
|
+
import { modelOf } from './query-where.js';
|
|
7
|
+
const recorder = new AsyncLocalStorage();
|
|
8
|
+
/** Runs `run`, keeping each read the kernel makes until it settles. */
|
|
9
|
+
export async function recording(run) {
|
|
10
|
+
const kept = { reads: [], expires: undefined };
|
|
11
|
+
const result = await recorder.run(kept, run);
|
|
12
|
+
const { reads, expires } = kept;
|
|
13
|
+
return {
|
|
14
|
+
result,
|
|
15
|
+
reads: [...reads],
|
|
16
|
+
...(expires !== undefined && { expires: new Date(expires) }),
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
/** Keeps `at` when it is the earliest expiry, if a recording runs. */
|
|
20
|
+
export function recordExpiry(at) {
|
|
21
|
+
const time = at.getTime();
|
|
22
|
+
if (Number.isNaN(time))
|
|
23
|
+
throw new TypeError('expires takes a valid date.');
|
|
24
|
+
const kept = recorder.getStore();
|
|
25
|
+
if (kept === undefined)
|
|
26
|
+
return;
|
|
27
|
+
if (kept.expires === undefined || time < kept.expires)
|
|
28
|
+
kept.expires = time;
|
|
29
|
+
}
|
|
30
|
+
/** The ids of `rows`, each once: included rows repeat a shared one. */
|
|
31
|
+
const idsOf = (rows) => [
|
|
32
|
+
...new Set(rows.flatMap((row) => {
|
|
33
|
+
const id = Reflect.get(Object(row), 'id');
|
|
34
|
+
return typeof id === 'string' ? [id] : [];
|
|
35
|
+
})),
|
|
36
|
+
];
|
|
37
|
+
/** Adds the fields `where` tests on its rows; false when it tests a text search. */
|
|
38
|
+
function filtered(where, into) {
|
|
39
|
+
if (where === undefined)
|
|
40
|
+
return true;
|
|
41
|
+
for (const key of Object.keys(where)) {
|
|
42
|
+
if (key === '_search')
|
|
43
|
+
return false;
|
|
44
|
+
if (key !== 'AND' && key !== 'OR' && key !== 'NOT')
|
|
45
|
+
into.add(key);
|
|
46
|
+
}
|
|
47
|
+
const nested = [
|
|
48
|
+
...(where.AND ?? []),
|
|
49
|
+
...(where.OR ?? []),
|
|
50
|
+
...(where.NOT ?? []),
|
|
51
|
+
];
|
|
52
|
+
return nested.every((filter) => filtered(filter, into));
|
|
53
|
+
}
|
|
54
|
+
/** A sort's or a key's field on the row: the relation it goes through first. */
|
|
55
|
+
const ownField = (key) => key.through?.[0]?.relation ?? key.field;
|
|
56
|
+
/** The fields a read returned and the ones that chose its rows. */
|
|
57
|
+
function dependsOn(read) {
|
|
58
|
+
const filters = new Set();
|
|
59
|
+
// A sort by relevance comes with a text search, which `filtered` refuses.
|
|
60
|
+
const known = filtered(read.where, filters) && filtered(read.cursor, filters);
|
|
61
|
+
for (const order of read.orderBy ?? [])
|
|
62
|
+
filters.add(ownField(order));
|
|
63
|
+
for (const key of read.distinct ?? [])
|
|
64
|
+
filters.add(ownField(key));
|
|
65
|
+
filters.delete('id');
|
|
66
|
+
return {
|
|
67
|
+
...(read.fields && { fields: read.fields }),
|
|
68
|
+
...(known && { filters: [...filters] }),
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
/** Keeps the relation `name` of `model` follows, and returns its target. */
|
|
72
|
+
function follow(models, reads, model, name, reached = {}) {
|
|
73
|
+
const field = modelOf(models, model).fields.get(name);
|
|
74
|
+
if (field === undefined || field.kind === 'scalar')
|
|
75
|
+
return undefined;
|
|
76
|
+
reads.push({ model: field.target, ...reached, relation: `${model}.${name}` });
|
|
77
|
+
return field.target;
|
|
78
|
+
}
|
|
79
|
+
/** Keeps the `one` relations `steps` go through from `model`. */
|
|
80
|
+
function through(models, reads, model, steps = []) {
|
|
81
|
+
let on = model;
|
|
82
|
+
for (const { relation, where } of steps) {
|
|
83
|
+
if (on === undefined)
|
|
84
|
+
return undefined;
|
|
85
|
+
on = follow(models, reads, on, relation, where && { where });
|
|
86
|
+
}
|
|
87
|
+
return on;
|
|
88
|
+
}
|
|
89
|
+
/** The relations `read` follows from `model`'s rows found as `rows`. */
|
|
90
|
+
function followed(models, reads, model, read, rows) {
|
|
91
|
+
for (const order of read.orderBy ?? []) {
|
|
92
|
+
const on = through(models, reads, model, order.through);
|
|
93
|
+
if (on !== undefined && order.count !== undefined) {
|
|
94
|
+
follow(models, reads, on, order.field, { where: order.count });
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
for (const [name, where] of Object.entries(read.count ?? {})) {
|
|
98
|
+
follow(models, reads, model, name, { where });
|
|
99
|
+
}
|
|
100
|
+
for (const [name, nested] of Object.entries(read.include ?? {})) {
|
|
101
|
+
const found = rows.flatMap((row) => {
|
|
102
|
+
const value = Reflect.get(Object(row), name);
|
|
103
|
+
if (Array.isArray(value))
|
|
104
|
+
return value;
|
|
105
|
+
return value === null || value === undefined ? [] : [value];
|
|
106
|
+
});
|
|
107
|
+
const target = follow(models, reads, model, name, {
|
|
108
|
+
...(nested.where && { where: nested.where }),
|
|
109
|
+
ids: idsOf(found),
|
|
110
|
+
...dependsOn(nested),
|
|
111
|
+
});
|
|
112
|
+
if (target !== undefined)
|
|
113
|
+
followed(models, reads, target, nested, found);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
/** Keeps a find, if a recording runs. */
|
|
117
|
+
export function recordFind(models, read, rows) {
|
|
118
|
+
const reads = recorder.getStore()?.reads;
|
|
119
|
+
if (reads === undefined)
|
|
120
|
+
return;
|
|
121
|
+
const { model, where } = read;
|
|
122
|
+
reads.push({
|
|
123
|
+
model,
|
|
124
|
+
...(where && { where }),
|
|
125
|
+
ids: idsOf(rows),
|
|
126
|
+
...dependsOn(read),
|
|
127
|
+
});
|
|
128
|
+
followed(models, reads, model, read, rows);
|
|
129
|
+
}
|
|
130
|
+
/** Keeps a count or an aggregate, if a recording runs. */
|
|
131
|
+
export function recordCount(models, read) {
|
|
132
|
+
const reads = recorder.getStore()?.reads;
|
|
133
|
+
if (reads === undefined)
|
|
134
|
+
return;
|
|
135
|
+
const { model, where } = read;
|
|
136
|
+
const filters = new Set();
|
|
137
|
+
const known = filtered(where, filters);
|
|
138
|
+
const by = 'by' in read ? (read.by ?? []) : [];
|
|
139
|
+
for (const key of by)
|
|
140
|
+
filters.add(ownField(key));
|
|
141
|
+
const aggregates = 'aggregates' in read ? read.aggregates : [];
|
|
142
|
+
const fields = aggregates.flatMap(({ field }) => field ?? []);
|
|
143
|
+
filters.delete('id');
|
|
144
|
+
reads.push({
|
|
145
|
+
model,
|
|
146
|
+
...(where && { where }),
|
|
147
|
+
fields: [...new Set(fields)],
|
|
148
|
+
...(known && { filters: [...filters] }),
|
|
149
|
+
});
|
|
150
|
+
for (const key of by)
|
|
151
|
+
through(models, reads, model, key.through);
|
|
152
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { Draft } from './write-data.ts';
|
|
2
|
+
/** A `connectOrCreate`: the unique target it looks for, the row it creates. */
|
|
3
|
+
export type OrCreate = {
|
|
4
|
+
readonly where: unknown;
|
|
5
|
+
readonly create: Draft;
|
|
6
|
+
};
|
|
7
|
+
/** A `many` relation's `update`: a unique target, and its data. */
|
|
8
|
+
export type Updating = {
|
|
9
|
+
readonly where: unknown;
|
|
10
|
+
readonly data: Draft;
|
|
11
|
+
};
|
|
12
|
+
/** A `one` relation write. */
|
|
13
|
+
export type OneWrite = {
|
|
14
|
+
readonly kind: 'connect';
|
|
15
|
+
readonly id: string;
|
|
16
|
+
} | {
|
|
17
|
+
readonly kind: 'create';
|
|
18
|
+
readonly data: Draft;
|
|
19
|
+
} | ({
|
|
20
|
+
readonly kind: 'connectOrCreate';
|
|
21
|
+
} & OrCreate) | {
|
|
22
|
+
readonly kind: 'update';
|
|
23
|
+
readonly data: Draft;
|
|
24
|
+
} | {
|
|
25
|
+
readonly kind: 'delete';
|
|
26
|
+
} | {
|
|
27
|
+
readonly kind: 'disconnect';
|
|
28
|
+
};
|
|
29
|
+
export declare function oneWrite(value: unknown, path: string, model: string, creating: boolean): OneWrite;
|
|
30
|
+
/** A `many` relation write, each operation's list. */
|
|
31
|
+
export type ManyWrite = {
|
|
32
|
+
readonly connect: string[];
|
|
33
|
+
readonly disconnect: string[];
|
|
34
|
+
readonly create: Draft[];
|
|
35
|
+
readonly set: string[] | undefined;
|
|
36
|
+
readonly connectOrCreate: OrCreate[];
|
|
37
|
+
readonly update: Updating[];
|
|
38
|
+
readonly delete: unknown[];
|
|
39
|
+
};
|
|
40
|
+
export declare function manyWrite(value: unknown, path: string, model: string, creating: boolean): ManyWrite;
|
|
41
|
+
//# sourceMappingURL=relation-write.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"relation-write.d.ts","sourceRoot":"","sources":["../src/relation-write.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAA;AAsE5C,+EAA+E;AAC/E,MAAM,MAAM,QAAQ,GAAG;IAAE,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,KAAK,CAAA;CAAE,CAAA;AAU1E,mEAAmE;AACnE,MAAM,MAAM,QAAQ,GAAG;IAAE,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAA;CAAE,CAAA;AAgBxE,8BAA8B;AAC9B,MAAM,MAAM,QAAQ,GACjB;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IAAC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;CAAE,GACjD;IAAE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAA;CAAE,GACjD,CAAC;IAAE,QAAQ,CAAC,IAAI,EAAE,iBAAiB,CAAA;CAAE,GAAG,QAAQ,CAAC,GACjD;IAAE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAA;CAAE,GACjD;IAAE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAA;CAAE,GAC3B;IAAE,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAA;CAAE,CAAA;AAElC,wBAAgB,QAAQ,CACvB,KAAK,EAAE,OAAO,EACd,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,OAAO,GACf,QAAQ,CAkBV;AAED,sDAAsD;AACtD,MAAM,MAAM,SAAS,GAAG;IACvB,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,CAAA;IAC1B,QAAQ,CAAC,UAAU,EAAE,MAAM,EAAE,CAAA;IAC7B,QAAQ,CAAC,MAAM,EAAE,KAAK,EAAE,CAAA;IACxB,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,SAAS,CAAA;IAClC,QAAQ,CAAC,eAAe,EAAE,QAAQ,EAAE,CAAA;IACpC,QAAQ,CAAC,MAAM,EAAE,QAAQ,EAAE,CAAA;IAC3B,QAAQ,CAAC,MAAM,EAAE,OAAO,EAAE,CAAA;CAC1B,CAAA;AAED,wBAAgB,SAAS,CACxB,KAAK,EAAE,OAAO,EACd,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,OAAO,GACf,SAAS,CAsBX"}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
// A relation write's operations (D-20, D-128, D-138), their shape checked:
|
|
2
|
+
// `connect`, `connectOrCreate` and `create`, and in an update `delete`,
|
|
3
|
+
// `disconnect`, `update` and, on a `many`, `set`.
|
|
4
|
+
import { InvalidQuery } from './errors.js';
|
|
5
|
+
import { clean, entries, isObject, unknownKey } from './query-where.js';
|
|
6
|
+
/** `{ id }`, exactly. */
|
|
7
|
+
function ref(value, path) {
|
|
8
|
+
const given = entries(value, path);
|
|
9
|
+
const [first] = given;
|
|
10
|
+
if (given.length !== 1 ||
|
|
11
|
+
first?.[0] !== 'id' ||
|
|
12
|
+
typeof first[1] !== 'string' ||
|
|
13
|
+
!clean(first[1])) {
|
|
14
|
+
throw new InvalidQuery(path, `${path} must be { id }.`);
|
|
15
|
+
}
|
|
16
|
+
return first[1];
|
|
17
|
+
}
|
|
18
|
+
/** The operations a relation write takes (D-128, D-138). */
|
|
19
|
+
function allowed(kind, creating) {
|
|
20
|
+
if (creating)
|
|
21
|
+
return ['connect', 'connectOrCreate', 'create'];
|
|
22
|
+
const changing = kind === 'one' ? ['update'] : ['set', 'update'];
|
|
23
|
+
return [
|
|
24
|
+
'connect',
|
|
25
|
+
'connectOrCreate',
|
|
26
|
+
'create',
|
|
27
|
+
'delete',
|
|
28
|
+
'disconnect',
|
|
29
|
+
...changing,
|
|
30
|
+
].toSorted();
|
|
31
|
+
}
|
|
32
|
+
function operations(value, path, model, kind, creating) {
|
|
33
|
+
if (value === null) {
|
|
34
|
+
throw new InvalidQuery(path, `${path} cannot be null; use disconnect.`);
|
|
35
|
+
}
|
|
36
|
+
const given = new Map(entries(value, path));
|
|
37
|
+
for (const key of given.keys()) {
|
|
38
|
+
if (!allowed(kind, creating).includes(key))
|
|
39
|
+
throw unknownKey(`${path}.${key}`, model);
|
|
40
|
+
}
|
|
41
|
+
return given;
|
|
42
|
+
}
|
|
43
|
+
/** Data a nested `create` or `update` writes (D-128, D-138). */
|
|
44
|
+
function made(value, path) {
|
|
45
|
+
if (!isObject(value)) {
|
|
46
|
+
throw new InvalidQuery(path, `${path} must be an object.`);
|
|
47
|
+
}
|
|
48
|
+
return value;
|
|
49
|
+
}
|
|
50
|
+
/** An object of exactly `keys`. */
|
|
51
|
+
function exactly(value, path, keys) {
|
|
52
|
+
const given = new Map(entries(value, path));
|
|
53
|
+
if (given.size !== 2 || !keys.every((key) => given.has(key))) {
|
|
54
|
+
throw new InvalidQuery(path, `${path} must be { ${keys.join(', ')} }.`);
|
|
55
|
+
}
|
|
56
|
+
return given;
|
|
57
|
+
}
|
|
58
|
+
function orCreate(value, path) {
|
|
59
|
+
const given = exactly(value, path, ['where', 'create']);
|
|
60
|
+
return {
|
|
61
|
+
where: given.get('where'),
|
|
62
|
+
create: made(given.get('create'), `${path}.create`),
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
function updating(value, path) {
|
|
66
|
+
const given = exactly(value, path, ['where', 'data']);
|
|
67
|
+
return {
|
|
68
|
+
where: given.get('where'),
|
|
69
|
+
data: made(given.get('data'), `${path}.data`),
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
function list(value, path) {
|
|
73
|
+
if (!Array.isArray(value))
|
|
74
|
+
throw new InvalidQuery(path, `${path} must be a list.`);
|
|
75
|
+
return value;
|
|
76
|
+
}
|
|
77
|
+
export function oneWrite(value, path, model, creating) {
|
|
78
|
+
const given = operations(value, path, model, 'one', creating);
|
|
79
|
+
const [entry] = given;
|
|
80
|
+
if (entry === undefined || given.size !== 1) {
|
|
81
|
+
throw new InvalidQuery(path, `${path} takes one of ${allowed('one', creating).join(', ')}.`);
|
|
82
|
+
}
|
|
83
|
+
const [key, operand] = entry;
|
|
84
|
+
const at = `${path}.${key}`;
|
|
85
|
+
if (key === 'connect')
|
|
86
|
+
return { kind: key, id: ref(operand, at) };
|
|
87
|
+
if (key === 'create' || key === 'update') {
|
|
88
|
+
return { kind: key, data: made(operand, at) };
|
|
89
|
+
}
|
|
90
|
+
if (key === 'connectOrCreate')
|
|
91
|
+
return { kind: key, ...orCreate(operand, at) };
|
|
92
|
+
if (operand !== true)
|
|
93
|
+
throw new InvalidQuery(at, `${at} must be true.`);
|
|
94
|
+
return { kind: key === 'delete' ? 'delete' : 'disconnect' };
|
|
95
|
+
}
|
|
96
|
+
export function manyWrite(value, path, model, creating) {
|
|
97
|
+
const given = operations(value, path, model, 'many', creating);
|
|
98
|
+
const each = (key, read) => list(given.get(key) ?? [], `${path}.${key}`).map((item, index) => read(item, `${path}.${key}.${index}`));
|
|
99
|
+
if (given.has('set') && (given.has('connect') || given.has('disconnect'))) {
|
|
100
|
+
throw new InvalidQuery(`${path}.set`, `${path}.set cannot be used with connect or disconnect.`);
|
|
101
|
+
}
|
|
102
|
+
return {
|
|
103
|
+
// A row disconnected and connected again stays linked (D-101).
|
|
104
|
+
connect: each('connect', ref),
|
|
105
|
+
disconnect: each('disconnect', ref),
|
|
106
|
+
create: each('create', made),
|
|
107
|
+
set: given.has('set') ? each('set', ref) : undefined,
|
|
108
|
+
connectOrCreate: each('connectOrCreate', orCreate),
|
|
109
|
+
update: each('update', updating),
|
|
110
|
+
delete: each('delete', (item) => item),
|
|
111
|
+
};
|
|
112
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { Filter } from './adapter.ts';
|
|
2
|
+
import type { FieldOptions } from './definition.ts';
|
|
3
|
+
import type { Models, Scope } from './query-where.ts';
|
|
4
|
+
import type { Caller } from './rules.ts';
|
|
5
|
+
import type { FieldInfo, ModelInfo } from './schema.ts';
|
|
6
|
+
type Many = Extract<FieldInfo, {
|
|
7
|
+
readonly kind: 'many';
|
|
8
|
+
}>;
|
|
9
|
+
/**
|
|
10
|
+
* Stops startup on a filter that names no `many` of the model, or on a view
|
|
11
|
+
* without one (D-151).
|
|
12
|
+
*/
|
|
13
|
+
export declare function checkRelations(model: ModelInfo): void;
|
|
14
|
+
/**
|
|
15
|
+
* A view (D-151): `source`, the `many` its `from` names, under its own name
|
|
16
|
+
* and options. Stops startup unless `from` names a `many` of the model to the
|
|
17
|
+
* same target that is no view.
|
|
18
|
+
*/
|
|
19
|
+
export declare function viewField(view: {
|
|
20
|
+
readonly model: string;
|
|
21
|
+
readonly name: string;
|
|
22
|
+
readonly target: string;
|
|
23
|
+
readonly options: FieldOptions;
|
|
24
|
+
}, from: string, source: FieldInfo | 'view' | undefined): FieldInfo;
|
|
25
|
+
/** Whether `.with({ relations })` filters `field` of `model`. */
|
|
26
|
+
export declare function isFiltered(model: ModelInfo, field: string): boolean;
|
|
27
|
+
/** Fails on a `set` of a filtered relation, which would detach hidden rows. */
|
|
28
|
+
export declare function refuseSet(model: ModelInfo, field: string, path: string): void;
|
|
29
|
+
/** The relation filters of one query, run once each for its caller. */
|
|
30
|
+
export declare class Relations {
|
|
31
|
+
private readonly filters;
|
|
32
|
+
private readonly models;
|
|
33
|
+
private readonly caller;
|
|
34
|
+
constructor(models: Models, caller: Caller);
|
|
35
|
+
/** The rows of `model.field` its filter keeps; `undefined` for all. */
|
|
36
|
+
filter(model: ModelInfo, field: Many): Filter | undefined;
|
|
37
|
+
private run;
|
|
38
|
+
}
|
|
39
|
+
/** The scope of trusted filters: no access rule, relation filters only. */
|
|
40
|
+
export declare function trusted(relations: Relations): Scope;
|
|
41
|
+
export {};
|
|
42
|
+
//# sourceMappingURL=relations.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"relations.d.ts","sourceRoot":"","sources":["../src/relations.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAA;AAC1C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAA;AAEnD,OAAO,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAA;AAErD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAA;AAExC,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAEvD,KAAK,IAAI,GAAG,OAAO,CAAC,SAAS,EAAE;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CAAA;AAQzD;;;GAGG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,SAAS,GAAG,IAAI,CAmBrD;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CACxB,IAAI,EAAE;IACL,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,OAAO,EAAE,YAAY,CAAA;CAC9B,EACD,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,SAAS,GAAG,MAAM,GAAG,SAAS,GACpC,SAAS,CAkBX;AAED,iEAAiE;AACjE,wBAAgB,UAAU,CAAC,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAEnE;AAED,+EAA+E;AAC/E,wBAAgB,SAAS,CAAC,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAM7E;AAMD,uEAAuE;AACvE,qBAAa,SAAS;IACrB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAGrB;IACH,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAQ;IAC/B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAQ;IAE/B,YAAY,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAGzC;IAED,uEAAuE;IACvE,MAAM,CAAC,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,IAAI,GAAG,MAAM,GAAG,SAAS,CAcxD;IAED,OAAO,CAAC,GAAG;CA0BX;AAED,2EAA2E;AAC3E,wBAAgB,OAAO,CAAC,SAAS,EAAE,SAAS,GAAG,KAAK,CAOnD"}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { InvalidQuery, Refused } from './errors.js';
|
|
2
|
+
import { isObject, modelOf, normalizeWhere } from './query-where.js';
|
|
3
|
+
import { filterOf, isReadFilter, readFilter } from './rules.js';
|
|
4
|
+
function relationsOf(model) {
|
|
5
|
+
const { rules } = model.definition;
|
|
6
|
+
const relations = isObject(rules) ? rules['relations'] : undefined;
|
|
7
|
+
return isObject(relations) ? relations : {};
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Stops startup on a filter that names no `many` of the model, or on a view
|
|
11
|
+
* without one (D-151).
|
|
12
|
+
*/
|
|
13
|
+
export function checkRelations(model) {
|
|
14
|
+
for (const [name, rule] of Object.entries(relationsOf(model))) {
|
|
15
|
+
const where = `${model.name}.with.relations.${name}`;
|
|
16
|
+
if (model.fields.get(name)?.kind !== 'many') {
|
|
17
|
+
throw new Refused(`${where}: ${name} is not a many relation.`);
|
|
18
|
+
}
|
|
19
|
+
if (!isObject(rule) || typeof rule['where'] !== 'function') {
|
|
20
|
+
throw new Refused(`${where} takes a where function.`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
for (const field of model.fields.values()) {
|
|
24
|
+
if (field.kind === 'many' && field.from !== undefined) {
|
|
25
|
+
if (!isFiltered(model, field.name)) {
|
|
26
|
+
throw new Refused(`${model.name}.${field.name} is a view; filter it in .with({ relations }).`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* A view (D-151): `source`, the `many` its `from` names, under its own name
|
|
33
|
+
* and options. Stops startup unless `from` names a `many` of the model to the
|
|
34
|
+
* same target that is no view.
|
|
35
|
+
*/
|
|
36
|
+
export function viewField(view, from, source) {
|
|
37
|
+
const { model, name, target, options } = view;
|
|
38
|
+
const at = `${model}.${name}`;
|
|
39
|
+
if (options['inverse'] !== undefined) {
|
|
40
|
+
throw new Refused(`${at} is a view and takes no inverse.`);
|
|
41
|
+
}
|
|
42
|
+
if (source === 'view') {
|
|
43
|
+
throw new Refused(`${at}: from '${from}' is a view; name its relation.`);
|
|
44
|
+
}
|
|
45
|
+
if (source?.kind !== 'many') {
|
|
46
|
+
throw new Refused(`${at}: from '${from}' is not a many relation.`);
|
|
47
|
+
}
|
|
48
|
+
if (source.target !== target) {
|
|
49
|
+
throw new Refused(`${at}: from '${from}' relates to ${source.target}, not ${target}.`);
|
|
50
|
+
}
|
|
51
|
+
return { ...source, name, from, options };
|
|
52
|
+
}
|
|
53
|
+
/** Whether `.with({ relations })` filters `field` of `model`. */
|
|
54
|
+
export function isFiltered(model, field) {
|
|
55
|
+
return Object.hasOwn(relationsOf(model), field);
|
|
56
|
+
}
|
|
57
|
+
/** Fails on a `set` of a filtered relation, which would detach hidden rows. */
|
|
58
|
+
export function refuseSet(model, field, path) {
|
|
59
|
+
if (!isFiltered(model, field))
|
|
60
|
+
return;
|
|
61
|
+
throw new InvalidQuery(path, `${model.name}.${field} is filtered; use connect and disconnect, not set.`);
|
|
62
|
+
}
|
|
63
|
+
/** No row passes: a filter that returns `false`. */
|
|
64
|
+
const closed = { OR: [] };
|
|
65
|
+
const pending = Symbol('pending');
|
|
66
|
+
/** The relation filters of one query, run once each for its caller. */
|
|
67
|
+
export class Relations {
|
|
68
|
+
filters = new Map();
|
|
69
|
+
models;
|
|
70
|
+
caller;
|
|
71
|
+
constructor(models, caller) {
|
|
72
|
+
this.models = models;
|
|
73
|
+
this.caller = caller;
|
|
74
|
+
}
|
|
75
|
+
/** The rows of `model.field` its filter keeps; `undefined` for all. */
|
|
76
|
+
filter(model, field) {
|
|
77
|
+
const key = `${model.name}.${field.name}`;
|
|
78
|
+
const known = this.filters.get(key);
|
|
79
|
+
if (known === pending) {
|
|
80
|
+
throw new TypeError(`${key}: its filter reads the relation itself.`);
|
|
81
|
+
}
|
|
82
|
+
if (this.filters.has(key))
|
|
83
|
+
return known;
|
|
84
|
+
const rule = relationsOf(model)[field.name];
|
|
85
|
+
this.filters.set(key, pending);
|
|
86
|
+
const filter = isObject(rule)
|
|
87
|
+
? this.run(model, field, rule['where'])
|
|
88
|
+
: undefined;
|
|
89
|
+
this.filters.set(key, filter);
|
|
90
|
+
return filter;
|
|
91
|
+
}
|
|
92
|
+
run(model, field, rule) {
|
|
93
|
+
const path = `${model.name}.with.relations.${field.name}.where`;
|
|
94
|
+
if (typeof rule !== 'function')
|
|
95
|
+
throw new TypeError(`${path} is a function.`);
|
|
96
|
+
const result = Reflect.apply(rule, undefined, [
|
|
97
|
+
{ caller: this.caller, where: readFilter },
|
|
98
|
+
]);
|
|
99
|
+
if (result === true)
|
|
100
|
+
return undefined;
|
|
101
|
+
if (result === false)
|
|
102
|
+
return closed;
|
|
103
|
+
if (!isReadFilter(result)) {
|
|
104
|
+
throw new TypeError(`${path} returns where({ ... }) or a boolean.`);
|
|
105
|
+
}
|
|
106
|
+
const target = modelOf(this.models, field.target);
|
|
107
|
+
// Trusted server code, as a read rule's own filter (D-86).
|
|
108
|
+
return normalizeWhere(this.models, target, filterOf(result), path, trusted(this));
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
/** The scope of trusted filters: no access rule, relation filters only. */
|
|
112
|
+
export function trusted(relations) {
|
|
113
|
+
return {
|
|
114
|
+
check: () => undefined,
|
|
115
|
+
ruled: () => false,
|
|
116
|
+
readable: () => undefined,
|
|
117
|
+
related: (model, field) => relations.filter(model, field),
|
|
118
|
+
};
|
|
119
|
+
}
|
package/dist/rules.d.ts
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import type { RecordOf } from './record.ts';
|
|
2
|
+
import type { modelFields } from './symbols.ts';
|
|
3
|
+
export type Operation = 'read' | 'create' | 'update' | 'delete';
|
|
4
|
+
/**
|
|
5
|
+
* The application declares its user model here, once; without it,
|
|
6
|
+
* `caller.user` is `{ id }` (D-24, D-62).
|
|
7
|
+
*/
|
|
8
|
+
export interface Register {
|
|
9
|
+
}
|
|
10
|
+
/** The user record a registry gives `caller.user`. */
|
|
11
|
+
export type UserOf<R> = R extends {
|
|
12
|
+
readonly user: {
|
|
13
|
+
readonly [modelFields]: infer F;
|
|
14
|
+
};
|
|
15
|
+
} ? RecordOf<F> : {
|
|
16
|
+
readonly id: string;
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* The keys plugins add to a user caller, each plugin declaring its own
|
|
20
|
+
* (D-121): `declare module 'ultimabase' { interface UserCallerKeys { ... } }`.
|
|
21
|
+
*/
|
|
22
|
+
export interface UserCallerKeys {
|
|
23
|
+
}
|
|
24
|
+
type Master = {
|
|
25
|
+
readonly kind: 'master';
|
|
26
|
+
readonly master: {
|
|
27
|
+
readonly source: 'code' | 'key';
|
|
28
|
+
};
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Who code says sends an operation, as `as` and a plugin's `identify` give
|
|
32
|
+
* it: a user caller without the keys plugins add (D-121).
|
|
33
|
+
*/
|
|
34
|
+
export type Identity = {
|
|
35
|
+
readonly kind: 'anonymous';
|
|
36
|
+
} | {
|
|
37
|
+
readonly kind: 'user';
|
|
38
|
+
readonly user: UserOf<Register>;
|
|
39
|
+
} | Master;
|
|
40
|
+
/** Who sends an operation, as rules, hooks and functions see it (D-19). */
|
|
41
|
+
export type Caller = {
|
|
42
|
+
readonly kind: 'anonymous';
|
|
43
|
+
} | ({
|
|
44
|
+
readonly kind: 'user';
|
|
45
|
+
readonly user: UserOf<Register>;
|
|
46
|
+
} & UserCallerKeys) | Master;
|
|
47
|
+
/** A Master caller; the kernel's code and transports only (D-19, D-49). */
|
|
48
|
+
export declare function createMaster(source: 'code' | 'key'): Master;
|
|
49
|
+
/** Whether a caller is a Master the kernel created. */
|
|
50
|
+
export declare function isMaster(caller: object): caller is Master;
|
|
51
|
+
/** Server code acting with every right; only the kernel creates it. */
|
|
52
|
+
export declare const master: Master;
|
|
53
|
+
/** A rule that reads only the caller and the operation fits any access key. */
|
|
54
|
+
export type AnyRule = (facts: {
|
|
55
|
+
readonly caller: Caller;
|
|
56
|
+
readonly operation: Operation;
|
|
57
|
+
}) => boolean;
|
|
58
|
+
/** A rule that reads only the caller also fits a function's access (D-27). */
|
|
59
|
+
export type CallerRule = (facts: {
|
|
60
|
+
readonly caller: Caller;
|
|
61
|
+
}) => boolean;
|
|
62
|
+
type Allow = {
|
|
63
|
+
readonly public: CallerRule;
|
|
64
|
+
readonly readOnly: AnyRule;
|
|
65
|
+
readonly private: CallerRule;
|
|
66
|
+
};
|
|
67
|
+
/** Open to everyone, open to reads, closed to everyone but Master (D-10). */
|
|
68
|
+
export declare const allow: Allow;
|
|
69
|
+
/** The read filter a read rule's `where` returns (D-10). */
|
|
70
|
+
export declare function readFilter(where: unknown): object;
|
|
71
|
+
/** Whether a rule's result is a read filter. */
|
|
72
|
+
export declare function isReadFilter(value: unknown): value is object;
|
|
73
|
+
/** The `where` a read filter holds. */
|
|
74
|
+
export declare function filterOf(token: object): unknown;
|
|
75
|
+
/** A rule already typed, such as a `const` holding `signedIn(...)`. */
|
|
76
|
+
type Named = (facts: never) => unknown;
|
|
77
|
+
type Both<U> = (U extends unknown ? (u: U) => void : never) extends (u: infer I) => void ? I : never;
|
|
78
|
+
/** The rule combining named rules: it needs every rule's facts. */
|
|
79
|
+
type Combined<Rules extends readonly Named[]> = (facts: Both<Parameters<Rules[number]>[0]>) => ReturnType<Rules[number]>;
|
|
80
|
+
/**
|
|
81
|
+
* True when one of the rules is (D-10). The facts and the result come from
|
|
82
|
+
* where it is used, never from the rules, so each rule is typed by its place.
|
|
83
|
+
* In a read rule, the filters combine with `OR` (D-81): `true` from any rule
|
|
84
|
+
* lets every row through, `false` adds nothing.
|
|
85
|
+
*/
|
|
86
|
+
export declare function anyOf<Facts, R = boolean>(...rules: readonly NoInfer<(facts: Facts) => R>[]): (facts: Facts) => R;
|
|
87
|
+
/** Outside any place, the facts are those every rule needs. */
|
|
88
|
+
export declare function anyOf<const Rules extends readonly Named[]>(...rules: Rules): Combined<Rules>;
|
|
89
|
+
/**
|
|
90
|
+
* True when every rule is (D-81), typed as `anyOf`. In a read rule, the
|
|
91
|
+
* filters combine with `AND`: `false` from any rule hides every row, `true`
|
|
92
|
+
* adds nothing.
|
|
93
|
+
*/
|
|
94
|
+
export declare function allOf<Facts, R = boolean>(...rules: readonly NoInfer<(facts: Facts) => R>[]): (facts: Facts) => R;
|
|
95
|
+
export declare function allOf<const Rules extends readonly Named[]>(...rules: Rules): Combined<Rules>;
|
|
96
|
+
export {};
|
|
97
|
+
//# sourceMappingURL=rules.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rules.d.ts","sourceRoot":"","sources":["../src/rules.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AAC3C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAE/C,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,CAAA;AAE/D;;;GAGG;AACH,MAAM,WAAW,QAAQ;CAAG;AAE5B,sDAAsD;AACtD,MAAM,MAAM,MAAM,CAAC,CAAC,IAAI,CAAC,SAAS;IACjC,QAAQ,CAAC,IAAI,EAAE;QAAE,QAAQ,CAAC,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC,CAAA;KAAE,CAAA;CAClD,GACE,QAAQ,CAAC,CAAC,CAAC,GACX;IAAE,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;CAAE,CAAA;AAE1B;;;GAGG;AACH,MAAM,WAAW,cAAc;CAAG;AAElC,KAAK,MAAM,GAAG;IACb,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAA;IACvB,QAAQ,CAAC,MAAM,EAAE;QAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,KAAK,CAAA;KAAE,CAAA;CACpD,CAAA;AAED;;;GAGG;AACH,MAAM,MAAM,QAAQ,GACjB;IAAE,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAA;CAAE,GAC9B;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAA;CAAE,GAC1D,MAAM,CAAA;AAET,2EAA2E;AAC3E,MAAM,MAAM,MAAM,GACf;IAAE,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAA;CAAE,GAC9B,CAAC;IACD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAA;CAC9B,GAAG,cAAc,CAAC,GACnB,MAAM,CAAA;AAIT,2EAA2E;AAC3E,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,KAAK,GAAG,MAAM,CAO3D;AAED,uDAAuD;AACvD,wBAAgB,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,IAAI,MAAM,CAEzD;AAED,uEAAuE;AACvE,eAAO,MAAM,MAAM,EAAE,MAA6B,CAAA;AAElD,+EAA+E;AAC/E,MAAM,MAAM,OAAO,GAAG,CAAC,KAAK,EAAE;IAC7B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAA;CAC7B,KAAK,OAAO,CAAA;AAEb,8EAA8E;AAC9E,MAAM,MAAM,UAAU,GAAG,CAAC,KAAK,EAAE;IAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,KAAK,OAAO,CAAA;AAExE,KAAK,KAAK,GAAG;IACZ,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAA;IAC3B,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAA;IAC1B,QAAQ,CAAC,OAAO,EAAE,UAAU,CAAA;CAC5B,CAAA;AAED,6EAA6E;AAC7E,eAAO,MAAM,KAAK,EAAE,KAIlB,CAAA;AAIF,4DAA4D;AAC5D,wBAAgB,UAAU,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAIjD;AAED,gDAAgD;AAChD,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAE5D;AAED,uCAAuC;AACvC,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAE/C;AAsCD,uEAAuE;AACvE,KAAK,KAAK,GAAG,CAAC,KAAK,EAAE,KAAK,KAAK,OAAO,CAAA;AACtC,KAAK,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,SAAS,OAAO,GAAG,CAAC,CAAC,EAAE,CAAC,KAAK,IAAI,GAAG,KAAK,CAAC,SAAS,CACnE,CAAC,EAAE,MAAM,CAAC,KACN,IAAI,GACN,CAAC,GACD,KAAK,CAAA;AACR,mEAAmE;AACnE,KAAK,QAAQ,CAAC,KAAK,SAAS,SAAS,KAAK,EAAE,IAAI,CAC/C,KAAK,EAAE,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KACrC,UAAU,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAA;AAE9B;;;;;GAKG;AACH,wBAAgB,KAAK,CAAC,KAAK,EAAE,CAAC,GAAG,OAAO,EACvC,GAAG,KAAK,EAAE,SAAS,OAAO,CAAC,CAAC,KAAK,EAAE,KAAK,KAAK,CAAC,CAAC,EAAE,GAC/C,CAAC,KAAK,EAAE,KAAK,KAAK,CAAC,CAAA;AACtB,+DAA+D;AAC/D,wBAAgB,KAAK,CAAC,KAAK,CAAC,KAAK,SAAS,SAAS,KAAK,EAAE,EACzD,GAAG,KAAK,EAAE,KAAK,GACb,QAAQ,CAAC,KAAK,CAAC,CAAA;AAKlB;;;;GAIG;AACH,wBAAgB,KAAK,CAAC,KAAK,EAAE,CAAC,GAAG,OAAO,EACvC,GAAG,KAAK,EAAE,SAAS,OAAO,CAAC,CAAC,KAAK,EAAE,KAAK,KAAK,CAAC,CAAC,EAAE,GAC/C,CAAC,KAAK,EAAE,KAAK,KAAK,CAAC,CAAA;AACtB,wBAAgB,KAAK,CAAC,KAAK,CAAC,KAAK,SAAS,SAAS,KAAK,EAAE,EACzD,GAAG,KAAK,EAAE,KAAK,GACb,QAAQ,CAAC,KAAK,CAAC,CAAA"}
|
package/dist/rules.js
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
const masters = new WeakSet();
|
|
2
|
+
/** A Master caller; the kernel's code and transports only (D-19, D-49). */
|
|
3
|
+
export function createMaster(source) {
|
|
4
|
+
const caller = Object.freeze({
|
|
5
|
+
kind: 'master',
|
|
6
|
+
master: Object.freeze({ source }),
|
|
7
|
+
});
|
|
8
|
+
masters.add(caller);
|
|
9
|
+
return caller;
|
|
10
|
+
}
|
|
11
|
+
/** Whether a caller is a Master the kernel created. */
|
|
12
|
+
export function isMaster(caller) {
|
|
13
|
+
return masters.has(caller);
|
|
14
|
+
}
|
|
15
|
+
/** Server code acting with every right; only the kernel creates it. */
|
|
16
|
+
export const master = createMaster('code');
|
|
17
|
+
/** Open to everyone, open to reads, closed to everyone but Master (D-10). */
|
|
18
|
+
export const allow = Object.freeze({
|
|
19
|
+
public: () => true,
|
|
20
|
+
readOnly: ({ operation }) => operation === 'read',
|
|
21
|
+
private: () => false,
|
|
22
|
+
});
|
|
23
|
+
const filters = new WeakMap();
|
|
24
|
+
/** The read filter a read rule's `where` returns (D-10). */
|
|
25
|
+
export function readFilter(where) {
|
|
26
|
+
const token = Object.freeze({});
|
|
27
|
+
filters.set(token, where);
|
|
28
|
+
return token;
|
|
29
|
+
}
|
|
30
|
+
/** Whether a rule's result is a read filter. */
|
|
31
|
+
export function isReadFilter(value) {
|
|
32
|
+
return typeof value === 'object' && value !== null && filters.has(value);
|
|
33
|
+
}
|
|
34
|
+
/** The `where` a read filter holds. */
|
|
35
|
+
export function filterOf(token) {
|
|
36
|
+
return filters.get(token);
|
|
37
|
+
}
|
|
38
|
+
// A rule returns a boolean, or a filter in a read rule (D-76, D-81).
|
|
39
|
+
function collect(rules, facts, decisive) {
|
|
40
|
+
const found = [];
|
|
41
|
+
for (const rule of rules) {
|
|
42
|
+
const result = Reflect.apply(rule, undefined, [facts]);
|
|
43
|
+
if (result === decisive)
|
|
44
|
+
return { decided: true, filters: [] };
|
|
45
|
+
if (isReadFilter(result))
|
|
46
|
+
found.push(result);
|
|
47
|
+
else if (typeof result !== 'boolean') {
|
|
48
|
+
throw new TypeError('A rule returns a boolean or a read filter.');
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return { decided: false, filters: found };
|
|
52
|
+
}
|
|
53
|
+
function combine(rules, decisive, operator) {
|
|
54
|
+
return (facts) => {
|
|
55
|
+
const { decided, filters: found } = collect(rules, facts, decisive);
|
|
56
|
+
if (decided)
|
|
57
|
+
return decisive;
|
|
58
|
+
const [first, ...others] = found;
|
|
59
|
+
if (first === undefined)
|
|
60
|
+
return !decisive;
|
|
61
|
+
if (others.length === 0)
|
|
62
|
+
return first;
|
|
63
|
+
return readFilter({ [operator]: found.map(filterOf) });
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
export function anyOf(...rules) {
|
|
67
|
+
return combine(rules, true, 'OR');
|
|
68
|
+
}
|
|
69
|
+
export function allOf(...rules) {
|
|
70
|
+
return combine(rules, false, 'AND');
|
|
71
|
+
}
|