Last active
June 14, 2017 05:59
-
-
Save neoeinstein/54e2ef5f5d724dda14d15b24b9c11bae to your computer and use it in GitHub Desktop.
EventStore projections from Fable. A noble attempt.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
namespace Fable.Import | |
open System | |
open Fable.Core | |
open Fable.Import.JS | |
module EventStore = | |
type [<Erase>] Initializer<'state> = unit -> 'state | |
type [<Erase>] EventHandler<'state,'event> = System.Func<'state,EventEnvelope<'event>,'state> | |
and [<Erase>] EventHandler<'state,'event,'metadata> = System.Func<'state,EventEnvelope<'event,'metadata>,'state> | |
and EventEnvelope<'event,'metadata,'streamMetadata,'linkMetadata> = | |
abstract member bodyRaw : string | |
abstract member eventType : string | |
abstract member streamId : string | |
abstract member sequenceNumber : uint64 | |
abstract member metadataRaw : string | |
abstract member streamMetadataRaw : string | |
abstract member linkMetadataRaw : string | |
abstract member partition : string option | |
abstract member metadata : 'metadata with get | |
abstract member streamMetadata : 'streamMetadata with get | |
abstract member linkMetadata : 'linkMetadata with get | |
abstract member data : 'event with get | |
abstract member jsonError : string option with get | |
abstract member isJson : bool | |
and [<Erase>] EventEnvelope<'event> = EventEnvelope<'event,obj,obj,obj> | |
and [<Erase>] EventEnvelope<'event,'metadata> = EventEnvelope<'event,'metadata,obj,obj> | |
type Transformable = | |
abstract member transformBy : ('a -> 'b) -> TransformableState | |
abstract member filterBy : ('a -> bool) -> TransformableState | |
abstract member outputTo : resultStream: string * ?partitionStreamPattern: string -> unit | |
and TransformableState = | |
inherit Transformable | |
abstract member outputState : unit -> Transformable | |
and WhenOnly<'handler> = | |
abstract member ``when`` : 'handler -> TransformableState | |
abstract member whenAny : 'handler -> TransformableState | |
and FromStream<'handler> = | |
inherit WhenOnly<'handler> | |
abstract member partitionBy : 'a -> WhenOnly<'handler> | |
abstract member outputState : unit -> Transformable | |
and FromCatalog<'handler> = | |
abstract member foreachStream : unit -> WhenOnly<'handler> | |
and FromGroup<'handler> = | |
inherit FromStream<'handler> | |
inherit FromCatalog<'handler> | |
let [<Global>] fromCategory (category : string) : FromGroup<'handler> = failwith "JS Only" | |
let [<Global>] fromAll () : FromGroup<'handler> = failwith "JS Only" | |
let [<Global>] fromStream (stream : string) : FromStream<'handler> = failwith "JS Only" | |
let [<Global>] fromStreams (streams : string list) : FromStream<'handler> = failwith "JS Only" | |
let [<Global>] fromStreamCatalog (streamCatalog : string) : FromCatalog<'handler> = failwith "JS Only" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module ProjectionExample | |
open Fable.Core | |
open Fable.Import | |
[<AutoOpen>] | |
module Prelude = | |
module Option = | |
let fill def = function | |
| Some x -> x | |
| None -> def | |
[<StringEnum>] | |
type RequestType = | |
| NotRequired | |
| Requested | |
| Required | |
type [<Erase>] Location = Location of string | |
type State = | |
{ isReady : bool | |
name : string option | |
local : Map<Location, LocalData> } | |
and LocalData = | |
{ requestType : RequestType | |
hasValues : bool } | |
// Partial active patterns returning `unit option` don't work since | |
// Some () ==> null, None ==> null, so use total active patterns... | |
let (|IsEmpty|IsNotEmpty|) (s : State) = | |
if Map.isEmpty s.local then IsEmpty else IsNotEmpty | |
let (|HasRequirements|HasNoRequirements|) (s : State) = | |
if Map.forall (fun k v -> match v.requestType with Required -> false | _ -> true) s.local then HasNoRequirements else HasRequirements | |
let (|HasName|MissingName|) (s : State) = | |
match s.name with Some _ -> HasName | None -> MissingName | |
let (|AllRequiredHaveValues|SomeRequiredHaveNoValue|) (s : State) = | |
if s.local |> Map.forall (fun k v -> match v.requestType, v.hasValues with Required, false -> false | _ -> true) then AllRequiredHaveValues else SomeRequiredHaveNoValue | |
let (|ShouldBeMarkedReady|ShouldNotBeMarkedReady|) (s:State) = | |
match s with | |
| IsEmpty | |
| HasNoRequirements | |
| HasName & AllRequiredHaveValues -> ShouldBeMarkedReady | |
| _ -> ShouldNotBeMarkedReady | |
type ValueReceivedEvent = | |
{ location : Location | |
values : string list } | |
and RequestUpdatedEvent = | |
{ location : Location | |
requestType : RequestType } | |
and NameUpdatedEvent = | |
{ name : string option } | |
type EventHandler = | |
{ ``$init`` : unit -> State | |
ValueReceived : EventStore.EventHandler<State,ValueReceivedEvent> | |
RequestUpdated : EventStore.EventHandler<State,RequestUpdatedEvent> | |
NameUpdated : EventStore.EventHandler<State,NameUpdatedEvent> } | |
let getLocalData (x : Location) (s : State) : LocalData = | |
Map.tryFind x s.local |> Option.fill { requestType = NotRequired; hasValues = false } | |
let recalculateIsReady (s : State) : State = | |
match s with | |
| ShouldBeMarkedReady -> { s with isReady = true } | |
| _ -> { s with isReady = false } | |
let updateLocalData f l s : State = | |
recalculateIsReady { s with local = Map.add l (f (getLocalData l s)) s.local } | |
let handleValueReceivedEvent (s : State) (e : ValueReceivedEvent) : State = | |
updateLocalData (fun l -> { l with hasValues = List.length e.values > 0 }) e.location s | |
let handleRequestUpdatedEvent (s : State) (e : RequestUpdatedEvent) : State = | |
updateLocalData (fun l -> { l with requestType = e.requestType }) e.location s | |
let handleNameUpdatedEvent (s : State) (e : NameUpdatedEvent) : State = | |
recalculateIsReady { s with name = e.name } | |
let handleIgnoringEnvelope f : EventStore.EventHandler<_,_> = | |
EventStore.EventHandler<_,_>(fun s env -> f s env.data) | |
let handler : EventHandler = | |
{ ``$init`` = fun () -> { isReady = true; name = None; local = Map.empty } | |
ValueReceived = handleIgnoringEnvelope handleValueReceivedEvent | |
RequestUpdated = handleIgnoringEnvelope handleRequestUpdatedEvent | |
NameUpdated = handleIgnoringEnvelope handleNameUpdatedEvent } | |
EventStore.fromCategory("interestingCategory") | |
.foreachStream() | |
.``when``(handler) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// This file was generated from Fable. The module syntax used | |
// is "UMD" since commonjs uses `require`, which is not provided | |
// by Event Store. Attempts to inline Fable Core failed. Event Store | |
// reported that `Symbol` was undefined. Attempts at inlining | |
// `es5-shim` and `es6-shim` also failed since `es5-shim` was unable | |
// to find a "global" object from which it could hang values. | |
(function (global, factory) { | |
if (typeof define === "function" && define.amd) { | |
define(["exports", "fable-core"], factory); | |
} else if (typeof exports !== "undefined") { | |
factory(exports, require("fable-core")); | |
} else { | |
var mod = { | |
exports: {} | |
}; | |
factory(mod.exports, global.fableCore); | |
global.unknown = mod.exports; | |
} | |
})(this, function (exports, _fableCore) { | |
"use strict"; | |
Object.defineProperty(exports, "__esModule", { | |
value: true | |
}); | |
exports.handler = exports.handleIgnoringEnvelope = exports.handleNameUpdatedEvent = exports.handleRequestUpdatedEvent = exports.handleValueReceivedEvent = exports.updateLocalData = exports.recalculateIsReady = exports.getLocalData = exports.EventHandler = exports.NameUpdatedEvent = exports.RequestUpdatedEvent = exports.ValueReceivedEvent = exports.$ShouldBeMarkedReady$ShouldNotBeMarkedReady$ = exports.$AllRequiredHaveValues$SomeRequiredHaveNoValue$ = exports.$HasName$MissingName$ = exports.$HasRequirements$HasNoRequirements$ = exports.$IsEmpty$IsNotEmpty$ = exports.LocalData = exports.State = exports.RequestType = exports.Prelude = undefined; | |
function _classCallCheck(instance, Constructor) { | |
if (!(instance instanceof Constructor)) { | |
throw new TypeError("Cannot call a class as a function"); | |
} | |
} | |
var Prelude = exports.Prelude = function ($exports) { | |
var Option = $exports.Option = function ($exports) { | |
var fill = $exports.fill = function (def, _arg1) { | |
var x; | |
return _arg1 == null ? def : (x = _arg1, x); | |
}; | |
return $exports; | |
}({}); | |
return $exports; | |
}({}); | |
var RequestType = exports.RequestType = function RequestType() { | |
_classCallCheck(this, RequestType); | |
this.Case = arguments[0]; | |
this.Fields = []; | |
for (var i = 1; i < arguments.length; i++) { | |
this.Fields[i - 1] = arguments[i]; | |
} | |
}; | |
var State = exports.State = function State($arg0, $arg1, $arg2) { | |
_classCallCheck(this, State); | |
this.isReady = $arg0; | |
this.name = $arg1; | |
this.local = $arg2; | |
}; | |
var LocalData = exports.LocalData = function LocalData($arg0, $arg1) { | |
_classCallCheck(this, LocalData); | |
this.requestType = $arg0; | |
this.hasValues = $arg1; | |
}; | |
var $IsEmpty$IsNotEmpty$ = exports.$IsEmpty$IsNotEmpty$ = function (s) { | |
return s.local.size === 0 ? new _fableCore.Choice("Choice1Of2") : new _fableCore.Choice("Choice2Of2"); | |
}; | |
var $HasRequirements$HasNoRequirements$ = exports.$HasRequirements$HasNoRequirements$ = function (s) { | |
return _fableCore.Map.forall(function (k, v) { | |
var matchValue; | |
return matchValue = v.requestType, matchValue === "required" ? false : true; | |
}, s.local) ? new _fableCore.Choice("Choice2Of2") : new _fableCore.Choice("Choice1Of2"); | |
}; | |
var $HasName$MissingName$ = exports.$HasName$MissingName$ = function (s) { | |
var matchValue; | |
return matchValue = s.name, matchValue == null ? new _fableCore.Choice("Choice2Of2") : new _fableCore.Choice("Choice1Of2"); | |
}; | |
var $AllRequiredHaveValues$SomeRequiredHaveNoValue$ = exports.$AllRequiredHaveValues$SomeRequiredHaveNoValue$ = function (s) { | |
return _fableCore.Map.forall(function (k, v) { | |
var matchValue, $target1; | |
return matchValue = [v.requestType, v.hasValues], $target1 = function () { | |
return true; | |
}, matchValue[0] === "required" ? matchValue[1] ? $target1() : false : $target1(); | |
}, s.local) ? new _fableCore.Choice("Choice1Of2") : new _fableCore.Choice("Choice2Of2"); | |
}; | |
var $ShouldBeMarkedReady$ShouldNotBeMarkedReady$ = exports.$ShouldBeMarkedReady$ShouldNotBeMarkedReady$ = function (s) { | |
var $target0, $target1, activePatternResult231, activePatternResult232, activePatternResult233, activePatternResult234; | |
return $target0 = function () { | |
return new _fableCore.Choice("Choice1Of2"); | |
}, $target1 = function () { | |
return new _fableCore.Choice("Choice2Of2"); | |
}, (activePatternResult231 = $IsEmpty$IsNotEmpty$(s), activePatternResult231.Case === "Choice1Of2" ? $target0() : (activePatternResult232 = $HasRequirements$HasNoRequirements$(s), activePatternResult232.Case === "Choice2Of2" ? $target0() : (activePatternResult233 = $HasName$MissingName$(s), activePatternResult233.Case === "Choice1Of2" ? (activePatternResult234 = $AllRequiredHaveValues$SomeRequiredHaveNoValue$(s), activePatternResult234.Case === "Choice1Of2" ? $target0() : $target1()) : $target1()))); | |
}; | |
var ValueReceivedEvent = exports.ValueReceivedEvent = function ValueReceivedEvent($arg0, $arg1) { | |
_classCallCheck(this, ValueReceivedEvent); | |
this.location = $arg0; | |
this.values = $arg1; | |
}; | |
var RequestUpdatedEvent = exports.RequestUpdatedEvent = function RequestUpdatedEvent($arg0, $arg1) { | |
_classCallCheck(this, RequestUpdatedEvent); | |
this.location = $arg0; | |
this.requestType = $arg1; | |
}; | |
var NameUpdatedEvent = exports.NameUpdatedEvent = function NameUpdatedEvent($arg0) { | |
_classCallCheck(this, NameUpdatedEvent); | |
this.name = $arg0; | |
}; | |
var EventHandler = exports.EventHandler = function EventHandler($arg0, $arg1, $arg2, $arg3) { | |
_classCallCheck(this, EventHandler); | |
this.$init = $arg0; | |
this.ValueReceived = $arg1; | |
this.RequestUpdated = $arg2; | |
this.NameUpdated = $arg3; | |
}; | |
var getLocalData = exports.getLocalData = function (x, s) { | |
return Prelude.Option.fill(new LocalData("notRequired", false), s.local.get(x)); | |
}; | |
var recalculateIsReady = exports.recalculateIsReady = function (s) { | |
var activePatternResult286; | |
return activePatternResult286 = $ShouldBeMarkedReady$ShouldNotBeMarkedReady$(s), activePatternResult286.Case === "Choice1Of2" ? new State(true, s.name, s.local) : new State(false, s.name, s.local); | |
}; | |
var updateLocalData = exports.updateLocalData = function (f, l, s) { | |
var local; | |
return recalculateIsReady((local = new Map(s.local).set(l, f(getLocalData(l, s))), new State(s.isReady, s.name, local))); | |
}; | |
var handleValueReceivedEvent = exports.handleValueReceivedEvent = function (s, e) { | |
return updateLocalData(function (l) { | |
var hasValues; | |
return hasValues = e.values.length > 0, new LocalData(l.requestType, hasValues); | |
}, e.location, s); | |
}; | |
var handleRequestUpdatedEvent = exports.handleRequestUpdatedEvent = function (s, e) { | |
return updateLocalData(function (l) { | |
return new LocalData(e.requestType, l.hasValues); | |
}, e.location, s); | |
}; | |
var handleNameUpdatedEvent = exports.handleNameUpdatedEvent = function (s, e) { | |
var name; | |
return recalculateIsReady((name = e.name, new State(s.isReady, name, s.local))); | |
}; | |
var handleIgnoringEnvelope = exports.handleIgnoringEnvelope = function (f) { | |
return function (s, env) { | |
return f(s)(env.data); | |
}; | |
}; | |
var handler = exports.handler = new EventHandler(function (unitVar0) { | |
return new State(true, null, new Map()); | |
}, handleIgnoringEnvelope(function (s) { | |
return function (e) { | |
return handleValueReceivedEvent(s, e); | |
}; | |
}), handleIgnoringEnvelope(function (s) { | |
return function (e) { | |
return handleRequestUpdatedEvent(s, e); | |
}; | |
}), handleIgnoringEnvelope(function (s) { | |
return function (e) { | |
return handleNameUpdatedEvent(s, e); | |
}; | |
})); | |
fromCategory("interestingCategory").foreachStream().when(handler); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment