@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
@@ -0,0 +1,62 @@
1
+ /**
2
+ * The two-step every decode in this package performs, in one implementation:
3
+ * deserialise the wire bytes through the `Serializer` its caller already holds,
4
+ * then `Schema.decodeUnknownEffect` the result, each half's failure wrapped as the
5
+ * `CodecError` whose `reason` says WHICH half failed.
6
+ *
7
+ * **What it is the single home of.** The two `reason` strings `'deserialise'`
8
+ * and `'decode'`, and their ORDER. Why those strings are observable surface —
9
+ * fixed rather than cosmetic, because an application quarantines an event,
10
+ * alerts or fails a command off them — is argued in `decode.ts`'s module doc and
11
+ * is not restated here. What this module adds is that the argument had TWO
12
+ * implementations standing behind it: the slice stage (`decodeEvents`) and the
13
+ * general/registry path (`makeRegistry`'s `decode`) each wrote the pair out, in
14
+ * one package, and only the slice copy was pinned by a test. A reworded `reason`
15
+ * on the registry path would have been caught by nothing. Sharing the step
16
+ * DELETES that drift channel rather than alarming it, which is why this exists
17
+ * as a function and not as a second test.
18
+ *
19
+ * **The order is contract, not accident.** Deserialise has to run first because
20
+ * `Schema.decodeUnknownEffect` has nothing to read until it has, and the observable
21
+ * consequence is that the two reasons PARTITION the causes an operator acts on:
22
+ * rotten bytes are always `'deserialise'` and never `'decode'`, so a storage or
23
+ * producer problem never arrives dressed as schema drift. Swapping the labels
24
+ * would not break a decode; it would send every incident to the wrong team.
25
+ *
26
+ * **Why a `Serializer` VALUE rather than reading the tag here.** The two callers
27
+ * acquire it at deliberately different moments and this step must not flatten
28
+ * that. `decodeEvents` reads it ONCE when the stream starts (`Stream.unwrap`), so
29
+ * a backlog of a million events does not re-read context per event; the
30
+ * registry's `decode` is a per-event `Effect` and yields it inside its own
31
+ * `Effect.gen`. Taking the resolved service preserves both, and keeps
32
+ * `Serializer` off this signature's `R`, so the requirement stays declared where
33
+ * the acquisition actually happens — which is where a reader goes looking for it.
34
+ *
35
+ * **Why the LOOKUP is not folded in**, though both callers do one immediately
36
+ * before calling this: the two paths disagree about what a miss MEANS, and the
37
+ * disagreement is deliberate. The slice path fails `CodecError` with
38
+ * `no schema for event type '<type>'` — a caller named the type and the record it
39
+ * supplied has no entry for it, so the wiring is wrong. The registry path fails
40
+ * `UnknownEventType` — the type arrived off the WIRE and the registry does not
41
+ * know it, which is ADR-0005 decode-totality and the one fault a rolling
42
+ * deployment is EXPECTED to meet. `errors.ts` argues why those two sentences stay
43
+ * greppable apart. Folding the lookup in would mean a per-caller miss
44
+ * constructor: a second seam, bought to share the one thing the two paths
45
+ * genuinely do not have in common.
46
+ *
47
+ * `R` is the SCHEMA's requirement, propagated untouched — an ADR-0004 decryption
48
+ * transform reaches the caller's `R` through here with no cast. The three
49
+ * parameters are positional because their types are mutually unassignable, so a
50
+ * transposed call is a compile error rather than a silent inversion.
51
+ *
52
+ * PACKAGE-INTERNAL: exported to its two callers and no further, deliberately
53
+ * absent from `index.ts`. It is a shared step, not a capability on offer — a
54
+ * consumer holding bytes and a `Schema` wants `decodeSlices` or a `Registry`,
55
+ * both of which do the lookup this one refuses to.
56
+ */
57
+ import type { Context } from 'effect';
58
+ import { Effect, Schema } from 'effect';
59
+ import { CodecError } from './errors.js';
60
+ import type { Serializer } from './Serializer.js';
61
+ export declare const deserialiseThenDecode: <R>(serializer: Context.Service.Shape<typeof Serializer>, schema: Schema.Decoder<unknown, R>, bytes: Uint8Array) => Effect.Effect<unknown, CodecError, R>;
62
+ //# sourceMappingURL=deserialiseThenDecode.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deserialiseThenDecode.d.ts","sourceRoot":"","sources":["../../src/deserialiseThenDecode.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuDG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAA;AACrC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AACvC,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAA;AACrC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAE9C,eAAO,MAAM,qBAAqB,GAAI,CAAC,EACrC,YAAY,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,UAAU,CAAC,EACpD,QAAQ,MAAM,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,EAClC,OAAO,UAAU,KAChB,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,UAAU,EAAE,CAAC,CAUpC,CAAA"}
@@ -0,0 +1,80 @@
1
+ declare const CodecError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
2
+ readonly _tag: "CodecError";
3
+ } & Readonly<A>;
4
+ /** A serialisation or `Schema` decode/encode failure on the codec path. */
5
+ export declare class CodecError extends CodecError_base<{
6
+ readonly reason: string;
7
+ readonly cause?: unknown;
8
+ }> {
9
+ /**
10
+ * `reason` again, as the `Error` field that says why — so the fault renders
11
+ * ITSELF.
12
+ *
13
+ * `Data.TaggedError` sets `name` to the tag and leaves `message` empty, so
14
+ * without this a `CodecError` stringifies to `'CodecError'`, and the sentence
15
+ * naming the schema, the event type or the missing key that failed reaches a log
16
+ * line only where the code doing the logging knows to reach in for `reason`.
17
+ * Filled, `Error.prototype.toString` yields `'CodecError: <reason>'`, which is
18
+ * what `Cause.pretty` (it reads `message` off anything `instanceof Error`) and the
19
+ * read side's projection supervisor both report — neither of them holding a table
20
+ * of this class's fields to keep in step with it.
21
+ *
22
+ * This class is on the package's PUBLISHED surface, so be exact about what the
23
+ * field moves. Everything that renders the error for a HUMAN: `String(err)`,
24
+ * `Cause.pretty` and therefore a failed fibre's `cause` annotation on every
25
+ * shipped logger, and an unhandled failure's report. Nothing that renders it for
26
+ * a MACHINE: `Data.Error` overrides `YieldableError`'s `toJSON` with
27
+ * `{ ...plainArgs, ...this }` — own enumerables plus the constructor args, and a
28
+ * prototype accessor is neither — so `JSON.stringify(err)` still emits the same
29
+ * `reason`/`_tag` and a structured log of a raw `CodecError` keeps its shape.
30
+ *
31
+ * A GETTER rather than a second field, so `reason` stays the single declaration.
32
+ * It is visible because `Data.TaggedError`'s constructor is
33
+ * `super(args?.message, …)` followed by copying the args' own enumerable keys
34
+ * onto the instance (verified against `effect@4`'s `internal/core.ts`): the
35
+ * args above carry no `message`, so nothing shadows this prototype accessor
36
+ * with an own property.
37
+ */
38
+ get message(): string;
39
+ }
40
+ declare const UnknownEventType_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
41
+ readonly _tag: "UnknownEventType";
42
+ } & Readonly<A>;
43
+ /** A wire event whose runtime type string is outside the registry's known set. */
44
+ export declare class UnknownEventType extends UnknownEventType_base<{
45
+ readonly type: string;
46
+ }> {
47
+ /**
48
+ * The offending type, in a sentence saying what the registry did with it — so the
49
+ * fault renders ITSELF, exactly as `CodecError` above does.
50
+ *
51
+ * `Data.TaggedError` sets `name` to the tag and leaves `message` empty, so without
52
+ * this a decode that met an unregistered type stringifies to `'UnknownEventType'`
53
+ * — the one datum that makes the failure actionable, the type string itself,
54
+ * reaching a log line only where the logging code knows to reach in for `type`.
55
+ * Filled, `Error.prototype.toString` yields the tag followed by
56
+ * `event type 'nope.v1' is not in the registry`, which is what `Cause.pretty`
57
+ * prints and what an operator greps for. It matters here in particular because
58
+ * this class is the home of ADR-0005 decode-totality: a wire event outside the
59
+ * registered set is the expected consequence of a deployment reading a log a newer
60
+ * writer has extended, and the first question is always WHICH type — which the tag
61
+ * alone cannot answer.
62
+ *
63
+ * The wording deliberately differs from `CodecError`'s. That one says there is no
64
+ * schema for a type, and is raised on the TYPED path, where a caller named the
65
+ * type and the schema map it supplied has no entry for it. This one is the general
66
+ * (registry) path, where the type arrived off the WIRE and the registry is what
67
+ * does not know it. Distinct sentences keep the two greppable apart, which a
68
+ * shared phrasing would cost for nothing.
69
+ *
70
+ * A GETTER rather than a second field, so `type` stays the single declaration.
71
+ * What filling this field reaches, what it deliberately leaves untouched, and why
72
+ * a prototype accessor is visible at all are argued once on `CodecError` above and
73
+ * not restated here — it is the same package, the same published surface and the
74
+ * same mechanism, and two copies of that paragraph would be two things to keep in
75
+ * step. The read side's four getters point at one home for the identical reason.
76
+ */
77
+ get message(): string;
78
+ }
79
+ export {};
80
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/errors.ts"],"names":[],"mappings":";;;AAgBA,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;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA4BG;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"}
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Event definitions: a tagged `Schema` built through `event(type, fields,
3
+ * tagsFor)`. Each def is a `Schema.Codec<Payload, Wire, R, R>` plus its branded versioned
4
+ * `type` (ADR-0005) captured *beside* the payload, and a required `tagsFor`
5
+ * mapper deriving the event's tags from its decoded payload (ADR-0006).
6
+ *
7
+ * The single `EventType` brand is applied once, here, so no consumer ever casts
8
+ * a type string: `def.type` is already assignable to core's `EventType`. The
9
+ * payload `A`, wire encoding `I`, and requirement `R` are inferred from the
10
+ * `Schema.Struct` of `fields` — including any requirement a field's `Schema`
11
+ * transform carries (e.g. an application `Encrypted(...)` field contributes its
12
+ * `CipherKey` to `R`).
13
+ *
14
+ * `tagsFor` is the only way to tag an event: `toEvent` derives the wire tags by
15
+ * calling it, and there is no `tags` option to override them. Its
16
+ * `NonEmptyReadonlyArray<Tag>` return makes "every event carries at least one
17
+ * tag" a compile-time guarantee (returning `[]` is a type error at the def
18
+ * site), and its input being only this event's payload guarantees `tag ⊆ event
19
+ * data` structurally. An event with no natural business identity returns an
20
+ * explicit app-defined `system:<name>` tag, never an empty array.
21
+ *
22
+ * A mapper must be a *pure* function of its payload and key off fields in their
23
+ * schema-canonical form (normalising in the mapper to match a field's schema),
24
+ * so the tag it derives is stable under the schema round-trip: a later retag
25
+ * re-derives tags from decoded history, and `tagsFor(decode(encode(p)))` must
26
+ * equal `tagsFor(p)` for that to be sound. Plain scalar fields satisfy this
27
+ * trivially; a field whose schema canonicalises on decode (trim, lowercase,
28
+ * normalise) needs the mapper to apply the same normalisation (ADR-0006).
29
+ */
30
+ import type { EventType, Tag } from '@kairos-es/core';
31
+ import type { Array as Arr } from 'effect';
32
+ import { Schema } from 'effect';
33
+ /**
34
+ * A tag mapper: derives an event's tags from its decoded payload, returning at
35
+ * least one `Tag`. Named so a standalone mapper can be typed and shared (e.g.
36
+ * across two versioned defs of one logical event) and so the signature is
37
+ * written once, not per use site.
38
+ */
39
+ export type TagsFor<A> = (payload: A) => Arr.NonEmptyReadonlyArray<Tag>;
40
+ /**
41
+ * An event definition: its branded `type`, the `Schema` mapping its typed
42
+ * payload `A` to its wire encoding `I` (requiring `R`), and the `tagsFor` mapper
43
+ * that derives the event's tags from a decoded payload.
44
+ *
45
+ * `R` is ONE requirement for both directions, which is why the schema is a
46
+ * `Schema.Codec<A, I, R, R>` rather than leaving decoding and encoding services
47
+ * apart. A def is decoded on the read path and encoded by `toEvent`, so a
48
+ * consumer providing a def's services provides them for both; an application
49
+ * encryption transform needs its key either way. A struct whose two directions
50
+ * differ still fits, the codec being covariant in both, and `R` is then their
51
+ * union.
52
+ *
53
+ * `tagsFor` CONSUMES an `A`, so `EventDef` is invariant in `A` whatever the
54
+ * schema's own variance, and is therefore not declared `out A`.
55
+ */
56
+ export interface EventDef<A = unknown, I = unknown, R = never> {
57
+ readonly type: EventType;
58
+ readonly schema: Schema.Codec<A, I, R, R>;
59
+ readonly tagsFor: TagsFor<A>;
60
+ }
61
+ /**
62
+ * The heterogeneous bound for arrays/records of mixed defs, where the precise
63
+ * `A`/`I` differ per element. `tagsFor` erases to `(a: any) => …`, which is
64
+ * harmless: only the write path (`toEvent`) calls it, and the registry/decode
65
+ * path never does (stored events already carry tags).
66
+ */
67
+ export type EventDefAny = EventDef<any, any, unknown>;
68
+ /**
69
+ * Define an event from a versioned `type` literal, its payload `fields`, and its
70
+ * required `tagsFor` mapper. Builds `Schema.Struct(fields)` and brands the type
71
+ * literal *once* (the single sanctioned cast). The returned def is typed so
72
+ * `def.type` is a branded literal assignable to core's `EventType` with no
73
+ * consumer cast, `A`/`I`/`R` are inferred from the struct, and `tagsFor`
74
+ * receives the def's decoded payload type.
75
+ */
76
+ export declare const event: <const Type extends string, Fields extends Schema.Struct.Fields>(type: Type, fields: Fields, tagsFor: TagsFor<Schema.Struct.Type<Fields>>) => {
77
+ readonly type: Type & EventType;
78
+ readonly schema: Schema.Struct<Fields>;
79
+ readonly tagsFor: TagsFor<Schema.Struct.Type<Fields>>;
80
+ };
81
+ //# sourceMappingURL=event.d.ts.map
@@ -0,0 +1 @@
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;;;;;;;;;;;;;;;GAeG;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,KAAK,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAA;IACzC,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"}
@@ -0,0 +1,134 @@
1
+ /**
2
+ * @kairos-es/codec — the schema-first codec seam, mapping typed domain events
3
+ * to and from the store's opaque wire bytes, with an application-owned
4
+ * encryption seam (ADR-0004).
5
+ *
6
+ * Two entry points:
7
+ * - The *typed slice path* (primary): `event(type, fields, tagsFor)` defines a
8
+ * tagged `Schema` plus a required mapper deriving the event's tags from its
9
+ * payload (ADR-0006); `projection(init).on(def, fold)...build()` composes the
10
+ * specific defs a decision model consumes, inferring its state and requirement
11
+ * `R`; `modelAtHead(slices)` reads, decodes through each def's `Schema`, and folds
12
+ * purely, and `buildDecisionModel(slices)` adds the `AppendCondition` to it. A handler for an un-imported event is a compile error, and
13
+ * a mistyped type cannot silently match nothing.
14
+ * - The *general path* (minimal): `makeRegistry(defs)` decodes an arbitrary wire
15
+ * event by its runtime type string; an unknown type is a typed
16
+ * `UnknownEventType` (ADR-0005 decode-totality). The payload stays `unknown`
17
+ * on this path: the type is discovered at runtime, so no def was named to
18
+ * narrow it (see `registry.ts`).
19
+ *
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`).
23
+ * Serialisation is a pluggable `Serializer` (JSON
24
+ * default). Encryption, when needed, is an *application*-authored `Schema`
25
+ * transform on a payload field; the codec only propagates its requirement and
26
+ * error channels — it owns no cipher and imports no `node:crypto`.
27
+ *
28
+ * `Decider` + `runDecider(decider, command)` close that path into the write
29
+ * side's whole command cycle: build the decision model, decide, append under the
30
+ * read's own condition. They live HERE, beside the `buildDecisionModel` they
31
+ * drive, rather than in `@kairos-es/testing` where they began — that package
32
+ * peers `@effect/vitest`, and a production command handler must not name it (see
33
+ * `decider.ts` for the full argument). `decider(projections, decide)` builds one
34
+ * with all four type arguments inferred, so a command's slice record and its
35
+ * error union are written once, in the two function bodies, rather than restated
36
+ * as an annotation above them.
37
+ *
38
+ * A decision may also answer `Unchanged` — "already done, nothing to append" —
39
+ * built with `Decision.unchanged(reason?)` and recognised ONLY by
40
+ * `isUnchanged`, by its `UnchangedTypeId`. It is a success, not an error and not
41
+ * an empty batch: every runner returns it in place of a `Position` and appends
42
+ * nothing. `Decider`'s fifth type parameter carries the widening and defaults to
43
+ * the non-empty batch, and each runner's result is `DecisionResult<O>`, so a
44
+ * decider or body that cannot answer `Unchanged` keeps a plain `Position` — an
45
+ * untyped one and one generic in its success type included — and its caller is
46
+ * never asked to handle it. `decision.ts` owns the argument, and the one type
47
+ * query that does read differently.
48
+ *
49
+ * `transact(precondition)(body)` is the layered write path, for a decision whose
50
+ * second query cannot be written until an earlier read has been folded. It opens
51
+ * an ambient read ledger, evaluates the precondition inside it, hands that
52
+ * precondition's output to the body as a service, and appends what the body
53
+ * returned under the union of every query either of them read, at the earliest
54
+ * head observed. No condition reaches the caller. `unsafeAppend(events, reason)`
55
+ * is the one sanctioned way out. `precondition.ts` owns the argument for the
56
+ * bracket and everything on it, and `readLedger.ts` for the ledger the seam rests
57
+ * on — including why the derivation itself is deliberately not on this surface.
58
+ *
59
+ * ## ⚠ `runDecider` and `decideOnly` are TWO DISPOSITIONS OF ONE CYCLE
60
+ *
61
+ * They are read as a pair or not at all, because each refuses exactly where the
62
+ * other is the answer:
63
+ *
64
+ * | | outside a bracket | inside a bracket |
65
+ * | ------------ | ---------------------------------------- | ----------------------------------- |
66
+ * | `runDecider` | ✅ the ordinary single-hop case | ⛔ a DEFECT, refused at the store |
67
+ * | `decideOnly` | ⛔ unrunnable, and `tsc` says so | ✅ the layered case |
68
+ *
69
+ * Both carry `Unchanged` the same way, which is what keeps them one pair:
70
+ * `runDecider` returns it having appended nothing, and `decideOnly` passes it to
71
+ * the body unaltered, where `transact` returns it having appended nothing —
72
+ * and, having no write to guard, without refusing an empty bracket as
73
+ * `NoReadsRecorded`.
74
+ *
75
+ * ⚠ *Unrunnable* rather than *unwritable*, and the difference is measured. A bare
76
+ * `decideOnly(d, cmd)` outside a bracket COMPILES, and so does an exported helper
77
+ * over it: its `ReadLedger` requirement simply propagates, as any unmet requirement
78
+ * does. The refusal lands where the context is DISCHARGED — an `Effect.provide`, a
79
+ * `runPromise`, an annotation asking for `R = never` — which may be another file.
80
+ * So `pnpm check` catches it without anything running, which is the whole gain over
81
+ * the runtime defect this replaced; what it does not do is underline the call.
82
+ *
83
+ * Neither is expressed over the other, and that is forced rather than chosen: a
84
+ * definition of `runDecider` as *"`decideOnly`, then append"* would have to call
85
+ * `decideOnly` outside a bracket, which does not typecheck, and the append it then
86
+ * made inside one would be refused. The two refusals are the same fact seen from
87
+ * either side — an append inside a bracket belongs to the bracket — so any
88
+ * expression of one over the other has to defeat one of them.
89
+ *
90
+ * ⚠ The two refusals arrive at DIFFERENT MOMENTS, which is the part a caller acts
91
+ * on. `runDecider` inside a bracket is refused when it RUNS, at core's shared append
92
+ * decorator — so the same refusal also meets a raw `store.append` and a runner one
93
+ * call away in a helper. `decideOnly` outside a bracket is refused by `tsc`, at the
94
+ * discharge point above, by naming `ReadLedger` in its requirement channel. Why each
95
+ * was given the instrument it was is a DECISION and belongs to the record that took
96
+ * it: ADR-0014's `Status`. A lint rule reaches the first case earlier still, while it
97
+ * is being written; `biome-plugins/README.md` in the source repository owns what a
98
+ * rule there can and cannot see.
99
+ *
100
+ * The remedy is one identifier away in each direction, which is the whole reason a
101
+ * refusal is affordable at all. `decider.ts` and `precondition.ts` carry the two
102
+ * halves.
103
+ *
104
+ * `modelAtHead(slices)` is that same read-decode-fold WITHOUT the
105
+ * `AppendCondition`: `{ state, head }`, one slice record materialised at read
106
+ * time with no table, no checkpoint and no runner, current as of the `head` its
107
+ * read returned — which is a stronger currency claim than a maintained view's
108
+ * checkpoint can make. `buildDecisionModel` is expressed over it, so the write
109
+ * path's decision model and a read-time materialisation share one body rather
110
+ * than two that could drift (see `decisionModel.ts` for both arguments).
111
+ *
112
+ * `decodeSlices(slices)` is the decode stage `modelAtHead` folds through,
113
+ * exported because the read side needs the *same* decode over a live
114
+ * `subscribe` stream. Exposing it keeps that single implementation shared
115
+ * rather than reimplemented downstream. It is deliberately ONE symbol taking
116
+ * the slice record, not a merge-then-decode pair: the pair had exactly one
117
+ * legal composition, and its only degree of freedom was to decode against a
118
+ * record other than the one the query came from (see `decode.ts`).
119
+ */
120
+ export { type Decider, decider, runDecider } from './decider.js';
121
+ export { Decision, type DecisionOutput, type DecisionResult, isUnchanged, Unchanged, UnchangedTypeId, } from './decision.js';
122
+ export { buildDecisionModel, modelAtHead, type RequirementOf, } from './decisionModel.js';
123
+ export { decodeSlices } from './decode.js';
124
+ export { CodecError, UnknownEventType } from './errors.js';
125
+ export { type EventDef, type EventDefAny, event, type TagsFor } from './event.js';
126
+ export { MetadataSource } from './MetadataSource.js';
127
+ export { decideOnly, type Precondition, transact, unsafeAppend, } from './precondition.js';
128
+ export { type ProjectionBuilder, projection, type SliceProjection, } from './projection.js';
129
+ export { type CloseHop, type Hop, NoReadsRecorded, ReadLedger, TaglessSliceInLedger, } from './readLedger.js';
130
+ export { makeRegistry, type Registry } from './registry.js';
131
+ export { Serializer, SerializerDefault } from './Serializer.js';
132
+ export { toEvent } from './toEvent.js';
133
+ export { Uuid, UuidLive, UuidTest } from './Uuid.js';
134
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsHG;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,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAA;AACjD,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"}
@@ -0,0 +1,3 @@
1
+ {
2
+ "type": "module"
3
+ }
@@ -0,0 +1,244 @@
1
+ /**
2
+ * The bracket that owns the append, the precondition value it takes, and the two
3
+ * functions that make a decision inside it writable.
4
+ *
5
+ * ## What the bracket is for, and why no condition reaches the caller
6
+ *
7
+ * `transact(precondition)(body)` evaluates a guard, hands its output to the body
8
+ * as a service, and appends whatever the body produced under the union of every
9
+ * read EITHER of them performed. A caller writes no condition, no union, no floor
10
+ * and no `min`.
11
+ *
12
+ * That is not an ergonomic preference. Owning the append does not make a wrong
13
+ * condition a type error — it makes one UNWRITABLE, which is the stronger
14
+ * guarantee: a returned condition is a condition a caller can decline to use, and
15
+ * a decision that looks guarded and is not is the exact failure this seam exists
16
+ * to close. It is what Axon Framework 5 does, where the condition is married to
17
+ * the events at prepare-commit and the caller never holds one.
18
+ *
19
+ * One thing is deliberately NOT borrowed from that prior art. There, an append
20
+ * with no prior read leaves the condition unset and the append unguarded, so the
21
+ * method name reads as unconditional while the behaviour is not. `NoReadsRecorded`
22
+ * closes that door here and must not be lost.
23
+ *
24
+ * ⚠ The name says *declare, then do*. It is NOT a database transaction: an append
25
+ * under a condition is not two-phase, and nothing rolls back.
26
+ *
27
+ * ## A body may answer `Unchanged`, and then there is nothing to guard
28
+ *
29
+ * The body's success is a `DecisionOutput`: a non-empty batch, or `Unchanged` —
30
+ * "already done". On `Unchanged` the bracket appends nothing, derives no
31
+ * condition and returns the `Unchanged`, and the result type is
32
+ * `DecisionResult<O>`, so a body that cannot answer it keeps a plain `Position`.
33
+ * `decideOnly` passes a decider's `Unchanged` through to the body unaltered.
34
+ *
35
+ * It is NOT refused as `NoReadsRecorded`, even from a bracket that read nothing,
36
+ * and that is not a hole in the refusal. `NoReadsRecorded` exists because an
37
+ * append with no prior read would go out UNGUARDED while looking guarded. An
38
+ * `Unchanged` appends nothing, so there is no write for a condition to guard and
39
+ * nothing for the refusal to protect. The same empty bracket returning EVENTS is
40
+ * still refused, exactly as before. `decision.ts` owns what `Unchanged` is.
41
+ *
42
+ * ## Order is the design, and it is the one line here that must not move
43
+ *
44
+ * The precondition is evaluated INSIDE the bracket and never before it. Evaluated
45
+ * before, its reads are not part of what it protects — the ledger does not exist
46
+ * yet, so its hops are lost and the write is guarded only by the business read.
47
+ * The check would look exactly as it does now and would guard nothing.
48
+ *
49
+ * ## Why the precondition's output crosses as a SERVICE
50
+ *
51
+ * A body that needs authorisation names the tag in its requirement channel, so
52
+ * `transact` must provide it, so the body CANNOT BE RUN without a precondition.
53
+ * That is a PRESENCE check, and presence is the one direction the type system can
54
+ * decide here — `Exclude<R, Id>` in the return type is where it is spent. The
55
+ * alternative, a curried parameter, buys nothing equivalent: a body is free to
56
+ * ignore a parameter.
57
+ *
58
+ * ## Composition is a combinator, not a list
59
+ *
60
+ * `transact` takes exactly ONE precondition. A variadic argument asserts
61
+ * INDEPENDENCE, and the interesting case is DEPENDENT — *may act for this
62
+ * organisation*, then *may sign in that organisation* — which a flat list cannot
63
+ * express and which is the very shape this change exists for. A dependent pair
64
+ * needs nothing anyway: a precondition is an ordinary `Effect` evaluated inside
65
+ * the open ledger, so two compose with `Effect.gen` and both sets of reads land in
66
+ * the same condition. An INDEPENDENT set wants a combinator over a RECORD,
67
+ * evaluated SEQUENTIALLY — fail-fast is the point, and concurrent reads are
68
+ * precisely the case where the earliest-head reduction stops being redundant. That
69
+ * combinator is purely additive and is not shipped here.
70
+ *
71
+ * ## Conflict retry goes OUTSIDE the bracket, never inside
72
+ *
73
+ * `retryOnConflict(transact(p)(body))`, never a retry within the body. The bracket
74
+ * takes an opaque `Effect`, so no type can express that rule and the lint gate is
75
+ * what enforces it: in the source repository, `biome-plugins/noRetryInBracket.grit`
76
+ * owns why and `test/retryInBracket.test.ts` executes it.
77
+ *
78
+ * ## ⚠⚠ THE ONE MISTAKE NOTHING CATCHES: a view read inside a bracket
79
+ *
80
+ * **A projection read, a raw SQL read, or any read that does not fold through
81
+ * `modelAtHead`, performed inside a bracket, contributes NO HOP.** Its query is
82
+ * absent from the derived union and its head is absent from the floor, so the
83
+ * decision rests on data the condition does not guard — and the bracket looks
84
+ * guarded, which is the exact failure this seam exists to close, wearing the fix's
85
+ * own name.
86
+ *
87
+ * **Nothing will tell you — not here and not in your own tree.** Not the type
88
+ * system: a requirement channel is ADDITIVE, so it can demand a capability's
89
+ * presence and never its absence. Not `NoReadsRecorded`: that fires on an EMPTY
90
+ * bracket and never on an under-populated one, so a bracket holding one genuine fold
91
+ * beside a view read passes it. And not a lint rule — one was written for exactly
92
+ * this, kept for a while and deleted, because a syntactic gate has no call graph and
93
+ * no types, so it could only name a receiver by convention, and because a lint
94
+ * plugin runs only where its own config is and so never travels with the library.
95
+ * `biome-plugins/README.md` in the source repository records the test that rule
96
+ * failed.
97
+ *
98
+ * So this is a rule a reader keeps, and knowing it is the guarantee: **inside a
99
+ * bracket, read through `modelAtHead`. If a read must
100
+ * consult a materialised view, do it OUTSIDE the bracket** — where it is ordinary
101
+ * code, because its answer is then not part of what the append is guarded on.
102
+ * ADR-0014's *decision read* rule is the test for which one a given read is.
103
+ */
104
+ import type { AppendConditionFailed, DcbEvent, Position } from '@kairos-es/core';
105
+ import { AppendIsOwned, DcbEventStore } from '@kairos-es/core';
106
+ import type { Array as Arr } from 'effect';
107
+ import { Context, Effect } from 'effect';
108
+ import type { Decider } from './decider.js';
109
+ import { type DecisionOutput, type DecisionResult } from './decision.js';
110
+ import { type RequirementOf } from './decisionModel.js';
111
+ import type { CodecError } from './errors.js';
112
+ import type { SliceProjection } from './projection.js';
113
+ import { NoReadsRecorded, ReadLedger, type TaglessSliceInLedger } from './readLedger.js';
114
+ import type { Serializer } from './Serializer.js';
115
+ import type { Uuid } from './Uuid.js';
116
+ /**
117
+ * A named, reusable guard plus the tag its output is handed across.
118
+ *
119
+ * A VALUE and not a function of the command, deliberately: the point of the seam
120
+ * is that a guard is written once, centrally, and referenced by however many
121
+ * commands need it. A command supplies its own arguments when it BUILDS the value
122
+ * (`mayActFor(userId, orgId, verb)`), and what `transact` receives is the built
123
+ * thing.
124
+ *
125
+ * `Service` is fully generic and the library never reads it. Every consumer-side
126
+ * authorisation shape — a plain resolved identifier, a branded proof, a gate with
127
+ * a private minting closure — is application code this genericity accommodates
128
+ * without the library knowing anything about it. That is a requirement rather than
129
+ * an incidental property: hard-coding it would foreclose a consumer's later
130
+ * choice, and it costs nothing to keep.
131
+ *
132
+ * ⚠ `evaluate` is an `Effect`, so it is a FIELD OF AN OBJECT LITERAL and its
133
+ * expression is evaluated when the `Precondition` VALUE is built — before the
134
+ * bracket opens and before any cheap check inside it runs. Anything a builder does
135
+ * eagerly there (parsing an argument, resolving an identifier) happens outside the
136
+ * ordering guarantee above, and a throw there is an uncatchable defect on the very
137
+ * path meant to deny the input cheaply. Wrap such work in `Effect.suspend`.
138
+ */
139
+ export interface Precondition<Id, Service, E, R> {
140
+ readonly tag: Context.Key<Id, Service>;
141
+ readonly evaluate: Effect.Effect<Service, E, R>;
142
+ }
143
+ /**
144
+ * The DCB command cycle MINUS the append: read the decision model, decode, fold,
145
+ * decide, and RETURN the events for the bracket to append — or the decider's
146
+ * `Unchanged`, passed through as it came, for the bracket to append nothing.
147
+ *
148
+ * Bracket-only, and the refusal is STATIC — see the section below for the mechanism
149
+ * and, more importantly, for where the error actually lands, which is not where a
150
+ * reader first expects. Forced through anyway, being outside a bracket is a DEFECT
151
+ * rather than an error-channel value: it is not a data-dependent outcome a caller
152
+ * could handle, and the events produced would be dropped with nothing to append them.
153
+ *
154
+ * It is also what makes the nesting refusal liveable. A command runner inside a
155
+ * bracket is refused at the store, and this is the function a builder reaches for
156
+ * instead — one identifier away. A refusal is only affordable when the fix is that
157
+ * close.
158
+ *
159
+ * `modelAtHead` and NOT `buildDecisionModel`: this function must not derive a
160
+ * condition at all. The bracket owns that, and owning it in ONE place is what
161
+ * stops a caller appending under the last hop's condition alone.
162
+ *
163
+ * ## Bracket-only is a REQUIREMENT, so `tsc` refuses it rather than a run
164
+ *
165
+ * `ReadLedger` is named in the requirement channel and `transact` is the only thing
166
+ * that provides it, so a program that calls this outside a bracket cannot be RUN,
167
+ * and `tsc` is what says so — nothing has to execute for a caller to be told.
168
+ *
169
+ * ⚠ Measured, because the obvious stronger reading is wrong: the call site itself
170
+ * COMPILES. A bare `decideOnly(d, cmd)` outside a bracket is fine, and so is an
171
+ * exported helper over it; the requirement just propagates, as any unmet requirement
172
+ * does. What fails is the DISCHARGE — `Effect.provide`, `runPromise`, an annotation
173
+ * asking for `R = never` — possibly in another file. That is still a static refusal
174
+ * and it is the whole gain over the runtime defect this replaced. It is not a
175
+ * squiggle under the mistake.
176
+ *
177
+ * That is a PRESENCE check, and presence is the direction the type system can
178
+ * decide here — the same direction `transact`'s `Exclude<R, Id>` already spends on
179
+ * the precondition's output. ADR-0014's 2026-08-29 amendment carries the rule that
180
+ * follows: look for a presence check before reaching for a defect. This function
181
+ * shipped a defect first, and the check was available all along.
182
+ *
183
+ * ⚠ Two lines carry it and their JOBS differ. The RETURN ANNOTATION is the surface a
184
+ * consumer is judged against, and what the discharge above meets. The
185
+ * `yield* ReadLedger` keeps the requirement in the INFERRED type, so it survives the
186
+ * annotation being edited, and it dies at run time for the audience no type reaches
187
+ * — a JavaScript consumer runs no compile step — rather than handing back events
188
+ * with no bracket to append them. Nothing here READS the ledger; `modelAtHead` does
189
+ * the recording, ambiently, through `Effect.serviceOption`.
190
+ *
191
+ * ⚠ Do not try to write down which check catches which deletion. It was tried twice
192
+ * and was wrong twice, and `test/appendOwnership.test.ts` in the source repository
193
+ * says why the second failure means there should be no third attempt. Plant the
194
+ * mutation instead.
195
+ */
196
+ export declare const decideOnly: <P extends Record<string, SliceProjection>, Command, E, R, O extends DecisionOutput = Arr.NonEmptyReadonlyArray<DcbEvent>>(decider: Decider<P, Command, E, R, O>, command: Command) => Effect.Effect<O, E | CodecError | TaglessSliceInLedger, ReadLedger | DcbEventStore | Serializer | Uuid | R | RequirementOf<P>>;
197
+ /**
198
+ * The sanctioned exception to the nesting refusal: append these events under NO
199
+ * condition, and say in the identifier and in a required `reason` why that is
200
+ * deliberate.
201
+ *
202
+ * It covers three cases and no others: deliberate narrowing, a genuinely
203
+ * unconditional creation, and a second append inside one bracket. The `reason` is
204
+ * ceremony on purpose, and ADR-0014 decision 6 owns why a hatch has to cost more
205
+ * than the safe path — that argument is a DECISION and belongs to the record that
206
+ * took it, so it is not restated here. How biome turns a required argument into a
207
+ * review signal is the source repository's `biome-plugins/unsafeAppendReported.grit`.
208
+ *
209
+ * ⚠ It BYPASSES the derived condition rather than composing with it. A caller who
210
+ * genuinely needs *the union, minus one event type* must restate the whole
211
+ * condition here. If that turns up in practice, a composing override is the
212
+ * additive answer and this hatch is not it.
213
+ *
214
+ * `Effect.updateContext` over
215
+ * `Context.omit(AppendIsOwned)` is what lets it through the store's ownership
216
+ * refusal — the one route, and `AppendIsOwned.ts` owns why removing the marker is
217
+ * the shape rather than a second tag or a sanctioning flag.
218
+ */
219
+ export declare const unsafeAppend: (events: Arr.NonEmptyReadonlyArray<DcbEvent>, reason: string) => Effect.Effect<Position, AppendConditionFailed, DcbEventStore>;
220
+ /**
221
+ * Open a bracket over ONE decision: evaluate the precondition inside it, hand its
222
+ * output to the body under the precondition's tag, and append what the body
223
+ * produced under the union of every read either of them performed.
224
+ *
225
+ * Curried so the precondition reads as the thing the command DECLARES and the body
226
+ * reads as the thing it DOES.
227
+ *
228
+ * The ownership marker is provided ALONGSIDE the ledger and to the body only, so
229
+ * any append inside the body — a nested bracket, a command runner, a raw
230
+ * `store.append` — is refused at the store as a defect, while the bracket's own
231
+ * closing append below runs outside that scope and is unaffected. That is why the
232
+ * refusal is on APPENDING rather than on opening a bracket: a command runner
233
+ * appends without opening one, and its events usually match the union the bracket
234
+ * is about to guard on, so the bracket's own close would be refused by a condition
235
+ * its own body invalidated.
236
+ *
237
+ * The body may succeed with `Unchanged` (see the module documentation), and the
238
+ * result is then `DecisionResult<O>`: `Position` for a body that cannot answer
239
+ * it, `Position | Unchanged` for one that can. `O` is last and defaulted, so an
240
+ * explicit `<E, R>` written against the narrower signature still means what it
241
+ * did.
242
+ */
243
+ export declare const transact: <Id, Service, PE, PR>(precondition: Precondition<Id, Service, PE, PR>) => <E, R, O extends DecisionOutput = Arr.NonEmptyReadonlyArray<DcbEvent>>(body: Effect.Effect<O, E, R>) => Effect.Effect<DecisionResult<O>, E | PE | NoReadsRecorded | AppendConditionFailed, Exclude<Exclude<R, Id>, ReadLedger | AppendIsOwned> | Exclude<PR, ReadLedger | AppendIsOwned> | DcbEventStore>;
244
+ //# sourceMappingURL=precondition.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"precondition.d.ts","sourceRoot":"","sources":["../../src/precondition.ts"],"names":[],"mappings":"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"}