@bjornpagen/bumbledb 1.3.0 → 2.0.0-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/COOKBOOK.md +224 -95
- package/README.md +114 -45
- package/dist/alternatives.d.ts +5 -6
- package/dist/alternatives.d.ts.map +1 -1
- package/dist/alternatives.js +5 -6
- package/dist/alternatives.js.map +1 -1
- package/dist/bin.d.ts +3 -0
- package/dist/bin.d.ts.map +1 -0
- package/dist/bin.js +85 -0
- package/dist/bin.js.map +1 -0
- package/dist/capacity.d.ts +14 -17
- package/dist/capacity.d.ts.map +1 -1
- package/dist/capacity.js +7 -7
- package/dist/capacity.js.map +1 -1
- package/dist/changes.d.ts +32 -41
- package/dist/changes.d.ts.map +1 -1
- package/dist/changes.js +86 -110
- package/dist/changes.js.map +1 -1
- package/dist/closed.d.ts +2 -2
- package/dist/closed.d.ts.map +1 -1
- package/dist/closed.js +5 -5
- package/dist/closed.js.map +1 -1
- package/dist/codec.d.ts +20 -27
- package/dist/codec.d.ts.map +1 -1
- package/dist/codec.js +48 -22
- package/dist/codec.js.map +1 -1
- package/dist/compile.d.ts +26 -50
- package/dist/compile.d.ts.map +1 -1
- package/dist/compile.js +75 -64
- package/dist/compile.js.map +1 -1
- package/dist/database/database.d.ts +98 -0
- package/dist/database/database.d.ts.map +1 -0
- package/dist/database/database.js +249 -0
- package/dist/database/database.js.map +1 -0
- package/dist/database/driver.d.ts +40 -0
- package/dist/database/driver.d.ts.map +1 -0
- package/dist/database/driver.js +68 -0
- package/dist/database/driver.js.map +1 -0
- package/dist/database/fs.d.ts +12 -0
- package/dist/database/fs.d.ts.map +1 -0
- package/dist/database/fs.js +120 -0
- package/dist/database/fs.js.map +1 -0
- package/dist/database/generate.d.ts +26 -0
- package/dist/database/generate.d.ts.map +1 -0
- package/dist/database/generate.js +102 -0
- package/dist/database/generate.js.map +1 -0
- package/dist/database/hosted.d.ts +32 -0
- package/dist/database/hosted.d.ts.map +1 -0
- package/dist/database/hosted.js +44 -0
- package/dist/database/hosted.js.map +1 -0
- package/dist/database/io.d.ts +94 -0
- package/dist/database/io.d.ts.map +1 -0
- package/dist/database/io.js +35 -0
- package/dist/database/io.js.map +1 -0
- package/dist/database/mem.d.ts +26 -0
- package/dist/database/mem.d.ts.map +1 -0
- package/dist/database/mem.js +67 -0
- package/dist/database/mem.js.map +1 -0
- package/dist/database/migration.d.ts +55 -0
- package/dist/database/migration.d.ts.map +1 -0
- package/dist/database/migration.js +46 -0
- package/dist/database/migration.js.map +1 -0
- package/dist/database/s3.d.ts +36 -0
- package/dist/database/s3.d.ts.map +1 -0
- package/dist/database/s3.js +141 -0
- package/dist/database/s3.js.map +1 -0
- package/dist/db.d.ts +44 -128
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +163 -287
- package/dist/db.js.map +1 -1
- package/dist/engine.d.ts +7 -0
- package/dist/engine.d.ts.map +1 -0
- package/dist/engine.js +2 -0
- package/dist/engine.js.map +1 -0
- package/dist/errors.d.ts +152 -46
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +107 -19
- package/dist/errors.js.map +1 -1
- package/dist/face.d.ts +5 -5
- package/dist/face.d.ts.map +1 -1
- package/dist/face.js +6 -6
- package/dist/face.js.map +1 -1
- package/dist/fields.d.ts +4 -5
- package/dist/fields.d.ts.map +1 -1
- package/dist/fields.js +3 -3
- package/dist/fields.js.map +1 -1
- package/dist/index.d.ts +60 -58
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +27 -29
- package/dist/index.js.map +1 -1
- package/dist/judgment.d.ts +1 -1
- package/dist/judgment.js +1 -1
- package/dist/law.d.ts +16 -68
- package/dist/law.d.ts.map +1 -1
- package/dist/law.js +3 -3
- package/dist/law.js.map +1 -1
- package/dist/lower.d.ts +3 -3
- package/dist/lower.d.ts.map +1 -1
- package/dist/lower.js +35 -39
- package/dist/lower.js.map +1 -1
- package/dist/native/addon.d.ts +23 -0
- package/dist/native/addon.d.ts.map +1 -0
- package/dist/native/addon.js +15 -0
- package/dist/native/addon.js.map +1 -0
- package/dist/native/binding.d.ts +996 -0
- package/dist/native/json.d.ts +14 -0
- package/dist/native/json.d.ts.map +1 -0
- package/dist/native/json.js +73 -0
- package/dist/native/json.js.map +1 -0
- package/dist/native/load.d.ts +9 -0
- package/dist/native/load.d.ts.map +1 -0
- package/dist/native/load.js +30 -0
- package/dist/native/load.js.map +1 -0
- package/dist/native/op.d.ts +29 -0
- package/dist/native/op.d.ts.map +1 -0
- package/dist/native/op.js +50 -0
- package/dist/native/op.js.map +1 -0
- package/dist/query/atom.d.ts +13 -14
- package/dist/query/atom.d.ts.map +1 -1
- package/dist/query/atom.js +2 -2
- package/dist/query/atom.js.map +1 -1
- package/dist/query/compute.d.ts +7 -7
- package/dist/query/compute.d.ts.map +1 -1
- package/dist/query/compute.js +7 -16
- package/dist/query/compute.js.map +1 -1
- package/dist/query/find.d.ts +8 -8
- package/dist/query/find.d.ts.map +1 -1
- package/dist/query/find.js +1 -1
- package/dist/query/lower.d.ts +31 -17
- package/dist/query/lower.d.ts.map +1 -1
- package/dist/query/lower.js +260 -368
- package/dist/query/lower.js.map +1 -1
- package/dist/query/run.d.ts +5 -5
- package/dist/query/run.d.ts.map +1 -1
- package/dist/query/run.js +32 -15
- package/dist/query/run.js.map +1 -1
- package/dist/query/scope.d.ts +20 -28
- package/dist/query/scope.d.ts.map +1 -1
- package/dist/query/scope.js +13 -8
- package/dist/query/scope.js.map +1 -1
- package/dist/query/segments.d.ts +2 -2
- package/dist/query/segments.d.ts.map +1 -1
- package/dist/query/segments.js +3 -3
- package/dist/query/segments.js.map +1 -1
- package/dist/relation.d.ts +1 -1
- package/dist/relation.d.ts.map +1 -1
- package/dist/relation.js +4 -4
- package/dist/relation.js.map +1 -1
- package/dist/result.d.ts +15 -22
- package/dist/result.d.ts.map +1 -1
- package/dist/result.js +30 -39
- package/dist/result.js.map +1 -1
- package/dist/rows.d.ts +9 -33
- package/dist/rows.d.ts.map +1 -1
- package/dist/rows.js +8 -12
- package/dist/rows.js.map +1 -1
- package/dist/runtime.d.ts +17 -43
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +41 -241
- package/dist/runtime.js.map +1 -1
- package/dist/scalar.d.ts +36 -106
- package/dist/scalar.d.ts.map +1 -1
- package/dist/scalar.js +36 -185
- package/dist/scalar.js.map +1 -1
- package/dist/schema.d.ts +7 -18
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +18 -228
- package/dist/schema.js.map +1 -1
- package/dist/selection.d.ts +4 -4
- package/dist/selection.d.ts.map +1 -1
- package/dist/selection.js +5 -5
- package/dist/selection.js.map +1 -1
- package/dist/shape.d.ts +5 -5
- package/dist/shape.d.ts.map +1 -1
- package/dist/spec.d.ts +7 -79
- package/dist/spec.d.ts.map +1 -1
- package/dist/spec.js.map +1 -1
- package/dist/statements.d.ts +3 -3
- package/dist/statements.d.ts.map +1 -1
- package/dist/statements.js +10 -10
- package/dist/statements.js.map +1 -1
- package/dist/uuid.d.ts +1 -1
- package/dist/uuid.d.ts.map +1 -1
- package/dist/uuid.js +1 -1
- package/dist/uuid.js.map +1 -1
- package/dist/values.d.ts +2 -2
- package/dist/values.d.ts.map +1 -1
- package/dist/values.js +2 -2
- package/dist/values.js.map +1 -1
- package/package.json +18 -18
- package/src/alternatives.ts +7 -8
- package/src/bin.ts +82 -0
- package/src/capacity.ts +20 -23
- package/src/changes.ts +134 -197
- package/src/closed.ts +5 -5
- package/src/codec.ts +77 -51
- package/src/compile.ts +91 -113
- package/src/database/database.ts +413 -0
- package/src/database/driver.ts +134 -0
- package/src/database/fs.ts +119 -0
- package/src/database/generate.ts +116 -0
- package/src/database/hosted.ts +102 -0
- package/src/database/io.ts +134 -0
- package/src/database/mem.ts +113 -0
- package/src/database/migration.ts +107 -0
- package/src/database/s3.ts +231 -0
- package/src/db.ts +290 -547
- package/src/engine.ts +14 -0
- package/src/errors.ts +118 -39
- package/src/face.ts +9 -9
- package/src/fields.ts +6 -7
- package/src/index.ts +102 -109
- package/src/judgment.ts +1 -1
- package/src/law.ts +18 -70
- package/src/lower.ts +61 -72
- package/src/native/addon.ts +54 -0
- package/src/native/binding.d.ts +996 -0
- package/src/native/json.ts +100 -0
- package/src/native/load.ts +31 -0
- package/src/native/op.ts +73 -0
- package/src/query/atom.ts +15 -16
- package/src/query/compute.ts +13 -22
- package/src/query/find.ts +8 -8
- package/src/query/lower.ts +323 -414
- package/src/query/run.ts +42 -21
- package/src/query/scope.ts +37 -38
- package/src/query/segments.ts +4 -4
- package/src/relation.ts +4 -4
- package/src/result.ts +53 -80
- package/src/rows.ts +18 -46
- package/src/runtime.ts +50 -346
- package/src/scalar.ts +76 -336
- package/src/schema.ts +23 -284
- package/src/selection.ts +6 -6
- package/src/shape.ts +5 -5
- package/src/spec.ts +7 -85
- package/src/statements.ts +11 -11
- package/src/uuid.ts +1 -1
- package/src/values.ts +4 -4
- package/dist/close.d.ts +0 -24
- package/dist/close.d.ts.map +0 -1
- package/dist/close.js +0 -34
- package/dist/close.js.map +0 -1
- package/dist/db-native.d.ts +0 -196
- package/dist/db-native.d.ts.map +0 -1
- package/dist/db-native.js +0 -13
- package/dist/db-native.js.map +0 -1
- package/dist/internal/log.d.ts +0 -26
- package/dist/internal/log.d.ts.map +0 -1
- package/dist/internal/log.js +0 -12
- package/dist/internal/log.js.map +0 -1
- package/dist/migration.d.ts +0 -31
- package/dist/migration.d.ts.map +0 -1
- package/dist/migration.js +0 -47
- package/dist/migration.js.map +0 -1
- package/dist/native.d.ts +0 -562
- package/dist/native.d.ts.map +0 -1
- package/dist/native.js +0 -123
- package/dist/native.js.map +0 -1
- package/dist/query/description.d.ts +0 -41
- package/dist/query/description.d.ts.map +0 -1
- package/dist/query/description.js +0 -311
- package/dist/query/description.js.map +0 -1
- package/dist/query/parse-ir.d.ts +0 -10
- package/dist/query/parse-ir.d.ts.map +0 -1
- package/dist/query/parse-ir.js +0 -280
- package/dist/query/parse-ir.js.map +0 -1
- package/dist/runtime-codes.d.ts +0 -2
- package/dist/runtime-codes.d.ts.map +0 -1
- package/dist/runtime-codes.js +0 -18
- package/dist/runtime-codes.js.map +0 -1
- package/dist/runtime-errors.d.ts +0 -118
- package/dist/runtime-errors.d.ts.map +0 -1
- package/dist/runtime-errors.js +0 -98
- package/dist/runtime-errors.js.map +0 -1
- package/dist/runtime-native.d.ts +0 -141
- package/dist/runtime-native.d.ts.map +0 -1
- package/dist/runtime-native.js +0 -5
- package/dist/runtime-native.js.map +0 -1
- package/pack-provenance.json +0 -7
- package/src/close.ts +0 -53
- package/src/db-native.ts +0 -243
- package/src/internal/log.ts +0 -48
- package/src/migration.ts +0 -61
- package/src/native.ts +0 -736
- package/src/query/description.ts +0 -424
- package/src/query/parse-ir.ts +0 -291
- package/src/runtime-codes.ts +0 -17
- package/src/runtime-errors.ts +0 -109
- package/src/runtime-native.ts +0 -189
package/COOKBOOK.md
CHANGED
|
@@ -3,7 +3,9 @@
|
|
|
3
3
|
Worked recipes for the Effect-native surface: typed schema values,
|
|
4
4
|
declared keys and laws, application-owned `Uuid` identity, one bounded
|
|
5
5
|
native runtime, scoped resources, immutable final-state changes, `Option`
|
|
6
|
-
reads, sealed complete results,
|
|
6
|
+
reads, sealed complete results, one-shot page streams, and durable
|
|
7
|
+
`Database`s over an object store. Recipes 1 to 18 use the embedded `Db` from
|
|
8
|
+
`@bjornpagen/bumbledb/engine`; recipes 19 and 20 use `Database`.
|
|
7
9
|
|
|
8
10
|
Every `ts` fence below is extracted and type-checked against the real
|
|
9
11
|
package surface by `test/cookbook-doc.test.ts`; the imports fence here is
|
|
@@ -11,7 +13,7 @@ prepended to every recipe. The examples are lazy Effect programs — nothing
|
|
|
11
13
|
below runs a database at import time.
|
|
12
14
|
|
|
13
15
|
```ts
|
|
14
|
-
import { Effect, Option, Result, Stream } from "effect"
|
|
16
|
+
import { Effect, Layer, Option, Result, Schema as EffectSchema, Stream } from "effect"
|
|
15
17
|
import {
|
|
16
18
|
alternatives,
|
|
17
19
|
bool,
|
|
@@ -21,23 +23,25 @@ import {
|
|
|
21
23
|
closedId,
|
|
22
24
|
Compute,
|
|
23
25
|
contained,
|
|
24
|
-
|
|
25
|
-
|
|
26
|
+
Database,
|
|
27
|
+
decodeBoundaryField,
|
|
26
28
|
duration,
|
|
29
|
+
encodeBoundaryField,
|
|
27
30
|
f64,
|
|
31
|
+
fieldSchema,
|
|
32
|
+
FsStore,
|
|
28
33
|
i64,
|
|
29
34
|
uuid,
|
|
30
35
|
Uuid,
|
|
31
36
|
interval,
|
|
32
37
|
key,
|
|
33
38
|
mirrors,
|
|
34
|
-
|
|
39
|
+
Bumble,
|
|
35
40
|
on,
|
|
36
41
|
query,
|
|
37
|
-
queryFromDescription,
|
|
38
42
|
ref,
|
|
39
43
|
relation,
|
|
40
|
-
|
|
44
|
+
S3Store,
|
|
41
45
|
schema,
|
|
42
46
|
select,
|
|
43
47
|
str,
|
|
@@ -46,14 +50,16 @@ import {
|
|
|
46
50
|
weigh,
|
|
47
51
|
within
|
|
48
52
|
} from "@bjornpagen/bumbledb"
|
|
53
|
+
import { Db } from "@bjornpagen/bumbledb/engine"
|
|
49
54
|
import type {
|
|
50
|
-
ApplyOutcome,
|
|
51
55
|
CompleteResult,
|
|
52
56
|
Fact,
|
|
53
57
|
FloatIntervalValue,
|
|
54
58
|
IntervalValue,
|
|
55
|
-
|
|
59
|
+
Migrations,
|
|
60
|
+
S3Sender
|
|
56
61
|
} from "@bjornpagen/bumbledb"
|
|
62
|
+
import type { ApplyOutcome, QueryReader } from "@bjornpagen/bumbledb/engine"
|
|
57
63
|
|
|
58
64
|
declare const localPath: string
|
|
59
65
|
```
|
|
@@ -97,7 +103,7 @@ void [Learning, scoreIsNumber, unitsAreExact]
|
|
|
97
103
|
|
|
98
104
|
## 2. One runtime layer; explicit create and open
|
|
99
105
|
|
|
100
|
-
`
|
|
106
|
+
`Bumble.layer()` acquires the single native runtime
|
|
101
107
|
with scope; reuse ONE layer value so Effect's memoization shares it.
|
|
102
108
|
Optional configuration controls workers, outstanding jobs/handles, and cleanup
|
|
103
109
|
reporting. Defaults are up to four workers, 128 queued jobs, 128 cleanup reports,
|
|
@@ -105,10 +111,8 @@ reporting. Defaults are up to four workers, 128 queued jobs, 128 cleanup reports
|
|
|
105
111
|
Operations use ordinary allocation without byte/row/work quotas or execution
|
|
106
112
|
deadlines. Effect interruption requests cooperative cancellation. Runtime
|
|
107
113
|
inspection reports outstanding work, not memory usage.
|
|
108
|
-
Database `inspect()
|
|
109
|
-
|
|
110
|
-
None of these measures process heap usage or resident RAM. Log history
|
|
111
|
-
inspection uses the same `StorageInspection` fields.
|
|
114
|
+
Database `inspect()` reports the generation, the database's bytes on disk and
|
|
115
|
+
the native operations it retains; none of these measures heap or resident RAM.
|
|
112
116
|
`Db.open` never creates a missing database; `Db.create` refuses existing
|
|
113
117
|
authority. Both are scoped acquisitions.
|
|
114
118
|
|
|
@@ -131,7 +135,7 @@ const createOnce = Effect.scoped(
|
|
|
131
135
|
)
|
|
132
136
|
|
|
133
137
|
// One boundary; an Effect app provides the layer in its own graph instead.
|
|
134
|
-
const layer =
|
|
138
|
+
const layer = Bumble.layer()
|
|
135
139
|
void [openExisting.pipe(Effect.provide(layer)), createOnce.pipe(Effect.provide(layer))]
|
|
136
140
|
```
|
|
137
141
|
|
|
@@ -155,15 +159,14 @@ const applyOnce = Effect.scoped(
|
|
|
155
159
|
const draft = yield* ChangeSet.builder(Tasks)
|
|
156
160
|
yield* draft.insert(Task, [{ id: taskId, title: "write the cookbook", done: 0n }])
|
|
157
161
|
const changes = yield* draft.finish()
|
|
158
|
-
const outcome: ApplyOutcome = yield* db.apply(changes
|
|
159
|
-
switch (outcome.
|
|
160
|
-
case "
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
case "invariant-rejected":
|
|
162
|
+
const outcome: ApplyOutcome = yield* db.apply(changes)
|
|
163
|
+
switch (outcome._tag) {
|
|
164
|
+
case "Committed":
|
|
165
|
+
return outcome.generation
|
|
166
|
+
case "Rejected":
|
|
164
167
|
// Complete statement diagnostics, typed data — never a throw.
|
|
165
168
|
return yield* Effect.fail(outcome.violations)
|
|
166
|
-
case "
|
|
169
|
+
case "Moved":
|
|
167
170
|
return outcome.current
|
|
168
171
|
}
|
|
169
172
|
})
|
|
@@ -173,9 +176,9 @@ void applyOnce
|
|
|
173
176
|
|
|
174
177
|
For a noncommitting check, `db.judge` runs the same admission procedure and
|
|
175
178
|
aborts its private candidate on the native worker. It briefly takes the
|
|
176
|
-
writer; it does not hold a writer session open in JavaScript.
|
|
177
|
-
|
|
178
|
-
Operational errors stay in the Effect error channel.
|
|
179
|
+
writer; it does not hold a writer session open in JavaScript. A judgment
|
|
180
|
+
carries the generation it judged against, and an admitted one also carries the
|
|
181
|
+
net proposed fact counts. Operational errors stay in the Effect error channel.
|
|
179
182
|
|
|
180
183
|
```ts
|
|
181
184
|
const judgeOnce = Effect.scoped(
|
|
@@ -184,15 +187,15 @@ const judgeOnce = Effect.scoped(
|
|
|
184
187
|
const draft = yield* ChangeSet.builder(Tasks)
|
|
185
188
|
const id = yield* Effect.sync(() => crypto.randomUUID())
|
|
186
189
|
yield* draft.insert(Task, [{ id, title: "inspect a candidate", done: 0n }])
|
|
187
|
-
return yield* db.judge(yield* draft.finish()
|
|
190
|
+
return yield* db.judge(yield* draft.finish())
|
|
188
191
|
})
|
|
189
192
|
)
|
|
190
193
|
void judgeOnce
|
|
191
194
|
```
|
|
192
195
|
|
|
193
196
|
Judgment is optional: `apply` always judges for itself. To act on a judgment,
|
|
194
|
-
|
|
195
|
-
another writer could not change the database between these operations.
|
|
197
|
+
judge and apply with the same snapshot's witness and handle `Moved`; do not
|
|
198
|
+
assume another writer could not change the database between these operations.
|
|
196
199
|
|
|
197
200
|
Change sets also work as inspectable values without a database. Their
|
|
198
201
|
`counts` report distinct requested additions/removals, not the net change
|
|
@@ -318,39 +321,6 @@ const readTwoAuthors = (reader: QueryReader<typeof Library>) =>
|
|
|
318
321
|
void readTwoAuthors
|
|
319
322
|
```
|
|
320
323
|
|
|
321
|
-
Generated queries use the same checked rule, scalar, and execution machinery.
|
|
322
|
-
`describeQuery` exposes the logical IR and its names as owned data. Its relation
|
|
323
|
-
ordinals refer to the supplied schema's declaration order; variables are local
|
|
324
|
-
to each rule. Intermediate names are local labels. The result-field record
|
|
325
|
-
passed to `queryFromDescription` must match the actual derived head, including
|
|
326
|
-
closed vocabularies, and determines the returned row type. Parameters supplied
|
|
327
|
-
to a generated query are checked against their uses during execution.
|
|
328
|
-
|
|
329
|
-
```ts
|
|
330
|
-
const Event = relation("Event", { id: uuid, amount: u64 })
|
|
331
|
-
const Events = schema("Events", { Event }, [key(Event, ["id"])])
|
|
332
|
-
const countEvents = query(Events).rule((r) =>
|
|
333
|
-
r.match(Event, {}).find({ count: r.count() })
|
|
334
|
-
)
|
|
335
|
-
const description = describeQuery(countEvents)
|
|
336
|
-
const totalEvents = queryFromDescription(
|
|
337
|
-
Events,
|
|
338
|
-
{ ...description, columns: ["total"] },
|
|
339
|
-
{ total: u64 }
|
|
340
|
-
)
|
|
341
|
-
const readTotal = (reader: QueryReader<typeof Events>) =>
|
|
342
|
-
Effect.scoped(Effect.gen(function* () {
|
|
343
|
-
const result = yield* reader.execute(totalEvents, {})
|
|
344
|
-
const rows: readonly { readonly total: bigint }[] = yield* result.collect()
|
|
345
|
-
return rows
|
|
346
|
-
}))
|
|
347
|
-
void readTotal
|
|
348
|
-
```
|
|
349
|
-
|
|
350
|
-
Descriptions contain bigint and byte values, so they are not a JSON wire format.
|
|
351
|
-
An accepted description is an authored query; native preparation still checks
|
|
352
|
-
engine semantics. It carries no snapshot, prepared handle, or result rows.
|
|
353
|
-
|
|
354
324
|
## 6. Grouped exact aggregates
|
|
355
325
|
|
|
356
326
|
Aggregates fold the group's distinct full bindings: keep the identity-bearing
|
|
@@ -458,9 +428,9 @@ void [okOrRefused, mintOnce]
|
|
|
458
428
|
|
|
459
429
|
## 10. Witnessed correction: exact expected state
|
|
460
430
|
|
|
461
|
-
Read under a short scope, keep the
|
|
462
|
-
|
|
463
|
-
|
|
431
|
+
Read under a short scope, keep the snapshot's witness (an opaque value that
|
|
432
|
+
outlives the snapshot), and pass it to `apply` as the expected state. An intervening net change moves the apply instead of
|
|
433
|
+
silently overwriting.
|
|
464
434
|
|
|
465
435
|
```ts
|
|
466
436
|
const Account = relation("Account", { id: uuid, balance: i64 })
|
|
@@ -485,7 +455,7 @@ const correct = (accountId: Uuid) =>
|
|
|
485
455
|
yield* draft.delete(Account, [observed.previous])
|
|
486
456
|
yield* draft.insert(Account, [{ ...observed.previous, balance: observed.previous.balance + 1n }])
|
|
487
457
|
const changes = yield* draft.finish()
|
|
488
|
-
return yield* db.apply(changes,
|
|
458
|
+
return yield* db.apply(changes, observed.at)
|
|
489
459
|
})
|
|
490
460
|
)
|
|
491
461
|
void correct
|
|
@@ -509,44 +479,28 @@ const dense: FloatIntervalValue = { start: 0.25, end: 1.5 }
|
|
|
509
479
|
void [Windows, discrete, dense]
|
|
510
480
|
```
|
|
511
481
|
|
|
512
|
-
## 12. Scoped ownership
|
|
482
|
+
## 12. Scoped ownership
|
|
513
483
|
|
|
514
|
-
Every native resource
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
484
|
+
Every native resource belongs to the scope that acquired it: the database, snapshots, prepared
|
|
485
|
+
queries, results and change sets are released when their scope closes, innermost first. There is
|
|
486
|
+
no `close()`; to release something early, acquire it in a smaller scope. A release that cannot
|
|
487
|
+
drain cleanly is a `CloseFailure` defect in the Cause, never a swallowed failure.
|
|
518
488
|
|
|
519
489
|
```ts
|
|
520
490
|
const Item = relation("Item", { id: uuid, label: str })
|
|
521
491
|
const Items = schema("Items", { Item }, [key(Item, ["id"])])
|
|
522
492
|
|
|
523
|
-
const
|
|
493
|
+
const shortLived = Effect.scoped(
|
|
524
494
|
Effect.gen(function* () {
|
|
525
495
|
const db = yield* Db.open(localPath, Items)
|
|
526
|
-
const
|
|
527
|
-
|
|
528
|
-
// `failed` retain native Closing accounting — they are never
|
|
529
|
-
// counted as reclaimed.
|
|
530
|
-
return report.kind
|
|
496
|
+
const generation = yield* Effect.scoped(Effect.map(db.snapshot(), (snapshot) => snapshot.generation))
|
|
497
|
+
return generation
|
|
531
498
|
})
|
|
532
499
|
)
|
|
533
|
-
void
|
|
500
|
+
void shortLived
|
|
534
501
|
```
|
|
535
502
|
|
|
536
|
-
## 13.
|
|
537
|
-
|
|
538
|
-
`Scalar.field("units")` is not a typed program. Builders accept it inside
|
|
539
|
-
arithmetic. Native compilation binds it against the verified source
|
|
540
|
-
schema — including empty input — before any manifest write or freeze.
|
|
541
|
-
|
|
542
|
-
```ts
|
|
543
|
-
const incrementUnits = Scalar.add(Scalar.field("units"), Scalar.u64(1n))
|
|
544
|
-
const asFloat = Scalar.toF64(Scalar.add(Scalar.field("units"), Scalar.u64(1n)))
|
|
545
|
-
void [incrementUnits, asFloat]
|
|
546
|
-
```
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
## 14. Derive slices, measure them, and round the total
|
|
503
|
+
## 13. Derive slices, measure them, and round the total
|
|
550
504
|
|
|
551
505
|
An earning range crosses two rate bands. Intersection produces a relation of
|
|
552
506
|
nonempty segments, measurement produces exact integer widths, and ordinary
|
|
@@ -586,7 +540,7 @@ const calculate = Effect.scoped(Effect.gen(function* () {
|
|
|
586
540
|
{ id: 1n, schedule: 1n, span: { start: 0n, end: 100n }, numerator: 1n },
|
|
587
541
|
{ id: 2n, schedule: 1n, span: { start: 100n, end: 200n }, numerator: 2n }
|
|
588
542
|
])
|
|
589
|
-
yield* db.apply(yield* change.finish()
|
|
543
|
+
yield* db.apply(yield* change.finish())
|
|
590
544
|
const snapshot = yield* db.snapshot()
|
|
591
545
|
return yield* (yield* snapshot.execute(amounts, {})).collect()
|
|
592
546
|
}))
|
|
@@ -601,7 +555,7 @@ calculation; summing separately rounded contributions can give a different resul
|
|
|
601
555
|
Checked multiplication and aggregate result ranges still apply before the final
|
|
602
556
|
`mulDiv`. The query creates no stored slice or copied-width facts.
|
|
603
557
|
|
|
604
|
-
##
|
|
558
|
+
## 14. Subtract a window, coalesce coverage, and measure
|
|
605
559
|
|
|
606
560
|
Binary difference returns zero, one, or two maximal nonempty pieces. A subsequent
|
|
607
561
|
`pack` stage coalesces overlapping or adjacent coverage per owner; measurement
|
|
@@ -656,7 +610,7 @@ segments, not arbitrary sums of already-rounded floating lengths.
|
|
|
656
610
|
Each difference subtracts one interval. Unioning `A minus B1` with `A minus B2`
|
|
657
611
|
does not subtract their combined coverage from `A`.
|
|
658
612
|
|
|
659
|
-
##
|
|
613
|
+
## 15. Exhaustive alternatives with ordinary laws
|
|
660
614
|
|
|
661
615
|
Declare each key once. The helper expands a closed roster into ordinary
|
|
662
616
|
containment and mirrors statements in roster order. Equivalent independently
|
|
@@ -691,7 +645,7 @@ Each payload keeps its own inferred fields and ordinary relation representation.
|
|
|
691
645
|
Evidence and ownership still need their own laws. The expansion has the same
|
|
692
646
|
native descriptor, fingerprint, and admission costs as the identical manual laws.
|
|
693
647
|
|
|
694
|
-
##
|
|
648
|
+
## 16. Exact integer quotients and explicit rounding
|
|
695
649
|
|
|
696
650
|
`mulDiv` requires matching integer operands and a strictly positive divisor.
|
|
697
651
|
It computes the product exactly in a wider native integer and checks the public
|
|
@@ -717,3 +671,178 @@ returns `-4`. Exact quotients do not change. `u64::MAX * 2 / 2` succeeds;
|
|
|
717
671
|
Existing checked multiply/divide retain their intermediate-overflow behavior,
|
|
718
672
|
so replacing them with `mulDiv` is an explicit semantic choice. All three modes
|
|
719
673
|
perform bounded work without an additional scan, index, or JavaScript evaluator.
|
|
674
|
+
|
|
675
|
+
## 17. Use field codecs directly
|
|
676
|
+
|
|
677
|
+
Fields carry their host-value schema and strict JSON-boundary representation.
|
|
678
|
+
Use the same descriptors in relations, selected input records, and individual
|
|
679
|
+
values. Integer JSON values are canonical decimal strings; float JSON values
|
|
680
|
+
are canonical `$f64` bit images. Native row bytes remain a separate format.
|
|
681
|
+
|
|
682
|
+
```ts
|
|
683
|
+
const Method = closed("Method", ["Electronic", "Postal"])
|
|
684
|
+
const input = EffectSchema.Struct({
|
|
685
|
+
amount: fieldSchema(i64),
|
|
686
|
+
method: fieldSchema(closedId(Method)),
|
|
687
|
+
window: fieldSchema(interval(i64))
|
|
688
|
+
})
|
|
689
|
+
const valid = EffectSchema.is(input)({
|
|
690
|
+
amount: -25n,
|
|
691
|
+
method: "Electronic",
|
|
692
|
+
window: { start: 0n, end: 60n }
|
|
693
|
+
})
|
|
694
|
+
const amount = Result.getOrThrow(decodeBoundaryField(i64, "-25"))
|
|
695
|
+
const json = Result.getOrThrow(encodeBoundaryField(i64, amount)) // "-25"
|
|
696
|
+
void [valid, json]
|
|
697
|
+
```
|
|
698
|
+
|
|
699
|
+
The codecs enforce integer bounds, closed handles, byte widths, valid intervals,
|
|
700
|
+
and canonical floating-point values through the same interpreter used by rows.
|
|
701
|
+
Application parsing of dates, currency, or nonblank text composes with the field
|
|
702
|
+
schema using ordinary Effect Schema operations.
|
|
703
|
+
|
|
704
|
+
## 18. Combine projections and computations in one query
|
|
705
|
+
|
|
706
|
+
Every arm supplies the same ordered output columns and compatible field types.
|
|
707
|
+
A stored scalar and a computed scalar can occupy the same projection column.
|
|
708
|
+
|
|
709
|
+
```ts
|
|
710
|
+
const Entry = relation("Entry", { id: u64, amount: i64, previous: i64, correction: bool })
|
|
711
|
+
const Ledger = schema("Ledger", { Entry }, [])
|
|
712
|
+
const amounts = query(Ledger)
|
|
713
|
+
.rule((r) => {
|
|
714
|
+
const { id, amount } = v(Entry)
|
|
715
|
+
return r.match(Entry, { id, amount, correction: false }).find({ id, amount })
|
|
716
|
+
})
|
|
717
|
+
.rule((r) => {
|
|
718
|
+
const { id, amount, previous } = v(Entry)
|
|
719
|
+
return r.match(Entry, { id, amount, previous, correction: true })
|
|
720
|
+
.find({ id, amount: Compute.subtract(amount, previous) })
|
|
721
|
+
})
|
|
722
|
+
const totals = query(Ledger).rule((r) => {
|
|
723
|
+
const row = v(amounts)
|
|
724
|
+
return r.match(amounts, row).find({ id: row.id, total: r.sum(row.amount) })
|
|
725
|
+
})
|
|
726
|
+
void totals
|
|
727
|
+
```
|
|
728
|
+
|
|
729
|
+
Identical output facts deduplicate before the following stage aggregates them.
|
|
730
|
+
An evidenced zero remains a row; an absent group has none. Interval projections
|
|
731
|
+
can combine with produced interval pieces of the same element kind; a width
|
|
732
|
+
refinement survives only when every arm preserves it. Aggregate operators must
|
|
733
|
+
agree across arms and cannot share a column with a projection.
|
|
734
|
+
|
|
735
|
+
Each expression retains its original failure boundary. Moving filters across
|
|
736
|
+
an imported stage can change which arithmetic executes; a later filter cannot
|
|
737
|
+
erase an upstream overflow.
|
|
738
|
+
|
|
739
|
+
## 19. A durable database: idempotent submits and fresh reads
|
|
740
|
+
|
|
741
|
+
A `Database` is a log in an object store read through a disposable local
|
|
742
|
+
cache. Provide it once with `Database.layer` under a service key; in a Lambda,
|
|
743
|
+
build the layer during init. Production opens with `onOpen: "verify"` after
|
|
744
|
+
`bumbledb migrate` ran in the deploy pipeline.
|
|
745
|
+
|
|
746
|
+
Mint one request id per intent and reuse it for every retry of that intent: a
|
|
747
|
+
command is decided at most once. `Decided` carries the receipt (`Committed`,
|
|
748
|
+
`NoChange`, `PreconditionFailed` or `InvariantRejected`). Every refusal except
|
|
749
|
+
`Unknown` proves the command is not in the log; after `Unknown`, `resolve`
|
|
750
|
+
answers from the log. `precondition: reader.revision` decides the command only
|
|
751
|
+
if nothing committed since that read, which makes read-modify-write exact.
|
|
752
|
+
|
|
753
|
+
```ts
|
|
754
|
+
const Task = relation("Task", { id: uuid, title: str, done: bool })
|
|
755
|
+
const Tasks = schema("Tasks", { Task }, [key(Task, ["id"])])
|
|
756
|
+
const openTasks = query(Tasks).rule((r) => {
|
|
757
|
+
const { id, title } = v(Task)
|
|
758
|
+
return r.match(Task, { id, title, done: false }).find({ id, title })
|
|
759
|
+
})
|
|
760
|
+
|
|
761
|
+
declare const migrations: Migrations
|
|
762
|
+
declare const s3: S3Sender
|
|
763
|
+
|
|
764
|
+
const TasksDb = Database.tag<typeof Tasks>("app/TasksDb")
|
|
765
|
+
const TasksLive = Database.layer(TasksDb, {
|
|
766
|
+
schema: Tasks,
|
|
767
|
+
migrations,
|
|
768
|
+
store: S3Store.make({
|
|
769
|
+
client: s3,
|
|
770
|
+
log: { bucket: "tasks--use1-az4--x-s3" },
|
|
771
|
+
checkpoints: { bucket: "tasks-checkpoints" },
|
|
772
|
+
prefix: "prod/"
|
|
773
|
+
}),
|
|
774
|
+
cache: { directory: "/tmp/tasks" },
|
|
775
|
+
onOpen: "verify"
|
|
776
|
+
}).pipe(Layer.provide(Bumble.layer()))
|
|
777
|
+
|
|
778
|
+
const completeFirst = Effect.scoped(
|
|
779
|
+
Effect.gen(function* () {
|
|
780
|
+
const db = yield* TasksDb
|
|
781
|
+
const reader = yield* db.read("latest")
|
|
782
|
+
const [first] = yield* (yield* reader.execute(openTasks, {})).collect()
|
|
783
|
+
if (first === undefined) {
|
|
784
|
+
return "nothing open"
|
|
785
|
+
}
|
|
786
|
+
const draft = yield* ChangeSet.builder(Tasks)
|
|
787
|
+
yield* draft.delete(Task, [{ ...first, done: false }])
|
|
788
|
+
yield* draft.insert(Task, [{ ...first, done: true }])
|
|
789
|
+
const requestId = Database.requestId()
|
|
790
|
+
const outcome = yield* db.submit(yield* draft.finish(), { requestId, precondition: reader.revision })
|
|
791
|
+
if (outcome._tag === "Decided") {
|
|
792
|
+
return outcome.receipt.outcome._tag
|
|
793
|
+
}
|
|
794
|
+
if (outcome.refusal._tag === "Unknown") {
|
|
795
|
+
const receipt = yield* db.resolve(requestId)
|
|
796
|
+
return Option.isSome(receipt) ? receipt.value.outcome._tag : "not decided"
|
|
797
|
+
}
|
|
798
|
+
return outcome.refusal._tag
|
|
799
|
+
})
|
|
800
|
+
)
|
|
801
|
+
|
|
802
|
+
void completeFirst.pipe(Effect.provide(TasksLive))
|
|
803
|
+
```
|
|
804
|
+
|
|
805
|
+
`read` takes `"cached"` (the cache as it is), `"latest"` (one request to find
|
|
806
|
+
the log's tip) or `{ atLeast: seq }` (read-your-writes after a receipt's `seq`).
|
|
807
|
+
|
|
808
|
+
## 20. One database per tenant
|
|
809
|
+
|
|
810
|
+
`Database.pool` opens a database per tenant on first use and closes it after
|
|
811
|
+
it has been idle for `idleTimeToLive`. Each tenant has its own log and cache;
|
|
812
|
+
`FsStore` runs the same protocol on a local directory, and `MemStore` is the
|
|
813
|
+
in-memory store for tests.
|
|
814
|
+
|
|
815
|
+
```ts
|
|
816
|
+
const Note = relation("Note", { id: uuid, text: str })
|
|
817
|
+
const Notes = schema("Notes", { Note }, [key(Note, ["id"])])
|
|
818
|
+
const allNotes = query(Notes).rule((r) => {
|
|
819
|
+
const { id, text } = v(Note)
|
|
820
|
+
return r.match(Note, { id, text }).find({ id, text })
|
|
821
|
+
})
|
|
822
|
+
|
|
823
|
+
declare const noteMigrations: Migrations
|
|
824
|
+
|
|
825
|
+
const perTenant = Effect.scoped(
|
|
826
|
+
Effect.gen(function* () {
|
|
827
|
+
const pool = yield* Database.pool({
|
|
828
|
+
schema: Notes,
|
|
829
|
+
migrations: noteMigrations,
|
|
830
|
+
store: (tenant) => FsStore.make(`data/${tenant}/log`),
|
|
831
|
+
cache: (tenant) => ({ directory: `data/${tenant}/cache` }),
|
|
832
|
+
onOpen: "migrate",
|
|
833
|
+
idleTimeToLive: "10 minutes"
|
|
834
|
+
})
|
|
835
|
+
const count = (tenant: string) =>
|
|
836
|
+
Effect.scoped(
|
|
837
|
+
Effect.gen(function* () {
|
|
838
|
+
const db = yield* pool.get(tenant)
|
|
839
|
+
const reader = yield* db.read("cached")
|
|
840
|
+
return (yield* (yield* reader.execute(allNotes, {})).collect()).length
|
|
841
|
+
})
|
|
842
|
+
)
|
|
843
|
+
return yield* Effect.all([count("acme"), count("globex")])
|
|
844
|
+
})
|
|
845
|
+
)
|
|
846
|
+
|
|
847
|
+
void perTenant.pipe(Effect.provide(Bumble.layer()))
|
|
848
|
+
```
|