@kairos-es/core 1.0.0-alpha.4 → 1.0.0-alpha.6
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 +2 -2
- 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 +29 -13
- package/dist/cjs/Event.js.map +1 -1
- package/dist/cjs/Query.js +1 -1
- package/dist/cjs/Query.js.map +1 -1
- package/dist/cjs/SubscribeMachine.js +158 -67
- package/dist/cjs/SubscribeMachine.js.map +1 -1
- package/dist/cjs/inMemory.js +4 -4
- 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 +3 -2
- 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 → dts-cjs}/Event.d.ts +31 -20
- package/dist/dts-cjs/Event.d.ts.map +1 -0
- package/dist/{dts → dts-cjs}/Query.d.ts +5 -5
- package/dist/dts-cjs/SubscribeMachine.d.ts +289 -0
- 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 +289 -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 +2 -2
- 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 +30 -14
- package/dist/esm/Event.js.map +1 -1
- package/dist/esm/Query.js +1 -1
- package/dist/esm/Query.js.map +1 -1
- package/dist/esm/SubscribeMachine.js +157 -67
- package/dist/esm/SubscribeMachine.js.map +1 -1
- package/dist/esm/inMemory.js +5 -5
- 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 +5 -4
- package/src/DcbEventStore.ts +3 -3
- package/src/Event.ts +49 -14
- package/src/Query.ts +1 -1
- package/src/SubscribeMachine.ts +278 -96
- package/src/inMemory.ts +6 -6
- 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.map +0 -1
- package/dist/dts/SubscribeMachine.d.ts +0 -179
- package/dist/dts/SubscribeMachine.d.ts.map +0 -1
- package/dist/dts/retryOnConflict.d.ts.map +0 -1
- /package/dist/{dts → dts-cjs}/Projection.d.ts +0 -0
- /package/dist/{dts → dts-cjs}/Projection.d.ts.map +0 -0
- /package/dist/{dts → dts-cjs}/Query.d.ts.map +0 -0
- /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/AppendIsOwned.ts
CHANGED
|
@@ -30,15 +30,15 @@
|
|
|
30
30
|
*
|
|
31
31
|
* ## Present means refused, so a sanctioned append REMOVES the marker
|
|
32
32
|
*
|
|
33
|
-
* The check reads the
|
|
33
|
+
* The check reads the service through `Effect.serviceOption`, so its ABSENCE is the
|
|
34
34
|
* normal case and costs the `append` signature nothing — `DcbEventStore.append`
|
|
35
35
|
* keeps `R = never` and no caller provides anything. Presence is what refuses.
|
|
36
36
|
*
|
|
37
37
|
* That shape decides how the two legitimate appends inside a bracket get through,
|
|
38
38
|
* and there is only one route: run with the marker STRIPPED from the context
|
|
39
|
-
* (`Effect.
|
|
39
|
+
* (`Effect.updateContext` over `Context.omit(AppendIsOwned)`). The bracket's own
|
|
40
40
|
* closing append is issued outside the scope it provided the marker in, and the
|
|
41
|
-
* codec's escape hatch strips it explicitly. Neither needs a second
|
|
41
|
+
* codec's escape hatch strips it explicitly. Neither needs a second service, a
|
|
42
42
|
* sanctioning flag or a mutable cell, and there is no way to be exempt by
|
|
43
43
|
* accident: an append is either lexically outside the marked region or it says in
|
|
44
44
|
* as many words that it is removing the marker.
|
|
@@ -50,13 +50,13 @@ import { Context, Effect } from 'effect'
|
|
|
50
50
|
* owns it.
|
|
51
51
|
*
|
|
52
52
|
* `by` is a human-readable owner name and exists for the defect message alone —
|
|
53
|
-
* the refusal is decided by the
|
|
53
|
+
* the refusal is decided by the service's PRESENCE, never by this value, so nothing
|
|
54
54
|
* branches on it and no caller need agree on a vocabulary for it.
|
|
55
55
|
*/
|
|
56
|
-
export class AppendIsOwned extends Context.
|
|
56
|
+
export class AppendIsOwned extends Context.Service<
|
|
57
57
|
AppendIsOwned,
|
|
58
58
|
{ readonly by: string }
|
|
59
|
-
>() {}
|
|
59
|
+
>()('@kairos-es/core/AppendIsOwned') {}
|
|
60
60
|
|
|
61
61
|
/**
|
|
62
62
|
* Die if an append is already owned here; do nothing otherwise.
|
|
@@ -70,7 +70,7 @@ export class AppendIsOwned extends Context.Tag('@kairos-es/core/AppendIsOwned')<
|
|
|
70
70
|
* its escape hatch for a deliberate second append — which is what makes a defect
|
|
71
71
|
* affordable here.
|
|
72
72
|
*
|
|
73
|
-
* `Effect.serviceOption` is what keeps this free: it reads the
|
|
73
|
+
* `Effect.serviceOption` is what keeps this free: it reads the service if present and
|
|
74
74
|
* adds NOTHING to the requirement channel, so every existing append is unchanged
|
|
75
75
|
* and unprovisioned.
|
|
76
76
|
*/
|
package/src/ContractStore.ts
CHANGED
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
* channel (`never` for the pure in-memory engine; `SqlError` for either SQL one).
|
|
9
9
|
* The decorator wraps those mechanics with the policy the contract demands — the
|
|
10
10
|
* policy the first two engines hand-duplicated, with the Postgres one even
|
|
11
|
-
* needing a bespoke `catchAll`/`instanceof
|
|
11
|
+
* needing a bespoke `catchAll`/`instanceof` (Effect 3's `catchAll`, which Effect 4
|
|
12
|
+
* names `catch`):
|
|
12
13
|
*
|
|
13
14
|
* - `assertServableQuery` at every verb's entry (read, the append condition's
|
|
14
15
|
* guard query, subscribe) — a non-servable query, or one carrying a key the
|
|
@@ -75,7 +76,7 @@ export interface RawDcbEventStore<E> {
|
|
|
75
76
|
*/
|
|
76
77
|
export const makeContractStore = <E>(
|
|
77
78
|
raw: RawDcbEventStore<E>,
|
|
78
|
-
): DcbEventStore['
|
|
79
|
+
): DcbEventStore['Service'] => ({
|
|
79
80
|
read: (query, options) =>
|
|
80
81
|
Effect.suspend(() => {
|
|
81
82
|
// Defects (surface when the Effect runs), keeping `read` at `E = never`.
|
|
@@ -90,7 +91,7 @@ export const makeContractStore = <E>(
|
|
|
90
91
|
append: (events, condition) =>
|
|
91
92
|
// The ownership refusal runs FIRST and sits OUTSIDE the suspend, because it is
|
|
92
93
|
// the only one of append's checks that is EFFECTFUL: it consults a
|
|
93
|
-
// `Context.
|
|
94
|
+
// `Context.Service` rather than inspecting the arguments. `Effect.flatMap` over an
|
|
94
95
|
// already-lazy effect defers it exactly as the suspend defers the throws below,
|
|
95
96
|
// so nothing runs at the call site either way; what it buys is that the
|
|
96
97
|
// "you are inside a bracket" defect arrives before any argument is judged, which
|
|
@@ -156,7 +157,7 @@ export const makeContractStore = <E>(
|
|
|
156
157
|
// engine's channel simply answers false and dies as it should. It also
|
|
157
158
|
// narrows the raw `AppendConditionFailed | E` to the contract's single
|
|
158
159
|
// error, which is what keeps `append` at `E = AppendConditionFailed`.
|
|
159
|
-
return Effect.
|
|
160
|
+
return Effect.catch(raw.append(events, condition), (error) =>
|
|
160
161
|
Predicate.isTagged('AppendConditionFailed')(error)
|
|
161
162
|
? Effect.fail(error)
|
|
162
163
|
: Effect.die(error),
|
package/src/DcbEventStore.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The `DcbEventStore` contract: a `Context.
|
|
2
|
+
* The `DcbEventStore` contract: a `Context.Service` with no bundled implementation.
|
|
3
3
|
* Consumers supply a `Layer` (the in-memory one lives in this package; each SQL
|
|
4
4
|
* engine is a package of its own — `@kairos-es/store-postgres` and
|
|
5
5
|
* `@kairos-es/store-sqlite`).
|
|
@@ -42,7 +42,7 @@ export interface ReadResponse {
|
|
|
42
42
|
readonly head: Position
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
export class DcbEventStore extends Context.
|
|
45
|
+
export class DcbEventStore extends Context.Service<
|
|
46
46
|
DcbEventStore,
|
|
47
47
|
{
|
|
48
48
|
/**
|
|
@@ -74,4 +74,4 @@ export class DcbEventStore extends Context.Tag('@kairos-es/core/DcbEventStore')<
|
|
|
74
74
|
after?: Position,
|
|
75
75
|
) => Stream.Stream<SequencedEvent, never, Scope.Scope>
|
|
76
76
|
}
|
|
77
|
-
>() {}
|
|
77
|
+
>()('@kairos-es/core/DcbEventStore') {}
|
package/src/Event.ts
CHANGED
|
@@ -6,19 +6,55 @@
|
|
|
6
6
|
* payloads into these bytes. Ordering is by `position` alone; `occurredAt` is
|
|
7
7
|
* producer-supplied domain time and is informational only.
|
|
8
8
|
*/
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
BigInt as BigInt_,
|
|
11
|
+
Effect,
|
|
12
|
+
Option,
|
|
13
|
+
Schema,
|
|
14
|
+
SchemaIssue,
|
|
15
|
+
SchemaTransformation,
|
|
16
|
+
} from 'effect'
|
|
10
17
|
|
|
11
18
|
/**
|
|
12
19
|
* A position in the single global event log: a branded, non-negative `bigint`.
|
|
13
20
|
*
|
|
14
21
|
* The log is 64-bit (`bigserial` in Postgres) and exceeds JS `number` safety at
|
|
15
22
|
* 2^53, so positions are a `bigint`. They are compared, never arithmetic'd.
|
|
16
|
-
*
|
|
23
|
+
* Encoded, a position is a decimal string (JSON-safe, `String(b)`); decoded, a
|
|
17
24
|
* `bigint`. `0n` is the "before the first event" sentinel used by `after` and by
|
|
18
25
|
* an empty store's head.
|
|
26
|
+
*
|
|
27
|
+
* The string-to-`bigint` step is hand-built rather than Effect 4's
|
|
28
|
+
* `Schema.BigIntFromString`, because that schema accepts only `^-?\d+$` and so
|
|
29
|
+
* refuses inputs the encoded side has always admitted — `" 5"`, `"+5"`,
|
|
30
|
+
* `"0x10"`. The decode here is the global `BigInt(s)` behind a blank-string
|
|
31
|
+
* refusal, which is `BigInt.fromString` and is exactly what Effect 3's
|
|
32
|
+
* `Schema.BigInt` ran, so a position string any earlier release decoded still
|
|
33
|
+
* decodes to the same value. The non-negative check then runs on the `bigint`,
|
|
34
|
+
* as it always did.
|
|
19
35
|
*/
|
|
20
|
-
export const Position = Schema.
|
|
21
|
-
Schema.
|
|
36
|
+
export const Position = Schema.String.pipe(
|
|
37
|
+
Schema.decodeTo(
|
|
38
|
+
Schema.BigInt,
|
|
39
|
+
SchemaTransformation.transformEffect<bigint, string>({
|
|
40
|
+
decode: (s, options) =>
|
|
41
|
+
Option.match(BigInt_.fromString(s), {
|
|
42
|
+
onNone: () =>
|
|
43
|
+
Effect.fail(
|
|
44
|
+
new SchemaIssue.InvalidValue(
|
|
45
|
+
{
|
|
46
|
+
message: `Unable to decode ${JSON.stringify(s)} into a bigint`,
|
|
47
|
+
},
|
|
48
|
+
s,
|
|
49
|
+
options,
|
|
50
|
+
),
|
|
51
|
+
),
|
|
52
|
+
onSome: Effect.succeed,
|
|
53
|
+
}),
|
|
54
|
+
encode: (b) => Effect.succeed(String(b)),
|
|
55
|
+
}),
|
|
56
|
+
),
|
|
57
|
+
Schema.check(Schema.isGreaterThanOrEqualToBigInt(0n)),
|
|
22
58
|
Schema.brand('Position'),
|
|
23
59
|
)
|
|
24
60
|
export type Position = typeof Position.Type
|
|
@@ -33,7 +69,7 @@ export const ORIGIN: Position = Position.make(0n)
|
|
|
33
69
|
* item's tags (superset test).
|
|
34
70
|
*/
|
|
35
71
|
export const Tag = Schema.String.pipe(
|
|
36
|
-
Schema.
|
|
72
|
+
Schema.check(Schema.isPattern(/^[^:]+:.+/, { identifier: 'Tag' })),
|
|
37
73
|
Schema.brand('Tag'),
|
|
38
74
|
)
|
|
39
75
|
export type Tag = typeof Tag.Type
|
|
@@ -95,7 +131,7 @@ export const tagIdentity = (key: string): TagIdentity => {
|
|
|
95
131
|
* `ReadLimit.make(n)`.
|
|
96
132
|
*/
|
|
97
133
|
export const ReadLimit = Schema.Int.pipe(
|
|
98
|
-
Schema.
|
|
134
|
+
Schema.check(Schema.isGreaterThanOrEqualTo(0)),
|
|
99
135
|
Schema.brand('ReadLimit'),
|
|
100
136
|
)
|
|
101
137
|
export type ReadLimit = typeof ReadLimit.Type
|
|
@@ -105,7 +141,7 @@ export type ReadLimit = typeof ReadLimit.Type
|
|
|
105
141
|
* in the name (e.g. `course-registered.v2`); only non-emptiness is enforced.
|
|
106
142
|
*/
|
|
107
143
|
export const EventType = Schema.String.pipe(
|
|
108
|
-
Schema.
|
|
144
|
+
Schema.check(Schema.isNonEmpty()),
|
|
109
145
|
Schema.brand('EventType'),
|
|
110
146
|
)
|
|
111
147
|
export type EventType = typeof EventType.Type
|
|
@@ -159,10 +195,7 @@ export type EventType = typeof EventType.Type
|
|
|
159
195
|
* alone: it would make an event the store accepted unreadable for ever, so a
|
|
160
196
|
* value check belongs at write or nowhere.
|
|
161
197
|
*/
|
|
162
|
-
export const Metadata = Schema.Record(
|
|
163
|
-
key: Schema.String,
|
|
164
|
-
value: Schema.String,
|
|
165
|
-
})
|
|
198
|
+
export const Metadata = Schema.Record(Schema.String, Schema.String)
|
|
166
199
|
export type Metadata = typeof Metadata.Type
|
|
167
200
|
|
|
168
201
|
/**
|
|
@@ -172,6 +205,8 @@ export type Metadata = typeof Metadata.Type
|
|
|
172
205
|
*
|
|
173
206
|
* `data` is `Uint8Array` (opaque bytes); the store does not interpret it.
|
|
174
207
|
* `uuid` is producer-supplied to keep `core` deterministic (no RNG in core).
|
|
208
|
+
* It is checked for UUID SHAPE only (`isGUID`), not for a version nibble: a
|
|
209
|
+
* stricter check would refuse ids an existing log already holds.
|
|
175
210
|
* `occurredAt` is informational only; ordering is by position alone.
|
|
176
211
|
* `metadata` mirrors `tags` rather than `data`: REQUIRED, with an empty bag
|
|
177
212
|
* legal. Required because an absent-versus-empty divergence between engines is
|
|
@@ -181,10 +216,10 @@ export type Metadata = typeof Metadata.Type
|
|
|
181
216
|
*/
|
|
182
217
|
export const DcbEvent = Schema.Struct({
|
|
183
218
|
type: EventType,
|
|
184
|
-
data: Schema.
|
|
219
|
+
data: Schema.Uint8Array,
|
|
185
220
|
tags: Schema.Array(Tag),
|
|
186
|
-
uuid: Schema.
|
|
187
|
-
occurredAt: Schema.
|
|
221
|
+
uuid: Schema.String.check(Schema.isGUID()),
|
|
222
|
+
occurredAt: Schema.DateTimeUtcFromString,
|
|
188
223
|
metadata: Metadata,
|
|
189
224
|
})
|
|
190
225
|
export type DcbEvent = typeof DcbEvent.Type
|
package/src/Query.ts
CHANGED
|
@@ -273,7 +273,7 @@ export const classifyServableQuery = (query: Query): ServableQueryShape => {
|
|
|
273
273
|
}
|
|
274
274
|
const only = query.items.length === 1 ? query.items[0] : undefined
|
|
275
275
|
if (only !== undefined && only.tags.length === 0) {
|
|
276
|
-
return Arr.
|
|
276
|
+
return Arr.isReadonlyArrayNonEmpty(only.types)
|
|
277
277
|
? { kind: 'typeOnly', types: only.types }
|
|
278
278
|
: { kind: 'wildcard' }
|
|
279
279
|
}
|