@kairos-es/core 1.0.0-alpha.3 → 1.0.0-alpha.5
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/README.md +3 -3
- package/dist/cjs/AppendCondition.js +1 -3
- package/dist/cjs/AppendCondition.js.map +1 -1
- package/dist/cjs/AppendIsOwned.js +6 -6
- package/dist/cjs/AppendIsOwned.js.map +1 -1
- package/dist/cjs/ContractStore.js +13 -4
- package/dist/cjs/ContractStore.js.map +1 -1
- package/dist/cjs/DcbEventStore.js +1 -1
- package/dist/cjs/DcbEventStore.js.map +1 -1
- package/dist/cjs/Event.js +87 -11
- package/dist/cjs/Event.js.map +1 -1
- package/dist/cjs/Projection.js +15 -9
- package/dist/cjs/Projection.js.map +1 -1
- package/dist/cjs/Query.js +59 -3
- package/dist/cjs/Query.js.map +1 -1
- package/dist/cjs/SubscribeMachine.js +20 -22
- package/dist/cjs/SubscribeMachine.js.map +1 -1
- package/dist/cjs/inMemory.js +113 -43
- package/dist/cjs/inMemory.js.map +1 -1
- package/dist/cjs/retryOnConflict.js +4 -10
- package/dist/cjs/retryOnConflict.js.map +1 -1
- package/dist/cjs/testing.js +2 -2
- package/dist/cjs/testing.js.map +1 -1
- package/dist/{dts → dts-cjs}/AppendCondition.d.ts +5 -7
- package/dist/{dts → dts-cjs}/AppendCondition.d.ts.map +1 -1
- package/dist/{dts → dts-cjs}/AppendIsOwned.d.ts +6 -6
- package/dist/{dts → dts-cjs}/AppendIsOwned.d.ts.map +1 -1
- package/dist/{dts → dts-cjs}/ContractStore.d.ts +7 -4
- package/dist/dts-cjs/ContractStore.d.ts.map +1 -0
- package/dist/{dts → dts-cjs}/DcbEventStore.d.ts +2 -2
- package/dist/{dts → dts-cjs}/DcbEventStore.d.ts.map +1 -1
- package/dist/dts-cjs/Event.d.ts +174 -0
- package/dist/dts-cjs/Event.d.ts.map +1 -0
- package/dist/{dts → dts-cjs}/Projection.d.ts +1 -1
- package/dist/{dts → dts-cjs}/Projection.d.ts.map +1 -1
- package/dist/{dts → dts-cjs}/Query.d.ts +19 -5
- package/dist/dts-cjs/Query.d.ts.map +1 -0
- package/dist/{dts → dts-cjs}/SubscribeMachine.d.ts +11 -15
- package/dist/dts-cjs/SubscribeMachine.d.ts.map +1 -0
- package/dist/{dts → dts-cjs}/inMemory.d.ts.map +1 -1
- package/dist/{dts → dts-cjs}/index.d.ts +1 -1
- package/dist/dts-cjs/package.json +3 -0
- package/dist/{dts → dts-cjs}/retryOnConflict.d.ts +1 -7
- package/dist/dts-cjs/retryOnConflict.d.ts.map +1 -0
- package/dist/{dts → dts-cjs}/testing.d.ts +1 -1
- package/dist/dts-esm/AppendCondition.d.ts +21 -0
- package/dist/dts-esm/AppendCondition.d.ts.map +1 -0
- package/dist/dts-esm/AppendIsOwned.d.ts +78 -0
- package/dist/dts-esm/AppendIsOwned.d.ts.map +1 -0
- package/dist/dts-esm/ContractStore.d.ts +62 -0
- package/dist/dts-esm/ContractStore.d.ts.map +1 -0
- package/dist/dts-esm/DcbEventStore.d.ts +64 -0
- package/dist/dts-esm/DcbEventStore.d.ts.map +1 -0
- package/dist/dts-esm/Event.d.ts +174 -0
- package/dist/dts-esm/Event.d.ts.map +1 -0
- package/dist/dts-esm/Projection.d.ts +124 -0
- package/dist/dts-esm/Projection.d.ts.map +1 -0
- package/dist/dts-esm/Query.d.ts +180 -0
- package/dist/dts-esm/Query.d.ts.map +1 -0
- package/dist/dts-esm/SubscribeMachine.d.ts +175 -0
- package/dist/dts-esm/SubscribeMachine.d.ts.map +1 -0
- package/dist/dts-esm/errors.d.ts +17 -0
- package/dist/dts-esm/errors.d.ts.map +1 -0
- package/dist/dts-esm/inMemory.d.ts +76 -0
- package/dist/dts-esm/inMemory.d.ts.map +1 -0
- package/dist/dts-esm/index.d.ts +40 -0
- package/dist/dts-esm/index.d.ts.map +1 -0
- package/dist/dts-esm/package.json +3 -0
- package/dist/dts-esm/retryOnConflict.d.ts +64 -0
- package/dist/dts-esm/retryOnConflict.d.ts.map +1 -0
- package/dist/dts-esm/testing.d.ts +39 -0
- package/dist/dts-esm/testing.d.ts.map +1 -0
- package/dist/esm/AppendCondition.js +1 -3
- package/dist/esm/AppendCondition.js.map +1 -1
- package/dist/esm/AppendIsOwned.js +6 -6
- package/dist/esm/AppendIsOwned.js.map +1 -1
- package/dist/esm/ContractStore.js +13 -4
- package/dist/esm/ContractStore.js.map +1 -1
- package/dist/esm/DcbEventStore.js +1 -1
- package/dist/esm/DcbEventStore.js.map +1 -1
- package/dist/esm/Event.js +87 -11
- package/dist/esm/Event.js.map +1 -1
- package/dist/esm/Projection.js +15 -9
- package/dist/esm/Projection.js.map +1 -1
- package/dist/esm/Query.js +56 -1
- package/dist/esm/Query.js.map +1 -1
- package/dist/esm/SubscribeMachine.js +21 -23
- package/dist/esm/SubscribeMachine.js.map +1 -1
- package/dist/esm/inMemory.js +114 -44
- package/dist/esm/inMemory.js.map +1 -1
- package/dist/esm/index.js +1 -1
- package/dist/esm/retryOnConflict.js +4 -10
- package/dist/esm/retryOnConflict.js.map +1 -1
- package/dist/esm/testing.js +2 -2
- package/dist/esm/testing.js.map +1 -1
- package/package.json +21 -11
- package/src/AppendCondition.ts +1 -1
- package/src/AppendIsOwned.ts +7 -7
- package/src/ContractStore.ts +25 -8
- package/src/DcbEventStore.ts +3 -3
- package/src/Event.ts +108 -11
- package/src/Projection.ts +19 -8
- package/src/Query.ts +74 -1
- package/src/SubscribeMachine.ts +38 -31
- package/src/inMemory.ts +123 -51
- package/src/index.ts +1 -1
- package/src/retryOnConflict.ts +6 -11
- package/src/testing.ts +2 -2
- package/testing/package.json +1 -1
- package/dist/dts/ContractStore.d.ts.map +0 -1
- package/dist/dts/Event.d.ts +0 -104
- package/dist/dts/Event.d.ts.map +0 -1
- package/dist/dts/Query.d.ts.map +0 -1
- package/dist/dts/SubscribeMachine.d.ts.map +0 -1
- package/dist/dts/retryOnConflict.d.ts.map +0 -1
- /package/dist/{dts → dts-cjs}/errors.d.ts +0 -0
- /package/dist/{dts → dts-cjs}/errors.d.ts.map +0 -0
- /package/dist/{dts → dts-cjs}/inMemory.d.ts +0 -0
- /package/dist/{dts → dts-cjs}/index.d.ts.map +0 -0
- /package/dist/{dts → dts-cjs}/testing.d.ts.map +0 -0
package/src/inMemory.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*
|
|
5
5
|
* The log is a `Ref<ReadonlyArray<SequencedEvent>>` in ascending position
|
|
6
6
|
* order. The whole check-then-append is run inside a single
|
|
7
|
-
* `
|
|
7
|
+
* `Semaphore.make(1)`+`withPermits(1)` critical section: the conditional
|
|
8
8
|
* read and the commit must not be separable, and that atomicity is the entire
|
|
9
9
|
* concurrency guarantee (exactly-one-wins under a race). Positions come from a
|
|
10
10
|
* monotonic counter starting at 1n (contiguous here; the contract only promises
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
* discharges the ascending-commit-order precondition `SubscribeMachine.ts` states.
|
|
26
26
|
*/
|
|
27
27
|
import type { Array as Arr, Scope } from 'effect'
|
|
28
|
-
import { Effect, Layer, PubSub, Ref, Stream } from 'effect'
|
|
28
|
+
import { DateTime, Effect, Layer, PubSub, Ref, Semaphore, Stream } from 'effect'
|
|
29
29
|
import type { AppendCondition } from './AppendCondition'
|
|
30
30
|
import { makeContractStore, type RawDcbEventStore } from './ContractStore'
|
|
31
31
|
import type { ReadOptions, ReadResponse } from './DcbEventStore'
|
|
@@ -41,6 +41,68 @@ import {
|
|
|
41
41
|
subscribeStream,
|
|
42
42
|
} from './SubscribeMachine'
|
|
43
43
|
|
|
44
|
+
/**
|
|
45
|
+
* A copy of `event` that shares no mutable structure with it: fresh `data` bytes, a
|
|
46
|
+
* fresh `tags` array, a fresh `metadata` bag and a fresh `occurredAt`. Only `type`
|
|
47
|
+
* and `uuid`, which are strings, are carried as they are.
|
|
48
|
+
*
|
|
49
|
+
* Each copy is chosen for what it does on EVERY input the type admits, not on the
|
|
50
|
+
* common one. `data` is copied through the `Uint8Array` constructor and not with
|
|
51
|
+
* `slice`, because `DcbEvent` admits any `Uint8Array` subclass and a subclass may
|
|
52
|
+
* answer `slice` with a VIEW of the same memory — Node's `Buffer` does. A
|
|
53
|
+
* `DateTime.Utc` is an ordinary object whose fields can be written past its
|
|
54
|
+
* `readonly` type, so `occurredAt` is rebuilt from its instant rather than shared.
|
|
55
|
+
*
|
|
56
|
+
* ## Why the oracle copies at BOTH edges
|
|
57
|
+
*
|
|
58
|
+
* This store is the reference the SQL engines are judged against, and a SQL engine
|
|
59
|
+
* shares nothing with its caller: it serialises on the way in and builds fresh objects
|
|
60
|
+
* on the way out. A store that kept the caller's objects instead would let a caller
|
|
61
|
+
* rewrite the log AFTER the append — mutate a `Uint8Array`, push to a `tags` array,
|
|
62
|
+
* assign into a `metadata` bag — and every later read would see the rewritten event
|
|
63
|
+
* with no append, no position and no wake behind it. A reference that can be edited
|
|
64
|
+
* after the fact judges nothing.
|
|
65
|
+
*
|
|
66
|
+
* So `append` stores a copy (the log never holds a caller's object) and every read
|
|
67
|
+
* path hands out a copy (a reader cannot reach the log through what it was given).
|
|
68
|
+
* Copying on the way in alone would close the first route and leave the second open:
|
|
69
|
+
* the same object would then be shared by every reader of the log.
|
|
70
|
+
*/
|
|
71
|
+
const copyEvent = (event: DcbEvent): DcbEvent => ({
|
|
72
|
+
type: event.type,
|
|
73
|
+
data: new Uint8Array(event.data),
|
|
74
|
+
tags: [...event.tags],
|
|
75
|
+
uuid: event.uuid,
|
|
76
|
+
occurredAt: DateTime.makeUnsafe(DateTime.toEpochMillis(event.occurredAt)),
|
|
77
|
+
metadata: { ...event.metadata },
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* A copy of `query` that shares no array with it, taking only the keys the grammar
|
|
82
|
+
* defines.
|
|
83
|
+
*
|
|
84
|
+
* `subscribe` is the one verb that holds a query beyond its own call: the fetch seam
|
|
85
|
+
* judges it again on every page for as long as the subscription runs. Each SQL engine
|
|
86
|
+
* compiles its plan once, when the subscription starts, so an edit the caller makes
|
|
87
|
+
* to its own object afterwards reaches nothing there. The oracle has to answer the
|
|
88
|
+
* same, for the reason it copies events (above): a reference that the caller can
|
|
89
|
+
* change mid-stream judges nothing. Only `types` and `tags` are carried because they
|
|
90
|
+
* are all `matchesQueryItem` reads, and a key the grammar does not define was
|
|
91
|
+
* already refused at the contract decorator before this runs.
|
|
92
|
+
*/
|
|
93
|
+
const copyQuery = (query: Query): Query => ({
|
|
94
|
+
items: query.items.map((item) => ({
|
|
95
|
+
types: [...item.types],
|
|
96
|
+
tags: [...item.tags],
|
|
97
|
+
})),
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
/** A `SequencedEvent` whose event is a `copyEvent` of the stored one. */
|
|
101
|
+
const copySequenced = (sequenced: SequencedEvent): SequencedEvent => ({
|
|
102
|
+
event: copyEvent(sequenced.event),
|
|
103
|
+
position: sequenced.position,
|
|
104
|
+
})
|
|
105
|
+
|
|
44
106
|
/** Filter a snapshot by `query` and `after` (strict `position > after`). */
|
|
45
107
|
const filterLog = (
|
|
46
108
|
log: ReadonlyArray<SequencedEvent>,
|
|
@@ -80,7 +142,7 @@ const readSnapshot = (
|
|
|
80
142
|
? lastPosition(log)
|
|
81
143
|
: lastPosition(limited)) ?? ORIGIN
|
|
82
144
|
|
|
83
|
-
return { events: Stream.fromIterable(limited), head }
|
|
145
|
+
return { events: Stream.fromIterable(limited.map(copySequenced)), head }
|
|
84
146
|
}
|
|
85
147
|
|
|
86
148
|
/**
|
|
@@ -144,7 +206,7 @@ const fetchPage = (
|
|
|
144
206
|
) {
|
|
145
207
|
const sequenced = log[index]
|
|
146
208
|
if (matchesSequenced(sequenced, query)) {
|
|
147
|
-
page.push(sequenced)
|
|
209
|
+
page.push(copySequenced(sequenced))
|
|
148
210
|
}
|
|
149
211
|
}
|
|
150
212
|
return page
|
|
@@ -226,7 +288,7 @@ const make = (
|
|
|
226
288
|
options?.catchUpPageSize ?? DEFAULT_CATCH_UP_PAGE_SIZE
|
|
227
289
|
const pollInterval = options?.pollInterval ?? DEFAULT_POLL_INTERVAL_MILLIS
|
|
228
290
|
const log = yield* Ref.make<ReadonlyArray<SequencedEvent>>([])
|
|
229
|
-
const mutex = yield*
|
|
291
|
+
const mutex = yield* Semaphore.make(1)
|
|
230
292
|
const hub = yield* PubSub.unbounded<SequencedEvent>()
|
|
231
293
|
const nextPosition = yield* Ref.make<bigint>(1n)
|
|
232
294
|
|
|
@@ -247,53 +309,60 @@ const make = (
|
|
|
247
309
|
events: Arr.NonEmptyReadonlyArray<DcbEvent>,
|
|
248
310
|
condition?: AppendCondition,
|
|
249
311
|
): Effect.Effect<Position, AppendConditionFailed> =>
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
312
|
+
Effect.suspend(() => {
|
|
313
|
+
// The events are copied when the append STARTS, before it waits for the
|
|
314
|
+
// permit, as each SQL engine serialises them before its first suspension:
|
|
315
|
+
// an edit the caller makes while this append is queued must not reach the
|
|
316
|
+
// log here when it could not reach theirs.
|
|
317
|
+
const owned = events.map(copyEvent)
|
|
318
|
+
// Critical section: check + commit are atomic (the exactly-one-wins guarantee).
|
|
319
|
+
return mutex.withPermits(1)(
|
|
320
|
+
Effect.gen(function* () {
|
|
321
|
+
const snapshot = yield* Ref.get(log)
|
|
254
322
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
323
|
+
if (condition !== undefined) {
|
|
324
|
+
const conflicts = filterLog(
|
|
325
|
+
snapshot,
|
|
326
|
+
condition.failIfEventsMatch,
|
|
327
|
+
condition.after,
|
|
328
|
+
)
|
|
329
|
+
// Detect by presence: any matching event after `condition.after`
|
|
330
|
+
// fails the append. The error carries only the condition (see
|
|
331
|
+
// `AppendConditionFailed`); a correct retry re-reads to the new head.
|
|
332
|
+
if (conflicts.length > 0) {
|
|
333
|
+
return yield* new AppendConditionFailed({ condition })
|
|
334
|
+
}
|
|
266
335
|
}
|
|
267
|
-
}
|
|
268
336
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
337
|
+
// Optional test seam between the conflict-check and the commit (see
|
|
338
|
+
// `InMemoryStoreOptions.beforeCommit`). Defaults to `Effect.void`, a
|
|
339
|
+
// synchronous no-op, so the shipped critical section stays atomic by
|
|
340
|
+
// construction; the concurrency contract test injects `Effect.yieldNow()`
|
|
341
|
+
// here to make the exactly-one-wins guard genuinely depend on the mutex.
|
|
342
|
+
yield* beforeCommit
|
|
275
343
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
344
|
+
const start = yield* Ref.get(nextPosition)
|
|
345
|
+
const appended: Array<SequencedEvent> = owned.map(
|
|
346
|
+
(event, index) => ({
|
|
347
|
+
event,
|
|
348
|
+
position: (start + BigInt(index)) as Position,
|
|
349
|
+
}),
|
|
350
|
+
)
|
|
351
|
+
const end = start + BigInt(events.length)
|
|
352
|
+
yield* Ref.set(log, [...snapshot, ...appended])
|
|
353
|
+
yield* Ref.set(nextPosition, end)
|
|
354
|
+
// The hub is a WAKE SOURCE and nothing else: the subscribe machine never
|
|
355
|
+
// reads what it carries, and its poll delivers with or without one. That
|
|
356
|
+
// is why suppressing the publish is a legitimate test seam rather than a
|
|
357
|
+
// broken store (see `InMemoryStoreOptions.suppressWake`).
|
|
358
|
+
if (!suppressWake) {
|
|
359
|
+
yield* PubSub.publishAll(hub, appended)
|
|
360
|
+
}
|
|
361
|
+
// `events` is non-empty, so the last new position is `end - 1`.
|
|
362
|
+
return (end - 1n) as Position
|
|
363
|
+
}),
|
|
364
|
+
)
|
|
365
|
+
})
|
|
297
366
|
|
|
298
367
|
const subscribe = (
|
|
299
368
|
query: Query,
|
|
@@ -312,16 +381,19 @@ const make = (
|
|
|
312
381
|
// exclusive cursor is what makes the catch-up/live seam exactly-once, so there
|
|
313
382
|
// is nothing left here for a critical section to protect. `append` keeps its
|
|
314
383
|
// permit, which is the whole exactly-one-wins guarantee.
|
|
315
|
-
Stream.
|
|
384
|
+
Stream.unwrap(
|
|
316
385
|
Effect.gen(function* () {
|
|
386
|
+
// Taken when the subscription starts, as the SQL engines compile theirs
|
|
387
|
+
// (see `copyQuery`).
|
|
388
|
+
const judged = copyQuery(query)
|
|
317
389
|
const wakes = yield* PubSub.subscribe(hub)
|
|
318
390
|
return subscribeStream<never>(
|
|
319
391
|
{
|
|
320
392
|
fetch: (from, limit) =>
|
|
321
393
|
Effect.map(Ref.get(log), (snapshot) =>
|
|
322
|
-
fetchPage(snapshot,
|
|
394
|
+
fetchPage(snapshot, judged, from, limit),
|
|
323
395
|
),
|
|
324
|
-
listen: Stream.
|
|
396
|
+
listen: Stream.fromSubscription(wakes),
|
|
325
397
|
pollInterval,
|
|
326
398
|
catchUpPageSize,
|
|
327
399
|
},
|
package/src/index.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @kairos-es/core: DCB-first event-sourcing contract and in-memory store for
|
|
3
|
-
* Effect
|
|
3
|
+
* Effect.
|
|
4
4
|
*
|
|
5
5
|
* Exposes the domain types (`DcbEvent`, `SequencedEvent`, `Position`, `Tag`,
|
|
6
6
|
* `EventType`), the selection language (`Query`, `QueryItem`, the pure match
|
package/src/retryOnConflict.ts
CHANGED
|
@@ -35,12 +35,6 @@
|
|
|
35
35
|
* jitter draws from `Random.next`, a default service on the same footing as the
|
|
36
36
|
* clock rather than a host `Math.random`. No `node:*`, no wall-clock reading, no
|
|
37
37
|
* ungoverned RNG.
|
|
38
|
-
*
|
|
39
|
-
* Effect v3 caveat: the `Schedule` API below is the THREE-type-parameter
|
|
40
|
-
* `Schedule<Out, In, R>` that `effect@3` exports, with `Schedule.intersect`. The
|
|
41
|
-
* currently-published docs show a newer four-type-parameter `Schedule` without
|
|
42
|
-
* `intersect` — do not copy them; this file is written against the installed
|
|
43
|
-
* `effect@3` type definitions.
|
|
44
38
|
*/
|
|
45
39
|
import { Effect, Predicate, Schedule } from 'effect'
|
|
46
40
|
import type { AppendConditionFailed } from './errors'
|
|
@@ -49,9 +43,9 @@ import type { AppendConditionFailed } from './errors'
|
|
|
49
43
|
* The default conflict backoff: exponential from 20ms, jittered, capped at five
|
|
50
44
|
* retries.
|
|
51
45
|
*
|
|
52
|
-
* `Schedule.
|
|
53
|
-
* `recurs(5)` arm is what bounds the attempts and the
|
|
54
|
-
* spaces them. Jitter matters more here than in a typical backoff: contending
|
|
46
|
+
* `Schedule.max` continues only while BOTH schedules continue, waiting the longer
|
|
47
|
+
* of their two delays, so the `recurs(5)` arm is what bounds the attempts and the
|
|
48
|
+
* exponential arm is what spaces them. Jitter matters more here than in a typical backoff: contending
|
|
55
49
|
* deciders conflict precisely because they read the same boundary at the same
|
|
56
50
|
* time, so an unjittered backoff would re-synchronise them into the same conflict
|
|
57
51
|
* on every round. The figures are the conventional starting point (Emmett,
|
|
@@ -59,10 +53,10 @@ import type { AppendConditionFailed } from './errors'
|
|
|
59
53
|
* command with a tighter latency budget, or one contending harder, should pass its
|
|
60
54
|
* own schedule.
|
|
61
55
|
*/
|
|
62
|
-
const conflictBackoff = Schedule.
|
|
56
|
+
const conflictBackoff = Schedule.max([
|
|
63
57
|
Schedule.jittered(Schedule.exponential('20 millis', 2)),
|
|
64
58
|
Schedule.recurs(5),
|
|
65
|
-
)
|
|
59
|
+
])
|
|
66
60
|
|
|
67
61
|
/**
|
|
68
62
|
* Retry `command` while — and only while — it fails `AppendConditionFailed`.
|
|
@@ -91,6 +85,7 @@ export const retryOnConflict = <A, E, R>(
|
|
|
91
85
|
schedule: Schedule.Schedule<
|
|
92
86
|
unknown,
|
|
93
87
|
AppendConditionFailed | E,
|
|
88
|
+
never,
|
|
94
89
|
R
|
|
95
90
|
> = conflictBackoff,
|
|
96
91
|
): Effect.Effect<A, AppendConditionFailed | E, R> =>
|
package/src/testing.ts
CHANGED
|
@@ -21,10 +21,10 @@ import { DateTime } from 'effect'
|
|
|
21
21
|
*
|
|
22
22
|
* Ordering in the log is by `position` alone, so an event's `occurredAt` is
|
|
23
23
|
* informational; pinning it to the epoch keeps serialised bytes and comparisons
|
|
24
|
-
* stable across runs. Built via `DateTime.
|
|
24
|
+
* stable across runs. Built via `DateTime.makeUnsafe(0)` (the value is a
|
|
25
25
|
* compile-time constant, not read from the host clock).
|
|
26
26
|
*/
|
|
27
|
-
export const EPOCH = DateTime.
|
|
27
|
+
export const EPOCH = DateTime.makeUnsafe(0)
|
|
28
28
|
|
|
29
29
|
/**
|
|
30
30
|
* Build a fresh, deterministic uuid generator: a zero-based counter producing
|
package/testing/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ContractStore.d.ts","sourceRoot":"","sources":["../../src/ContractStore.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,OAAO,KAAK,EAAE,KAAK,IAAI,GAAG,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAA;AACjD,OAAO,EAAE,MAAM,EAAqB,MAAM,EAAE,MAAM,QAAQ,CAAA;AAC1D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AAExD,OAAO,KAAK,EAAE,aAAa,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAA;AAC/E,OAAO,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAEjE,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,UAAU,CAAA;AACrD,OAAO,EAAuB,KAAK,KAAK,EAAE,MAAM,SAAS,CAAA;AAKzD;;;;;;GAMG;AACH,MAAM,WAAW,gBAAgB,CAAC,CAAC;IACjC,QAAQ,CAAC,IAAI,EAAE,CACb,KAAK,EAAE,KAAK,EACZ,OAAO,CAAC,EAAE,WAAW,KAClB,MAAM,CAAC,MAAM,CAAC,YAAY,EAAE,CAAC,CAAC,CAAA;IACnC,QAAQ,CAAC,MAAM,EAAE,CACf,MAAM,EAAE,GAAG,CAAC,qBAAqB,CAAC,QAAQ,CAAC,EAC3C,SAAS,CAAC,EAAE,eAAe,KACxB,MAAM,CAAC,MAAM,CAAC,QAAQ,EAAE,qBAAqB,GAAG,CAAC,CAAC,CAAA;IACvD,QAAQ,CAAC,SAAS,EAAE,CAClB,KAAK,EAAE,KAAK,EACZ,KAAK,CAAC,EAAE,QAAQ,KACb,MAAM,CAAC,MAAM,CAAC,cAAc,EAAE,CAAC,EAAE,KAAK,CAAC,KAAK,CAAC,CAAA;CACnD;AAED;;;GAGG;AACH,eAAO,MAAM,iBAAiB,GAAI,CAAC,EACjC,KAAK,gBAAgB,CAAC,CAAC,CAAC,KACvB,aAAa,CAAC,MAAM,CAsFrB,CAAA"}
|
package/dist/dts/Event.d.ts
DELETED
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Core domain types for the DCB event log: branded scalars and the wire
|
|
3
|
-
* `DcbEvent`/`SequencedEvent` shapes.
|
|
4
|
-
*
|
|
5
|
-
* `data` is opaque bytes at the store layer; the codec (`@kairos-es/codec`) turns typed
|
|
6
|
-
* payloads into these bytes. Ordering is by `position` alone; `occurredAt` is
|
|
7
|
-
* producer-supplied domain time and is informational only.
|
|
8
|
-
*/
|
|
9
|
-
import { Schema } from 'effect';
|
|
10
|
-
/**
|
|
11
|
-
* A position in the single global event log: a branded, non-negative `bigint`.
|
|
12
|
-
*
|
|
13
|
-
* The log is 64-bit (`bigserial` in Postgres) and exceeds JS `number` safety at
|
|
14
|
-
* 2^53, so positions are a `bigint`. They are compared, never arithmetic'd.
|
|
15
|
-
* `Schema.BigInt` encodes to a decimal string (JSON-safe) and decodes to
|
|
16
|
-
* `bigint`. `0n` is the "before the first event" sentinel used by `after` and by
|
|
17
|
-
* an empty store's head.
|
|
18
|
-
*/
|
|
19
|
-
export declare const Position: Schema.brand<Schema.filter<typeof Schema.BigInt>, "Position">;
|
|
20
|
-
export type Position = typeof Position.Type;
|
|
21
|
-
/** The sentinel position meaning "before the first event". */
|
|
22
|
-
export declare const ORIGIN: Position;
|
|
23
|
-
/**
|
|
24
|
-
* A business identifier attached to an event, following the `key:value`
|
|
25
|
-
* convention (e.g. `student:s1`). Tags replace the per-stream id of classic
|
|
26
|
-
* event sourcing; a query matches an event when the event carries all of an
|
|
27
|
-
* item's tags (superset test).
|
|
28
|
-
*/
|
|
29
|
-
export declare const Tag: Schema.brand<Schema.filter<typeof Schema.String>, "Tag">;
|
|
30
|
-
export type Tag = typeof Tag.Type;
|
|
31
|
-
/**
|
|
32
|
-
* A reusable tag identity: a `key` plus a `.make(value)` that composes the
|
|
33
|
-
* branded `Tag` `` `${key}:${value}` `` through core's `Tag` schema. Define an
|
|
34
|
-
* identity once (`const CourseId = tagIdentity('course')`) and reuse
|
|
35
|
-
* `CourseId.make(id)` everywhere that entity is tagged. The `key` is exposed for
|
|
36
|
-
* key-aware tooling (e.g. a tag-vocabulary index, a future retag pass).
|
|
37
|
-
*
|
|
38
|
-
* The same constructor authors an event's tags (in a codec `tagsFor` mapper,
|
|
39
|
-
* ADR-0006) and a query's tags, so the `key:value` string a mapper writes and
|
|
40
|
-
* the string a query matches are produced by one function and cannot drift. It
|
|
41
|
-
* lives here beside `Tag` — pure (no clock, no RNG, no effect) — so it is
|
|
42
|
-
* reachable from every tag-authoring site, including core's own query
|
|
43
|
-
* constructors.
|
|
44
|
-
*
|
|
45
|
-
* `key` and `value` are each a single colon-free atom: the `key:value` grammar
|
|
46
|
-
* splits on the first colon, so both are validated to be non-empty and contain
|
|
47
|
-
* no `:` — `key` once at construction, `value` on each `make`. This keeps
|
|
48
|
-
* composition unambiguous and catches the double-prefix footgun loudly:
|
|
49
|
-
* `CourseId.make('course:123')` throws rather than silently yielding
|
|
50
|
-
* `course:course:123`. Raw `Tag.make` remains the escape hatch for a value that
|
|
51
|
-
* must itself contain a colon.
|
|
52
|
-
*/
|
|
53
|
-
export interface TagIdentity {
|
|
54
|
-
readonly key: string;
|
|
55
|
-
readonly make: (value: string) => Tag;
|
|
56
|
-
}
|
|
57
|
-
export declare const tagIdentity: (key: string) => TagIdentity;
|
|
58
|
-
/**
|
|
59
|
-
* A read cap: a branded non-negative integer bounding how many events a `read`
|
|
60
|
-
* returns. `0` returns none; an absent limit returns all matching events.
|
|
61
|
-
*
|
|
62
|
-
* The bound lives in the type, not merely in prose: a negative or non-integer
|
|
63
|
-
* limit is a programming error, so it is a DEFECT at the store boundary — the
|
|
64
|
-
* store validates through this schema and dies rather than surfacing it on the
|
|
65
|
-
* error channel (`read` keeps `E = never`). Constructed at call sites with
|
|
66
|
-
* `ReadLimit.make(n)`.
|
|
67
|
-
*/
|
|
68
|
-
export declare const ReadLimit: Schema.brand<Schema.filter<typeof Schema.Int>, "ReadLimit">;
|
|
69
|
-
export type ReadLimit = typeof ReadLimit.Type;
|
|
70
|
-
/**
|
|
71
|
-
* An event type name, a branded non-empty string. Versioning is by convention
|
|
72
|
-
* in the name (e.g. `course-registered.v2`); only non-emptiness is enforced.
|
|
73
|
-
*/
|
|
74
|
-
export declare const EventType: Schema.brand<Schema.filter<typeof Schema.String>, "EventType">;
|
|
75
|
-
export type EventType = typeof EventType.Type;
|
|
76
|
-
/**
|
|
77
|
-
* An event as stored: an opaque payload plus its type, tags, a
|
|
78
|
-
* producer-supplied `uuid`, and producer-supplied `occurredAt` domain time.
|
|
79
|
-
*
|
|
80
|
-
* `data` is `Uint8Array` (opaque bytes); the store does not interpret it.
|
|
81
|
-
* `uuid` is producer-supplied to keep `core` deterministic (no RNG in core).
|
|
82
|
-
* `occurredAt` is informational only; ordering is by position alone.
|
|
83
|
-
*/
|
|
84
|
-
export declare const DcbEvent: Schema.Struct<{
|
|
85
|
-
type: Schema.brand<Schema.filter<typeof Schema.String>, "EventType">;
|
|
86
|
-
data: typeof Schema.Uint8ArrayFromSelf;
|
|
87
|
-
tags: Schema.Array$<Schema.brand<Schema.filter<typeof Schema.String>, "Tag">>;
|
|
88
|
-
uuid: typeof Schema.UUID;
|
|
89
|
-
occurredAt: typeof Schema.DateTimeUtc;
|
|
90
|
-
}>;
|
|
91
|
-
export type DcbEvent = typeof DcbEvent.Type;
|
|
92
|
-
/** A persisted event: the event together with the position it was written at. */
|
|
93
|
-
export declare const SequencedEvent: Schema.Struct<{
|
|
94
|
-
event: Schema.Struct<{
|
|
95
|
-
type: Schema.brand<Schema.filter<typeof Schema.String>, "EventType">;
|
|
96
|
-
data: typeof Schema.Uint8ArrayFromSelf;
|
|
97
|
-
tags: Schema.Array$<Schema.brand<Schema.filter<typeof Schema.String>, "Tag">>;
|
|
98
|
-
uuid: typeof Schema.UUID;
|
|
99
|
-
occurredAt: typeof Schema.DateTimeUtc;
|
|
100
|
-
}>;
|
|
101
|
-
position: Schema.brand<Schema.filter<typeof Schema.BigInt>, "Position">;
|
|
102
|
-
}>;
|
|
103
|
-
export type SequencedEvent = typeof SequencedEvent.Type;
|
|
104
|
-
//# sourceMappingURL=Event.d.ts.map
|
package/dist/dts/Event.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"Event.d.ts","sourceRoot":"","sources":["../../src/Event.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAE/B;;;;;;;;GAQG;AACH,eAAO,MAAM,QAAQ,+DAGpB,CAAA;AACD,MAAM,MAAM,QAAQ,GAAG,OAAO,QAAQ,CAAC,IAAI,CAAA;AAE3C,8DAA8D;AAC9D,eAAO,MAAM,MAAM,EAAE,QAA4B,CAAA;AAEjD;;;;;GAKG;AACH,eAAO,MAAM,GAAG,0DAGf,CAAA;AACD,MAAM,MAAM,GAAG,GAAG,OAAO,GAAG,CAAC,IAAI,CAAA;AAEjC;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,IAAI,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,GAAG,CAAA;CACtC;AAED,eAAO,MAAM,WAAW,GAAI,KAAK,MAAM,KAAG,WAiBzC,CAAA;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,SAAS,6DAGrB,CAAA;AACD,MAAM,MAAM,SAAS,GAAG,OAAO,SAAS,CAAC,IAAI,CAAA;AAE7C;;;GAGG;AACH,eAAO,MAAM,SAAS,gEAGrB,CAAA;AACD,MAAM,MAAM,SAAS,GAAG,OAAO,SAAS,CAAC,IAAI,CAAA;AAE7C;;;;;;;GAOG;AACH,eAAO,MAAM,QAAQ;;;;;;EAMnB,CAAA;AACF,MAAM,MAAM,QAAQ,GAAG,OAAO,QAAQ,CAAC,IAAI,CAAA;AAE3C,iFAAiF;AACjF,eAAO,MAAM,cAAc;;;;;;;;;EAGzB,CAAA;AACF,MAAM,MAAM,cAAc,GAAG,OAAO,cAAc,CAAC,IAAI,CAAA"}
|
package/dist/dts/Query.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"Query.d.ts","sourceRoot":"","sources":["../../src/Query.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,OAAO,EAAE,KAAK,IAAI,GAAG,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAC7C,OAAO,EAAE,KAAK,QAAQ,EAAE,SAAS,EAAE,KAAK,cAAc,EAAE,GAAG,EAAE,MAAM,SAAS,CAAA;AAE5E;;;GAGG;AACH,eAAO,MAAM,SAAS;;;EAGpB,CAAA;AACF,MAAM,MAAM,SAAS,GAAG,OAAO,SAAS,CAAC,IAAI,CAAA;AAE7C,iEAAiE;AACjE,eAAO,MAAM,KAAK;;;;;EAEhB,CAAA;AACF,MAAM,MAAM,KAAK,GAAG,OAAO,KAAK,CAAC,IAAI,CAAA;AAErC;;;GAGG;AACH,eAAO,MAAM,gBAAgB,GAAI,OAAO,QAAQ,EAAE,MAAM,SAAS,KAAG,OAOnE,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,YAAY,GAAI,OAAO,QAAQ,EAAE,OAAO,KAAK,KAAG,OAK5D,CAAA;AAED,qDAAqD;AACrD,eAAO,MAAM,gBAAgB,GAC3B,WAAW,cAAc,EACzB,OAAO,KAAK,KACX,OAA+C,CAAA;AAElD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,eAAO,MAAM,mBAAmB,GAAI,OAAO,KAAK,KAAG,IAKlD,CAAA;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,kBAAkB,GAAI,OAAO,KAAK,KAAG,MAAM,GAAG,SAkB1D,CAAA;AAED;;;;;;;;;GASG;AACH,MAAM,MAAM,kBAAkB,GAC1B;IAAE,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAA;CAAE,GAC7B;IACE,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAA;IACzB,QAAQ,CAAC,KAAK,EAAE,GAAG,CAAC,qBAAqB,CAAC,SAAS,CAAC,CAAA;CACrD,GACD;IAAE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAA;CAAE,CAAA;AAE/B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,eAAO,MAAM,qBAAqB,GAAI,OAAO,KAAK,KAAG,kBAWpD,CAAA;AAoDD,iDAAiD;AACjD,eAAO,MAAM,KAAK;wBAlCF,KAAK;4BATC;QACpB,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC,GAAG,EAAE,GAAG,aAAa,CAAC,GAAG,CAAC,CAAC,CAAA;QACpD,QAAQ,CAAC,KAAK,CAAC,EAAE,aAAa,CAAC,SAAS,CAAC,CAAA;KAC1C,KAAG,SAAS;yBAkCJ,SAAS,CAAC,SAAS,EAAE,GAAG,aAAa,CAAC,SAAS,CAAC,CAAC,KACvD,KAAK;0BAtBA,SAAS,CAAC,GAAG,EAAE,GAAG,aAAa,CAAC,GAAG,CAAC,CAAC,UACnC,aAAa,CAAC,SAAS,CAAC,KAC/B,KAAK;4BAUC,SAAS,CAAC,SAAS,EAAE,GAAG,aAAa,CAAC,SAAS,CAAC,CAAC,KACvD,KAAK;CAoBE,CAAA"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"SubscribeMachine.d.ts","sourceRoot":"","sources":["../../src/SubscribeMachine.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqEG;AACH,OAAO,EAEL,MAAM,EAIN,MAAM,EACN,KAAK,KAAK,EACV,MAAM,EACP,MAAM,QAAQ,CAAA;AACf,OAAO,KAAK,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AACvD,OAAO,EAAU,SAAS,EAAE,MAAM,SAAS,CAAA;AAE3C;;;;;;GAMG;AACH,eAAO,MAAM,0BAA0B,MAAM,CAAA;AAE7C;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,4BAA4B,OAAO,CAAA;AAEhD;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,eAAO,MAAM,wBAAwB;;;;;;;CAQ3B,CAAA;AAcV;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,gBAAgB,CAAC,CAAC;IACjC;;;OAGG;IACH,QAAQ,CAAC,KAAK,EAAE,CACd,IAAI,EAAE,QAAQ,EACd,KAAK,EAAE,SAAS,KACb,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,cAAc,CAAC,EAAE,CAAC,CAAC,CAAA;IACpD;;;;;;;;;OASG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC,CAAA;IAC3C,8EAA8E;IAC9E,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAA;IAC7B,iEAAiE;IACjE,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAA;CACjC;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,eAAe,GAAI,CAAC,EAC/B,MAAM,gBAAgB,CAAC,CAAC,CAAC,EACzB,OAAO,QAAQ,GAAG,SAAS,KAC1B,MAAM,CAAC,MAAM,CAAC,cAAc,EAAE,CAAC,EAAE,KAAK,CAAC,KAAK,CAyG5C,CAAA"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"retryOnConflict.d.ts","sourceRoot":"","sources":["../../src/retryOnConflict.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AACH,OAAO,EAAE,MAAM,EAAa,QAAQ,EAAE,MAAM,QAAQ,CAAA;AACpD,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,UAAU,CAAA;AAqBrD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,eAAO,MAAM,eAAe,GAAI,CAAC,EAAE,CAAC,EAAE,CAAC,EACrC,SAAS,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,qBAAqB,GAAG,CAAC,EAAE,CAAC,CAAC,EACvD,WAAU,QAAQ,CAAC,QAAQ,CACzB,OAAO,EACP,qBAAqB,GAAG,CAAC,EACzB,CAAC,CACgB,KAClB,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,qBAAqB,GAAG,CAAC,EAAE,CAAC,CAI5C,CAAA"}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|