@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/errors.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Data } from "effect"
|
|
1
|
+
import { Data, Schema } from "effect"
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Pure authoring failures, SDK-invariant defects and internal native-boundary
|
|
@@ -8,8 +8,23 @@ import { Data } from "effect"
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
/** A pure schema, query, parameter, or value-authoring refusal. */
|
|
11
|
+
export const AuthoringDiagnostic = Schema.Struct({
|
|
12
|
+
code: Schema.Literals([
|
|
13
|
+
"InvalidValue",
|
|
14
|
+
"InvalidRecord",
|
|
15
|
+
"UnknownField",
|
|
16
|
+
"MissingField",
|
|
17
|
+
"InvalidDeclaration",
|
|
18
|
+
"InvalidQuery"
|
|
19
|
+
]),
|
|
20
|
+
context: Schema.String,
|
|
21
|
+
expected: Schema.String
|
|
22
|
+
})
|
|
23
|
+
export type AuthoringDiagnostic = typeof AuthoringDiagnostic.Type
|
|
24
|
+
|
|
11
25
|
export class AuthoringError extends Data.TaggedError("AuthoringError")<{
|
|
12
26
|
readonly message: string
|
|
27
|
+
readonly diagnostic?: AuthoringDiagnostic
|
|
13
28
|
}> {}
|
|
14
29
|
|
|
15
30
|
/** A contradiction in an SDK/native result, not an application refusal. */
|
package/src/face.ts
CHANGED
|
@@ -1,19 +1,24 @@
|
|
|
1
|
-
import type { AnyClosed
|
|
1
|
+
import type { AnyClosed } from "#closed.ts"
|
|
2
|
+
import { memberDescriptor, sealedFieldOf } from "#closed.ts"
|
|
2
3
|
import { AuthoringError } from "#errors.ts"
|
|
3
|
-
import type
|
|
4
|
+
import { type AnyField, assertDeclarationRecord, type SignatureOf } from "#fields.ts"
|
|
4
5
|
import type { Same } from "#judgment.ts"
|
|
5
|
-
import type { AnyRelation,
|
|
6
|
+
import type { AnyRelation, FieldsShape } from "#relation.ts"
|
|
7
|
+
import { type AnySelected, type FieldsOf, type SelectionBinding, selectionBindings } from "#selection.ts"
|
|
6
8
|
import { renderLiteralSet } from "#spec.ts"
|
|
9
|
+
import { arrayValue, recordValue } from "#values.ts"
|
|
7
10
|
|
|
8
11
|
const emptySelection: readonly SelectionBinding[] = Object.freeze([])
|
|
9
12
|
|
|
10
|
-
type OwnerOf<S extends FaceSource> = S extends AnySelected
|
|
13
|
+
type OwnerOf<S extends FaceSource> = S extends AnySelected ? S["relation"] : S
|
|
11
14
|
|
|
12
15
|
function faceParts(source: FaceSource): {
|
|
13
16
|
readonly owner: FaceOwner
|
|
14
17
|
readonly selection: readonly SelectionBinding[]
|
|
15
18
|
} {
|
|
19
|
+
assertDeclarationRecord("face source", source)
|
|
16
20
|
if ("relation" in source) {
|
|
21
|
+
recordValue("selected relation", source, ["relation", "selection"])
|
|
17
22
|
return { owner: source.relation, selection: source.selection }
|
|
18
23
|
}
|
|
19
24
|
return { owner: source, selection: emptySelection }
|
|
@@ -21,31 +26,17 @@ function faceParts(source: FaceSource): {
|
|
|
21
26
|
|
|
22
27
|
type FaceOwner = AnyRelation | AnyClosed
|
|
23
28
|
|
|
24
|
-
interface
|
|
29
|
+
interface Face<O extends FaceOwner = FaceOwner, P extends readonly string[] = readonly string[]> {
|
|
25
30
|
readonly owner: O
|
|
26
31
|
readonly projection: P
|
|
27
32
|
readonly selection: readonly SelectionBinding[]
|
|
28
33
|
}
|
|
29
34
|
|
|
30
|
-
|
|
31
|
-
readonly source: S
|
|
32
|
-
readonly projection: P
|
|
33
|
-
readonly data: FaceData<OwnerOf<S>, P>
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
type AnyFace = Face<FaceSource, readonly string[]>
|
|
35
|
+
type AnyFace = Face<FaceOwner, readonly string[]>
|
|
37
36
|
|
|
38
|
-
type FaceSource = AnyRelation | AnyClosed | AnySelected
|
|
37
|
+
type FaceSource = AnyRelation | AnyClosed | AnySelected
|
|
39
38
|
|
|
40
|
-
type FaceFields<S extends FaceSource> = S
|
|
41
|
-
? keyof RelationFields<S["relation"]> & string
|
|
42
|
-
: S extends AnySelectedClosed
|
|
43
|
-
? "id" | (keyof S["relation"]["columns"] & string)
|
|
44
|
-
: S extends AnyRelation
|
|
45
|
-
? keyof RelationFields<S> & string
|
|
46
|
-
: S extends { readonly axioms: Readonly<Record<string, infer Row>> }
|
|
47
|
-
? "id" | (keyof Row & string)
|
|
48
|
-
: never
|
|
39
|
+
type FaceFields<S extends FaceSource> = keyof FieldsOf<OwnerOf<S>> & string
|
|
49
40
|
|
|
50
41
|
/**
|
|
51
42
|
* The wire shape a face projects: the field's {@link SignatureOf} with an
|
|
@@ -61,28 +52,13 @@ type ShapeIn<Fields extends FieldsShape, K extends string> = K extends keyof Fie
|
|
|
61
52
|
? ProjectedSignature<Fields[K]>
|
|
62
53
|
: undefined
|
|
63
54
|
|
|
64
|
-
type ProjectedShape<S extends FaceSource, K extends string> = S
|
|
65
|
-
? ShapeIn<RelationFields<S["relation"]>, K>
|
|
66
|
-
: S extends AnySelectedClosed
|
|
67
|
-
? K extends "id"
|
|
68
|
-
? ProjectedSignature<S["relation"]["id"]>
|
|
69
|
-
: ShapeIn<S["relation"]["columns"], K>
|
|
70
|
-
: S extends AnyRelation
|
|
71
|
-
? ShapeIn<RelationFields<S>, K>
|
|
72
|
-
: S extends {
|
|
73
|
-
readonly id: infer Id extends AnyField
|
|
74
|
-
readonly columns: infer Cols extends Record<string, PayloadField>
|
|
75
|
-
}
|
|
76
|
-
? K extends "id"
|
|
77
|
-
? ProjectedSignature<Id>
|
|
78
|
-
: ShapeIn<Cols, K>
|
|
79
|
-
: undefined
|
|
55
|
+
type ProjectedShape<S extends FaceSource, K extends string> = ShapeIn<FieldsOf<OwnerOf<S>>, K>
|
|
80
56
|
|
|
81
57
|
type ShapesOf<S extends FaceSource, P extends readonly string[]> = {
|
|
82
58
|
readonly [I in keyof P]: ProjectedShape<S, P[I] & string>
|
|
83
59
|
}
|
|
84
60
|
|
|
85
|
-
type FaceShapes<F extends AnyFace> =
|
|
61
|
+
type FaceShapes<F extends AnyFace> = ShapesOf<F["owner"], F["projection"]>
|
|
86
62
|
|
|
87
63
|
type Arity<F extends AnyFace> = F["projection"]["length"]
|
|
88
64
|
|
|
@@ -106,50 +82,45 @@ interface FaceShapeMismatch<Left, Right> {
|
|
|
106
82
|
type SameShapes<A extends AnyFace, B extends AnyFace> =
|
|
107
83
|
Same<FaceShapes<A>, FaceShapes<B>> extends true ? unknown : FaceShapeMismatch<FaceShapes<A>, FaceShapes<B>>
|
|
108
84
|
|
|
109
|
-
function on<S extends FaceSource, const F extends FaceFields<S>>(source: S, field: F): Face<S
|
|
85
|
+
function on<S extends FaceSource, const F extends FaceFields<S>>(source: S, field: F): Face<OwnerOf<S>, readonly [F]>
|
|
110
86
|
function on<S extends FaceSource, const P extends readonly [FaceFields<S>, ...FaceFields<S>[]]>(
|
|
111
87
|
source: S,
|
|
112
88
|
fields: P
|
|
113
|
-
): Face<S
|
|
114
|
-
function on
|
|
115
|
-
const projection: readonly string[] = Object.freeze(typeof fields === "string" ? [fields] : [...fields])
|
|
89
|
+
): Face<OwnerOf<S>, P>
|
|
90
|
+
function on(source: FaceSource, fields: string | readonly string[]): AnyFace {
|
|
116
91
|
const parts = faceParts(source)
|
|
117
|
-
|
|
92
|
+
return faceDescriptor({
|
|
118
93
|
owner: parts.owner,
|
|
119
|
-
projection,
|
|
94
|
+
projection: typeof fields === "string" ? [fields] : fields,
|
|
120
95
|
selection: parts.selection
|
|
121
96
|
})
|
|
122
|
-
const value = Object.freeze({ source, projection, data })
|
|
123
|
-
if (!faceMinted<S, readonly string[]>(value, source, projection)) {
|
|
124
|
-
throw new AuthoringError({ message: `face over ${parts.owner.name}: face construction incomplete` })
|
|
125
|
-
}
|
|
126
|
-
return value
|
|
127
97
|
}
|
|
128
98
|
|
|
129
|
-
/**
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
)
|
|
99
|
+
/** Own and check a structural face. No constructor-only admission token. */
|
|
100
|
+
function faceDescriptor<F extends AnyFace>(input: F): F
|
|
101
|
+
function faceDescriptor(input: unknown): AnyFace
|
|
102
|
+
function faceDescriptor(raw: unknown): AnyFace {
|
|
103
|
+
const input = recordValue("face", raw, ["owner", "projection", "selection"])
|
|
104
|
+
const owner = memberDescriptor(input.owner)
|
|
105
|
+
const projection = arrayValue("face projection", input.projection, (_, value) => {
|
|
106
|
+
if (typeof value !== "string") throw new AuthoringError({ message: "face: expected a field name" })
|
|
107
|
+
return value
|
|
108
|
+
})
|
|
109
|
+
if (projection.length === 0) {
|
|
110
|
+
throw new AuthoringError({ message: "face: expected a nonempty projection" })
|
|
111
|
+
}
|
|
112
|
+
const projected = new Set<string>()
|
|
113
|
+
for (const field of projection) {
|
|
114
|
+
if (typeof field !== "string" || sealedFieldOf(owner, field) === undefined)
|
|
115
|
+
throw new AuthoringError({ message: `face ${owner.name}: unknown field ${String(field)}` })
|
|
116
|
+
if (projected.has(field)) throw new AuthoringError({ message: `face ${owner.name}: duplicate field ${field}` })
|
|
117
|
+
projected.add(field)
|
|
118
|
+
}
|
|
119
|
+
const selection = selectionBindings(owner, input.selection)
|
|
120
|
+
return Object.freeze({ owner, projection, selection })
|
|
150
121
|
}
|
|
151
122
|
|
|
152
|
-
function renderFace(face:
|
|
123
|
+
function renderFace(face: AnyFace): string {
|
|
153
124
|
const projection = face.projection.join(", ")
|
|
154
125
|
if (face.selection.length === 0) {
|
|
155
126
|
return `${face.owner.name}(${projection})`
|
|
@@ -167,7 +138,6 @@ export type {
|
|
|
167
138
|
Arity,
|
|
168
139
|
Face,
|
|
169
140
|
FaceArityMismatch,
|
|
170
|
-
FaceData,
|
|
171
141
|
FaceFields,
|
|
172
142
|
FaceOwner,
|
|
173
143
|
FaceShapeMismatch,
|
|
@@ -178,4 +148,4 @@ export type {
|
|
|
178
148
|
SameArity,
|
|
179
149
|
SameShapes
|
|
180
150
|
}
|
|
181
|
-
export { on, renderFace }
|
|
151
|
+
export { faceDescriptor, on, renderFace }
|
package/src/fields.ts
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { regex } from "arkregex"
|
|
2
|
-
import { Result } from "effect"
|
|
3
2
|
import { AuthoringError } from "#errors.ts"
|
|
4
|
-
import { DbError } from "#runtime-errors.ts"
|
|
5
3
|
import type { LiteralSpec } from "#spec.ts"
|
|
6
|
-
import { Uuid } from "#uuid.ts"
|
|
4
|
+
import type { Uuid } from "#uuid.ts"
|
|
5
|
+
import { arrayValue, fieldValue, recordValue, taggedValueOf, U64_MAX } from "#values.ts"
|
|
7
6
|
|
|
8
7
|
const INTEGER_INDEX_NAME = regex("^(?:0|[1-9][0-9]*)$")
|
|
9
8
|
|
|
@@ -23,54 +22,15 @@ interface IntervalValue {
|
|
|
23
22
|
/**
|
|
24
23
|
* A half-open dense float interval `[start, end)` as a plain value object:
|
|
25
24
|
* two canonical binary64 bounds on the dense numeric line.
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
* denote a missing bound, not a member point.
|
|
25
|
+
* Field-directed validation rejects NaN endpoints and empty intervals and
|
|
26
|
+
* normalizes signed zero. Infinite bounds denote a missing bound, not a
|
|
27
|
+
* member point; a plain object alone is not evidence of validation.
|
|
30
28
|
*/
|
|
31
29
|
interface FloatIntervalValue {
|
|
32
30
|
readonly start: number
|
|
33
31
|
readonly end: number
|
|
34
32
|
}
|
|
35
33
|
|
|
36
|
-
/**
|
|
37
|
-
* Constructs a checked interval literal — the `start..end` spelling.
|
|
38
|
-
* Half-open and nonempty by construction. Fallible pure parsing
|
|
39
|
-
* returns `Result` (use `Effect.fromResult(span(...))` inside a generator),
|
|
40
|
-
* never hidden I/O and never a thrown domain outcome.
|
|
41
|
-
*
|
|
42
|
-
* Two element domains, selected by argument type: `span(0n, 60n)` is a
|
|
43
|
-
* discrete integer interval; `span(0.5, 1.5)` is a dense float interval
|
|
44
|
-
* with canonical endpoints (NaN refused, `-0` normalized to `+0`,
|
|
45
|
-
* `-Infinity` legal only as the lower bound and `+Infinity` only as the
|
|
46
|
-
* upper bound — both enforced by strict numeric `start < end`).
|
|
47
|
-
*/
|
|
48
|
-
function span(start: bigint, end: bigint): Result.Result<IntervalValue, DbError>
|
|
49
|
-
function span(start: number, end: number): Result.Result<FloatIntervalValue, DbError>
|
|
50
|
-
function span(
|
|
51
|
-
start: bigint | number,
|
|
52
|
-
end: bigint | number
|
|
53
|
-
): Result.Result<IntervalValue, DbError> | Result.Result<FloatIntervalValue, DbError> {
|
|
54
|
-
if (typeof start === "bigint" && typeof end === "bigint") {
|
|
55
|
-
if (start >= end) {
|
|
56
|
-
return Result.fail(new DbError({ operation: "span", reason: { _tag: "InvalidArgument" } }))
|
|
57
|
-
}
|
|
58
|
-
return Result.succeed(Object.freeze({ start, end }))
|
|
59
|
-
}
|
|
60
|
-
if (typeof start === "number" && typeof end === "number") {
|
|
61
|
-
if (Number.isNaN(start) || Number.isNaN(end)) {
|
|
62
|
-
return Result.fail(new DbError({ operation: "span", reason: { _tag: "InvalidArgument" } }))
|
|
63
|
-
}
|
|
64
|
-
const lo = Object.is(start, -0) ? 0 : start
|
|
65
|
-
const hi = Object.is(end, -0) ? 0 : end
|
|
66
|
-
if (!(lo < hi)) {
|
|
67
|
-
return Result.fail(new DbError({ operation: "span", reason: { _tag: "InvalidArgument" } }))
|
|
68
|
-
}
|
|
69
|
-
return Result.succeed(Object.freeze({ start: lo, end: hi }))
|
|
70
|
-
}
|
|
71
|
-
return Result.fail(new DbError({ operation: "span", reason: { _tag: "InvalidArgument" } }))
|
|
72
|
-
}
|
|
73
|
-
|
|
74
34
|
/**
|
|
75
35
|
* Nonempty declaration-order handle vector — the ONE roster carrier. The
|
|
76
36
|
* handle union is `Handles[number]`, the ordinal of a handle is its tuple
|
|
@@ -187,11 +147,11 @@ type Infer<F extends AnyField> = F extends { readonly kind: "bool" }
|
|
|
187
147
|
? Uuid
|
|
188
148
|
: F extends { readonly kind: "bytes" }
|
|
189
149
|
? Uint8Array
|
|
190
|
-
: F extends { readonly kind: "interval"; readonly element:
|
|
191
|
-
?
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
150
|
+
: F extends { readonly kind: "interval"; readonly element: infer Element }
|
|
151
|
+
? Element extends "f64"
|
|
152
|
+
? FloatIntervalValue
|
|
153
|
+
: IntervalValue
|
|
154
|
+
: never
|
|
195
155
|
|
|
196
156
|
/**
|
|
197
157
|
* The typed shape refusal shared by every literal machine — the selection
|
|
@@ -263,47 +223,10 @@ function isFloatIntervalValue(value: unknown): value is FloatIntervalValue {
|
|
|
263
223
|
)
|
|
264
224
|
}
|
|
265
225
|
|
|
266
|
-
function handleLiteral(closed: AnyClosedRoster, value: unknown): LiteralSpec {
|
|
267
|
-
if (typeof value !== "string") {
|
|
268
|
-
throw literalShapeError("selection literal", `a ${closed.name} handle name (string)`, value)
|
|
269
|
-
}
|
|
270
|
-
if (!closed.handles.includes(value)) {
|
|
271
|
-
throw new AuthoringError({
|
|
272
|
-
message: `"${value}" is not a handle of ${closed.name} — the roster is ${closed.handles.join(", ")}`
|
|
273
|
-
})
|
|
274
|
-
}
|
|
275
|
-
return { kind: "handle", handle: value }
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
function intervalLiteral(element: IntervalElementKind, value: unknown): LiteralSpec {
|
|
279
|
-
if (element === "f64") {
|
|
280
|
-
if (!isFloatIntervalValue(value)) {
|
|
281
|
-
throw literalShapeError("selection literal", "float interval ({ start, end } numbers)", value)
|
|
282
|
-
}
|
|
283
|
-
if (Number.isNaN(value.start) || Number.isNaN(value.end) || !(value.start < value.end)) {
|
|
284
|
-
throw new AuthoringError({
|
|
285
|
-
message: "selection literal: a float interval is half-open and nonempty with non-NaN canonical endpoints"
|
|
286
|
-
})
|
|
287
|
-
}
|
|
288
|
-
return {
|
|
289
|
-
kind: "value",
|
|
290
|
-
value: {
|
|
291
|
-
kind: "intervalF64",
|
|
292
|
-
start: Object.is(value.start, -0) ? 0 : value.start,
|
|
293
|
-
end: Object.is(value.end, -0) ? 0 : value.end
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
if (!isIntervalValue(value)) {
|
|
298
|
-
throw literalShapeError("selection literal", "interval ({ start, end } bigints)", value)
|
|
299
|
-
}
|
|
300
|
-
if (element === "u64") {
|
|
301
|
-
return { kind: "value", value: { kind: "intervalU64", start: value.start, end: value.end } }
|
|
302
|
-
}
|
|
303
|
-
return { kind: "value", value: { kind: "intervalI64", start: value.start, end: value.end } }
|
|
304
|
-
}
|
|
305
|
-
|
|
306
226
|
function assertDeclarationOrderKey(where: string, name: string): void {
|
|
227
|
+
if (typeof name !== "string" || !name.isWellFormed()) {
|
|
228
|
+
throw new AuthoringError({ message: `${where}: expected a well-formed Unicode name` })
|
|
229
|
+
}
|
|
307
230
|
if (INTEGER_INDEX_NAME.test(name)) {
|
|
308
231
|
throw new AuthoringError({
|
|
309
232
|
message: `${where}: name ${name} is an integer index — JavaScript object keys re-order integer indices, breaking the declaration-order law; use a non-numeric name`
|
|
@@ -317,12 +240,92 @@ function assertDeclarationOrderKey(where: string, name: string): void {
|
|
|
317
240
|
}
|
|
318
241
|
|
|
319
242
|
function assertDeclarationRecord(where: string, record: object): void {
|
|
243
|
+
if (typeof record !== "object" || record === null) {
|
|
244
|
+
throw new AuthoringError({ message: `${where}: expected a plain declaration record` })
|
|
245
|
+
}
|
|
320
246
|
const proto = Object.getPrototypeOf(record)
|
|
321
247
|
if (proto !== Object.prototype && proto !== null) {
|
|
322
248
|
throw new AuthoringError({
|
|
323
249
|
message: `${where}: the declaration record's prototype was replaced — a plain \`__proto__: {...}\` entry is the prototype setter, so its key silently vanishes from the declaration; spell it computed (["__proto__"]: {...}) to declare it as data`
|
|
324
250
|
})
|
|
325
251
|
}
|
|
252
|
+
for (const key of Reflect.ownKeys(record)) {
|
|
253
|
+
const property = Object.getOwnPropertyDescriptor(record, key)
|
|
254
|
+
if (typeof key !== "string" || property?.enumerable !== true || !("value" in property)) {
|
|
255
|
+
throw new AuthoringError({ message: `${where}: declarations require enumerable own data fields` })
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/** Own a checked roster; never freeze the caller's array. */
|
|
261
|
+
function ownHandles<const H extends ClosedHandleTuple>(context: string, handles: H): H
|
|
262
|
+
function ownHandles(context: string, handles: unknown): ClosedHandleTuple
|
|
263
|
+
function ownHandles(context: string, handles: unknown): ClosedHandleTuple {
|
|
264
|
+
if (!Array.isArray(handles) || handles.length === 0) {
|
|
265
|
+
throw new AuthoringError({ message: `${context}: expected a nonempty handle tuple` })
|
|
266
|
+
}
|
|
267
|
+
const seen = new Set<string>()
|
|
268
|
+
const values = arrayValue(context, handles, (_, handle) => {
|
|
269
|
+
if (typeof handle !== "string") throw new AuthoringError({ message: `${context}: expected string handles` })
|
|
270
|
+
if (!handle.isWellFormed()) throw new AuthoringError({ message: `${context}: expected well-formed string handles` })
|
|
271
|
+
if (seen.has(handle)) throw new AuthoringError({ message: `${context}: duplicate handle ${handle}` })
|
|
272
|
+
seen.add(handle)
|
|
273
|
+
return handle
|
|
274
|
+
})
|
|
275
|
+
const first = values[0]
|
|
276
|
+
if (first === undefined) throw new AuthoringError({ message: `${context}: expected a nonempty handle tuple` })
|
|
277
|
+
return Object.freeze([first, ...values.slice(1)])
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/** Checked structural field descriptors, shared by declaration boundaries. */
|
|
281
|
+
function fieldDescriptor<F extends AnyField>(context: string, input: F): F
|
|
282
|
+
function fieldDescriptor(context: string, input: unknown): AnyField
|
|
283
|
+
function fieldDescriptor(context: string, input: unknown): AnyField {
|
|
284
|
+
if (typeof input !== "object" || input === null || !("kind" in input)) {
|
|
285
|
+
throw new AuthoringError({ message: `${context}: expected a field descriptor` })
|
|
286
|
+
}
|
|
287
|
+
assertDeclarationRecord(context, input)
|
|
288
|
+
switch (input.kind) {
|
|
289
|
+
case "bool":
|
|
290
|
+
case "str":
|
|
291
|
+
case "i64":
|
|
292
|
+
case "f64":
|
|
293
|
+
case "uuid":
|
|
294
|
+
recordValue(context, input, ["kind"])
|
|
295
|
+
return Object.freeze({ kind: input.kind })
|
|
296
|
+
case "u64": {
|
|
297
|
+
if (!("closed" in input)) {
|
|
298
|
+
recordValue(context, input, ["kind"])
|
|
299
|
+
return u64
|
|
300
|
+
}
|
|
301
|
+
recordValue(context, input, ["kind", "closed"])
|
|
302
|
+
const roster = recordValue(context, input.closed, ["name", "handles"])
|
|
303
|
+
if (typeof roster.name !== "string") throw new AuthoringError({ message: `${context}: expected a roster name` })
|
|
304
|
+
assertDeclarationOrderKey(context, roster.name)
|
|
305
|
+
return Object.freeze({
|
|
306
|
+
kind: "u64",
|
|
307
|
+
closed: Object.freeze({ name: roster.name, handles: ownHandles(context, roster.handles) })
|
|
308
|
+
})
|
|
309
|
+
}
|
|
310
|
+
case "bytes": {
|
|
311
|
+
const value = recordValue(context, input, ["kind", "width"])
|
|
312
|
+
if (typeof value.width !== "number") throw new AuthoringError({ message: `${context}: expected a bytes width` })
|
|
313
|
+
return bytes(value.width)
|
|
314
|
+
}
|
|
315
|
+
case "interval": {
|
|
316
|
+
const value = recordValue(context, input, ["kind", "element", "width"])
|
|
317
|
+
if (value.element !== "u64" && value.element !== "i64" && value.element !== "f64") {
|
|
318
|
+
throw new AuthoringError({ message: `${context}: expected an interval element kind` })
|
|
319
|
+
}
|
|
320
|
+
if (value.width === undefined) return interval({ kind: value.element })
|
|
321
|
+
if (value.element === "f64" || typeof value.width !== "bigint") {
|
|
322
|
+
throw new AuthoringError({ message: `${context}: only discrete intervals accept a bigint width` })
|
|
323
|
+
}
|
|
324
|
+
return interval({ kind: value.element }, value.width)
|
|
325
|
+
}
|
|
326
|
+
default:
|
|
327
|
+
throw new AuthoringError({ message: `${context}: unknown field kind` })
|
|
328
|
+
}
|
|
326
329
|
}
|
|
327
330
|
|
|
328
331
|
const u64: U64Field = Object.freeze({ kind: "u64" })
|
|
@@ -371,69 +374,19 @@ function interval(
|
|
|
371
374
|
"interval(f64) takes no width — a fixed-width float interval is unrepresentable (rounded start + width is not an exact fixed length on the dense line); applications supply two checked bounds"
|
|
372
375
|
})
|
|
373
376
|
}
|
|
374
|
-
if (width !== undefined && width < 1n) {
|
|
377
|
+
if (width !== undefined && (typeof width !== "bigint" || width < 1n || width > U64_MAX)) {
|
|
375
378
|
throw new AuthoringError({
|
|
376
|
-
message:
|
|
379
|
+
message: "interval width must be a bigint in 1..=u64::MAX"
|
|
377
380
|
})
|
|
378
381
|
}
|
|
379
382
|
return Object.freeze({ kind: "interval", element: elementKind, width })
|
|
380
383
|
}
|
|
381
384
|
|
|
382
385
|
function literalOf(field: AnyField, value: unknown): LiteralSpec {
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
return handleLiteral(roster, value)
|
|
386
|
-
}
|
|
387
|
-
switch (field.kind) {
|
|
388
|
-
case "bool": {
|
|
389
|
-
if (typeof value !== "boolean") {
|
|
390
|
-
throw literalShapeError("selection literal", "boolean", value)
|
|
391
|
-
}
|
|
392
|
-
return { kind: "value", value: { kind: "bool", value } }
|
|
393
|
-
}
|
|
394
|
-
case "u64": {
|
|
395
|
-
if (typeof value !== "bigint") {
|
|
396
|
-
throw literalShapeError("selection literal", "bigint", value)
|
|
397
|
-
}
|
|
398
|
-
return { kind: "value", value: { kind: "u64", value } }
|
|
399
|
-
}
|
|
400
|
-
case "i64": {
|
|
401
|
-
if (typeof value !== "bigint") {
|
|
402
|
-
throw literalShapeError("selection literal", "bigint", value)
|
|
403
|
-
}
|
|
404
|
-
return { kind: "value", value: { kind: "i64", value } }
|
|
405
|
-
}
|
|
406
|
-
case "str": {
|
|
407
|
-
if (typeof value !== "string") {
|
|
408
|
-
throw literalShapeError("selection literal", "string", value)
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
if (!value.isWellFormed()) {
|
|
412
|
-
throw literalShapeError("selection literal", "well-formed string", value)
|
|
413
|
-
}
|
|
414
|
-
return { kind: "value", value: { kind: "string", value } }
|
|
415
|
-
}
|
|
416
|
-
case "f64": {
|
|
417
|
-
if (typeof value !== "number") {
|
|
418
|
-
throw literalShapeError("selection literal", "number", value)
|
|
419
|
-
}
|
|
420
|
-
return { kind: "value", value: { kind: "f64", value } }
|
|
421
|
-
}
|
|
422
|
-
case "uuid": {
|
|
423
|
-
if (!Uuid.isUuid(value)) {
|
|
424
|
-
throw literalShapeError("selection literal", "a UUID (canonical UUID text)", value)
|
|
425
|
-
}
|
|
426
|
-
return { kind: "value", value: { kind: "uuid", value } }
|
|
427
|
-
}
|
|
428
|
-
case "bytes": {
|
|
429
|
-
if (!(value instanceof Uint8Array)) {
|
|
430
|
-
throw literalShapeError("selection literal", "Uint8Array", value)
|
|
431
|
-
}
|
|
432
|
-
return { kind: "value", value: { kind: "fixedBytes", value } }
|
|
433
|
-
}
|
|
434
|
-
case "interval":
|
|
435
|
-
return intervalLiteral(field.element, value)
|
|
386
|
+
if ("closed" in field) {
|
|
387
|
+
return { kind: "handle", handle: fieldValue("selection literal", field, value) }
|
|
436
388
|
}
|
|
389
|
+
return { kind: "value", value: taggedValueOf("selection literal", field, value) }
|
|
437
390
|
}
|
|
438
391
|
|
|
439
392
|
export type {
|
|
@@ -463,16 +416,17 @@ export {
|
|
|
463
416
|
bool,
|
|
464
417
|
bytes,
|
|
465
418
|
f64,
|
|
419
|
+
fieldDescriptor,
|
|
466
420
|
i64,
|
|
467
421
|
interval,
|
|
468
422
|
isFloatIntervalValue,
|
|
469
423
|
isIntervalValue,
|
|
470
424
|
literalOf,
|
|
471
425
|
literalShapeError,
|
|
426
|
+
ownHandles,
|
|
472
427
|
rosterOf,
|
|
473
428
|
rostersAgree,
|
|
474
429
|
signaturesAgree,
|
|
475
|
-
span,
|
|
476
430
|
str,
|
|
477
431
|
u64,
|
|
478
432
|
uuid
|
package/src/immutable.ts
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/** Only frozen plain data graphs are stable cache keys. Typed-array contents
|
|
2
|
+
* remain mutable even behind a frozen parent, so they never qualify. */
|
|
3
|
+
const immutable = new WeakSet<object>()
|
|
4
|
+
|
|
5
|
+
function isImmutable(input: unknown, visiting = new WeakSet<object>()): boolean {
|
|
6
|
+
if (typeof input !== "object" || input === null) return typeof input !== "function"
|
|
7
|
+
if (immutable.has(input)) return true
|
|
8
|
+
const prototype = Object.getPrototypeOf(input)
|
|
9
|
+
if (
|
|
10
|
+
(prototype !== Object.prototype && prototype !== null && prototype !== Array.prototype) ||
|
|
11
|
+
!Object.isFrozen(input) ||
|
|
12
|
+
visiting.has(input)
|
|
13
|
+
)
|
|
14
|
+
return false
|
|
15
|
+
visiting.add(input)
|
|
16
|
+
for (const name of Reflect.ownKeys(input)) {
|
|
17
|
+
const property = Object.getOwnPropertyDescriptor(input, name)
|
|
18
|
+
if (property === undefined || !("value" in property) || !isImmutable(property.value, visiting)) {
|
|
19
|
+
visiting.delete(input)
|
|
20
|
+
return false
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
visiting.delete(input)
|
|
24
|
+
immutable.add(input)
|
|
25
|
+
return true
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Validation is never skipped for a mutable structural input. Validated
|
|
29
|
+
* immutable outputs can be reused without requiring constructor identity. */
|
|
30
|
+
function descriptorCache<A extends object>(parse: (input: unknown) => A): (input: unknown) => A {
|
|
31
|
+
const cache = new WeakMap<object, A>()
|
|
32
|
+
return (input) => {
|
|
33
|
+
if (typeof input === "object" && input !== null) {
|
|
34
|
+
const found = cache.get(input)
|
|
35
|
+
if (found !== undefined) return found
|
|
36
|
+
}
|
|
37
|
+
const result = parse(input)
|
|
38
|
+
if (isImmutable(result)) {
|
|
39
|
+
cache.set(result, result)
|
|
40
|
+
if (typeof input === "object" && input !== null && isImmutable(input)) cache.set(input, result)
|
|
41
|
+
}
|
|
42
|
+
return result
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Detached inspection data: share proven immutable branches, copy byte
|
|
47
|
+
* buffers and their parents, and preserve references within one snapshot.
|
|
48
|
+
* This is for already checked internal data, not an input validator. */
|
|
49
|
+
function snapshotData<A>(input: A, onCopy?: (source: object, snapshot: object) => void): A
|
|
50
|
+
function snapshotData(input: unknown, onCopy?: (source: object, snapshot: object) => void): unknown {
|
|
51
|
+
const copied = new WeakMap<object, object>()
|
|
52
|
+
function visit(value: unknown): unknown {
|
|
53
|
+
if (typeof value !== "object" || value === null || isImmutable(value)) return value
|
|
54
|
+
const existing = copied.get(value)
|
|
55
|
+
if (existing !== undefined) return existing
|
|
56
|
+
if (value instanceof Uint8Array) {
|
|
57
|
+
const bytes = Uint8Array.from(value)
|
|
58
|
+
copied.set(value, bytes)
|
|
59
|
+
return bytes
|
|
60
|
+
}
|
|
61
|
+
const result = Array.isArray(value) ? [] : Object.create(Object.getPrototypeOf(value))
|
|
62
|
+
copied.set(value, result)
|
|
63
|
+
for (const name of Reflect.ownKeys(value)) {
|
|
64
|
+
if (Array.isArray(value) && name === "length") continue
|
|
65
|
+
const property = Object.getOwnPropertyDescriptor(value, name)
|
|
66
|
+
if (property === undefined || !("value" in property)) throw new TypeError("snapshotData requires checked data")
|
|
67
|
+
Object.defineProperty(result, name, { value: visit(property.value), enumerable: property.enumerable === true })
|
|
68
|
+
}
|
|
69
|
+
Object.freeze(result)
|
|
70
|
+
onCopy?.(value, result)
|
|
71
|
+
return result
|
|
72
|
+
}
|
|
73
|
+
return visit(input)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export { descriptorCache, isImmutable, snapshotData }
|