@kairos-es/codec 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.
Files changed (143) hide show
  1. package/README.md +27 -2
  2. package/dist/cjs/MetadataSource.js +10 -0
  3. package/dist/cjs/MetadataSource.js.map +1 -0
  4. package/dist/cjs/Serializer.js +3 -3
  5. package/dist/cjs/Serializer.js.map +1 -1
  6. package/dist/cjs/Uuid.js +6 -3
  7. package/dist/cjs/Uuid.js.map +1 -1
  8. package/dist/cjs/decider.js +1 -1
  9. package/dist/cjs/decisionModel.js +1 -1
  10. package/dist/cjs/decisionModel.js.map +1 -1
  11. package/dist/cjs/decode.js +5 -5
  12. package/dist/cjs/decode.js.map +1 -1
  13. package/dist/cjs/deserialiseThenDecode.js +1 -1
  14. package/dist/cjs/deserialiseThenDecode.js.map +1 -1
  15. package/dist/cjs/errors.js +6 -4
  16. package/dist/cjs/errors.js.map +1 -1
  17. package/dist/cjs/event.js.map +1 -1
  18. package/dist/cjs/index.js +7 -0
  19. package/dist/cjs/index.js.map +1 -1
  20. package/dist/cjs/precondition.js +2 -2
  21. package/dist/cjs/precondition.js.map +1 -1
  22. package/dist/cjs/projection.js +9 -10
  23. package/dist/cjs/projection.js.map +1 -1
  24. package/dist/cjs/readLedger.js +20 -5
  25. package/dist/cjs/readLedger.js.map +1 -1
  26. package/dist/cjs/registry.js.map +1 -1
  27. package/dist/cjs/toEvent.js +58 -4
  28. package/dist/cjs/toEvent.js.map +1 -1
  29. package/dist/dts-cjs/MetadataSource.d.ts +8 -0
  30. package/dist/dts-cjs/MetadataSource.d.ts.map +1 -0
  31. package/dist/{dts → dts-cjs}/Serializer.d.ts +3 -3
  32. package/dist/{dts → dts-cjs}/Serializer.d.ts.map +1 -1
  33. package/dist/{dts → dts-cjs}/Uuid.d.ts +6 -3
  34. package/dist/{dts → dts-cjs}/Uuid.d.ts.map +1 -1
  35. package/dist/{dts → dts-cjs}/decider.d.ts +7 -7
  36. package/dist/{dts → dts-cjs}/decider.d.ts.map +1 -1
  37. package/dist/{dts → dts-cjs}/decision.d.ts +2 -2
  38. package/dist/{dts → dts-cjs}/decode.d.ts +3 -3
  39. package/dist/{dts → dts-cjs}/decode.d.ts.map +1 -1
  40. package/dist/{dts → dts-cjs}/deserialiseThenDecode.d.ts +3 -3
  41. package/dist/{dts → dts-cjs}/deserialiseThenDecode.d.ts.map +1 -1
  42. package/dist/{dts → dts-cjs}/errors.d.ts +4 -3
  43. package/dist/dts-cjs/errors.d.ts.map +1 -0
  44. package/dist/{dts → dts-cjs}/event.d.ts +18 -8
  45. package/dist/dts-cjs/event.d.ts.map +1 -0
  46. package/dist/{dts → dts-cjs}/index.d.ts +4 -2
  47. package/dist/dts-cjs/index.d.ts.map +1 -0
  48. package/dist/dts-cjs/package.json +3 -0
  49. package/dist/{dts → dts-cjs}/precondition.d.ts +2 -2
  50. package/dist/{dts → dts-cjs}/projection.d.ts +2 -2
  51. package/dist/dts-cjs/projection.d.ts.map +1 -0
  52. package/dist/{dts → dts-cjs}/readLedger.d.ts +4 -4
  53. package/dist/{dts → dts-cjs}/readLedger.d.ts.map +1 -1
  54. package/dist/{dts → dts-cjs}/toEvent.d.ts +15 -4
  55. package/dist/dts-cjs/toEvent.d.ts.map +1 -0
  56. package/dist/dts-esm/MetadataSource.d.ts +8 -0
  57. package/dist/dts-esm/MetadataSource.d.ts.map +1 -0
  58. package/dist/dts-esm/Serializer.d.ts +27 -0
  59. package/dist/dts-esm/Serializer.d.ts.map +1 -0
  60. package/dist/dts-esm/Uuid.d.ts +22 -0
  61. package/dist/dts-esm/Uuid.d.ts.map +1 -0
  62. package/dist/dts-esm/decider.d.ts +206 -0
  63. package/dist/dts-esm/decider.d.ts.map +1 -0
  64. package/dist/dts-esm/decision.d.ts +161 -0
  65. package/dist/dts-esm/decision.d.ts.map +1 -0
  66. package/dist/dts-esm/decisionModel.d.ts +149 -0
  67. package/dist/dts-esm/decisionModel.d.ts.map +1 -0
  68. package/dist/dts-esm/decode.d.ts +92 -0
  69. package/dist/dts-esm/decode.d.ts.map +1 -0
  70. package/dist/dts-esm/deserialiseThenDecode.d.ts +62 -0
  71. package/dist/dts-esm/deserialiseThenDecode.d.ts.map +1 -0
  72. package/dist/dts-esm/errors.d.ts +80 -0
  73. package/dist/dts-esm/errors.d.ts.map +1 -0
  74. package/dist/dts-esm/event.d.ts +81 -0
  75. package/dist/dts-esm/event.d.ts.map +1 -0
  76. package/dist/dts-esm/index.d.ts +134 -0
  77. package/dist/dts-esm/index.d.ts.map +1 -0
  78. package/dist/dts-esm/package.json +3 -0
  79. package/dist/dts-esm/precondition.d.ts +244 -0
  80. package/dist/dts-esm/precondition.d.ts.map +1 -0
  81. package/dist/dts-esm/projection.d.ts +56 -0
  82. package/dist/dts-esm/projection.d.ts.map +1 -0
  83. package/dist/dts-esm/readLedger.d.ts +263 -0
  84. package/dist/dts-esm/readLedger.d.ts.map +1 -0
  85. package/dist/dts-esm/registry.d.ts +46 -0
  86. package/dist/dts-esm/registry.d.ts.map +1 -0
  87. package/dist/dts-esm/toEvent.d.ts +45 -0
  88. package/dist/dts-esm/toEvent.d.ts.map +1 -0
  89. package/dist/esm/MetadataSource.js +3 -0
  90. package/dist/esm/MetadataSource.js.map +1 -0
  91. package/dist/esm/Serializer.js +3 -3
  92. package/dist/esm/Serializer.js.map +1 -1
  93. package/dist/esm/Uuid.js +6 -3
  94. package/dist/esm/Uuid.js.map +1 -1
  95. package/dist/esm/decider.js +1 -1
  96. package/dist/esm/decisionModel.js +1 -1
  97. package/dist/esm/decisionModel.js.map +1 -1
  98. package/dist/esm/decode.js +5 -5
  99. package/dist/esm/decode.js.map +1 -1
  100. package/dist/esm/deserialiseThenDecode.js +1 -1
  101. package/dist/esm/deserialiseThenDecode.js.map +1 -1
  102. package/dist/esm/errors.js +6 -4
  103. package/dist/esm/errors.js.map +1 -1
  104. package/dist/esm/event.js.map +1 -1
  105. package/dist/esm/index.js +4 -2
  106. package/dist/esm/index.js.map +1 -1
  107. package/dist/esm/precondition.js +2 -2
  108. package/dist/esm/precondition.js.map +1 -1
  109. package/dist/esm/projection.js +9 -10
  110. package/dist/esm/projection.js.map +1 -1
  111. package/dist/esm/readLedger.js +21 -6
  112. package/dist/esm/readLedger.js.map +1 -1
  113. package/dist/esm/registry.js.map +1 -1
  114. package/dist/esm/toEvent.js +58 -5
  115. package/dist/esm/toEvent.js.map +1 -1
  116. package/package.json +12 -7
  117. package/src/MetadataSource.ts +43 -0
  118. package/src/Serializer.ts +4 -4
  119. package/src/Uuid.ts +7 -4
  120. package/src/decider.ts +9 -9
  121. package/src/decision.ts +1 -1
  122. package/src/decisionModel.ts +1 -1
  123. package/src/decode.ts +12 -18
  124. package/src/deserialiseThenDecode.ts +5 -5
  125. package/src/errors.ts +6 -4
  126. package/src/event.ts +19 -9
  127. package/src/index.ts +4 -2
  128. package/src/precondition.ts +3 -3
  129. package/src/projection.ts +15 -20
  130. package/src/readLedger.ts +28 -12
  131. package/src/registry.ts +1 -1
  132. package/src/toEvent.ts +54 -7
  133. package/dist/dts/errors.d.ts.map +0 -1
  134. package/dist/dts/event.d.ts.map +0 -1
  135. package/dist/dts/index.d.ts.map +0 -1
  136. package/dist/dts/projection.d.ts.map +0 -1
  137. package/dist/dts/toEvent.d.ts.map +0 -1
  138. /package/dist/{dts → dts-cjs}/decision.d.ts.map +0 -0
  139. /package/dist/{dts → dts-cjs}/decisionModel.d.ts +0 -0
  140. /package/dist/{dts → dts-cjs}/decisionModel.d.ts.map +0 -0
  141. /package/dist/{dts → dts-cjs}/precondition.d.ts.map +0 -0
  142. /package/dist/{dts → dts-cjs}/registry.d.ts +0 -0
  143. /package/dist/{dts → dts-cjs}/registry.d.ts.map +0 -0
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * The two-step every decode in this package performs, in one implementation:
3
3
  * deserialise the wire bytes through the `Serializer` its caller already holds,
4
- * then `Schema.decodeUnknown` the result, each half's failure wrapped as the
4
+ * then `Schema.decodeUnknownEffect` the result, each half's failure wrapped as the
5
5
  * `CodecError` whose `reason` says WHICH half failed.
6
6
  *
7
7
  * **What it is the single home of.** The two `reason` strings `'deserialise'`
@@ -17,7 +17,7 @@
17
17
  * as a function and not as a second test.
18
18
  *
19
19
  * **The order is contract, not accident.** Deserialise has to run first because
20
- * `Schema.decodeUnknown` has nothing to read until it has, and the observable
20
+ * `Schema.decodeUnknownEffect` has nothing to read until it has, and the observable
21
21
  * consequence is that the two reasons PARTITION the causes an operator acts on:
22
22
  * rotten bytes are always `'deserialise'` and never `'decode'`, so a storage or
23
23
  * producer problem never arrives dressed as schema drift. Swapping the labels
@@ -60,8 +60,8 @@ import { CodecError } from './errors'
60
60
  import type { Serializer } from './Serializer'
61
61
 
62
62
  export const deserialiseThenDecode = <R>(
63
- serializer: Context.Tag.Service<Serializer>,
64
- schema: Schema.Schema<unknown, unknown, R>,
63
+ serializer: Context.Service.Shape<typeof Serializer>,
64
+ schema: Schema.Decoder<unknown, R>,
65
65
  bytes: Uint8Array,
66
66
  ): Effect.Effect<unknown, CodecError, R> =>
67
67
  Effect.try({
@@ -69,7 +69,7 @@ export const deserialiseThenDecode = <R>(
69
69
  catch: (cause) => new CodecError({ reason: 'deserialise', cause }),
70
70
  }).pipe(
71
71
  Effect.flatMap((raw) =>
72
- Schema.decodeUnknown(schema)(raw).pipe(
72
+ Schema.decodeUnknownEffect(schema)(raw).pipe(
73
73
  Effect.mapError((cause) => new CodecError({ reason: 'decode', cause })),
74
74
  ),
75
75
  ),
package/src/errors.ts CHANGED
@@ -3,7 +3,8 @@
3
3
  * never defects.
4
4
  *
5
5
  * `CodecError` covers every serialisation/`Schema` failure on the codec path
6
- * (deserialise threw, `Schema.decode`/`encode` produced a `ParseError`, a
6
+ * (deserialise threw, `Schema.decodeUnknownEffect`/`encodeEffect` produced a
7
+ * `SchemaError`, a
7
8
  * wrong/absent encryption key surfaced through a `Schema` transform, or a
8
9
  * `tagsFor` mapper threw while deriving an event's tags). It carries a
9
10
  * human-readable `reason` and the underlying `cause` (exact-optional).
@@ -42,9 +43,10 @@ export class CodecError extends Data.TaggedError('CodecError')<{
42
43
  *
43
44
  * A GETTER rather than a second field, so `reason` stays the single declaration.
44
45
  * It is visible because `Data.TaggedError`'s constructor is
45
- * `super(args?.message, …)` followed by `Object.assign(this, args)` (verified
46
- * against `effect@3.22`'s `Data.ts`): the args above carry no `message`, so
47
- * nothing shadows this prototype accessor with an own property.
46
+ * `super(args?.message, …)` followed by copying the args' own enumerable keys
47
+ * onto the instance (verified against `effect@4`'s `internal/core.ts`): the
48
+ * args above carry no `message`, so nothing shadows this prototype accessor
49
+ * with an own property.
48
50
  */
49
51
  override get message(): string {
50
52
  return this.reason
package/src/event.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Event definitions: a tagged `Schema` built through `event(type, fields,
3
- * tagsFor)`. Each def is a `Schema<Payload, Wire, R>` plus its branded versioned
3
+ * tagsFor)`. Each def is a `Schema.Codec<Payload, Wire, R, R>` plus its branded versioned
4
4
  * `type` (ADR-0005) captured *beside* the payload, and a required `tagsFor`
5
5
  * mapper deriving the event's tags from its decoded payload (ADR-0006).
6
6
  *
@@ -42,22 +42,32 @@ export type TagsFor<A> = (payload: A) => Arr.NonEmptyReadonlyArray<Tag>
42
42
  /**
43
43
  * An event definition: its branded `type`, the `Schema` mapping its typed
44
44
  * payload `A` to its wire encoding `I` (requiring `R`), and the `tagsFor` mapper
45
- * that derives the event's tags from a decoded payload. `Schema` is invariant in
46
- * `A`/`I`, so `EventDef` cannot be `out A`/`out I`.
45
+ * that derives the event's tags from a decoded payload.
46
+ *
47
+ * `R` is ONE requirement for both directions, which is why the schema is a
48
+ * `Schema.Codec<A, I, R, R>` rather than leaving decoding and encoding services
49
+ * apart. A def is decoded on the read path and encoded by `toEvent`, so a
50
+ * consumer providing a def's services provides them for both; an application
51
+ * encryption transform needs its key either way. A struct whose two directions
52
+ * differ still fits, the codec being covariant in both, and `R` is then their
53
+ * union.
54
+ *
55
+ * `tagsFor` CONSUMES an `A`, so `EventDef` is invariant in `A` whatever the
56
+ * schema's own variance, and is therefore not declared `out A`.
47
57
  */
48
58
  export interface EventDef<A = unknown, I = unknown, R = never> {
49
59
  readonly type: EventType
50
- readonly schema: Schema.Schema<A, I, R>
60
+ readonly schema: Schema.Codec<A, I, R, R>
51
61
  readonly tagsFor: TagsFor<A>
52
62
  }
53
63
 
54
64
  /**
55
- * The heterogeneous bound (mirrors `Schema.Schema.Any`) for arrays/records of
56
- * mixed defs, where the precise `A`/`I` differ per element. `tagsFor` erases to
57
- * `(a: any) => …`, which is harmless: only the write path (`toEvent`) calls it,
58
- * and the registry/decode path never does (stored events already carry tags).
65
+ * The heterogeneous bound for arrays/records of mixed defs, where the precise
66
+ * `A`/`I` differ per element. `tagsFor` erases to `(a: any) => …`, which is
67
+ * harmless: only the write path (`toEvent`) calls it, and the registry/decode
68
+ * path never does (stored events already carry tags).
59
69
  */
60
- // biome-ignore lint/suspicious/noExplicitAny: the sanctioned heterogeneous bound (mirrors Schema.Schema.Any); A/I differ per element, so any is the only sound erasure.
70
+ // biome-ignore lint/suspicious/noExplicitAny: the sanctioned heterogeneous bound; A/I differ per element and tagsFor consumes A, so any is the only sound erasure.
61
71
  export type EventDefAny = EventDef<any, any, unknown>
62
72
 
63
73
  /**
package/src/index.ts CHANGED
@@ -17,8 +17,9 @@
17
17
  * on this path: the type is discovered at runtime, so no def was named to
18
18
  * narrow it (see `registry.ts`).
19
19
  *
20
- * `toEvent(def, payload, { uuid?, occurredAt? })` produces a wire event (encode
21
- * -> serialise, tags derived from `def.tagsFor`, clock + uuid from services).
20
+ * `toEvent(def, payload, { uuid?, occurredAt?, metadata? })` produces a wire
21
+ * event (encode -> serialise, tags derived from `def.tagsFor`, clock + uuid from
22
+ * services, metadata from the call or the optional ambient `MetadataSource`).
22
23
  * Serialisation is a pluggable `Serializer` (JSON
23
24
  * default). Encryption, when needed, is an *application*-authored `Schema`
24
25
  * transform on a payload field; the codec only propagates its requirement and
@@ -134,6 +135,7 @@ export {
134
135
  export { decodeSlices } from './decode'
135
136
  export { CodecError, UnknownEventType } from './errors'
136
137
  export { type EventDef, type EventDefAny, event, type TagsFor } from './event'
138
+ export { MetadataSource } from './MetadataSource'
137
139
  export {
138
140
  decideOnly,
139
141
  type Precondition,
@@ -148,7 +148,7 @@ import type { Uuid } from './Uuid'
148
148
  * path meant to deny the input cheaply. Wrap such work in `Effect.suspend`.
149
149
  */
150
150
  export interface Precondition<Id, Service, E, R> {
151
- readonly tag: Context.Tag<Id, Service>
151
+ readonly tag: Context.Key<Id, Service>
152
152
  readonly evaluate: Effect.Effect<Service, E, R>
153
153
  }
154
154
 
@@ -243,7 +243,7 @@ export const decideOnly = <
243
243
  * condition here. If that turns up in practice, a composing override is the
244
244
  * additive answer and this hatch is not it.
245
245
  *
246
- * `Effect.mapInputContext` over
246
+ * `Effect.updateContext` over
247
247
  * `Context.omit(AppendIsOwned)` is what lets it through the store's ownership
248
248
  * refusal — the one route, and `AppendIsOwned.ts` owns why removing the marker is
249
249
  * the shape rather than a second tag or a sanctioning flag.
@@ -275,7 +275,7 @@ export const unsafeAppend = (
275
275
  // channel), so nothing here will fail to compile if this is deleted — what
276
276
  // catches it is `test/appendOwnership.test.ts` in the source repository, not the
277
277
  // type checker.
278
- Effect.mapInputContext(
278
+ Effect.updateContext(
279
279
  store.append(events),
280
280
  (context: Context.Context<never>) => Context.omit(AppendIsOwned)(context),
281
281
  ),
package/src/projection.ts CHANGED
@@ -5,7 +5,7 @@
5
5
  * A `SliceProjection<S, R>` is a core `AnyProjection<S>` (directly composable —
6
6
  * its `evolve` folds a `DecodedEvent`) enriched with the `type -> Schema` map
7
7
  * `decodeSlices` merges and decodes through. That map's value type carries `R`
8
- * covariantly (`Schema<unknown, unknown, R>`), so the union of the defs'
8
+ * covariantly (`Schema.Decoder<unknown, R>`), so the union of the defs'
9
9
  * requirements flows to the decision model with no cast (and no phantom marker).
10
10
  * Because `evolve` is already erased, a built slice drops straight into
11
11
  * `composeProjections` — no `AnyProjection.of` ceremony.
@@ -35,7 +35,7 @@ import type { EventDef } from './event'
35
35
  */
36
36
  export interface SliceProjection<out S = unknown, out R = unknown>
37
37
  extends AnyProjection<S> {
38
- readonly schemas: ReadonlyMap<string, Schema.Schema<unknown, unknown, R>>
38
+ readonly schemas: ReadonlyMap<string, Schema.Decoder<unknown, R>>
39
39
  }
40
40
 
41
41
  /** The fluent builder: accumulate `.on(def, fold)` steps, then `.build()`. */
@@ -75,7 +75,7 @@ export const projection = <S>(initialState: S): ProjectionBuilder<S, never> => {
75
75
  // threading `R` means the implementation states the same widening the
76
76
  // `ProjectionBuilder` interface promises, and `on` returns without a cast.
77
77
  const make = <R>(
78
- schemas: ReadonlyMap<string, Schema.Schema<unknown, unknown, R>>,
78
+ schemas: ReadonlyMap<string, Schema.Decoder<unknown, R>>,
79
79
  folds: ReadonlyMap<string, (state: S, payload: unknown) => S>,
80
80
  ): ProjectionBuilder<S, R> => ({
81
81
  on: <A, I, R2>(
@@ -101,25 +101,20 @@ export const projection = <S>(initialState: S): ProjectionBuilder<S, never> => {
101
101
  // The entry type is annotated `R | R2` rather than inferred, and that
102
102
  // annotation is what carries the requirement forward — `build` reads `R`
103
103
  // straight off this map, so `RequirementOf` ultimately reads it from here.
104
- // Widening the copied `R` entries into it costs nothing: `Schema` is
105
- // declared `<in out A, in out I, out R>` upstream, hence covariant in its
104
+ // Widening the copied `R` entries into it costs nothing: `Schema.Decoder`
105
+ // is declared `<out T, out RD>` upstream, hence covariant in its
106
106
  // requirement.
107
- const nextSchemas = new Map<
108
- string,
109
- Schema.Schema<unknown, unknown, R | R2>
110
- >(schemas)
111
- // Erasing `A`/`I` is the one unavoidable cast, and `as unknown as` is what
112
- // it takes: `Schema` is INVARIANT in both, so `Schema<A, I, R2>` and
113
- // `Schema<unknown, unknown, R2>` are unrelated in either direction. It is
114
- // sound because the payload is only ever read back through `evolve`, which
115
- // re-narrows by event type via the matching `fold`. The target names `R2`
116
- // and not `never` because `R2` is what the def actually requires — an
117
- // `Encrypted` field's `CipherKey` is not nothing — so the cast erases only
118
- // what it has to and states the rest truthfully.
119
- nextSchemas.set(
120
- def.type,
121
- def.schema as unknown as Schema.Schema<unknown, unknown, R2>,
107
+ const nextSchemas = new Map<string, Schema.Decoder<unknown, R | R2>>(
108
+ schemas,
122
109
  )
110
+ // Erasing `A`/`I` needs no cast: a `Schema.Codec` is covariant in both, so
111
+ // `Codec<A, I, R2, R2>` is already a `Decoder<unknown, R2>`. The erasure is
112
+ // sound because the payload is only ever read back through `evolve`, which
113
+ // re-narrows by event type via the matching `fold`. What survives it is
114
+ // `R2`, the def's DECODING requirement — an `Encrypted` field's `CipherKey`
115
+ // is not nothing — and that is the half a slice ever spends, since a slice
116
+ // decodes and never encodes.
117
+ nextSchemas.set(def.type, def.schema)
123
118
  const nextFolds = new Map(folds)
124
119
  // Contravariance in the payload parameter, so this one is a cast too: the
125
120
  // map holds folds over `unknown`, and `evolve` supplies the payload the
package/src/readLedger.ts CHANGED
@@ -73,6 +73,7 @@ import {
73
73
  ORIGIN,
74
74
  servableQueryFault,
75
75
  unionQueries,
76
+ unknownQueryKeyFault,
76
77
  } from '@kairos-es/core'
77
78
  import { Context, Data, Effect, Layer, Option, Ref } from 'effect'
78
79
 
@@ -97,7 +98,7 @@ export interface Hop {
97
98
  * requirement channel nothing is exactly what makes this change additive.
98
99
  * `modelAtHead` is that path and is why the rule exists.
99
100
  *
100
- * A BRACKET-ONLY function is the opposite case and yields the tag deliberately.
101
+ * A BRACKET-ONLY function is the opposite case and yields the service deliberately.
101
102
  * `decideOnly` does, and naming the requirement is what turns *"call me inside a
102
103
  * bracket"* from a runtime defect into a compile error — presence being the one
103
104
  * direction the type system can decide here. So the test is what the function
@@ -107,7 +108,7 @@ export interface Hop {
107
108
  * Recording is two-phase, and `record`'s own doc below owns why and what shape the
108
109
  * second phase takes.
109
110
  */
110
- export class ReadLedger extends Context.Tag('@kairos-es/codec/ReadLedger')<
111
+ export class ReadLedger extends Context.Service<
111
112
  ReadLedger,
112
113
  {
113
114
  /**
@@ -126,14 +127,14 @@ export class ReadLedger extends Context.Tag('@kairos-es/codec/ReadLedger')<
126
127
  readonly record: (query: Query) => Effect.Effect<CloseHop>
127
128
  readonly hops: Effect.Effect<ReadonlyArray<Hop>>
128
129
  }
129
- >() {}
130
+ >()('@kairos-es/codec/ReadLedger') {}
130
131
 
131
132
  /**
132
133
  * Fill in a recorded hop's head. Never called if the read does not complete.
133
134
  *
134
135
  * On the package's public surface because it is already ON it: `record` returns
135
- * one, `ReadLedger` is exported, so a consumer holding the tag holds this type and
136
- * could not name it. `Hop` was exported and this was not — the same interface, two
136
+ * one, `ReadLedger` is exported, so a consumer holding the service holds this
137
+ * type and could not name it. `Hop` was exported and this was not — the same interface, two
137
138
  * treatments.
138
139
  */
139
140
  export type CloseHop = (head: Position) => Effect.Effect<void>
@@ -302,7 +303,7 @@ export const makeReadLedger: Effect.Effect<{
302
303
  */
303
304
  const withLedger = <A, E = never>(
304
305
  absent: A,
305
- f: (ledger: Context.Tag.Service<ReadLedger>) => Effect.Effect<A, E>,
306
+ f: (ledger: Context.Service.Shape<typeof ReadLedger>) => Effect.Effect<A, E>,
306
307
  ): Effect.Effect<A, E> =>
307
308
  Effect.flatMap(Effect.serviceOption(ReadLedger), (ledger) =>
308
309
  Option.isNone(ledger) ? Effect.succeed(absent) : f(ledger.value),
@@ -329,14 +330,29 @@ const taglessKeys = (
329
330
  )
330
331
  .map(([key]) => key)
331
332
 
332
- /** Would recording this record's query leave the accumulated union unservable? */
333
+ /**
334
+ * Would recording this record's query leave the accumulated union unservable in
335
+ * its SHAPE — the one fault a slice's tags can fix, and so the one this ledger
336
+ * reports?
337
+ *
338
+ * An unknown key anywhere in the union answers no. It is not a tagless slice and no
339
+ * slice's tags would fix it, and the store refuses it as a defect naming the key at
340
+ * the read of the hop that carries it, so an earlier hop's key has already been
341
+ * refused once by the time a later record is judged. Judged here it would surface
342
+ * as this ledger's error, on the error channel, naming a slice with nothing wrong
343
+ * with it — and a hop is recorded before its read runs, so an earlier hop's key
344
+ * would go on doing that to every clean read after it.
345
+ */
333
346
  const unionWouldBeUnservable = (
334
347
  existing: ReadonlyArray<Hop>,
335
348
  incoming: Query,
336
- ): boolean =>
337
- servableQueryFault(
338
- unionQueries([...existing.map((h) => h.query), incoming]),
339
- ) !== undefined
349
+ ): boolean => {
350
+ const union = unionQueries([...existing.map((h) => h.query), incoming])
351
+ return (
352
+ unknownQueryKeyFault(union) === undefined &&
353
+ servableQueryFault(union) !== undefined
354
+ )
355
+ }
340
356
 
341
357
  /**
342
358
  * Refuse a read that would leave the bracket's accumulated union unservable,
@@ -389,6 +405,6 @@ export const ambientLayeredCondition: Effect.Effect<
389
405
  Option.Option<AppendCondition>
390
406
  > = withLedger(Option.none<AppendCondition>(), (ledger) =>
391
407
  Effect.map(ledger.hops, (hops) =>
392
- Option.fromNullable(deriveLayeredCondition(hops)),
408
+ Option.fromNullishOr(deriveLayeredCondition(hops)),
393
409
  ),
394
410
  )
package/src/registry.ts CHANGED
@@ -53,7 +53,7 @@ type RequirementOfDefs<Defs extends ReadonlyArray<EventDefAny>> =
53
53
  export const makeRegistry = <Defs extends ReadonlyArray<EventDefAny>>(
54
54
  defs: Defs,
55
55
  ): Registry<RequirementOfDefs<Defs>> => {
56
- const map = new Map<string, Schema.Schema<unknown, unknown, unknown>>()
56
+ const map = new Map<string, Schema.Decoder<unknown, unknown>>()
57
57
  for (const def of defs) {
58
58
  if (map.has(def.type)) {
59
59
  throw new Error(`duplicate event type '${def.type}' in registry`)
package/src/toEvent.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Event production (codec edge-helper): turn a typed payload into a wire
3
- * `DcbEvent`. Encode is `Schema.encode(def.schema)` (a `ParseError` becomes a
4
- * `CodecError`; any encryption transform runs here, *before* serialisation)
3
+ * `DcbEvent`. Encode is `Schema.encodeEffect(def.schema)` (a `SchemaError`
4
+ * becomes a `CodecError`; any encryption transform runs here, *before* serialisation)
5
5
  * then `serialiser.serialise`. The wire `tags` are derived from the def's
6
6
  * required `tagsFor` mapper (ADR-0006) — they are never hand-passed, so two
7
7
  * command sites emitting the same event type cannot tag it differently. A mapper
@@ -10,11 +10,19 @@
10
10
  * comes from `options.uuid` or the `Uuid` service; `occurredAt` from
11
11
  * `options.occurredAt` or the Effect clock (`DateTime.now`), keeping `core`
12
12
  * clock- and RNG-free while staying overridable for backdated imports.
13
+ * `metadata` comes from `options.metadata` or the OPTIONAL ambient
14
+ * `MetadataSource` service, and is `{}` when neither supplies it (ADR-0017). A
15
+ * bag core's `Metadata` schema refuses, such as one holding a value that is not a
16
+ * string, fails on the `CodecError` channel (`reason: 'metadata'`), whichever of
17
+ * the two supplied it. It never reaches `tagsFor`, the
18
+ * event's schema or the serialiser, so it can enter neither the tag junction
19
+ * table nor the encryption seam.
13
20
  */
14
- import type { DcbEvent } from '@kairos-es/core'
15
- import { DateTime, Effect, Schema } from 'effect'
21
+ import { type DcbEvent, Metadata } from '@kairos-es/core'
22
+ import { DateTime, Effect, Option, Schema } from 'effect'
16
23
  import { CodecError } from './errors'
17
24
  import type { EventDef } from './event'
25
+ import { MetadataSource } from './MetadataSource'
18
26
  import { Serializer } from './Serializer'
19
27
  import { Uuid } from './Uuid'
20
28
 
@@ -26,7 +34,10 @@ import { Uuid } from './Uuid'
26
34
  * wire-envelope fields (the query surface), so a `tagsFor` mapper may only key
27
35
  * off cleartext payload fields (ADR-0006). `uuid` / `occurredAt` are
28
36
  * exact-optional overrides; absent, they come from the `Uuid` service and the
29
- * Effect clock.
37
+ * Effect clock. `metadata` is an exact-optional override too; absent, it comes
38
+ * from the ambient `MetadataSource` if one is provided and is `{}` otherwise.
39
+ * The ambient read adds nothing to the requirement channel. A bag core's
40
+ * `Metadata` schema refuses fails as a `CodecError` with `reason: 'metadata'`.
30
41
  */
31
42
  export const toEvent = <A, I, R>(
32
43
  def: EventDef<A, I, R>,
@@ -34,10 +45,45 @@ export const toEvent = <A, I, R>(
34
45
  options?: {
35
46
  readonly uuid?: string
36
47
  readonly occurredAt?: DateTime.Utc
48
+ readonly metadata?: Metadata
37
49
  },
38
50
  ): Effect.Effect<DcbEvent, CodecError, Serializer | Uuid | R> =>
39
51
  Effect.gen(function* () {
40
- // Derive tags first. A mapper is app code that can throw (e.g. `Tag.make`
52
+ // Resolve metadata before anything else. Per-call wins, ambient fills,
53
+ // absent yields `{}`. `serviceOption` keeps `MetadataSource` out of the
54
+ // requirement channel.
55
+ //
56
+ // The bag is then CHECKED against core's `Metadata` schema, whichever arm
57
+ // supplied it. The type holds a typed caller to a bag of strings, but an
58
+ // `any` or untyped JavaScript gets past it, and the engines then disagree
59
+ // about what they stored: the cross-engine parity of this field rests on its
60
+ // values being strings, and this is the one place a value is refused. It is
61
+ // refused HERE, at write, and never on read: a read-side check would make an
62
+ // event the store had accepted unreadable for ever. A bad bag is the
63
+ // caller's mistake, so it fails on the `CodecError` channel exactly as a
64
+ // throwing `tagsFor` does, never as a defect. Checking consumes no sequence,
65
+ // so placing it above the tag derivation leaves the property below intact.
66
+ //
67
+ // The bag is COPIED, never stamped by reference: an ambient bag would
68
+ // otherwise be ONE object shared by every event of a request, and the
69
+ // caller's own object would be shared with the event it built, so a mutation
70
+ // of either before the append would rewrite the other. (After the append no
71
+ // store shares anything with its caller; that is each store's guarantee.)
72
+ const given: unknown =
73
+ options?.metadata === undefined
74
+ ? Option.getOrElse(
75
+ yield* Effect.serviceOption(MetadataSource),
76
+ (): Metadata => ({}),
77
+ )
78
+ : options.metadata
79
+ const metadata: Metadata = {
80
+ ...(yield* Schema.decodeUnknownEffect(Metadata)(given).pipe(
81
+ Effect.mapError(
82
+ (cause) => new CodecError({ reason: 'metadata', cause }),
83
+ ),
84
+ )),
85
+ }
86
+ // Derive tags next. A mapper is app code that can throw (e.g. `Tag.make`
41
87
  // rejects an ill-formed `key:value`), and the codec's contract is that such
42
88
  // failures are values on the `CodecError` channel, never defects (errors.ts).
43
89
  // Deriving before the uuid/clock effects also means a bad tag fails without
@@ -47,7 +93,7 @@ export const toEvent = <A, I, R>(
47
93
  catch: (cause) => new CodecError({ reason: 'tags', cause }),
48
94
  })
49
95
  const serializer = yield* Serializer
50
- const encoded = yield* Schema.encode(def.schema)(payload).pipe(
96
+ const encoded = yield* Schema.encodeEffect(def.schema)(payload).pipe(
51
97
  Effect.mapError((cause) => new CodecError({ reason: 'encode', cause })),
52
98
  )
53
99
  const data = serializer.serialise(encoded)
@@ -59,5 +105,6 @@ export const toEvent = <A, I, R>(
59
105
  tags,
60
106
  uuid,
61
107
  occurredAt,
108
+ metadata,
62
109
  }
63
110
  })
@@ -1 +0,0 @@
1
- {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/errors.ts"],"names":[],"mappings":";;;AAeA,2EAA2E;AAC3E,qBAAa,UAAW,SAAQ,gBAA+B;IAC7D,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CACzB,CAAC;IACA;;;;;;;;;;;;;;;;;;;;;;;;;;;OA2BG;IACH,IAAa,OAAO,IAAI,MAAM,CAE7B;CACF;;;;AAED,kFAAkF;AAClF,qBAAa,gBAAiB,SAAQ,sBAAqC;IACzE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CACtB,CAAC;IACA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA6BG;IACH,IAAa,OAAO,IAAI,MAAM,CAE7B;CACF"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"event.d.ts","sourceRoot":"","sources":["../../src/event.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,GAAG,EAAE,MAAM,iBAAiB,CAAA;AACrD,OAAO,KAAK,EAAE,KAAK,IAAI,GAAG,EAAE,MAAM,QAAQ,CAAA;AAC1C,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAE/B;;;;;GAKG;AACH,MAAM,MAAM,OAAO,CAAC,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,KAAK,GAAG,CAAC,qBAAqB,CAAC,GAAG,CAAC,CAAA;AAEvE;;;;;GAKG;AACH,MAAM,WAAW,QAAQ,CAAC,CAAC,GAAG,OAAO,EAAE,CAAC,GAAG,OAAO,EAAE,CAAC,GAAG,KAAK;IAC3D,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAA;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAA;IACvC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,CAAA;CAC7B;AAED;;;;;GAKG;AAEH,MAAM,MAAM,WAAW,GAAG,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,OAAO,CAAC,CAAA;AAErD;;;;;;;GAOG;AACH,eAAO,MAAM,KAAK,GAChB,KAAK,CAAC,IAAI,SAAS,MAAM,EACzB,MAAM,SAAS,MAAM,CAAC,MAAM,CAAC,MAAM,EAEnC,MAAM,IAAI,EACV,QAAQ,MAAM,EACd,SAAS,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,KAC3C;IACD,QAAQ,CAAC,IAAI,EAAE,IAAI,GAAG,SAAS,CAAA;IAC/B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;IACtC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAA;CAStD,CAAA"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqHG;AAEH,OAAO,EAAE,KAAK,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAA;AAC7D,OAAO,EACL,QAAQ,EACR,KAAK,cAAc,EACnB,KAAK,cAAc,EACnB,WAAW,EACX,SAAS,EACT,eAAe,GAChB,MAAM,YAAY,CAAA;AACnB,OAAO,EACL,kBAAkB,EAClB,WAAW,EACX,KAAK,aAAa,GACnB,MAAM,iBAAiB,CAAA;AACxB,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAA;AACvC,OAAO,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAA;AACvD,OAAO,EAAE,KAAK,QAAQ,EAAE,KAAK,WAAW,EAAE,KAAK,EAAE,KAAK,OAAO,EAAE,MAAM,SAAS,CAAA;AAC9E,OAAO,EACL,UAAU,EACV,KAAK,YAAY,EACjB,QAAQ,EACR,YAAY,GACb,MAAM,gBAAgB,CAAA;AACvB,OAAO,EACL,KAAK,iBAAiB,EACtB,UAAU,EACV,KAAK,eAAe,GACrB,MAAM,cAAc,CAAA;AACrB,OAAO,EACL,KAAK,QAAQ,EACb,KAAK,GAAG,EACR,eAAe,EACf,UAAU,EACV,oBAAoB,GACrB,MAAM,cAAc,CAAA;AACrB,OAAO,EAAE,YAAY,EAAE,KAAK,QAAQ,EAAE,MAAM,YAAY,CAAA;AACxD,OAAO,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAA;AAC5D,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AACnC,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAA"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"projection.d.ts","sourceRoot":"","sources":["../../src/projection.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,EACL,KAAK,aAAa,EAIlB,KAAK,GAAG,EACT,MAAM,iBAAiB,CAAA;AACxB,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AACpC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAA;AAEvC;;;;;GAKG;AACH,MAAM,WAAW,eAAe,CAAC,GAAG,CAAC,CAAC,GAAG,OAAO,EAAE,GAAG,CAAC,CAAC,GAAG,OAAO,CAC/D,SAAQ,aAAa,CAAC,CAAC,CAAC;IACxB,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,CAAA;CAC1E;AAED,8EAA8E;AAC9E,MAAM,WAAW,iBAAiB,CAAC,CAAC,EAAE,CAAC,GAAG,KAAK;IAC7C;;;;;;;OAOG;IACH,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,EACT,GAAG,EAAE,QAAQ,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,EACvB,IAAI,EAAE,CAAC,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,KAAK,CAAC,GAChC,iBAAiB,CAAC,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,CAAA;IAC/B;;;OAGG;IACH,KAAK,CAAC,OAAO,CAAC,EAAE;QAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,aAAa,CAAC,GAAG,CAAC,CAAA;KAAE,GAAG,eAAe,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;CAC/E;AAED;;;GAGG;AACH,eAAO,MAAM,UAAU,GAAI,CAAC,EAAE,cAAc,CAAC,KAAG,iBAAiB,CAAC,CAAC,EAAE,KAAK,CAiGzE,CAAA"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"toEvent.d.ts","sourceRoot":"","sources":["../../src/toEvent.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAA;AAC/C,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAU,MAAM,QAAQ,CAAA;AACjD,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAA;AACrC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAA;AACvC,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AACzC,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAA;AAE7B;;;;;;;;;GASG;AACH,eAAO,MAAM,OAAO,GAAI,CAAC,EAAE,CAAC,EAAE,CAAC,EAC7B,KAAK,QAAQ,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EACtB,SAAS,CAAC,EACV,UAAU;IACR,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,UAAU,CAAC,EAAE,QAAQ,CAAC,GAAG,CAAA;CACnC,KACA,MAAM,CAAC,MAAM,CAAC,QAAQ,EAAE,UAAU,EAAE,UAAU,GAAG,IAAI,GAAG,CAAC,CAyBxD,CAAA"}
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes