@bjornpagen/bumbledb 1.0.1 → 1.2.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/COOKBOOK.md +176 -36
- package/README.md +70 -26
- package/dist/capacity.d.ts +15 -55
- package/dist/capacity.d.ts.map +1 -1
- package/dist/capacity.js +63 -12
- package/dist/capacity.js.map +1 -1
- package/dist/changes.d.ts +28 -6
- package/dist/changes.d.ts.map +1 -1
- package/dist/changes.js +119 -71
- package/dist/changes.js.map +1 -1
- package/dist/closed.d.ts +23 -51
- package/dist/closed.d.ts.map +1 -1
- package/dist/closed.js +110 -168
- package/dist/closed.js.map +1 -1
- package/dist/codec.d.ts +8 -14
- package/dist/codec.d.ts.map +1 -1
- package/dist/codec.js +113 -188
- package/dist/codec.js.map +1 -1
- package/dist/compile.d.ts +17 -18
- package/dist/compile.d.ts.map +1 -1
- package/dist/compile.js +41 -20
- package/dist/compile.js.map +1 -1
- package/dist/db-native.d.ts +76 -41
- package/dist/db-native.d.ts.map +1 -1
- package/dist/db-native.js +8 -0
- package/dist/db-native.js.map +1 -1
- package/dist/db.d.ts +61 -26
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +152 -85
- package/dist/db.js.map +1 -1
- package/dist/errors.d.ts +11 -3
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +13 -1
- package/dist/errors.js.map +1 -1
- package/dist/face.d.ts +19 -25
- package/dist/face.d.ts.map +1 -1
- package/dist/face.js +30 -24
- package/dist/face.js.map +1 -1
- package/dist/fields.d.ts +13 -26
- package/dist/fields.d.ts.map +1 -1
- package/dist/fields.js +89 -116
- package/dist/fields.js.map +1 -1
- package/dist/immutable.d.ts +10 -0
- package/dist/immutable.d.ts.map +1 -0
- package/dist/immutable.js +75 -0
- package/dist/immutable.js.map +1 -0
- package/dist/index.d.ts +16 -12
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -4
- package/dist/index.js.map +1 -1
- package/dist/internal/log.d.ts +6 -4
- package/dist/internal/log.d.ts.map +1 -1
- package/dist/internal/log.js +3 -1
- package/dist/internal/log.js.map +1 -1
- package/dist/law.d.ts +11 -11
- package/dist/law.d.ts.map +1 -1
- package/dist/law.js +1 -1
- package/dist/law.js.map +1 -1
- package/dist/lower.d.ts.map +1 -1
- package/dist/lower.js +11 -6
- package/dist/lower.js.map +1 -1
- package/dist/migration.d.ts +2 -3
- package/dist/migration.d.ts.map +1 -1
- package/dist/migration.js +5 -5
- package/dist/migration.js.map +1 -1
- package/dist/native.d.ts +2 -2
- package/dist/native.d.ts.map +1 -1
- package/dist/native.js.map +1 -1
- package/dist/query/atom.d.ts +1 -1
- package/dist/query/atom.d.ts.map +1 -1
- package/dist/query/atom.js.map +1 -1
- package/dist/query/description.d.ts +33 -0
- package/dist/query/description.d.ts.map +1 -0
- package/dist/query/description.js +302 -0
- package/dist/query/description.js.map +1 -0
- package/dist/query/lower.d.ts +60 -4
- package/dist/query/lower.d.ts.map +1 -1
- package/dist/query/lower.js +126 -161
- package/dist/query/lower.js.map +1 -1
- package/dist/query/parse-ir.d.ts +7 -2
- package/dist/query/parse-ir.d.ts.map +1 -1
- package/dist/query/parse-ir.js +240 -53
- package/dist/query/parse-ir.js.map +1 -1
- package/dist/query/run.d.ts.map +1 -1
- package/dist/query/run.js +8 -10
- package/dist/query/run.js.map +1 -1
- package/dist/query/scope.d.ts +3 -4
- package/dist/query/scope.d.ts.map +1 -1
- package/dist/query/scope.js +5 -10
- package/dist/query/scope.js.map +1 -1
- package/dist/relation.d.ts +22 -48
- package/dist/relation.d.ts.map +1 -1
- package/dist/relation.js +30 -82
- package/dist/relation.js.map +1 -1
- package/dist/result.d.ts +10 -25
- package/dist/result.d.ts.map +1 -1
- package/dist/result.js +7 -12
- package/dist/result.js.map +1 -1
- package/dist/rows.d.ts +11 -14
- package/dist/rows.d.ts.map +1 -1
- package/dist/rows.js +49 -237
- package/dist/rows.js.map +1 -1
- package/dist/runtime-codes.d.ts +1 -1
- package/dist/runtime-codes.d.ts.map +1 -1
- package/dist/runtime-codes.js +1 -2
- package/dist/runtime-codes.js.map +1 -1
- package/dist/runtime-errors.d.ts +27 -15
- package/dist/runtime-errors.d.ts.map +1 -1
- package/dist/runtime-errors.js +44 -8
- package/dist/runtime-errors.js.map +1 -1
- package/dist/runtime-native.d.ts +16 -34
- package/dist/runtime-native.d.ts.map +1 -1
- package/dist/runtime-native.js.map +1 -1
- package/dist/runtime.d.ts +11 -28
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +14 -54
- package/dist/runtime.js.map +1 -1
- package/dist/schema.d.ts +9 -1
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +85 -24
- package/dist/schema.js.map +1 -1
- package/dist/selection.d.ts +27 -0
- package/dist/selection.d.ts.map +1 -0
- package/dist/selection.js +104 -0
- package/dist/selection.js.map +1 -0
- package/dist/shape.d.ts +6 -9
- package/dist/shape.d.ts.map +1 -1
- package/dist/statements.d.ts +17 -52
- package/dist/statements.d.ts.map +1 -1
- package/dist/statements.js +92 -111
- package/dist/statements.js.map +1 -1
- package/dist/values.d.ts +25 -0
- package/dist/values.d.ts.map +1 -0
- package/dist/values.js +203 -0
- package/dist/values.js.map +1 -0
- package/pack-provenance.json +3 -3
- package/package.json +4 -4
- package/src/capacity.ts +86 -76
- package/src/changes.ts +225 -94
- package/src/closed.ts +161 -314
- package/src/codec.ts +123 -213
- package/src/compile.ts +61 -38
- package/src/db-native.ts +73 -68
- package/src/db.ts +268 -165
- package/src/errors.ts +16 -1
- package/src/face.ts +44 -74
- package/src/fields.ts +100 -146
- package/src/immutable.ts +76 -0
- package/src/index.ts +38 -31
- package/src/internal/log.ts +4 -5
- package/src/law.ts +14 -14
- package/src/lower.ts +13 -10
- package/src/migration.ts +5 -12
- package/src/native.ts +2 -1
- package/src/query/atom.ts +1 -0
- package/src/query/description.ts +394 -0
- package/src/query/lower.ts +139 -173
- package/src/query/parse-ir.ts +247 -54
- package/src/query/run.ts +15 -9
- package/src/query/scope.ts +10 -18
- package/src/relation.ts +58 -142
- package/src/result.ts +16 -45
- package/src/rows.ts +57 -250
- package/src/runtime-codes.ts +1 -2
- package/src/runtime-errors.ts +48 -8
- package/src/runtime-native.ts +13 -46
- package/src/runtime.ts +51 -119
- package/src/schema.ts +99 -29
- package/src/selection.ts +124 -0
- package/src/shape.ts +9 -9
- package/src/statements.ts +138 -189
- package/src/values.ts +223 -0
package/src/selection.ts
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { type AnyClosed, memberDescriptor, sealedFieldOf, sealedFieldsOf } from "#closed.ts"
|
|
2
|
+
import { AuthoringError } from "#errors.ts"
|
|
3
|
+
import { type AnyField, assertDeclarationRecord, type ClosedIdField, type Infer, literalOf } from "#fields.ts"
|
|
4
|
+
import type { AnyRelation, RelationFields } from "#relation.ts"
|
|
5
|
+
import { type LiteralSetSpec, type LiteralSpec, renderLiteral } from "#spec.ts"
|
|
6
|
+
import { arrayValue, recordValue } from "#values.ts"
|
|
7
|
+
|
|
8
|
+
type Selectable = AnyRelation | AnyClosed
|
|
9
|
+
type FieldsOf<R extends Selectable> = R extends AnyRelation
|
|
10
|
+
? RelationFields<R>
|
|
11
|
+
: R extends AnyClosed
|
|
12
|
+
? { readonly id: ClosedIdField<R["name"], R["handles"]> } & R["columns"]
|
|
13
|
+
: never
|
|
14
|
+
type SelectionInput<R extends Selectable> = {
|
|
15
|
+
readonly [K in keyof FieldsOf<R>]?: Infer<FieldsOf<R>[K]> | readonly Infer<FieldsOf<R>[K]>[]
|
|
16
|
+
}
|
|
17
|
+
interface SelectionBinding {
|
|
18
|
+
readonly field: string
|
|
19
|
+
readonly set: LiteralSetSpec
|
|
20
|
+
}
|
|
21
|
+
interface Selected<R extends Selectable = Selectable> {
|
|
22
|
+
readonly relation: R
|
|
23
|
+
readonly selection: readonly SelectionBinding[]
|
|
24
|
+
}
|
|
25
|
+
type AnySelected = Selected
|
|
26
|
+
|
|
27
|
+
/** Checked logical literals use the same field interpreter as fact values. */
|
|
28
|
+
function selectionLiteral(field: AnyField, input: unknown): LiteralSpec {
|
|
29
|
+
const raw = recordValue("selection literal", input, "closed" in field ? ["kind", "handle"] : ["kind", "value"])
|
|
30
|
+
if ("closed" in field) {
|
|
31
|
+
if (raw.kind !== "handle") throw new AuthoringError({ message: "closed selection requires a handle literal" })
|
|
32
|
+
return Object.freeze(literalOf(field, raw.handle))
|
|
33
|
+
}
|
|
34
|
+
if (raw.kind !== "value") throw new AuthoringError({ message: "selection requires a value literal" })
|
|
35
|
+
const value = recordValue(
|
|
36
|
+
"selection value",
|
|
37
|
+
raw.value,
|
|
38
|
+
field.kind === "interval" ? ["kind", "start", "end"] : ["kind", "value"]
|
|
39
|
+
)
|
|
40
|
+
let kind: string = field.kind
|
|
41
|
+
if (field.kind === "str") kind = "string"
|
|
42
|
+
if (field.kind === "bytes") kind = "fixedBytes"
|
|
43
|
+
if (field.kind === "interval") kind = { u64: "intervalU64", i64: "intervalI64", f64: "intervalF64" }[field.element]
|
|
44
|
+
if (value.kind !== kind) throw new AuthoringError({ message: `selection requires the ${kind} value tag` })
|
|
45
|
+
const literal = literalOf(field, field.kind === "interval" ? { start: value.start, end: value.end } : value.value)
|
|
46
|
+
if (literal.kind === "value") Object.freeze(literal.value)
|
|
47
|
+
return Object.freeze(literal)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function selectionBindings(owner: Selectable, input: unknown): readonly SelectionBinding[] {
|
|
51
|
+
if (!Array.isArray(input)) throw new AuthoringError({ message: "selection: expected bindings" })
|
|
52
|
+
const selected = new Set<string>()
|
|
53
|
+
return Object.freeze(
|
|
54
|
+
arrayValue("selection", input, (_, raw) => {
|
|
55
|
+
const binding = recordValue("selection binding", raw, ["field", "set"])
|
|
56
|
+
if (typeof binding.field !== "string") throw new AuthoringError({ message: "selection: expected a field name" })
|
|
57
|
+
const field = sealedFieldOf(owner, binding.field)
|
|
58
|
+
if (field === undefined)
|
|
59
|
+
throw new AuthoringError({ message: `relation ${owner.name} has no field ${binding.field}` })
|
|
60
|
+
if (selected.has(binding.field)) throw new AuthoringError({ message: "selection: duplicate field" })
|
|
61
|
+
selected.add(binding.field)
|
|
62
|
+
const set = binding.set
|
|
63
|
+
if (typeof set !== "object" || set === null || !("kind" in set))
|
|
64
|
+
throw new AuthoringError({ message: "literal set: expected a descriptor" })
|
|
65
|
+
assertDeclarationRecord("literal set", set)
|
|
66
|
+
if (set.kind === "one") {
|
|
67
|
+
const rawSet = recordValue("literal set", set, ["kind", "literal"])
|
|
68
|
+
return Object.freeze({
|
|
69
|
+
field: binding.field,
|
|
70
|
+
set: Object.freeze({ kind: "one" as const, literal: selectionLiteral(field, rawSet.literal) })
|
|
71
|
+
})
|
|
72
|
+
}
|
|
73
|
+
if (set.kind !== "many") throw new AuthoringError({ message: "literal set: unknown kind" })
|
|
74
|
+
const rawSet = recordValue("literal set", set, ["kind", "literals"])
|
|
75
|
+
if (!Array.isArray(rawSet.literals) || rawSet.literals.length < 2)
|
|
76
|
+
throw new AuthoringError({ message: "literal set: expected at least two distinct literals" })
|
|
77
|
+
const literals = arrayValue("selection literals", rawSet.literals, (_, literal) =>
|
|
78
|
+
selectionLiteral(field, literal)
|
|
79
|
+
)
|
|
80
|
+
if (new Set(literals.map(renderLiteral)).size !== literals.length)
|
|
81
|
+
throw new AuthoringError({ message: "literal set: duplicate literal" })
|
|
82
|
+
return Object.freeze({
|
|
83
|
+
field: binding.field,
|
|
84
|
+
set: Object.freeze({ kind: "many" as const, literals: Object.freeze(literals) })
|
|
85
|
+
})
|
|
86
|
+
})
|
|
87
|
+
)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function select<R extends Selectable>(relation: R, input: SelectionInput<R>): Selected<R> {
|
|
91
|
+
const owner = memberDescriptor(relation)
|
|
92
|
+
assertDeclarationRecord(`relation ${owner.name} selection`, input)
|
|
93
|
+
const fields = sealedFieldsOf(owner)
|
|
94
|
+
const selection: SelectionBinding[] = Object.entries(input).map(([name, entry]) => {
|
|
95
|
+
const field = fields.find((field) => field.name === name)?.field
|
|
96
|
+
if (field === undefined) throw new AuthoringError({ message: `relation ${owner.name} has no field ${name}` })
|
|
97
|
+
if (!Array.isArray(entry)) return { field: name, set: { kind: "one", literal: literalOf(field, entry) } }
|
|
98
|
+
const context = `relation ${owner.name}.${name}`
|
|
99
|
+
if (entry.length < 2)
|
|
100
|
+
throw new AuthoringError({
|
|
101
|
+
message:
|
|
102
|
+
entry.length === 0
|
|
103
|
+
? `${context}: an empty literal set selects nothing — write the selection you mean`
|
|
104
|
+
: `${context}: a one-element literal set is the bare literal respelled — write the literal`
|
|
105
|
+
})
|
|
106
|
+
const literals = arrayValue(context, entry, (_, value) => literalOf(field, value))
|
|
107
|
+
const seen = new Set<string>()
|
|
108
|
+
for (const literal of literals) {
|
|
109
|
+
const rendered = renderLiteral(literal)
|
|
110
|
+
if (seen.has(rendered))
|
|
111
|
+
throw new AuthoringError({ message: `${context}: the literal set spells ${rendered} twice — write it once` })
|
|
112
|
+
seen.add(rendered)
|
|
113
|
+
}
|
|
114
|
+
return { field: name, set: { kind: "many", literals } }
|
|
115
|
+
})
|
|
116
|
+
if (selection.length === 0)
|
|
117
|
+
throw new AuthoringError({
|
|
118
|
+
message: `relation ${owner.name}: an empty selection is the bare relation respelled — pass the relation itself`
|
|
119
|
+
})
|
|
120
|
+
return Object.freeze({ relation: owner, selection: selectionBindings(owner, selection) })
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export type { AnySelected, FieldsOf, Selectable, Selected, SelectionBinding, SelectionInput }
|
|
124
|
+
export { select, selectionBindings, selectionLiteral }
|
package/src/shape.ts
CHANGED
|
@@ -3,26 +3,26 @@ import type { ParamsRecord } from "#query/scope.ts"
|
|
|
3
3
|
/**
|
|
4
4
|
* Shared derived types: `S` is a
|
|
5
5
|
* declared core schema value's type, `Rel<S>` its ordinary (writable)
|
|
6
|
-
* relations, `Fact<R>` the inferred row object, `Key<
|
|
7
|
-
*
|
|
6
|
+
* relations, `Fact<R>` the inferred row object, `Key<K>` the exact value
|
|
7
|
+
* of a declared key accepted by `QueryReader.get`, and `QueryTemplate<S, P, A>` the
|
|
8
8
|
* immutable schema-bound logical query template. All are derived from the
|
|
9
9
|
* existing typed descriptors — no second hand-maintained roster.
|
|
10
10
|
*/
|
|
11
11
|
import type { AnyRelation, Fact } from "#relation.ts"
|
|
12
12
|
import type { AnySchema } from "#schema.ts"
|
|
13
|
+
import type { KeyStatement } from "#statements.ts"
|
|
13
14
|
|
|
14
15
|
/** The ordinary relations of a schema (closed vocabularies are ground axioms, never ingestion targets). */
|
|
15
16
|
type Rel<S extends AnySchema> = Extract<S["relations"][keyof S["relations"]], AnyRelation>
|
|
16
17
|
|
|
17
18
|
/**
|
|
18
|
-
* The
|
|
19
|
-
*
|
|
20
|
-
* otherwise the first declared `key`). The exact projection is a schema
|
|
21
|
-
* fact, verified at execution against the compiled tables; the type admits
|
|
22
|
-
* any subset of the fact's own fields and the runtime refuses a mismatch
|
|
23
|
-
* with a typed `DbError` before any native work.
|
|
19
|
+
* The structural value of one declared key, derived from its owner's
|
|
20
|
+
* fields and its exact projection. Every selected field is required.
|
|
24
21
|
*/
|
|
25
|
-
type Key<
|
|
22
|
+
type Key<K extends KeyStatement<AnyRelation, readonly string[]>> = Pick<
|
|
23
|
+
Fact<K["owner"]>,
|
|
24
|
+
Extract<K["projection"][number], keyof Fact<K["owner"]>>
|
|
25
|
+
>
|
|
26
26
|
|
|
27
27
|
/** Immutable typed query template. */
|
|
28
28
|
type QueryTemplate<S extends AnySchema, P extends ParamsRecord, A> = Query<S["relations"], A, P>
|
package/src/statements.ts
CHANGED
|
@@ -2,69 +2,44 @@ import {
|
|
|
2
2
|
type BoundsOnTarget,
|
|
3
3
|
type CapacityWeight,
|
|
4
4
|
type CapacityWindow,
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
capacityWeight,
|
|
6
|
+
capacityWindow,
|
|
7
7
|
type UnitDimensionBan,
|
|
8
8
|
unitWeight,
|
|
9
9
|
type WeightOnSource
|
|
10
10
|
} from "#capacity.ts"
|
|
11
|
-
import { isClosedMember, sealedFieldOf } from "#closed.ts"
|
|
11
|
+
import { isClosedMember, memberDescriptor, sealedFieldOf } from "#closed.ts"
|
|
12
12
|
import { AuthoringError } from "#errors.ts"
|
|
13
|
-
import { type AnyFace,
|
|
14
|
-
import { type AnyClosedRoster, rosterOf, rostersAgree } from "#fields.ts"
|
|
13
|
+
import { type AnyFace, faceDescriptor, renderFace, type SameArity, type SameShapes } from "#face.ts"
|
|
14
|
+
import { type AnyClosedRoster, assertDeclarationRecord, rosterOf, rostersAgree, signaturesAgree } from "#fields.ts"
|
|
15
|
+
import { descriptorCache } from "#immutable.ts"
|
|
15
16
|
import type { AnyRelation, RelationFields } from "#relation.ts"
|
|
16
17
|
import { type CapacityWindowSpec, renderCapacityWindow, renderWeight, type WeightSpec } from "#spec.ts"
|
|
18
|
+
import { arrayValue, recordValue } from "#values.ts"
|
|
17
19
|
|
|
18
|
-
interface
|
|
20
|
+
interface KeyStatement<R extends AnyRelation = AnyRelation, Projection extends readonly string[] = readonly string[]> {
|
|
19
21
|
readonly kind: "key"
|
|
20
22
|
readonly owner: R
|
|
21
23
|
readonly projection: Projection
|
|
22
24
|
}
|
|
23
|
-
|
|
24
|
-
interface ContainmentData<Src extends FaceData = FaceData, Tgt extends FaceData = FaceData> {
|
|
25
|
+
interface ContainmentStatement<Src extends AnyFace = AnyFace, Tgt extends AnyFace = AnyFace> {
|
|
25
26
|
readonly kind: "containment"
|
|
26
27
|
readonly source: Src
|
|
27
28
|
readonly target: Tgt
|
|
28
29
|
}
|
|
29
|
-
|
|
30
|
-
interface MirrorsData<Src extends FaceData = FaceData, Tgt extends FaceData = FaceData> {
|
|
30
|
+
interface MirrorsStatement<Src extends AnyFace = AnyFace, Tgt extends AnyFace = AnyFace> {
|
|
31
31
|
readonly kind: "mirrors"
|
|
32
32
|
readonly source: Src
|
|
33
33
|
readonly target: Tgt
|
|
34
34
|
}
|
|
35
|
-
|
|
36
|
-
interface CapacityData<Tgt extends FaceData = FaceData, Src extends FaceData = FaceData> {
|
|
35
|
+
interface CapacityStatement<Tgt extends AnyFace = AnyFace, Src extends AnyFace = AnyFace> {
|
|
37
36
|
readonly kind: "capacity"
|
|
38
37
|
readonly target: Tgt
|
|
39
|
-
readonly weight:
|
|
40
|
-
readonly window:
|
|
38
|
+
readonly weight: CapacityWeight
|
|
39
|
+
readonly window: CapacityWindow
|
|
41
40
|
readonly source: Src
|
|
42
41
|
}
|
|
43
|
-
|
|
44
|
-
type StatementData = KeyData<AnyRelation, readonly string[]> | ContainmentData | MirrorsData | CapacityData
|
|
45
|
-
|
|
46
|
-
const admitted: unique symbol = Symbol("bumbledb.statement.admitted")
|
|
47
|
-
|
|
48
|
-
interface Statement {
|
|
49
|
-
readonly data: StatementData
|
|
50
|
-
readonly [admitted]: true
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function isStatement(value: unknown): value is Statement {
|
|
54
|
-
return typeof value === "object" && value !== null && admitted in value
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
interface ContainedStatement<Src extends FaceData, Tgt extends FaceData> extends Statement {
|
|
58
|
-
readonly data: ContainmentData<Src, Tgt> | MirrorsData<Src, Tgt>
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
interface CapacityStatement<Tgt extends FaceData, Src extends FaceData> extends Statement {
|
|
62
|
-
readonly data: CapacityData<Tgt, Src>
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
interface KeyStatement<R extends AnyRelation, Projection extends readonly string[]> extends Statement {
|
|
66
|
-
readonly data: KeyData<R, Projection>
|
|
67
|
-
}
|
|
42
|
+
type Statement = KeyStatement | ContainmentStatement | MirrorsStatement | CapacityStatement
|
|
68
43
|
|
|
69
44
|
function renderRosterSide(roster: AnyClosedRoster | undefined): string {
|
|
70
45
|
return roster === undefined ? "a bare column" : `a ${roster.name} reference`
|
|
@@ -79,7 +54,7 @@ function renderRosterSide(roster: AnyClosedRoster | undefined): string {
|
|
|
79
54
|
* `Db.create`'s colder engine refusal. The error carries the two faces'
|
|
80
55
|
* own facts: names, arities, and the rendered statement.
|
|
81
56
|
*/
|
|
82
|
-
function assertArityAgreement(source:
|
|
57
|
+
function assertArityAgreement(source: AnyFace, target: AnyFace, statement: Statement): void {
|
|
83
58
|
if (source.projection.length !== target.projection.length) {
|
|
84
59
|
throw new AuthoringError({
|
|
85
60
|
message: `${source.owner.name}(${source.projection.join(", ")}) and ${target.owner.name}(${target.projection.join(", ")}) project ${source.projection.length} vs ${target.projection.length} fields — positional pairing requires both faces to project equally many — ${renderStatement(statement)}`
|
|
@@ -87,7 +62,7 @@ function assertArityAgreement(source: FaceData, target: FaceData, statement: Sta
|
|
|
87
62
|
}
|
|
88
63
|
}
|
|
89
64
|
|
|
90
|
-
function assertRosterAgreement(source:
|
|
65
|
+
function assertRosterAgreement(source: AnyFace, target: AnyFace, statement: Statement): void {
|
|
91
66
|
source.projection.forEach(function agreeAt(fieldName, position) {
|
|
92
67
|
const targetField = target.projection[position]
|
|
93
68
|
if (targetField === undefined) {
|
|
@@ -103,79 +78,7 @@ function assertRosterAgreement(source: FaceData, target: FaceData, statement: St
|
|
|
103
78
|
})
|
|
104
79
|
}
|
|
105
80
|
|
|
106
|
-
|
|
107
|
-
* `R(X) -> R` — the FD key form, composite keys as tuples. No selection
|
|
108
|
-
* parameter exists (the FD-with-selection shape is unrepresentable, as in
|
|
109
|
-
* the grammar), and only ordinary relations are accepted: a closed
|
|
110
|
-
* relation's key `R(id) -> R` is materialized by the engine, so an
|
|
111
|
-
* explicit one would only ever be a duplicate. Every projected name is
|
|
112
|
-
* checked against `R`'s field block in the type, and the tuple is carried
|
|
113
|
-
* in the returned value's type ({@link KeyStatement}) — keyed point reads
|
|
114
|
-
* through THIS statement are typed field-for-field, descriptors resolvable
|
|
115
|
-
* through the owner's schema type. A DUPLICATE field in the projection is
|
|
116
|
-
* refused here, at the mint (the engine's `FieldSet` refuses the same
|
|
117
|
-
* duplicate at `Db.create`; canonical utterance says the twice-spelled
|
|
118
|
-
* field is the once-spelled projection respelled) — without this wall a
|
|
119
|
-
* `new Set`-collapsed duplicate could set-match a shorter target
|
|
120
|
-
* projection the engine refuses.
|
|
121
|
-
*/
|
|
122
|
-
function key<
|
|
123
|
-
R extends AnyRelation,
|
|
124
|
-
const Projection extends readonly [keyof RelationFields<R> & string, ...(keyof RelationFields<R> & string)[]]
|
|
125
|
-
>(relation: R, fields: Projection): KeyStatement<R, Projection> {
|
|
126
|
-
if (isClosedMember(relation)) {
|
|
127
|
-
throw new AuthoringError({
|
|
128
|
-
message: `key(${relation.name}, ...): closedness already materializes ${relation.name}(id) -> ${relation.name} — an explicit key on a closed relation is rejected as a duplicate`
|
|
129
|
-
})
|
|
130
|
-
}
|
|
131
|
-
const seen = new Set<string>()
|
|
132
|
-
for (const fieldName of fields) {
|
|
133
|
-
if (seen.has(fieldName)) {
|
|
134
|
-
throw new AuthoringError({
|
|
135
|
-
message: `key(${relation.name}, ...): the projection spells ${fieldName} twice — write it once (the canonical-utterance law: one meaning, one spelling)`
|
|
136
|
-
})
|
|
137
|
-
}
|
|
138
|
-
seen.add(fieldName)
|
|
139
|
-
}
|
|
140
|
-
const data: KeyData<R, Projection> = Object.freeze({
|
|
141
|
-
kind: "key",
|
|
142
|
-
owner: relation,
|
|
143
|
-
projection: Object.freeze(fields)
|
|
144
|
-
})
|
|
145
|
-
return Object.freeze({ data, [admitted]: true as const })
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
function contained<A extends AnyFace, B extends AnyFace>(
|
|
149
|
-
source: A,
|
|
150
|
-
target: B & SameArity<A, B> & SameShapes<A, B>
|
|
151
|
-
): ContainedStatement<A["data"], B["data"]> {
|
|
152
|
-
const data: ContainmentData<A["data"], B["data"]> = Object.freeze({
|
|
153
|
-
kind: "containment",
|
|
154
|
-
source: source.data,
|
|
155
|
-
target: target.data
|
|
156
|
-
})
|
|
157
|
-
const statement = Object.freeze({ data, [admitted]: true as const })
|
|
158
|
-
assertArityAgreement(data.source, data.target, statement)
|
|
159
|
-
assertRosterAgreement(data.source, data.target, statement)
|
|
160
|
-
return statement
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
function mirrors<A extends AnyFace, B extends AnyFace>(
|
|
164
|
-
source: A,
|
|
165
|
-
target: B & SameArity<A, B> & SameShapes<A, B>
|
|
166
|
-
): ContainedStatement<A["data"], B["data"]> {
|
|
167
|
-
const data: MirrorsData<A["data"], B["data"]> = Object.freeze({
|
|
168
|
-
kind: "mirrors",
|
|
169
|
-
source: source.data,
|
|
170
|
-
target: target.data
|
|
171
|
-
})
|
|
172
|
-
const statement = Object.freeze({ data, [admitted]: true as const })
|
|
173
|
-
assertArityAgreement(data.source, data.target, statement)
|
|
174
|
-
assertRosterAgreement(data.source, data.target, statement)
|
|
175
|
-
return statement
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
function assertWeightOnSource(weight: WeightSpec, source: FaceData, statement: Statement): void {
|
|
81
|
+
function assertWeightOnSource(weight: WeightSpec, source: AnyFace, statement: Statement): void {
|
|
179
82
|
if (weight.kind === "unit") {
|
|
180
83
|
return
|
|
181
84
|
}
|
|
@@ -197,7 +100,7 @@ function assertWeightOnSource(weight: WeightSpec, source: FaceData, statement: S
|
|
|
197
100
|
}
|
|
198
101
|
}
|
|
199
102
|
|
|
200
|
-
function assertBoundsOnTarget(window: CapacityWindowSpec, target:
|
|
103
|
+
function assertBoundsOnTarget(window: CapacityWindowSpec, target: AnyFace, statement: Statement): void {
|
|
201
104
|
const bounds = window.kind === "range" ? [window.lo, window.hi] : [window.kind === "exact" ? window.n : window.lo]
|
|
202
105
|
for (const bound of bounds) {
|
|
203
106
|
if (bound.kind === "lit") {
|
|
@@ -222,19 +125,112 @@ function assertBoundsOnTarget(window: CapacityWindowSpec, target: FaceData, stat
|
|
|
222
125
|
}
|
|
223
126
|
}
|
|
224
127
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
128
|
+
function assertShapes(source: AnyFace, target: AnyFace): void {
|
|
129
|
+
for (let i = 0; i < source.projection.length; i++) {
|
|
130
|
+
const a = sealedFieldOf(source.owner, source.projection[i] ?? "")
|
|
131
|
+
const b = sealedFieldOf(target.owner, target.projection[i] ?? "")
|
|
132
|
+
if (a === undefined || b === undefined) throw new AuthoringError({ message: "statement: unknown projected field" })
|
|
133
|
+
const left = a.kind === "interval" ? { ...a, width: undefined } : a
|
|
134
|
+
const right = b.kind === "interval" ? { ...b, width: undefined } : b
|
|
135
|
+
if (!signaturesAgree(left, right))
|
|
136
|
+
throw new AuthoringError({ message: "statement: projected field shapes do not agree" })
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** Every authoring path checks and owns the same plain descriptor. */
|
|
141
|
+
function statementDescriptor<S extends Statement>(input: S): S
|
|
142
|
+
function statementDescriptor(input: unknown): Statement
|
|
143
|
+
function statementDescriptor(input: unknown): Statement {
|
|
144
|
+
return checkedStatement(input)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const checkedStatement = descriptorCache((raw): Statement => {
|
|
148
|
+
const input = recordValue("statement", raw, typeof raw === "object" && raw !== null ? Object.keys(raw) : [])
|
|
149
|
+
assertDeclarationRecord("statement", input)
|
|
150
|
+
if (input?.kind === "key") {
|
|
151
|
+
recordValue("key", input, ["kind", "owner", "projection"])
|
|
152
|
+
const owner = memberDescriptor(input.owner as unknown)
|
|
153
|
+
if (isClosedMember(owner))
|
|
154
|
+
throw new AuthoringError({
|
|
155
|
+
message: `key(${owner.name}, ...): closedness already materializes its key; explicit keys on closed relations are duplicates`
|
|
156
|
+
})
|
|
157
|
+
const projection = arrayValue("key projection", input.projection, (_, value) => {
|
|
158
|
+
if (typeof value !== "string") throw new AuthoringError({ message: "key: expected a field name" })
|
|
159
|
+
return value
|
|
160
|
+
})
|
|
161
|
+
if (projection.length === 0) throw new AuthoringError({ message: "key: expected a nonempty field projection" })
|
|
162
|
+
const seen = new Set<string>()
|
|
163
|
+
for (const field of projection) {
|
|
164
|
+
if (typeof field !== "string" || sealedFieldOf(owner, field) === undefined)
|
|
165
|
+
throw new AuthoringError({ message: `key(${owner.name}, ...): unknown field ${String(field)}` })
|
|
166
|
+
if (seen.has(field))
|
|
167
|
+
throw new AuthoringError({ message: `key(${owner.name}, ...): the projection spells ${field} twice` })
|
|
168
|
+
seen.add(field)
|
|
169
|
+
}
|
|
170
|
+
return Object.freeze({ kind: "key", owner, projection })
|
|
171
|
+
}
|
|
172
|
+
if (input?.kind !== "containment" && input?.kind !== "mirrors" && input?.kind !== "capacity") {
|
|
173
|
+
throw new AuthoringError({ message: "statement: unknown kind" })
|
|
174
|
+
}
|
|
175
|
+
recordValue(
|
|
176
|
+
"statement",
|
|
177
|
+
input,
|
|
178
|
+
input.kind === "capacity" ? ["kind", "target", "weight", "window", "source"] : ["kind", "source", "target"]
|
|
179
|
+
)
|
|
180
|
+
const source = faceDescriptor(input.source)
|
|
181
|
+
const target = faceDescriptor(input.target)
|
|
182
|
+
const statement =
|
|
183
|
+
input.kind === "capacity"
|
|
184
|
+
? Object.freeze({
|
|
185
|
+
kind: input.kind,
|
|
186
|
+
source,
|
|
187
|
+
target,
|
|
188
|
+
weight: capacityWeight(input.weight),
|
|
189
|
+
window: capacityWindow(input.window)
|
|
190
|
+
})
|
|
191
|
+
: Object.freeze({ kind: input.kind, source, target })
|
|
192
|
+
assertArityAgreement(source, target, statement)
|
|
193
|
+
assertRosterAgreement(source, target, statement)
|
|
194
|
+
assertShapes(source, target)
|
|
195
|
+
if (statement.kind === "capacity") {
|
|
196
|
+
const { weight, window } = statement
|
|
197
|
+
if (weight.kind === "unit" && window.kind === "range" && window.hi.kind === "durationField") {
|
|
198
|
+
throw new AuthoringError({ message: "a unit (count) window against a duration bound mixes dimensions (C18)" })
|
|
199
|
+
}
|
|
200
|
+
assertWeightOnSource(weight, source, statement)
|
|
201
|
+
assertBoundsOnTarget(window, target, statement)
|
|
202
|
+
}
|
|
203
|
+
return statement
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
function key<
|
|
207
|
+
R extends AnyRelation,
|
|
208
|
+
const Projection extends readonly [keyof RelationFields<R> & string, ...(keyof RelationFields<R> & string)[]]
|
|
209
|
+
>(relation: R, fields: Projection): KeyStatement<R, Projection> {
|
|
210
|
+
return statementDescriptor({ kind: "key", owner: relation, projection: fields })
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function contained<A extends AnyFace, B extends AnyFace>(
|
|
214
|
+
source: A,
|
|
215
|
+
target: B & SameArity<A, B> & SameShapes<A, B>
|
|
216
|
+
): ContainmentStatement<A, B> {
|
|
217
|
+
return statementDescriptor({ kind: "containment", source, target })
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function mirrors<A extends AnyFace, B extends AnyFace>(
|
|
221
|
+
source: A,
|
|
222
|
+
target: B & SameArity<A, B> & SameShapes<A, B>
|
|
223
|
+
): MirrorsStatement<A, B> {
|
|
224
|
+
return statementDescriptor({ kind: "mirrors", source, target })
|
|
225
|
+
}
|
|
226
|
+
|
|
231
227
|
function capacity<B extends AnyFace, W extends CapacityWindow, A extends AnyFace>(
|
|
232
228
|
target: B,
|
|
233
229
|
options: {
|
|
234
230
|
readonly from: A & SameArity<B, A> & SameShapes<B, A>
|
|
235
231
|
readonly within: W & UnitDimensionBan<W> & BoundsOnTarget<W, B>
|
|
236
232
|
}
|
|
237
|
-
): CapacityStatement<B
|
|
233
|
+
): CapacityStatement<B, A>
|
|
238
234
|
function capacity<B extends AnyFace, M extends CapacityWeight, W extends CapacityWindow, A extends AnyFace>(
|
|
239
235
|
target: B,
|
|
240
236
|
options: {
|
|
@@ -242,84 +238,37 @@ function capacity<B extends AnyFace, M extends CapacityWeight, W extends Capacit
|
|
|
242
238
|
readonly weight: M & WeightOnSource<M, A>
|
|
243
239
|
readonly within: W & BoundsOnTarget<W, B>
|
|
244
240
|
}
|
|
245
|
-
): CapacityStatement<B
|
|
241
|
+
): CapacityStatement<B, A>
|
|
246
242
|
function capacity(
|
|
247
243
|
target: AnyFace,
|
|
248
|
-
options: {
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
):
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
message:
|
|
257
|
-
"capacity takes named options — capacity(target, { from, weight?, within }) (chapter 34's one spelling; the positional arguments are deleted)"
|
|
258
|
-
})
|
|
259
|
-
}
|
|
260
|
-
const windowValue = options.within
|
|
261
|
-
const source = options.from
|
|
262
|
-
if (!isCapacityWindow(windowValue)) {
|
|
263
|
-
throw new AuthoringError({
|
|
264
|
-
message:
|
|
265
|
-
"a capacity window is minted only by within() — a structural literal skips the ban table (the canonical-utterance law)"
|
|
266
|
-
})
|
|
267
|
-
}
|
|
268
|
-
let weight: WeightSpec = unitWeight
|
|
269
|
-
if (options.weight !== undefined) {
|
|
270
|
-
if (!isCapacityWeight(options.weight)) {
|
|
271
|
-
throw new AuthoringError({
|
|
272
|
-
message: "a capacity weight is minted only by weigh() — a structural literal skips the row-local weight wall"
|
|
273
|
-
})
|
|
274
|
-
}
|
|
275
|
-
weight = options.weight.weight
|
|
276
|
-
}
|
|
277
|
-
const window = windowValue.window
|
|
278
|
-
// The unit-floor and vacuous-window ban tables are deleted:
|
|
279
|
-
// `{1..*}`, `{N..*}` and `{0..*}` are accepted canonical laws now.
|
|
280
|
-
// CapacityDimensionMixing remains — genuinely different semantics.
|
|
281
|
-
if (weight.kind === "unit" && window.kind === "range" && window.hi.kind === "durationField") {
|
|
282
|
-
throw new AuthoringError({
|
|
283
|
-
message: `a unit (count) window against the duration() bound on ${window.hi.field} mixes dimensions (C18) — weigh the source with weigh(duration(field)), or bound by a u64 field or literal`
|
|
284
|
-
})
|
|
285
|
-
}
|
|
286
|
-
const data: CapacityData = Object.freeze({
|
|
244
|
+
options: { readonly from: AnyFace; readonly weight?: unknown; readonly within: unknown }
|
|
245
|
+
): CapacityStatement {
|
|
246
|
+
recordValue(
|
|
247
|
+
"capacity options",
|
|
248
|
+
options,
|
|
249
|
+
Object.hasOwn(options, "weight") ? ["from", "weight", "within"] : ["from", "within"]
|
|
250
|
+
)
|
|
251
|
+
return statementDescriptor({
|
|
287
252
|
kind: "capacity",
|
|
288
|
-
target
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
253
|
+
target,
|
|
254
|
+
source: options.from,
|
|
255
|
+
weight: options.weight === undefined ? unitWeight : capacityWeight(options.weight),
|
|
256
|
+
window: capacityWindow(options.within)
|
|
292
257
|
})
|
|
293
|
-
const statement = Object.freeze({ data, [admitted]: true as const })
|
|
294
|
-
assertArityAgreement(data.source, data.target, statement)
|
|
295
|
-
assertRosterAgreement(data.source, data.target, statement)
|
|
296
|
-
assertWeightOnSource(weight, data.source, statement)
|
|
297
|
-
assertBoundsOnTarget(window, data.target, statement)
|
|
298
|
-
return statement
|
|
299
258
|
}
|
|
300
259
|
|
|
301
260
|
function renderStatement(statement: Statement): string {
|
|
302
|
-
|
|
303
|
-
switch (data.kind) {
|
|
261
|
+
switch (statement.kind) {
|
|
304
262
|
case "key":
|
|
305
|
-
return `${
|
|
263
|
+
return `${statement.owner.name}(${statement.projection.join(", ")}) -> ${statement.owner.name}`
|
|
306
264
|
case "containment":
|
|
307
|
-
return `${renderFace(
|
|
265
|
+
return `${renderFace(statement.source)} <= ${renderFace(statement.target)}`
|
|
308
266
|
case "mirrors":
|
|
309
|
-
return `${renderFace(
|
|
267
|
+
return `${renderFace(statement.source)} == ${renderFace(statement.target)}`
|
|
310
268
|
case "capacity":
|
|
311
|
-
return `${renderFace(
|
|
269
|
+
return `${renderFace(statement.target)} <=${renderWeight(statement.weight)}${renderCapacityWindow(statement.window)} ${renderFace(statement.source)}`
|
|
312
270
|
}
|
|
313
271
|
}
|
|
314
272
|
|
|
315
|
-
export type {
|
|
316
|
-
|
|
317
|
-
CapacityStatement,
|
|
318
|
-
ContainedStatement,
|
|
319
|
-
ContainmentData,
|
|
320
|
-
KeyData,
|
|
321
|
-
KeyStatement,
|
|
322
|
-
Statement,
|
|
323
|
-
StatementData
|
|
324
|
-
}
|
|
325
|
-
export { capacity, contained, isStatement, key, mirrors, renderStatement }
|
|
273
|
+
export type { CapacityStatement, ContainmentStatement, KeyStatement, MirrorsStatement, Statement }
|
|
274
|
+
export { capacity, contained, key, mirrors, renderStatement, statementDescriptor }
|