Skip to content

Instantly share code, notes, and snippets.

@neoeinstein
Last active June 14, 2017 05:59
Show Gist options
  • Save neoeinstein/54e2ef5f5d724dda14d15b24b9c11bae to your computer and use it in GitHub Desktop.
Save neoeinstein/54e2ef5f5d724dda14d15b24b9c11bae to your computer and use it in GitHub Desktop.
EventStore projections from Fable. A noble attempt.
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"
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 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