@kairos-es/codec 1.0.0-alpha.3 → 1.0.0-alpha.4
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 +26 -1
- package/dist/cjs/MetadataSource.js +10 -0
- package/dist/cjs/MetadataSource.js.map +1 -0
- package/dist/cjs/index.js +7 -0
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/readLedger.js +17 -2
- package/dist/cjs/readLedger.js.map +1 -1
- package/dist/cjs/toEvent.js +57 -3
- package/dist/cjs/toEvent.js.map +1 -1
- package/dist/dts/MetadataSource.d.ts +8 -0
- package/dist/dts/MetadataSource.d.ts.map +1 -0
- package/dist/dts/index.d.ts +4 -2
- package/dist/dts/index.d.ts.map +1 -1
- package/dist/dts/readLedger.d.ts.map +1 -1
- package/dist/dts/toEvent.d.ts +13 -2
- package/dist/dts/toEvent.d.ts.map +1 -1
- package/dist/esm/MetadataSource.js +3 -0
- package/dist/esm/MetadataSource.js.map +1 -0
- package/dist/esm/index.js +4 -2
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/readLedger.js +18 -3
- package/dist/esm/readLedger.js.map +1 -1
- package/dist/esm/toEvent.js +57 -4
- package/dist/esm/toEvent.js.map +1 -1
- package/package.json +2 -2
- package/src/MetadataSource.ts +43 -0
- package/src/index.ts +4 -2
- package/src/readLedger.ts +21 -5
- package/src/toEvent.ts +51 -4
package/README.md
CHANGED
|
@@ -18,7 +18,32 @@ decision model or a read model folds through; `modelAtHead(slices)` reads, decod
|
|
|
18
18
|
through each def's `Schema`, and folds purely, returning `{ state, head }`;
|
|
19
19
|
`buildDecisionModel(slices)` is that same read plus the `AppendCondition` a write
|
|
20
20
|
needs; and `toEvent` produces a wire event (encode, serialise, tags derived, clock
|
|
21
|
-
and uuid from services).
|
|
21
|
+
and uuid from services, and a metadata bag).
|
|
22
|
+
|
|
23
|
+
Every event carries `metadata`, a flat bag of strings (core's `Metadata`) of context about the
|
|
24
|
+
recording — a `traceparent`, a correlation id — never anything the business asserts.
|
|
25
|
+
Pass it per call as `toEvent(def, payload, { metadata })`, or provide it once at the
|
|
26
|
+
edge of a request with `Effect.provideService(MetadataSource, bag)`; a per-call bag
|
|
27
|
+
replaces the ambient one, and with neither the event carries `{}`. A bag core's
|
|
28
|
+
`Metadata` schema refuses, such as one holding a value that is not a string, fails
|
|
29
|
+
`toEvent` with a `CodecError` (`reason: 'metadata'`). Metadata is cleartext, never
|
|
30
|
+
queryable and never seen by `tagsFor` or by a builder fold (`.on(def, fold)`). A
|
|
31
|
+
read model's `apply` sees it on each decoded event as `decoded.sequenced.event.metadata`.
|
|
32
|
+
|
|
33
|
+
To read metadata in a FOLD, leave the builder and write the `SliceProjection` by
|
|
34
|
+
hand: an `initialState`, a `query`, a `schemas` map from each event type it folds to
|
|
35
|
+
that def's schema, and an erased `evolve(state, decoded)` that reads
|
|
36
|
+
`decoded.sequenced.event.metadata`. It goes everywhere a built slice goes. Three
|
|
37
|
+
things the builder does for you, a hand-written slice must do itself:
|
|
38
|
+
|
|
39
|
+
- **Keep the def's requirement in the schema cast** —
|
|
40
|
+
`def.schema as unknown as Schema.Schema<unknown, unknown, R>` with the def's own
|
|
41
|
+
`R`. Cast a def that needs a service to `never` instead, and the slice compiles
|
|
42
|
+
without that service and dies at run time.
|
|
43
|
+
- **Check `decoded.sequenced.event.type` in `evolve`.** It is handed every event its
|
|
44
|
+
query matches, decoded through the schemas of every slice in the record, so an
|
|
45
|
+
event your `schemas` never named can still arrive, decoded by a neighbour's.
|
|
46
|
+
- **Cast `state` back to its own type**, since `evolve` is erased.
|
|
22
47
|
|
|
23
48
|
`modelAtHead` is the read side's third materialisation KIND — checkpoint-free and
|
|
24
49
|
read-time, no table, no runner, nothing maintained — and `head` is its consistency
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.MetadataSource = void 0;
|
|
7
|
+
var _effect = require("effect");
|
|
8
|
+
class MetadataSource extends /*#__PURE__*/_effect.Context.Tag('@kairos-es/codec/MetadataSource')() {}
|
|
9
|
+
exports.MetadataSource = MetadataSource;
|
|
10
|
+
//# sourceMappingURL=MetadataSource.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MetadataSource.js","names":["_effect","require","MetadataSource","Context","Tag","exports"],"sources":["../../src/MetadataSource.ts"],"sourcesContent":[null],"mappings":";;;;;;AAsCA,IAAAA,OAAA,GAAAC,OAAA;AAEM,MAAOC,cAAe,sBAAQC,eAAO,CAACC,GAAG,CAC7C,iCAAiC,CAClC,EAA4B;AAAAC,OAAA,CAAAH,cAAA,GAAAA,cAAA","ignoreList":[]}
|
package/dist/cjs/index.js
CHANGED
|
@@ -15,6 +15,12 @@ Object.defineProperty(exports, "Decision", {
|
|
|
15
15
|
return _decision.Decision;
|
|
16
16
|
}
|
|
17
17
|
});
|
|
18
|
+
Object.defineProperty(exports, "MetadataSource", {
|
|
19
|
+
enumerable: true,
|
|
20
|
+
get: function () {
|
|
21
|
+
return _MetadataSource.MetadataSource;
|
|
22
|
+
}
|
|
23
|
+
});
|
|
18
24
|
Object.defineProperty(exports, "NoReadsRecorded", {
|
|
19
25
|
enumerable: true,
|
|
20
26
|
get: function () {
|
|
@@ -165,6 +171,7 @@ var _decisionModel = require("./decisionModel.js");
|
|
|
165
171
|
var _decode = require("./decode.js");
|
|
166
172
|
var _errors = require("./errors.js");
|
|
167
173
|
var _event = require("./event.js");
|
|
174
|
+
var _MetadataSource = require("./MetadataSource.js");
|
|
168
175
|
var _precondition = require("./precondition.js");
|
|
169
176
|
var _projection = require("./projection.js");
|
|
170
177
|
var _readLedger = require("./readLedger.js");
|
package/dist/cjs/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["_decider","require","_decision","_decisionModel","_decode","_errors","_event","_precondition","_projection","_readLedger","_registry","_Serializer","_toEvent","_Uuid"],"sources":["../../src/index.ts"],"sourcesContent":[null],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","names":["_decider","require","_decision","_decisionModel","_decode","_errors","_event","_MetadataSource","_precondition","_projection","_readLedger","_registry","_Serializer","_toEvent","_Uuid"],"sources":["../../src/index.ts"],"sourcesContent":[null],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwHA,IAAAA,QAAA,GAAAC,OAAA;AACA,IAAAC,SAAA,GAAAD,OAAA;AAQA,IAAAE,cAAA,GAAAF,OAAA;AAKA,IAAAG,OAAA,GAAAH,OAAA;AACA,IAAAI,OAAA,GAAAJ,OAAA;AACA,IAAAK,MAAA,GAAAL,OAAA;AACA,IAAAM,eAAA,GAAAN,OAAA;AACA,IAAAO,aAAA,GAAAP,OAAA;AAMA,IAAAQ,WAAA,GAAAR,OAAA;AAKA,IAAAS,WAAA,GAAAT,OAAA;AAOA,IAAAU,SAAA,GAAAV,OAAA;AACA,IAAAW,WAAA,GAAAX,OAAA;AACA,IAAAY,QAAA,GAAAZ,OAAA;AACA,IAAAa,KAAA,GAAAb,OAAA","ignoreList":[]}
|
package/dist/cjs/readLedger.js
CHANGED
|
@@ -174,8 +174,23 @@ const recordQuery = query => withLedger(NO_CLOSE, ledger => ledger.record(query)
|
|
|
174
174
|
/** The tagless slice keys in a record, in declaration order. */
|
|
175
175
|
exports.recordQuery = recordQuery;
|
|
176
176
|
const taglessKeys = projections => Object.entries(projections).filter(([, projection]) => projection.query.items.some(item => item.tags.length === 0)).map(([key]) => key);
|
|
177
|
-
/**
|
|
178
|
-
|
|
177
|
+
/**
|
|
178
|
+
* Would recording this record's query leave the accumulated union unservable in
|
|
179
|
+
* its SHAPE — the one fault a slice's tags can fix, and so the one this ledger
|
|
180
|
+
* reports?
|
|
181
|
+
*
|
|
182
|
+
* An unknown key anywhere in the union answers no. It is not a tagless slice and no
|
|
183
|
+
* slice's tags would fix it, and the store refuses it as a defect naming the key at
|
|
184
|
+
* the read of the hop that carries it, so an earlier hop's key has already been
|
|
185
|
+
* refused once by the time a later record is judged. Judged here it would surface
|
|
186
|
+
* as this ledger's error, on the error channel, naming a slice with nothing wrong
|
|
187
|
+
* with it — and a hop is recorded before its read runs, so an earlier hop's key
|
|
188
|
+
* would go on doing that to every clean read after it.
|
|
189
|
+
*/
|
|
190
|
+
const unionWouldBeUnservable = (existing, incoming) => {
|
|
191
|
+
const union = (0, _core.unionQueries)([...existing.map(h => h.query), incoming]);
|
|
192
|
+
return (0, _core.unknownQueryKeyFault)(union) === undefined && (0, _core.servableQueryFault)(union) !== undefined;
|
|
193
|
+
};
|
|
179
194
|
/**
|
|
180
195
|
* Refuse a read that would leave the bracket's accumulated union unservable,
|
|
181
196
|
* BEFORE the read is paid for.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"readLedger.js","names":["_core","require","_effect","ReadLedger","Context","Tag","exports","NoReadsRecorded","Data","TaggedError","message","TaglessSliceInLedger","sliceKey","deriveLayeredCondition","hops","length","undefined","merged","unionQueries","map","hop","query","arrived","flatMap","head","after","reduce","a","b","ORIGIN","failIfEventsMatch","makeReadLedger","Effect","gen","ref","Ref","make","layer","Layer","succeed","record","modify","update","current","index","get","withLedger","absent","f","serviceOption","ledger","Option","isNone","value","NO_CLOSE","void","recordQuery","taglessKeys","projections","Object","entries","filter","projection","items","some","item","tags","key","unionWouldBeUnservable","existing","incoming","
|
|
1
|
+
{"version":3,"file":"readLedger.js","names":["_core","require","_effect","ReadLedger","Context","Tag","exports","NoReadsRecorded","Data","TaggedError","message","TaglessSliceInLedger","sliceKey","deriveLayeredCondition","hops","length","undefined","merged","unionQueries","map","hop","query","arrived","flatMap","head","after","reduce","a","b","ORIGIN","failIfEventsMatch","makeReadLedger","Effect","gen","ref","Ref","make","layer","Layer","succeed","record","modify","update","current","index","get","withLedger","absent","f","serviceOption","ledger","Option","isNone","value","NO_CLOSE","void","recordQuery","taglessKeys","projections","Object","entries","filter","projection","items","some","item","tags","key","unionWouldBeUnservable","existing","incoming","union","h","unknownQueryKeyFault","servableQueryFault","assertServableAddition","composeProjections","offending","ambientLayeredCondition","none","fromNullable"],"sources":["../../src/readLedger.ts"],"sourcesContent":[null],"mappings":"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","ignoreList":[]}
|
package/dist/cjs/toEvent.js
CHANGED
|
@@ -4,10 +4,33 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
6
|
exports.toEvent = void 0;
|
|
7
|
+
var _core = require("@kairos-es/core");
|
|
7
8
|
var _effect = require("effect");
|
|
8
9
|
var _errors = require("./errors.js");
|
|
10
|
+
var _MetadataSource = require("./MetadataSource.js");
|
|
9
11
|
var _Serializer = require("./Serializer.js");
|
|
10
12
|
var _Uuid = require("./Uuid.js");
|
|
13
|
+
/**
|
|
14
|
+
* Event production (codec edge-helper): turn a typed payload into a wire
|
|
15
|
+
* `DcbEvent`. Encode is `Schema.encode(def.schema)` (a `ParseError` becomes a
|
|
16
|
+
* `CodecError`; any encryption transform runs here, *before* serialisation)
|
|
17
|
+
* then `serialiser.serialise`. The wire `tags` are derived from the def's
|
|
18
|
+
* required `tagsFor` mapper (ADR-0006) — they are never hand-passed, so two
|
|
19
|
+
* command sites emitting the same event type cannot tag it differently. A mapper
|
|
20
|
+
* that throws (e.g. an ill-formed `key:value` rejected by `Tag.make`) is
|
|
21
|
+
* surfaced on the `CodecError` channel, not as a defect. `uuid`
|
|
22
|
+
* comes from `options.uuid` or the `Uuid` service; `occurredAt` from
|
|
23
|
+
* `options.occurredAt` or the Effect clock (`DateTime.now`), keeping `core`
|
|
24
|
+
* clock- and RNG-free while staying overridable for backdated imports.
|
|
25
|
+
* `metadata` comes from `options.metadata` or the OPTIONAL ambient
|
|
26
|
+
* `MetadataSource` service, and is `{}` when neither supplies it (ADR-0017). A
|
|
27
|
+
* bag core's `Metadata` schema refuses, such as one holding a value that is not a
|
|
28
|
+
* string, fails on the `CodecError` channel (`reason: 'metadata'`), whichever of
|
|
29
|
+
* the two supplied it. It never reaches `tagsFor`, the
|
|
30
|
+
* event's schema or the serialiser, so it can enter neither the tag junction
|
|
31
|
+
* table nor the encryption seam.
|
|
32
|
+
*/
|
|
33
|
+
|
|
11
34
|
/**
|
|
12
35
|
* Encode `payload` under `def` and stamp it into a wire `DcbEvent`, deriving its
|
|
13
36
|
* `tags` from `def.tagsFor(payload)` (a throwing mapper surfaces as a
|
|
@@ -16,10 +39,40 @@ var _Uuid = require("./Uuid.js");
|
|
|
16
39
|
* wire-envelope fields (the query surface), so a `tagsFor` mapper may only key
|
|
17
40
|
* off cleartext payload fields (ADR-0006). `uuid` / `occurredAt` are
|
|
18
41
|
* exact-optional overrides; absent, they come from the `Uuid` service and the
|
|
19
|
-
* Effect clock.
|
|
42
|
+
* Effect clock. `metadata` is an exact-optional override too; absent, it comes
|
|
43
|
+
* from the ambient `MetadataSource` if one is provided and is `{}` otherwise.
|
|
44
|
+
* The ambient read adds nothing to the requirement channel. A bag core's
|
|
45
|
+
* `Metadata` schema refuses fails as a `CodecError` with `reason: 'metadata'`.
|
|
20
46
|
*/
|
|
21
47
|
const toEvent = (def, payload, options) => _effect.Effect.gen(function* () {
|
|
22
|
-
//
|
|
48
|
+
// Resolve metadata before anything else. Per-call wins, ambient fills,
|
|
49
|
+
// absent yields `{}`. `serviceOption` keeps `MetadataSource` out of the
|
|
50
|
+
// requirement channel.
|
|
51
|
+
//
|
|
52
|
+
// The bag is then CHECKED against core's `Metadata` schema, whichever arm
|
|
53
|
+
// supplied it. The type holds a typed caller to a bag of strings, but an
|
|
54
|
+
// `any` or untyped JavaScript gets past it, and the engines then disagree
|
|
55
|
+
// about what they stored: the cross-engine parity of this field rests on its
|
|
56
|
+
// values being strings, and this is the one place a value is refused. It is
|
|
57
|
+
// refused HERE, at write, and never on read: a read-side check would make an
|
|
58
|
+
// event the store had accepted unreadable for ever. A bad bag is the
|
|
59
|
+
// caller's mistake, so it fails on the `CodecError` channel exactly as a
|
|
60
|
+
// throwing `tagsFor` does, never as a defect. Checking consumes no sequence,
|
|
61
|
+
// so placing it above the tag derivation leaves the property below intact.
|
|
62
|
+
//
|
|
63
|
+
// The bag is COPIED, never stamped by reference: an ambient bag would
|
|
64
|
+
// otherwise be ONE object shared by every event of a request, and the
|
|
65
|
+
// caller's own object would be shared with the event it built, so a mutation
|
|
66
|
+
// of either before the append would rewrite the other. (After the append no
|
|
67
|
+
// store shares anything with its caller; that is each store's guarantee.)
|
|
68
|
+
const given = options?.metadata === undefined ? _effect.Option.getOrElse(yield* _effect.Effect.serviceOption(_MetadataSource.MetadataSource), () => ({})) : options.metadata;
|
|
69
|
+
const metadata = {
|
|
70
|
+
...(yield* _effect.Schema.decodeUnknown(_core.Metadata)(given).pipe(_effect.Effect.mapError(cause => new _errors.CodecError({
|
|
71
|
+
reason: 'metadata',
|
|
72
|
+
cause
|
|
73
|
+
}))))
|
|
74
|
+
};
|
|
75
|
+
// Derive tags next. A mapper is app code that can throw (e.g. `Tag.make`
|
|
23
76
|
// rejects an ill-formed `key:value`), and the codec's contract is that such
|
|
24
77
|
// failures are values on the `CodecError` channel, never defects (errors.ts).
|
|
25
78
|
// Deriving before the uuid/clock effects also means a bad tag fails without
|
|
@@ -44,7 +97,8 @@ const toEvent = (def, payload, options) => _effect.Effect.gen(function* () {
|
|
|
44
97
|
data,
|
|
45
98
|
tags,
|
|
46
99
|
uuid,
|
|
47
|
-
occurredAt
|
|
100
|
+
occurredAt,
|
|
101
|
+
metadata
|
|
48
102
|
};
|
|
49
103
|
});
|
|
50
104
|
exports.toEvent = toEvent;
|
package/dist/cjs/toEvent.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"toEvent.js","names":["
|
|
1
|
+
{"version":3,"file":"toEvent.js","names":["_core","require","_effect","_errors","_MetadataSource","_Serializer","_Uuid","toEvent","def","payload","options","Effect","gen","given","metadata","undefined","Option","getOrElse","serviceOption","MetadataSource","Schema","decodeUnknown","Metadata","pipe","mapError","cause","CodecError","reason","tags","try","tagsFor","catch","serializer","Serializer","encoded","encode","schema","data","serialise","uuid","Uuid","generate","occurredAt","DateTime","now","type","exports"],"sources":["../../src/toEvent.ts"],"sourcesContent":[null],"mappings":";;;;;;AAoBA,IAAAA,KAAA,GAAAC,OAAA;AACA,IAAAC,OAAA,GAAAD,OAAA;AACA,IAAAE,OAAA,GAAAF,OAAA;AAEA,IAAAG,eAAA,GAAAH,OAAA;AACA,IAAAI,WAAA,GAAAJ,OAAA;AACA,IAAAK,KAAA,GAAAL,OAAA;AA1BA;;;;;;;;;;;;;;;;;;;;;AA4BA;;;;;;;;;;;;;AAaO,MAAMM,OAAO,GAAGA,CACrBC,GAAsB,EACtBC,OAAU,EACVC,OAIC,KAEDC,cAAM,CAACC,GAAG,CAAC,aAAS;EAClB;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,MAAMC,KAAK,GACTH,OAAO,EAAEI,QAAQ,KAAKC,SAAS,GAC3BC,cAAM,CAACC,SAAS,CACd,OAAON,cAAM,CAACO,aAAa,CAACC,8BAAc,CAAC,EAC3C,OAAiB,EAAE,CAAC,CACrB,GACDT,OAAO,CAACI,QAAQ;EACtB,MAAMA,QAAQ,GAAa;IACzB,IAAI,OAAOM,cAAM,CAACC,aAAa,CAACC,cAAQ,CAAC,CAACT,KAAK,CAAC,CAACU,IAAI,CACnDZ,cAAM,CAACa,QAAQ,CACZC,KAAK,IAAK,IAAIC,kBAAU,CAAC;MAAEC,MAAM,EAAE,UAAU;MAAEF;IAAK,CAAE,CAAC,CACzD,CACF;GACF;EACD;EACA;EACA;EACA;EACA;EACA,MAAMG,IAAI,GAAG,OAAOjB,cAAM,CAACkB,GAAG,CAAC;IAC7BA,GAAG,EAAEA,CAAA,KAAMrB,GAAG,CAACsB,OAAO,CAACrB,OAAO,CAAC;IAC/BsB,KAAK,EAAGN,KAAK,IAAK,IAAIC,kBAAU,CAAC;MAAEC,MAAM,EAAE,MAAM;MAAEF;IAAK,CAAE;GAC3D,CAAC;EACF,MAAMO,UAAU,GAAG,OAAOC,sBAAU;EACpC,MAAMC,OAAO,GAAG,OAAOd,cAAM,CAACe,MAAM,CAAC3B,GAAG,CAAC4B,MAAM,CAAC,CAAC3B,OAAO,CAAC,CAACc,IAAI,CAC5DZ,cAAM,CAACa,QAAQ,CAAEC,KAAK,IAAK,IAAIC,kBAAU,CAAC;IAAEC,MAAM,EAAE,QAAQ;IAAEF;EAAK,CAAE,CAAC,CAAC,CACxE;EACD,MAAMY,IAAI,GAAGL,UAAU,CAACM,SAAS,CAACJ,OAAO,CAAC;EAC1C,MAAMK,IAAI,GAAG7B,OAAO,EAAE6B,IAAI,KAAK,OAAO,CAAC,OAAOC,UAAI,EAAEC,QAAQ,CAAC;EAC7D,MAAMC,UAAU,GAAGhC,OAAO,EAAEgC,UAAU,KAAK,OAAOC,gBAAQ,CAACC,GAAG,CAAC;EAC/D,OAAO;IACLC,IAAI,EAAErC,GAAG,CAACqC,IAAI;IACdR,IAAI;IACJT,IAAI;IACJW,IAAI;IACJG,UAAU;IACV5B;GACD;AACH,CAAC,CAAC;AAAAgC,OAAA,CAAAvC,OAAA,GAAAA,OAAA","ignoreList":[]}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { Context } from 'effect';
|
|
2
|
+
declare const MetadataSource_base: Context.TagClass<MetadataSource, "@kairos-es/codec/MetadataSource", {
|
|
3
|
+
readonly [x: string]: string;
|
|
4
|
+
}>;
|
|
5
|
+
export declare class MetadataSource extends MetadataSource_base {
|
|
6
|
+
}
|
|
7
|
+
export {};
|
|
8
|
+
//# sourceMappingURL=MetadataSource.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MetadataSource.d.ts","sourceRoot":"","sources":["../../src/MetadataSource.ts"],"names":[],"mappings":"AAsCA,OAAO,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAA;;;;AAEhC,qBAAa,cAAe,SAAQ,mBAEP;CAAG"}
|
package/dist/dts/index.d.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
|
|
21
|
-
* -> serialise, tags derived from `def.tagsFor`, clock + uuid from
|
|
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
|
|
@@ -122,6 +123,7 @@ export { buildDecisionModel, modelAtHead, type RequirementOf, } from './decision
|
|
|
122
123
|
export { decodeSlices } from './decode.js';
|
|
123
124
|
export { CodecError, UnknownEventType } from './errors.js';
|
|
124
125
|
export { type EventDef, type EventDefAny, event, type TagsFor } from './event.js';
|
|
126
|
+
export { MetadataSource } from './MetadataSource.js';
|
|
125
127
|
export { decideOnly, type Precondition, transact, unsafeAppend, } from './precondition.js';
|
|
126
128
|
export { type ProjectionBuilder, projection, type SliceProjection, } from './projection.js';
|
|
127
129
|
export { type CloseHop, type Hop, NoReadsRecorded, ReadLedger, TaglessSliceInLedger, } from './readLedger.js';
|
package/dist/dts/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA
|
|
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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"readLedger.d.ts","sourceRoot":"","sources":["../../src/readLedger.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+DG;AACH,OAAO,KAAK,EACV,aAAa,EACb,eAAe,EACf,QAAQ,EACR,KAAK,EACN,MAAM,iBAAiB,CAAA;
|
|
1
|
+
{"version":3,"file":"readLedger.d.ts","sourceRoot":"","sources":["../../src/readLedger.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+DG;AACH,OAAO,KAAK,EACV,aAAa,EACb,eAAe,EACf,QAAQ,EACR,KAAK,EACN,MAAM,iBAAiB,CAAA;AAQxB,OAAO,EAAE,OAAO,EAAQ,MAAM,EAAE,KAAK,EAAE,MAAM,EAAO,MAAM,QAAQ,CAAA;AAElE;;;;;;;GAOG;AACH,MAAM,WAAW,GAAG;IAClB,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAA;IACrB,QAAQ,CAAC,IAAI,CAAC,EAAE,QAAQ,CAAA;CACzB;;IAuBG;;;;;;;;;;;;OAYG;qBACc,CAAC,KAAK,EAAE,KAAK,KAAK,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC;mBAC3C,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC;;AAnCpD;;;;;;;;;;;;;;;;;GAiBG;AACH,qBAAa,UAAW,SAAQ,eAmB7B;CAAG;AAEN;;;;;;;GAOG;AACH,MAAM,MAAM,QAAQ,GAAG,CAAC,IAAI,EAAE,QAAQ,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;;;;AAE9D;;;;;;;;;;;;;;;;;;GAkBG;AACH,qBAAa,eAAgB,SAAQ,qBACnC,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,CACrB;IACC;;;;;;;OAOG;IACH,IAAa,OAAO,IAAI,MAAM,CAM7B;CACF;;;;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,qBAAa,oBAAqB,SAAQ,0BAExC;IAAE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC;IAC9B,2DAA2D;IAC3D,IAAa,OAAO,IAAI,MAAM,CAO7B;CACF;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,eAAO,MAAM,sBAAsB,GACjC,MAAM,aAAa,CAAC,GAAG,CAAC,KACvB,eAAe,GAAG,SAiBpB,CAAA;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,cAAc,EAAE,MAAM,CAAC,MAAM,CAAC;IACzC,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,UAAU,CAAC,CAAA;IACvC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC,CAAA;CACjD,CAsBC,CAAA;AAsBF;;;;GAIG;AACH,eAAO,MAAM,WAAW,GAAI,OAAO,KAAK,KAAG,MAAM,CAAC,MAAM,CAAC,QAAQ,CACT,CAAA;AAoCxD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,eAAO,MAAM,sBAAsB,GACjC,aAAa,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,KACzC,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,oBAAoB,CAaxC,CAAA;AAEH;;;;;;GAMG;AACH,eAAO,MAAM,uBAAuB,EAAE,MAAM,CAAC,MAAM,CACjD,MAAM,CAAC,MAAM,CAAC,eAAe,CAAC,CAK/B,CAAA"}
|
package/dist/dts/toEvent.d.ts
CHANGED
|
@@ -10,8 +10,15 @@
|
|
|
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
|
|
21
|
+
import { type DcbEvent, Metadata } from '@kairos-es/core';
|
|
15
22
|
import { DateTime, Effect } from 'effect';
|
|
16
23
|
import { CodecError } from './errors.js';
|
|
17
24
|
import type { EventDef } from './event.js';
|
|
@@ -25,10 +32,14 @@ import { Uuid } from './Uuid.js';
|
|
|
25
32
|
* wire-envelope fields (the query surface), so a `tagsFor` mapper may only key
|
|
26
33
|
* off cleartext payload fields (ADR-0006). `uuid` / `occurredAt` are
|
|
27
34
|
* exact-optional overrides; absent, they come from the `Uuid` service and the
|
|
28
|
-
* Effect clock.
|
|
35
|
+
* Effect clock. `metadata` is an exact-optional override too; absent, it comes
|
|
36
|
+
* from the ambient `MetadataSource` if one is provided and is `{}` otherwise.
|
|
37
|
+
* The ambient read adds nothing to the requirement channel. A bag core's
|
|
38
|
+
* `Metadata` schema refuses fails as a `CodecError` with `reason: 'metadata'`.
|
|
29
39
|
*/
|
|
30
40
|
export declare const toEvent: <A, I, R>(def: EventDef<A, I, R>, payload: A, options?: {
|
|
31
41
|
readonly uuid?: string;
|
|
32
42
|
readonly occurredAt?: DateTime.Utc;
|
|
43
|
+
readonly metadata?: Metadata;
|
|
33
44
|
}) => Effect.Effect<DcbEvent, CodecError, Serializer | Uuid | R>;
|
|
34
45
|
//# sourceMappingURL=toEvent.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"toEvent.d.ts","sourceRoot":"","sources":["../../src/toEvent.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"toEvent.d.ts","sourceRoot":"","sources":["../../src/toEvent.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,OAAO,EAAE,KAAK,QAAQ,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAA;AACzD,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAkB,MAAM,QAAQ,CAAA;AACzD,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAA;AACrC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAA;AAEvC,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AACzC,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAA;AAE7B;;;;;;;;;;;;GAYG;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;IAClC,QAAQ,CAAC,QAAQ,CAAC,EAAE,QAAQ,CAAA;CAC7B,KACA,MAAM,CAAC,MAAM,CAAC,QAAQ,EAAE,UAAU,EAAE,UAAU,GAAG,IAAI,GAAG,CAAC,CA4DxD,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MetadataSource.js","names":["Context","MetadataSource","Tag"],"sources":["../../src/MetadataSource.ts"],"sourcesContent":[null],"mappings":"AAsCA,SAASA,OAAO,QAAQ,QAAQ;AAEhC,OAAM,MAAOC,cAAe,sBAAQD,OAAO,CAACE,GAAG,CAC7C,iCAAiC,CAClC,EAA4B","ignoreList":[]}
|
package/dist/esm/index.js
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
|
|
21
|
-
* -> serialise, tags derived from `def.tagsFor`, clock + uuid from
|
|
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
|
|
@@ -122,6 +123,7 @@ export { buildDecisionModel, modelAtHead } from "./decisionModel.js";
|
|
|
122
123
|
export { decodeSlices } from "./decode.js";
|
|
123
124
|
export { CodecError, UnknownEventType } from "./errors.js";
|
|
124
125
|
export { event } from "./event.js";
|
|
126
|
+
export { MetadataSource } from "./MetadataSource.js";
|
|
125
127
|
export { decideOnly, transact, unsafeAppend } from "./precondition.js";
|
|
126
128
|
export { projection } from "./projection.js";
|
|
127
129
|
export { NoReadsRecorded, ReadLedger, TaglessSliceInLedger } from "./readLedger.js";
|
package/dist/esm/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["decider","runDecider","Decision","isUnchanged","Unchanged","UnchangedTypeId","buildDecisionModel","modelAtHead","decodeSlices","CodecError","UnknownEventType","event","decideOnly","transact","unsafeAppend","projection","NoReadsRecorded","ReadLedger","TaglessSliceInLedger","makeRegistry","Serializer","SerializerDefault","toEvent","Uuid","UuidLive","UuidTest"],"sources":["../../src/index.ts"],"sourcesContent":[null],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.js","names":["decider","runDecider","Decision","isUnchanged","Unchanged","UnchangedTypeId","buildDecisionModel","modelAtHead","decodeSlices","CodecError","UnknownEventType","event","MetadataSource","decideOnly","transact","unsafeAppend","projection","NoReadsRecorded","ReadLedger","TaglessSliceInLedger","makeRegistry","Serializer","SerializerDefault","toEvent","Uuid","UuidLive","UuidTest"],"sources":["../../src/index.ts"],"sourcesContent":[null],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwHA,SAAuBA,OAAO,EAAEC,UAAU,QAAQ,cAAW;AAC7D,SACEC,QAAQ,EAGRC,WAAW,EACXC,SAAS,EACTC,eAAe,QACV,eAAY;AACnB,SACEC,kBAAkB,EAClBC,WAAW,QAEN,oBAAiB;AACxB,SAASC,YAAY,QAAQ,aAAU;AACvC,SAASC,UAAU,EAAEC,gBAAgB,QAAQ,aAAU;AACvD,SAA0CC,KAAK,QAAsB,YAAS;AAC9E,SAASC,cAAc,QAAQ,qBAAkB;AACjD,SACEC,UAAU,EAEVC,QAAQ,EACRC,YAAY,QACP,mBAAgB;AACvB,SAEEC,UAAU,QAEL,iBAAc;AACrB,SAGEC,eAAe,EACfC,UAAU,EACVC,oBAAoB,QACf,iBAAc;AACrB,SAASC,YAAY,QAAuB,eAAY;AACxD,SAASC,UAAU,EAAEC,iBAAiB,QAAQ,iBAAc;AAC5D,SAASC,OAAO,QAAQ,cAAW;AACnC,SAASC,IAAI,EAAEC,QAAQ,EAAEC,QAAQ,QAAQ,WAAQ","ignoreList":[]}
|
package/dist/esm/readLedger.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { composeProjections, ORIGIN, servableQueryFault, unionQueries } from '@kairos-es/core';
|
|
1
|
+
import { composeProjections, ORIGIN, servableQueryFault, unionQueries, unknownQueryKeyFault } from '@kairos-es/core';
|
|
2
2
|
import { Context, Data, Effect, Layer, Option, Ref } from 'effect';
|
|
3
3
|
/**
|
|
4
4
|
* The ambient ledger, present only inside a bracket.
|
|
@@ -163,8 +163,23 @@ const NO_CLOSE = () => Effect.void;
|
|
|
163
163
|
export const recordQuery = query => withLedger(NO_CLOSE, ledger => ledger.record(query));
|
|
164
164
|
/** The tagless slice keys in a record, in declaration order. */
|
|
165
165
|
const taglessKeys = projections => Object.entries(projections).filter(([, projection]) => projection.query.items.some(item => item.tags.length === 0)).map(([key]) => key);
|
|
166
|
-
/**
|
|
167
|
-
|
|
166
|
+
/**
|
|
167
|
+
* Would recording this record's query leave the accumulated union unservable in
|
|
168
|
+
* its SHAPE — the one fault a slice's tags can fix, and so the one this ledger
|
|
169
|
+
* reports?
|
|
170
|
+
*
|
|
171
|
+
* An unknown key anywhere in the union answers no. It is not a tagless slice and no
|
|
172
|
+
* slice's tags would fix it, and the store refuses it as a defect naming the key at
|
|
173
|
+
* the read of the hop that carries it, so an earlier hop's key has already been
|
|
174
|
+
* refused once by the time a later record is judged. Judged here it would surface
|
|
175
|
+
* as this ledger's error, on the error channel, naming a slice with nothing wrong
|
|
176
|
+
* with it — and a hop is recorded before its read runs, so an earlier hop's key
|
|
177
|
+
* would go on doing that to every clean read after it.
|
|
178
|
+
*/
|
|
179
|
+
const unionWouldBeUnservable = (existing, incoming) => {
|
|
180
|
+
const union = unionQueries([...existing.map(h => h.query), incoming]);
|
|
181
|
+
return unknownQueryKeyFault(union) === undefined && servableQueryFault(union) !== undefined;
|
|
182
|
+
};
|
|
168
183
|
/**
|
|
169
184
|
* Refuse a read that would leave the bracket's accumulated union unservable,
|
|
170
185
|
* BEFORE the read is paid for.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"readLedger.js","names":["composeProjections","ORIGIN","servableQueryFault","unionQueries","Context","Data","Effect","Layer","Option","Ref","ReadLedger","Tag","NoReadsRecorded","TaggedError","message","TaglessSliceInLedger","sliceKey","deriveLayeredCondition","hops","length","undefined","merged","map","hop","query","arrived","flatMap","head","after","reduce","a","b","failIfEventsMatch","makeReadLedger","gen","ref","make","layer","succeed","record","modify","update","current","index","get","withLedger","absent","f","serviceOption","ledger","isNone","value","NO_CLOSE","void","recordQuery","taglessKeys","projections","Object","entries","filter","projection","items","some","item","tags","key","unionWouldBeUnservable","existing","incoming","h","assertServableAddition","offending","ambientLayeredCondition","none","fromNullable"],"sources":["../../src/readLedger.ts"],"sourcesContent":[null],"mappings":"AAsEA,SACEA,kBAAkB,EAClBC,MAAM,EACNC,kBAAkB,EAClBC,YAAY,
|
|
1
|
+
{"version":3,"file":"readLedger.js","names":["composeProjections","ORIGIN","servableQueryFault","unionQueries","unknownQueryKeyFault","Context","Data","Effect","Layer","Option","Ref","ReadLedger","Tag","NoReadsRecorded","TaggedError","message","TaglessSliceInLedger","sliceKey","deriveLayeredCondition","hops","length","undefined","merged","map","hop","query","arrived","flatMap","head","after","reduce","a","b","failIfEventsMatch","makeReadLedger","gen","ref","make","layer","succeed","record","modify","update","current","index","get","withLedger","absent","f","serviceOption","ledger","isNone","value","NO_CLOSE","void","recordQuery","taglessKeys","projections","Object","entries","filter","projection","items","some","item","tags","key","unionWouldBeUnservable","existing","incoming","union","h","assertServableAddition","offending","ambientLayeredCondition","none","fromNullable"],"sources":["../../src/readLedger.ts"],"sourcesContent":[null],"mappings":"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","ignoreList":[]}
|
package/dist/esm/toEvent.js
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
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)
|
|
5
|
+
* then `serialiser.serialise`. The wire `tags` are derived from the def's
|
|
6
|
+
* required `tagsFor` mapper (ADR-0006) — they are never hand-passed, so two
|
|
7
|
+
* command sites emitting the same event type cannot tag it differently. A mapper
|
|
8
|
+
* that throws (e.g. an ill-formed `key:value` rejected by `Tag.make`) is
|
|
9
|
+
* surfaced on the `CodecError` channel, not as a defect. `uuid`
|
|
10
|
+
* comes from `options.uuid` or the `Uuid` service; `occurredAt` from
|
|
11
|
+
* `options.occurredAt` or the Effect clock (`DateTime.now`), keeping `core`
|
|
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.
|
|
20
|
+
*/
|
|
21
|
+
import { Metadata } from '@kairos-es/core';
|
|
22
|
+
import { DateTime, Effect, Option, Schema } from 'effect';
|
|
2
23
|
import { CodecError } from "./errors.js";
|
|
24
|
+
import { MetadataSource } from "./MetadataSource.js";
|
|
3
25
|
import { Serializer } from "./Serializer.js";
|
|
4
26
|
import { Uuid } from "./Uuid.js";
|
|
5
27
|
/**
|
|
@@ -10,10 +32,40 @@ import { Uuid } from "./Uuid.js";
|
|
|
10
32
|
* wire-envelope fields (the query surface), so a `tagsFor` mapper may only key
|
|
11
33
|
* off cleartext payload fields (ADR-0006). `uuid` / `occurredAt` are
|
|
12
34
|
* exact-optional overrides; absent, they come from the `Uuid` service and the
|
|
13
|
-
* Effect clock.
|
|
35
|
+
* Effect clock. `metadata` is an exact-optional override too; absent, it comes
|
|
36
|
+
* from the ambient `MetadataSource` if one is provided and is `{}` otherwise.
|
|
37
|
+
* The ambient read adds nothing to the requirement channel. A bag core's
|
|
38
|
+
* `Metadata` schema refuses fails as a `CodecError` with `reason: 'metadata'`.
|
|
14
39
|
*/
|
|
15
40
|
export const toEvent = (def, payload, options) => Effect.gen(function* () {
|
|
16
|
-
//
|
|
41
|
+
// Resolve metadata before anything else. Per-call wins, ambient fills,
|
|
42
|
+
// absent yields `{}`. `serviceOption` keeps `MetadataSource` out of the
|
|
43
|
+
// requirement channel.
|
|
44
|
+
//
|
|
45
|
+
// The bag is then CHECKED against core's `Metadata` schema, whichever arm
|
|
46
|
+
// supplied it. The type holds a typed caller to a bag of strings, but an
|
|
47
|
+
// `any` or untyped JavaScript gets past it, and the engines then disagree
|
|
48
|
+
// about what they stored: the cross-engine parity of this field rests on its
|
|
49
|
+
// values being strings, and this is the one place a value is refused. It is
|
|
50
|
+
// refused HERE, at write, and never on read: a read-side check would make an
|
|
51
|
+
// event the store had accepted unreadable for ever. A bad bag is the
|
|
52
|
+
// caller's mistake, so it fails on the `CodecError` channel exactly as a
|
|
53
|
+
// throwing `tagsFor` does, never as a defect. Checking consumes no sequence,
|
|
54
|
+
// so placing it above the tag derivation leaves the property below intact.
|
|
55
|
+
//
|
|
56
|
+
// The bag is COPIED, never stamped by reference: an ambient bag would
|
|
57
|
+
// otherwise be ONE object shared by every event of a request, and the
|
|
58
|
+
// caller's own object would be shared with the event it built, so a mutation
|
|
59
|
+
// of either before the append would rewrite the other. (After the append no
|
|
60
|
+
// store shares anything with its caller; that is each store's guarantee.)
|
|
61
|
+
const given = options?.metadata === undefined ? Option.getOrElse(yield* Effect.serviceOption(MetadataSource), () => ({})) : options.metadata;
|
|
62
|
+
const metadata = {
|
|
63
|
+
...(yield* Schema.decodeUnknown(Metadata)(given).pipe(Effect.mapError(cause => new CodecError({
|
|
64
|
+
reason: 'metadata',
|
|
65
|
+
cause
|
|
66
|
+
}))))
|
|
67
|
+
};
|
|
68
|
+
// Derive tags next. A mapper is app code that can throw (e.g. `Tag.make`
|
|
17
69
|
// rejects an ill-formed `key:value`), and the codec's contract is that such
|
|
18
70
|
// failures are values on the `CodecError` channel, never defects (errors.ts).
|
|
19
71
|
// Deriving before the uuid/clock effects also means a bad tag fails without
|
|
@@ -38,7 +90,8 @@ export const toEvent = (def, payload, options) => Effect.gen(function* () {
|
|
|
38
90
|
data,
|
|
39
91
|
tags,
|
|
40
92
|
uuid,
|
|
41
|
-
occurredAt
|
|
93
|
+
occurredAt,
|
|
94
|
+
metadata
|
|
42
95
|
};
|
|
43
96
|
});
|
|
44
97
|
//# sourceMappingURL=toEvent.js.map
|
package/dist/esm/toEvent.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"toEvent.js","names":["DateTime","Effect","Schema","CodecError","Serializer","Uuid","toEvent","def","payload","options","gen","
|
|
1
|
+
{"version":3,"file":"toEvent.js","names":["Metadata","DateTime","Effect","Option","Schema","CodecError","MetadataSource","Serializer","Uuid","toEvent","def","payload","options","gen","given","metadata","undefined","getOrElse","serviceOption","decodeUnknown","pipe","mapError","cause","reason","tags","try","tagsFor","catch","serializer","encoded","encode","schema","data","serialise","uuid","generate","occurredAt","now","type"],"sources":["../../src/toEvent.ts"],"sourcesContent":[null],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;AAoBA,SAAwBA,QAAQ,QAAQ,iBAAiB;AACzD,SAASC,QAAQ,EAAEC,MAAM,EAAEC,MAAM,EAAEC,MAAM,QAAQ,QAAQ;AACzD,SAASC,UAAU,QAAQ,aAAU;AAErC,SAASC,cAAc,QAAQ,qBAAkB;AACjD,SAASC,UAAU,QAAQ,iBAAc;AACzC,SAASC,IAAI,QAAQ,WAAQ;AAE7B;;;;;;;;;;;;;AAaA,OAAO,MAAMC,OAAO,GAAGA,CACrBC,GAAsB,EACtBC,OAAU,EACVC,OAIC,KAEDV,MAAM,CAACW,GAAG,CAAC,aAAS;EAClB;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,MAAMC,KAAK,GACTF,OAAO,EAAEG,QAAQ,KAAKC,SAAS,GAC3Bb,MAAM,CAACc,SAAS,CACd,OAAOf,MAAM,CAACgB,aAAa,CAACZ,cAAc,CAAC,EAC3C,OAAiB,EAAE,CAAC,CACrB,GACDM,OAAO,CAACG,QAAQ;EACtB,MAAMA,QAAQ,GAAa;IACzB,IAAI,OAAOX,MAAM,CAACe,aAAa,CAACnB,QAAQ,CAAC,CAACc,KAAK,CAAC,CAACM,IAAI,CACnDlB,MAAM,CAACmB,QAAQ,CACZC,KAAK,IAAK,IAAIjB,UAAU,CAAC;MAAEkB,MAAM,EAAE,UAAU;MAAED;IAAK,CAAE,CAAC,CACzD,CACF;GACF;EACD;EACA;EACA;EACA;EACA;EACA,MAAME,IAAI,GAAG,OAAOtB,MAAM,CAACuB,GAAG,CAAC;IAC7BA,GAAG,EAAEA,CAAA,KAAMf,GAAG,CAACgB,OAAO,CAACf,OAAO,CAAC;IAC/BgB,KAAK,EAAGL,KAAK,IAAK,IAAIjB,UAAU,CAAC;MAAEkB,MAAM,EAAE,MAAM;MAAED;IAAK,CAAE;GAC3D,CAAC;EACF,MAAMM,UAAU,GAAG,OAAOrB,UAAU;EACpC,MAAMsB,OAAO,GAAG,OAAOzB,MAAM,CAAC0B,MAAM,CAACpB,GAAG,CAACqB,MAAM,CAAC,CAACpB,OAAO,CAAC,CAACS,IAAI,CAC5DlB,MAAM,CAACmB,QAAQ,CAAEC,KAAK,IAAK,IAAIjB,UAAU,CAAC;IAAEkB,MAAM,EAAE,QAAQ;IAAED;EAAK,CAAE,CAAC,CAAC,CACxE;EACD,MAAMU,IAAI,GAAGJ,UAAU,CAACK,SAAS,CAACJ,OAAO,CAAC;EAC1C,MAAMK,IAAI,GAAGtB,OAAO,EAAEsB,IAAI,KAAK,OAAO,CAAC,OAAO1B,IAAI,EAAE2B,QAAQ,CAAC;EAC7D,MAAMC,UAAU,GAAGxB,OAAO,EAAEwB,UAAU,KAAK,OAAOnC,QAAQ,CAACoC,GAAG,CAAC;EAC/D,OAAO;IACLC,IAAI,EAAE5B,GAAG,CAAC4B,IAAI;IACdN,IAAI;IACJR,IAAI;IACJU,IAAI;IACJE,UAAU;IACVrB;GACD;AACH,CAAC,CAAC","ignoreList":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kairos-es/codec",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.4",
|
|
4
4
|
"description": "Schema-first codec with encryption seam for kairos-es",
|
|
5
5
|
"license": "BSD-3-Clause",
|
|
6
6
|
"repository": {
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"homepage": "https://github.com/neverbland/kairos-es",
|
|
13
13
|
"peerDependencies": {
|
|
14
14
|
"effect": "^3.22.0",
|
|
15
|
-
"@kairos-es/core": "^1.0.0-alpha.
|
|
15
|
+
"@kairos-es/core": "^1.0.0-alpha.4"
|
|
16
16
|
},
|
|
17
17
|
"main": "./dist/cjs/index.js",
|
|
18
18
|
"module": "./dist/esm/index.js",
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The ambient metadata service: an OPTIONAL source of the `metadata` bag that
|
|
3
|
+
* `toEvent` stamps on an event when the call itself passes none (ADR-0017).
|
|
4
|
+
*
|
|
5
|
+
* An application provides it once, at the edge of a unit of work — an HTTP
|
|
6
|
+
* request, a job — and every event appended inside that scope carries the bag
|
|
7
|
+
* with no command handler, decider or `toEvent` call site mentioning it:
|
|
8
|
+
*
|
|
9
|
+
* ```ts
|
|
10
|
+
* handleRequest.pipe(
|
|
11
|
+
* Effect.provideService(MetadataSource, { traceparent: header }),
|
|
12
|
+
* )
|
|
13
|
+
* ```
|
|
14
|
+
*
|
|
15
|
+
* It is read through `Effect.serviceOption`, never `yield* MetadataSource`, and
|
|
16
|
+
* that choice is the whole design. `serviceOption` returns `Effect<Option<…>>`
|
|
17
|
+
* with BOTH the requirement and the error channel as `never`, so this tag never
|
|
18
|
+
* appears in `toEvent`'s requirement channel nor in any signature above it, and
|
|
19
|
+
* no consumer is forced to provide a layer it does not want. The `Uuid` service
|
|
20
|
+
* beside this is a REQUIRED tag; adopting that shape here would put a metadata
|
|
21
|
+
* layer on every consumer of the codec. Core stays a passive carrier: the
|
|
22
|
+
* ambient read happens here, in the codec, where reading a service breaches no
|
|
23
|
+
* purity rule.
|
|
24
|
+
*
|
|
25
|
+
* A per-call `options.metadata` REPLACES this value rather than merging with it:
|
|
26
|
+
* per-call wins, ambient fills, and absent yields `{}`. A merge would make the
|
|
27
|
+
* bag an event carries depend on two sources at once, and a caller who passes a
|
|
28
|
+
* bag explicitly is stating what the event's bag is.
|
|
29
|
+
*
|
|
30
|
+
* One key is reserved, as a convention and not a column: `traceparent`. Reserved
|
|
31
|
+
* means that when a bag carries W3C trace context it carries it under that key,
|
|
32
|
+
* holding a W3C `traceparent` header value, so tracing tools match it; kairos
|
|
33
|
+
* itself never writes or reads it. Every other key is the application's: a key
|
|
34
|
+
* is kairos's to name only when kairos writes it or a standard owns the
|
|
35
|
+
* spelling, and `correlationId` / `causationId` are suggested spellings rather
|
|
36
|
+
* than reserved ones (ADR-0017).
|
|
37
|
+
*/
|
|
38
|
+
import type { Metadata } from '@kairos-es/core'
|
|
39
|
+
import { Context } from 'effect'
|
|
40
|
+
|
|
41
|
+
export class MetadataSource extends Context.Tag(
|
|
42
|
+
'@kairos-es/codec/MetadataSource',
|
|
43
|
+
)<MetadataSource, Metadata>() {}
|
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
|
|
21
|
-
* -> serialise, tags derived from `def.tagsFor`, clock + uuid from
|
|
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,
|
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
|
|
|
@@ -329,14 +330,29 @@ const taglessKeys = (
|
|
|
329
330
|
)
|
|
330
331
|
.map(([key]) => key)
|
|
331
332
|
|
|
332
|
-
/**
|
|
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
|
-
|
|
338
|
-
|
|
339
|
-
|
|
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,
|
package/src/toEvent.ts
CHANGED
|
@@ -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
|
|
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
|
-
//
|
|
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.decodeUnknown(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
|
|
@@ -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
|
})
|