<|start_filename|>src/Fable.SveltePlugins/Plugins.fs<|end_filename|>
namespace SveltePlugins

open System
open System.Text.RegularExpressions
open Fable.AST

[<assembly:Fable.ScanForPlugins>]
do()

type DeclaredTypePrinter = int -> Fable.Entity -> (* genArgs *) Fable.Type list -> string

type Helper =
    static member LowerFirst (str: string) =
            str.[0].ToString().ToLower() + str.[1..]

    static member Indent(indent, str) =
        (String.replicate (4 * indent) " ") + str

    static member PrintFunction(h, p, i, args, ?t, ?arrow) =
        let printArg (name: string, arg) =
            $"{name}: {Helper.PrintType(h, p, i, arg)}"

        let separator = if defaultArg arrow true then " => " else ": "
        let t = match t with Some t -> Helper.PrintType(h, p, i, t) | None -> "void"

        $"""({args |> List.map printArg |> String.concat ", "}){separator}{t}"""

    static member PrintType(h: Fable.PluginHelper, p: DeclaredTypePrinter, indent: int, t: Fable.Type) =
        let makeArgs argTpypes =
            argTpypes |> List.mapi (fun i t -> $"a{i}", t)

        let printRecord fields =
            [
                "{"
                for (name, t) in fields do
                    $"""{Helper.Indent(indent + 1, name)}: {Helper.PrintType(h, p, indent + 1, t)},"""
                Helper.Indent(indent, "}")
            ] |> String.concat Environment.NewLine

        match t with
        | Fable.Any -> "any"
        | Fable.Unit -> "undefined"
        | Fable.Boolean -> "boolean"
        | Fable.Char
        | Fable.String -> "string"
        | Fable.Regex -> "RegExp"
        | Fable.Enum _
        | Fable.Number _ -> "number"
        | Fable.Option t -> $"({Helper.PrintType(h, p, indent, t)}|undefined)"
        | Fable.Tuple ts -> $"""[{ts |> List.map (fun t -> Helper.PrintType(h, p, indent, t)) |> String.concat ", "}]"""
        | Fable.Array t -> Helper.PrintType(h, p, indent, t) + "[]"
        | Fable.List t -> $"Iterable<{Helper.PrintType(h, p, indent, t)}>"
        | Fable.DelegateType(args, ret)
        | AstUtils.NestedLambdaType(args, ret) ->
            Helper.PrintFunction(h, p, indent, makeArgs args, ret)
        // This is caught by the active pattern above but we need it
        // for the pattern matching to be exhaustive
        | Fable.LambdaType _ -> failwith "unexpected"
        | Fable.GenericParam name -> $"/* {name} */ any" // name // TODO: print generic args in function/type
        | Fable.DeclaredType(e, genArgs) ->
            let e = h.GetEntity(e)
            if e.IsFSharpRecord then
                e.FSharpFields |> List.map (fun f -> f.Name, f.FieldType) |> printRecord
            else
                match e.FullName, genArgs with
                | "System.Guid", _ -> "string"
                | "Microsoft.FSharp.Collections.FSharpMap`2", [k; v] ->
                    $"Iterable<[{Helper.PrintType(h, p, indent, k)}, {Helper.PrintType(h, p, indent, v)}]>"
                | _ -> p indent e genArgs
        | Fable.AnonymousRecordType(fieldNames, genArgs) -> List.zip (List.ofArray fieldNames) genArgs |> printRecord
        | Fable.MetaType -> "any" // failwith "System.Type is not supported"

    static member MakeDispatcher(helper: Fable.PluginHelper, dispatchRef, msgEntityRef, msgGenArgs) =
        let makeMember uciTag (uci: Fable.UnionCase) =
            let outerArgs =
                uci.UnionCaseFields
                |> List.map (fun fi -> AstUtils.makeIdent fi.Name)

            let msgArgs = outerArgs |> List.map Fable.IdentExpr
            let msg = Fable.Value(Fable.NewUnion(msgArgs, uciTag, msgEntityRef, msgGenArgs), None)
            let dispatchAction = AstUtils.makeCall (Fable.IdentExpr dispatchRef) [msg]

            Helper.LowerFirst uci.Name, Fable.Delegate(outerArgs, dispatchAction, None)

        helper.GetEntity(msgEntityRef).UnionCases
        |> List.mapi makeMember
        |> AstUtils.objExpr

type DispatcherAttribute() =
    inherit Fable.MemberDeclarationPluginAttribute()
    override _.FableMinimumVersion = "3.1.15"
    override _.Transform(_,_,decl) = decl
    override _.TransformCall(helper, _member, expr) =
        let fail() =
            helper.LogError("Cannot generate dispatcher", ?range=expr.Range)
            expr

        match expr with
        | Fable.Call(_,info,t,_) ->
            match info.Args with
            | [dispatch] ->
                match dispatch.Type.Generics with
                | Fable.DeclaredType(msgEntityRef, msgGenArgs)::_ ->
                    let bindDispatch, dispatchRef =
                        match dispatch with
                        | Fable.IdentExpr ident -> false, ident
                        | _ -> true, AstUtils.makeIdent "$dispatch"

                    let dispatcher = Helper.MakeDispatcher(helper, dispatchRef, msgEntityRef, msgGenArgs)
                    let dispatcher =
                        if bindDispatch then Fable.Let(dispatchRef, dispatch, dispatcher)
                        else dispatcher
                    Fable.TypeCast(dispatcher, t, None)
                | _ -> fail()
            | _ -> fail()
        | _ -> fail()

type GenerateDeclarationAttribute() =
    inherit Fable.MemberDeclarationPluginAttribute()
    override _.FableMinimumVersion = "3.0.0"
    override _.TransformCall(_,_,expr) = expr
    override _.Transform(h, _, decl) =
        let rec printer indent (e: Fable.Entity) genArgs =
            match e.FullName, genArgs with
            | "SvelteStore.IDispatcher`1", [Fable.DeclaredType(e, _)] ->
                let e = h.GetEntity(e)
                [
                    "{"
                    for uci in e.UnionCases do
                        let args = uci.UnionCaseFields |> List.map (fun f -> f.Name, f.FieldType)
                        let fn = Helper.PrintFunction(h, printer, indent, args, arrow=false)
                        $"""{Helper.Indent(indent + 1, Helper.LowerFirst uci.Name)}{fn},"""
                    Helper.Indent(indent, "}")

                ] |> String.concat Environment.NewLine

            | "SvelteStore.IWritable`1", [t]
            | "SvelteStore.IReadable`1", [t] ->
                $"Readable<{Helper.PrintType(h, printer, indent, t)}>"
            | fullname, _ -> $"/* {fullname} */ any"

        let args, returnType =
            if decl.Info.IsValue then
                match decl.Body.Type with
                | AstUtils.NestedLambdaType(args, ret) ->
                    args |> List.mapi (fun i t -> $"a{i}", t), ret
                | _ -> failwith "Only functions are supported at the moment"
            else
                decl.Args |> List.map (fun a -> a.Name, a.Type), decl.Body.Type

        let args =
            match args with
            | [_, Fable.Unit] -> []
            | args -> args

        let path = Regex.Replace(h.GetOutputPath(), @"\.js$", ".d.ts")
        let text = $"""import {{ Readable }} from "svelte/store";

export function {decl.Name}{Helper.PrintFunction(h, printer, 0, args, returnType, arrow=false)};"""
        IO.File.WriteAllText(path, text)
        decl


<|start_filename|>samples/App/src/Util.fs<|end_filename|>
module Util

open System

type Dispatch<'T> = ('T->'T) -> unit

type SingleObservable<'T>() =
    let mutable listener: IObserver<'T> option = None
    member _.Trigger v =
        match listener with
        | None -> ()
        | Some lis -> lis.OnNext v
    interface IObservable<'T> with
        member _.Subscribe w =
            listener <- Some w
            { new IDisposable with
                member _.Dispose() =
                    listener <- None }

type Async with
    static member AwaitObservable(obs: IObservable<'T>) = 
        Async.FromContinuations( fun (cont,_,_) ->
            let mutable disp: IDisposable = Unchecked.defaultof<_>
            disp <- obs.Subscribe(fun v ->
                disp.Dispose()
                cont(v))            
        )


<|start_filename|>samples/FelizSvelte/src/App.fs<|end_filename|>
module App

open Feliz
open Feliz.Router
open Feliz.Svelte

[<SvelteComponent "./TodoMVC.svelte">]
let TodoMvc() = React.imported()

[<SvelteComponent "./Dragging.svelte">]
let Dragging() = React.imported()

[<ReactComponent>]
let Index() = React.fragment [
    Html.h1 "Feliz ❤️ Svelte (and Lit!)"
    Html.p "Easily embed Svelte components inside Feliz applications"
    Html.ul [
        Html.li [
            Html.a [
                prop.href (Router.format "todo")
                prop.text "/todo"
            ]
        ]

        Html.li [
            Html.a [
                prop.href (Router.format "dragging")
                prop.text "/dragging"
            ]
        ]

        Html.li [
            Html.a [
                prop.href (Router.format "clock")
                prop.text "/clock"
            ]
        ]
    ]
    TimeFliesElmish.TimeFliesElmish "Time flies, hurry up!"
]

[<ReactComponent>]
let Application() =
    let currentUrl, setUrl = React.useState(Router.currentUrl())
    React.router [
        router.onUrlChanged setUrl
        router.children [
            match currentUrl with
            | [ ] -> Index()
            | [ "todo" ] -> TodoMvc()
            | [ "dragging" ] -> Dragging()
            | [ "clock" ] -> Clock.Clock()
            | _ -> Html.h1 "Not found"
        ]
    ]


<|start_filename|>samples/App/src/Dragging/Dragging.fs<|end_filename|>
module Dragging

open System
open Fable.Core
open Util

type Msg =
    | MouseDown of x:float * y:float * offsetX:float * offsetY:float
    | MouseMove of x:float * y:float
    | MouseUp

type Model =
    { position: float*float
      offset: float*float }

let rec draggingLoop (stream: IObservable<_>) = async {
    let! msg, dispatch = Async.AwaitObservable(stream)

    match msg with
    | MouseMove(x, y) ->
        dispatch(fun model -> { model with position=(x, y) })
        return! draggingLoop stream

    // If the left button is up, we don't do anything so we leave the dragging loop
    | MouseUp -> ()

    // Stay within the loop without changing the state
    | _ -> return! draggingLoop stream
}

let rec waitingLoop (stream: IObservable<_>) = async {
    let! msg, dispatch = Async.AwaitObservable(stream)

    match msg with
    | MouseDown(x, y, offsetX, offsetY) ->
        dispatch(fun _ -> { position = (x, y)
                            offset = (offsetX, offsetY) })
        do! draggingLoop stream
    | _ -> ()

    return! waitingLoop stream
}

[<SveltePlugins.GenerateDeclaration>]
let makeStore() =
    let stream = SingleObservable()
    let tcs = new Threading.CancellationTokenSource()

    let init () =
        Async.StartImmediate(waitingLoop stream, tcs.Token)
        { position=(0.,50.); offset=(0.,0.) }

    let dispose _ = tcs.Cancel()
    let store = SvelteStore.make init dispose ()
    let dispatch msg = stream.Trigger(msg, store.update)

    store, SvelteStore.makeDispatcher dispatch


<|start_filename|>samples/FelizSvelte/src/Main.fs<|end_filename|>
﻿module Main

open Feliz
open Browser.Dom

ReactDOM.render(App.Application(), document.getElementById("app-container"))

<|start_filename|>samples/FelizSvelte/src/Feliz.Store.fs<|end_filename|>
module Feliz.Store

open System
open Fable.Core
open Feliz

let disposable f =
    { new IDisposable with
        member _.Dispose() = f() }

type Store<'Model>(init: unit -> 'Model, dispose: 'Model -> unit) =
    let mutable uid = 0
    let mutable model = Unchecked.defaultof<_>
    let subscribers = Collections.Generic.Dictionary<_, IObserver<'Model>>()

    member _.Update(f: 'Model->'Model) =
        if subscribers.Count > 0 then
            let newModel = f model
            if not (obj.ReferenceEquals(model, newModel)) then
                model <- newModel
                subscribers.Values |> Seq.iter (fun s -> s.OnNext(model))

    member _.SubscribeImmediate(observer: IObserver<'Model>): 'Model * IDisposable =
        if subscribers.Count = 0 then
            model <- init()
        let id = uid
        uid <- uid + 1
        subscribers.Add(id, observer)
        model, disposable(fun () ->
            if subscribers.Remove(id) && subscribers.Count = 0 then
                dispose model
                model <- Unchecked.defaultof<_>)

    member this.SubscribeImmediate(observer: 'Model -> unit): 'Model * IDisposable =
        let observer =
            { new IObserver<_> with
                member _.OnNext(value) = observer value
                member _.OnCompleted() = ()
                member _.OnError(_error: exn) = () }
        this.SubscribeImmediate(observer)

    interface IObservable<'Model> with
        member this.Subscribe(observer: IObserver<'Model>): IDisposable =
            this.SubscribeImmediate(observer) |> snd

type DispatchStore<'Msg, 'Model>(init: unit -> 'Model, dispose: 'Model -> unit, dispatch: 'Msg -> unit) =
    inherit Store<'Model>(init, dispose)
    member _.Dispatch(msg) = dispatch msg

let useStore (store: Store<'Model>) =
    let mutable _disp: IDisposable = Unchecked.defaultof<_>
    let _setState: ('Model->unit) ref = ref Unchecked.defaultof<_>
    let state, setState = React.useState(fun () ->
        let model, disp = store.SubscribeImmediate(fun v -> _setState.contents v)
        _disp <- disp
        model)
    _setState := setState
    React.useEffect((fun () ->
        disposable(fun () -> _disp.Dispose())), [||])
    state

let useDispatchStore (store: DispatchStore<'Msg, 'Model>) =
    let mutable _disp: IDisposable = Unchecked.defaultof<_>
    let _setState: ('Model->unit) ref = ref Unchecked.defaultof<_>
    let state, setState = React.useState(fun () ->
        let model, disp = store.SubscribeImmediate(fun v -> _setState.contents v)
        _disp <- disp
        model)
    _setState := setState
    React.useEffect((fun () ->
        disposable(fun () -> _disp.Dispose())), [||])
    state, store.Dispatch

type Cmd<'Value, 'Msg> = (('Msg -> unit) -> unit) list

let makeElmishStore
        (init: unit -> 'Model * Cmd<'Value, 'Msg>)
        (update: 'Msg -> 'Model -> 'Model * Cmd<'Value, 'Msg>)
        (dispose: 'Model -> unit) =

    let mutable dispatch = Unchecked.defaultof<_>
    let mutable store: DispatchStore<_,_> = Unchecked.defaultof<_>

    let handleCmds = function
        | [] -> ()
        | cmds ->
            cmds
            |> List.map (fun cmd ->
                Promise.create(fun resolve _reject ->
                    cmd (dispatch >> resolve))
            )
            |> Promise.Parallel
            |> Promise.start

    dispatch <- fun msg ->
        let mutable _cmds = []
        store.Update(fun model ->
            let model, cmds = update msg model
            _cmds <- cmds
            model)
        handleCmds _cmds

    let init () =
        let model, cmds = init()
        JS.setTimeout (fun () -> handleCmds cmds) 0 |> ignore
        model

    store <- DispatchStore(init, dispose, dispatch)

    store

let makeProjectedStore
    (project: 'Model1 -> 'Model2)
    (dispatch: 'Msg2 -> 'Msg1)
    (store: DispatchStore<'Msg1, 'Model1>) =

    let mutable _disp: IDisposable = Unchecked.defaultof<_>
    let mutable projectedStore: DispatchStore<'Msg2, 'Model2> = Unchecked.defaultof<_>

    let init() =
        let model, disp = store.SubscribeImmediate(fun v ->
            projectedStore.Update(fun _ -> project v))
        _disp <- disp
        project model

    projectedStore <- DispatchStore(init, (fun _ -> _disp.Dispose()), dispatch >> store.Dispatch)
    projectedStore


<|start_filename|>src/Fable.ReactStore/Fable.ReactStore.fs<|end_filename|>
[<RequireQualifiedAccess>]
module ReactStore

open System
open Fable
open Fable.Core
open ElmishStore

type Dispose = delegate of unit -> unit

type ReactRef<'Value> =
    abstract current: 'Value with get, set

[<ImportMember("react")>]
let private useState(f: unit -> 'State): 'State * ('State -> unit) = jsNative

[<ImportMember("react")>]
let private useEffect(f: unit -> Dispose, deps: obj[]): unit = jsNative

[<ImportMember("react")>]
let private useRef(v: 'Value): ReactRef<'Value> = jsNative


let private useStateWithDisposable (f) =
    let _disp = useRef Unchecked.defaultof<IDisposable>
    let _setState = useRef Unchecked.defaultof<('Value -> unit) option>

    let state, setState = useState(fun () ->
        let initState, disp = f _setState
        _disp.current <- disp
        initState)

    useEffect((fun () ->
        _setState.current <- Some setState
        Dispose(fun () ->
            _setState.current <- None
            _disp.current.Dispose()
        )), [||])

    state

let useObservable (obs: IObservable<'Value>) =
    useStateWithDisposable (fun setState ->
        obs |> Store.subscribeImmediate(fun v ->
            setState.current |> Option.iter (fun f -> f v)))

let useStoreLazy (init: unit -> IStore<'Value>): 'Value * StoreUpdate<'Value> =
    let _store = useRef Unchecked.defaultof<_>

    let state = useStateWithDisposable (fun setState ->
        _store.current <- init()
        _store.current |> Store.subscribeImmediate(fun v ->
            setState.current |> Option.iter (fun f -> f v)))

    state, fun f -> _store.current.Update(f)

let useElmishStore (init: 'Props -> 'Value * Cmd<'Value, 'Msg>)
                   (update: 'Msg -> 'Value -> 'Value * Cmd<'Value, 'Msg>)
                   (dispose: 'Value -> unit)
                   (props: 'Props): 'Value * Dispatch<'Msg> =

    let _dispatch = useRef Unchecked.defaultof<_>

    let state = useStateWithDisposable (fun setState ->
        let store, dispatch = Store.makeElmish init update dispose props
        _dispatch.current <- dispatch
        store |> Store.subscribeImmediate(fun v ->
            setState.current |> Option.iter (fun f -> f v)))

    state, fun msg -> _dispatch.current msg

let useElmishStoreSimple (init: 'Props -> 'Value)
                         (update: 'Msg -> 'Value -> 'Value)
                         (dispose: 'Value -> unit)
                         (props: 'Props): 'Value * Dispatch<'Msg> =

    let init p = init p, []
    let update m v = update m v, []
    useElmishStore init update dispose props


<|start_filename|>src/Fable.SveltePlugins/AstUtils.fs<|end_filename|>
module internal AstUtils

open Fable
open Fable.AST
open System
open System.Linq
open System.Text.RegularExpressions

let (|NestedLambdaType|_|) e =
    let rec nestedLambda acc = function
        | Fable.LambdaType(arg, returnType) ->
            nestedLambda (arg::acc) returnType
        | returnType -> List.rev acc, returnType
    match e with
    | Fable.LambdaType(arg, t) -> nestedLambda [arg] t |> Some
    | _ -> None

let cleanFullDisplayName str =
    Regex.Replace(str, @"`\d+", "").Replace(".", "_")

let makeIdent name: Fable.Ident =
    { Name = name
      Type = Fable.Any
      IsCompilerGenerated = true
      IsThisArgument = false
      IsMutable = false
      Range = None }

let makeValue r value =
    Fable.Value(value, r)

let makeStrConst (x: string) =
    Fable.StringConstant x
    |> makeValue None

let nullValue = Fable.Expr.Value(Fable.ValueKind.Null(Fable.Type.Any), None)

let emitJs macro args  =
    let callInfo: Fable.CallInfo =
        { ThisArg = None
          Args = args
          SignatureArgTypes = []
          HasSpread = false
          IsJsConstructor = false
          CallMemberInfo = None }

    let emitInfo : Fable.AST.Fable.EmitInfo =
        { Macro = macro
          IsJsStatement = false
          CallInfo = callInfo }

    Fable.Expr.Emit(emitInfo, Fable.Type.Any, None)

let rec flattenList (head: Fable.Expr) (tail: Fable.Expr) =
    [
        yield head;
        match tail with
        | Fable.Expr.Value (value, range) ->
            match value with
            | Fable.ValueKind.NewList(Some(nextHead, nextTail), listType) ->
                yield! flattenList nextHead nextTail
            | Fable.ValueKind.NewList(None, listType) ->
                yield! [ ]
            | _ ->
                yield! [ Fable.Expr.Value (value, range) ]

        | _ ->
            yield! [ ]
    ]

let makeImport (selector: string) (path: string) =
    Fable.Import({ Selector = selector
                   Path = path
                   IsCompilerGenerated = true }, Fable.Any, None)

let isRecord (compiler: PluginHelper) (fableType: Fable.Type) =
    match fableType with
    | Fable.Type.AnonymousRecordType _ -> true
    | Fable.Type.DeclaredType (entity, genericArgs) -> compiler.GetEntity(entity).IsFSharpRecord
    | _ -> false

let isPascalCase (input: string) = not (String.IsNullOrWhiteSpace input) && List.contains input.[0] ['A' .. 'Z']
let isCamelCase (input: string) = not (isPascalCase input)

let isAnonymousRecord (fableType: Fable.Type) =
    match fableType with
    | Fable.Type.AnonymousRecordType  _ -> true
    | _ -> false

let isReactElement (fableType: Fable.Type) =
    match fableType with
    | Fable.Type.DeclaredType (entity, genericArgs) -> entity.FullName.EndsWith "ReactElement"
    | _ -> false

let recordHasField name (compiler: PluginHelper) (fableType: Fable.Type) =
    match fableType with
    | Fable.Type.AnonymousRecordType (fieldNames, genericArgs) ->
        fieldNames
        |> Array.exists (fun field -> field = name)

    | Fable.Type.DeclaredType (entity, genericArgs) ->
        compiler.GetEntity(entity).FSharpFields
        |> List.exists (fun field -> field.Name = name)

    | _ ->
        false

let makeCall callee args =
    let callInfo: Fable.CallInfo =
        { ThisArg = None
          Args = args
          SignatureArgTypes = []
          HasSpread = false
          IsJsConstructor = false
          CallMemberInfo = None }
    Fable.Call(callee, callInfo, Fable.Any, None)

type MemberInfo(?info: Fable.MemberInfo,
                ?isValue: bool) =
    let infoOr f v =
        match info with
        | Some i -> f i
        | None -> v
    let argOrInfoOr arg f v =
        match arg, info with
        | Some arg, _ -> arg
        | None, Some i -> f i
        | None, None -> v
    interface Fable.MemberInfo with
        member _.IsValue = argOrInfoOr isValue (fun i -> i.IsValue) false
        member _.Attributes = infoOr (fun i -> i.Attributes) Seq.empty
        member _.HasSpread = infoOr (fun i -> i.HasSpread) false
        member _.IsPublic = infoOr (fun i -> i.IsPublic) true
        member _.IsInstance = infoOr (fun i -> i.IsInstance) true
        member _.IsMutable = infoOr (fun i -> i.IsMutable) false
        member _.IsGetter = infoOr (fun i -> i.IsGetter) false
        member _.IsSetter = infoOr (fun i -> i.IsSetter) false
        member _.IsEnumerator = infoOr (fun i -> i.IsEnumerator) false
        member _.IsMangled = infoOr (fun i -> i.IsMangled) false

let objValue (k, v): Fable.MemberDecl =
    {
        Name = k
        FullDisplayName = k
        Args = []
        Body = v
        UsedNames = Set.empty
        Info = MemberInfo(isValue=true)
        ExportDefault = false
    }


let objExpr kvs = Fable.ObjectExpr(List.map objValue kvs, Fable.Any, None)

let capitalize (input: string) =
    if String.IsNullOrWhiteSpace input
    then ""
    else input.First().ToString().ToUpper() + String.Join("", input.Skip(1))

let camelCase (input: string) =
    if String.IsNullOrWhiteSpace input
    then ""
    else input.First().ToString().ToLower() + String.Join("", input.Skip(1))


<|start_filename|>package-lock.json<|end_filename|>
{
  "name": "Fable.Svelte",
  "lockfileVersion": 2,
  "requires": true,
  "packages": {
    "": {
      "devDependencies": {
        "fable-publish-utils": "^2.0.0"
      }
    },
    "node_modules/fable-publish-utils": {
      "version": "2.0.0",
      "resolved": "https://registry.npmjs.org/fable-publish-utils/-/fable-publish-utils-2.0.0.tgz",
      "integrity": "<KEY>
      "dev": true
    }
  },
  "dependencies": {
    "fable-publish-utils": {
      "version": "2.0.0",
      "resolved": "https://registry.npmjs.org/fable-publish-utils/-/fable-publish-utils-2.0.0.tgz",
      "integrity": "<KEY>
      "dev": true
    }
  }
}


<|start_filename|>src/Fable.Store/Types.fs<|end_filename|>
namespace Fable

open System

type StoreUpdate<'Value> = ('Value -> 'Value) -> unit

type StoreCons<'Value, 'Store> = 'Value -> ('Value -> unit) -> 'Store * StoreUpdate<'Value>

type IStore<'Value> =
    inherit IDisposable
    inherit IObservable<'Value>
    abstract Update: f:('Value -> 'Value) -> unit

type Store<'Value>(initValue: 'Value,  ?dispose: 'Value -> unit) =
    let mutable _uid = 0
    let mutable _disposed = false
    let mutable _value = initValue
    let subscribers = Collections.Generic.Dictionary<_, IObserver<'Value>>()

    member _.Dispose() =
        if not _disposed then
            subscribers.Clear()
            match dispose with Some d -> d _value | None -> ()

    member _.Update(f: 'Value -> 'Value) =
        _value <- f _value
        subscribers.Values
        |> Seq.iter (fun s -> s.OnNext(_value))

    member this.Subscribe(observer: IObserver<'Value>): IDisposable =
        if _disposed then
            failwith "The store has already been disposed"
        else
            // Immediately report current value
            observer.OnNext(_value)

            let id = _uid
            _uid <- _uid + 1
            subscribers.Add(id, observer)

            { new IDisposable with
                member _.Dispose() =
                    if subscribers.Remove(id) && subscribers.Count = 0 then
                        this.Dispose() }

    interface IStore<'Value> with
        member this.Dispose() = this.Dispose()
        member this.Update(f) = this.Update(f)
        member this.Subscribe(observer: IObserver<'Value>) = this.Subscribe(observer)


<|start_filename|>samples/App/src/TodoMVC/TodoMVC.fs<|end_filename|>
module TodoMVC

open System
open Fable.Core
open Browser

type Entry =
    { description : string
      completed : bool
      id : Guid }

type Model =
    { entries : Entry[] }

type Msg =
    | Add of text: string
    | Delete of id: Guid
    | Check of id: Guid * completed: bool

let newEntry desc =
  { description = desc
    completed = false
    id = Guid.NewGuid() }

let init() =
  let todos =
    match localStorage.getItem("svelte-todos") with
    | null | "" -> [||]
    | json -> JS.JSON.parse(json) :?> Entry[]
  { entries = todos }

let saveModel (model: Model) =
  localStorage.setItem("svelte-todos", JS.JSON.stringify(model.entries))
  model

let update (msg:Msg) (model:Model) =
    match msg with
    | Add field ->
        let xs = if String.IsNullOrEmpty field then model.entries
                 else Array.append model.entries [|newEntry field |]
        { model with entries = xs } |> saveModel

    | Delete id ->
        { model with entries = Array.filter (fun t -> t.id <> id) model.entries }
        |> saveModel

    | Check (id,isCompleted) ->
        let updateEntry t =
          if t.id = id then { t with completed = isCompleted } else t
        { model with entries = Array.map updateEntry model.entries }
        |> saveModel

[<SveltePlugins.GenerateDeclaration>]
let makeStore () =
  let store, dispatch = SvelteStore.makeElmishSimple init update ignore ()
  store, SvelteStore.makeDispatcher dispatch


<|start_filename|>samples/App/src/TimeFlies/TimeFlies.fs<|end_filename|>
module TimeFlies

open System
open Fable.Reaction
open FSharp.Control
open Browser

type Letter = { char: string; x: int; y: int }

type Model =
    { letters: Map<int, Letter>
      fps: int
      second: float
      count: int
      text: string }

type Msg = Letter of int * string * int * int

let init txt: Model =
    { letters = Map.empty
      count = 0
      second = 0.
      fps = 0
      text = txt }

let getOffset (element: Browser.Types.Element) =
    let doc = element.ownerDocument
    let docElem = doc.documentElement
    let clientTop = docElem.clientTop
    let clientLeft = docElem.clientLeft
    let scrollTop = window.pageYOffset
    let scrollLeft = window.pageXOffset

    int (scrollTop - clientTop), int (scrollLeft - clientLeft)

let startStream (update: Model -> unit) (model: Model) =
    let container = document.body
    let top, left = getOffset container

    asyncRx {
        let chars =
            Seq.toList model.text
            |> Seq.mapi (fun i c -> i, c)

        let! i, c = AsyncRx.ofSeq chars

        yield!
            AsyncRx.ofMouseMove ()
            |> AsyncRx.delay (100 * i)
            |> AsyncRx.requestAnimationFrame
            |> AsyncRx.map (fun m -> Letter(i, string c, int m.clientX + i * 10 + 15 - left, int m.clientY - top))
    }
    |> AsyncRx.toObservable
    |> Observable.scan
        (fun (model: Model) (Letter (i, c, x, y)) ->
            let second =
                DateTimeOffset.Now.ToUnixTimeSeconds() |> float

            { model with
                  letters = Map.add i { char = c; x = x; y = y } model.letters
                  second = second
                  fps = if second > model.second then model.count else model.fps
                  count = if second > model.second then 0 else model.count + 1 })
        model
    |> Observable.subscribe update

[<SveltePlugins.GenerateDeclaration>]
let makeStore = SvelteStore.makeRec <| fun store (txt: string) ->
    let update m = store.update(fun _ -> m)
    let v = init txt
    v, startStream update v


<|start_filename|>package.json<|end_filename|>
{
  "private": true,
  "devDependencies": {
    "fable-publish-utils": "^2.0.0"
  },
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  }
}


<|start_filename|>samples/FelizSvelte/src/Util.fs<|end_filename|>
module Util

open System

type Dispatch<'T> = ('T->'T) -> unit

type SingleObservable<'T>() =
    let mutable listener: IObserver<'T> option = None
    member _.Trigger v =
        match listener with
        | None -> ()
        | Some lis -> lis.OnNext v
    interface IObservable<'T> with
        member _.Subscribe w =
            listener <- Some w
            { new IDisposable with
                member _.Dispose() =
                    listener <- None }

type Async with
    static member AwaitObservable(obs: IObservable<'T>) =
        Async.FromContinuations( fun (cont,_,_) ->
            let mutable disp: IDisposable = Unchecked.defaultof<_>
            disp <- obs.Subscribe(fun v ->
                disp.Dispose()
                cont(v))
        )

type Time =
    | Hour of int
    | Minute of int
    | Second of int
    member this.Value =
        match this with
        | Hour n -> n
        | Second n -> n
        | Minute n -> n

    member this.ClockPercentage =
        (float this.Value) / this.FullRound

    member this.Stroke =
        match this with
        | Hour _ -> "lightgreen"
        | Minute _ -> "white"
        | Second _ -> "#023963"

    member this.StrokeWidth =
        match this with
        | Hour _ | Minute _ -> 2
        | Second _ -> 1

    member this.Length =
        match this with
        | Hour _ -> 25.
        | Minute _ -> 35.
        | Second _ -> 40.

    member this.FullRound =
        match this with
        | Hour _ -> 12.
        | Second _ | Minute _ -> 60.

type System.DateTime with
    member this.AsHour = Hour this.Hour
    member this.AsMinute = Minute this.Minute
    member this.AsSecond = Second this.Second

<|start_filename|>src/Fable.Store/Store.fs<|end_filename|>
[<RequireQualifiedAccessAttribute>]
module Store

open System
open Fable
open ElmishStore

let subscribeImmediate (cb: 'Value -> unit) (obs: IObservable<'Value>) =
    let mutable initValue = None
    let disp = obs.Subscribe(fun v ->
        match initValue with
        | None -> initValue <- Some v
        | Some _ -> cb v)

    match initValue with
    | None -> failwith "Observable doesn't report value immediately upon subscription"
    | Some v -> v, disp

let makeWithCons (init: 'Props -> 'Value)
                 (dispose: 'Value -> unit)
                 (cons: StoreCons<'Value, 'Store>)
                 (props: 'Props): 'Store =
    let value = init props
    let store, _ = cons value dispose
    store

let private makeCons i d =
    let s = new Store<_>(i, d)
    s :> IStore<'Value>, s.Update

let make (init: 'Props -> 'Value) (dispose: 'Value -> unit) (props: 'Props): IStore<'Value> =
    makeWithCons init dispose makeCons props

let makeElmishWithCons (init: 'Props -> 'Value * Cmd<'Value, 'Msg>)
                       (update: 'Msg -> 'Value -> 'Value * Cmd<'Value, 'Msg>)
                       (dispose: 'Value -> unit)
                       (cons: StoreCons<'Value, 'Store>)
                       (props: 'Props): 'Store * Dispatch<'Msg> =

    let mutable _cmdHandler = Unchecked.defaultof<Helpers.CmdHandler<'Value, 'Msg>>

    let initValue, initCmd = init props

    let store, storeUpdate = cons initValue (fun m ->
        _cmdHandler.Dispose()
        dispose m)

    let dispatch msg =
        let mutable _cmds = []
        storeUpdate(fun model ->
            let model, cmds = update msg model
            _cmds <- cmds
            model)
        _cmdHandler.Handle _cmds

    _cmdHandler <- Helpers.cmdHandler storeUpdate dispatch
    _cmdHandler.Handle initCmd

    store, dispatch

let makeElmish (init: 'Props -> 'Value * Cmd<'Value, 'Msg>)
               (update: 'Msg -> 'Value -> 'Value * Cmd<'Value, 'Msg>)
               (dispose: 'Value -> unit)
               (props: 'Props): IStore<'Value> * Dispatch<'Msg> =

    makeElmishWithCons init update dispose makeCons props


<|start_filename|>src/Fable.Store/ElmishStore.fs<|end_filename|>
namespace ElmishStore

open System
open Fable

type Dispatch<'Msg> = 'Msg -> unit

type Cmd<'Value, 'Msg> = (StoreUpdate<'Value> -> Dispatch<'Msg> -> unit) list

module internal Helpers =
    type CmdHandler<'Value, 'Msg>(handler, ?dispose) =
        member _.Handle(cmd: Cmd<'Value, 'Msg>): unit = handler cmd
        member _.Dispose() = match dispose with Some d -> d () | None -> ()
        interface IDisposable with
            member this.Dispose() = this.Dispose()

#if FABLE_COMPILER
    open Fable.Core

    let cmdHandler (update: StoreUpdate<'Value>) (dispatch: 'Msg -> unit): CmdHandler<'Value, 'Msg> =
        new CmdHandler<_, _>(List.iter (fun cmd ->
            JS.setTimeout (fun _ -> cmd update dispatch) 0 |> ignore))
#else
    let cmdHandler (update: StoreUpdate<'Value>) (dispatch: 'Msg -> unit): CmdHandler<'Value, 'Msg> =
        let cts = new Threading.CancellationTokenSource()

        let mb = MailboxProcessor.Start(fun inbox -> async {
            while true do
                match! inbox.Receive() with
                | Choice1Of2 updater -> update updater
                | Choice2Of2 msg -> dispatch msg
        }, cts.Token)

        new CmdHandler<_,_>(List.iter (fun cmd -> cmd (Choice1Of2 >> mb.Post) (Choice2Of2 >> mb.Post)), fun _ -> cts.Cancel())
#endif

[<RequireQualifiedAccess>]
module Cmd =
    // /// Execute the commands using the supplied dispatcher
    // let internal exec onError update (dispatch: Dispatch<'msg>) (cmd: Cmd<'value, 'msg>) =
    //     cmd |> List.iter (fun call -> try call update dispatch with ex -> onError ex)

    /// None - no commands, also known as `[]`
    let none : Cmd<'value, 'msg> =
        []

    /// When emitting the message, map to another type
    let map (f: 'a -> 'msg) (cmd: Cmd<'value, 'a>) : Cmd<'value, 'msg> =
        cmd |> List.map (fun g -> (fun update dispatch -> g update (f >> dispatch)))

    /// Aggregate multiple commands
    let batch (cmds: #seq<Cmd<'value, 'msg>>) : Cmd<'value, 'msg> =
        cmds |> List.concat

    module OfFunc =
        /// Command to evaluate a simple function and map the result
        /// into success or error (of exception)
        let either (task: 'a -> _) (arg: 'a) (ofSuccess: _ -> 'msg) (ofError: _ -> 'msg) : Cmd<'value, 'msg> =
            let bind _update dispatch =
                try
                    task arg
                    |> (ofSuccess >> dispatch)
                with x ->
                    x |> (ofError >> dispatch)
            [bind]

        /// Command to evaluate a simple function and map the success to a message
        /// discarding any possible error
        let perform (task: 'a -> _) (arg: 'a) (ofSuccess: _ -> 'msg) : Cmd<'value, 'msg> =
            let bind _update dispatch =
                try
                    task arg
                    |> (ofSuccess >> dispatch)
                with x ->
                    ()
            [bind]

        /// Command to evaluate a simple function and map the error (in case of exception)
        let attempt (task: 'a -> unit) (arg: 'a) (ofError: _ -> 'msg) : Cmd<'value, 'msg> =
            let bind _update dispatch =
                try
                    task arg
                with x ->
                    x |> (ofError >> dispatch)
            [bind]

        /// Command to issue a specific message
        let result (msg:'msg) : Cmd<'value, 'msg> =
            [fun _update dispatch -> dispatch msg]

    module OfAsyncWith =
        /// Command that will evaluate an async block and update the model on succes
        /// or report a message on error
        let update (start: Async<unit> -> unit)
                   (task: 'a -> Async<'result>)
                   (arg: 'a)
                   (onSuccess: 'result -> 'model -> 'model)
                   (onError: exn -> 'msg) : Cmd<'model, 'msg> =
            let bind update dispatch =
                async {
                    let! r = task arg |> Async.Catch
                    match r with
                    | Choice1Of2 x -> update (onSuccess x)
                    | Choice2Of2 x -> onError x |> dispatch
                }
            [fun u d -> bind u d |> start]

        /// Command that will evaluate an async block and map the result
        /// into success or error (of exception)
        let either (start: Async<unit> -> unit)
                   (task: 'a -> Async<_>)
                   (arg: 'a)
                   (ofSuccess: _ -> 'msg)
                   (ofError: _ -> 'msg) : Cmd<'value, 'msg> =
            let bind _update dispatch =
                async {
                    let! r = task arg |> Async.Catch
                    dispatch (match r with
                             | Choice1Of2 x -> ofSuccess x
                             | Choice2Of2 x -> ofError x)
                }
            [fun u d -> bind u d |> start]

        /// Command that will evaluate an async block and map the success
        let perform (start: Async<unit> -> unit)
                    (task: 'a -> Async<_>)
                    (arg: 'a)
                    (ofSuccess: _ -> 'msg) : Cmd<'value, 'msg> =
            let bind _update dispatch =
                async {
                    let! r = task arg |> Async.Catch
                    match r with
                    | Choice1Of2 x -> dispatch (ofSuccess x)
                    | _ -> ()
                }
            [fun u d -> bind u d |> start]

        /// Command that will evaluate an async block and map the error (of exception)
        let attempt (start: Async<unit> -> unit)
                    (task: 'a -> Async<_>)
                    (arg: 'a)
                    (ofError: _ -> 'msg) : Cmd<'value, 'msg> =
            let bind _update dispatch =
                async {
                    let! r = task arg |> Async.Catch
                    match r with
                    | Choice2Of2 x -> dispatch (ofError x)
                    | _ -> ()
                }
            [fun u d -> bind u d |> start]

        /// Command that will evaluate an async block to the message
        let result (start: Async<unit> -> unit)
                   (task: Async<'msg>) : Cmd<'value, 'msg> =
            let bind _update dispatch =
                async {
                    let! r = task
                    dispatch r
                }
            [fun u d -> bind u d |> start]

    module OfAsync =
#if FABLE_COMPILER
        let start x = Async.StartImmediate x
#else
        let inline start x = Async.Start x
#endif

        /// Command that will evaluate an async block and update the model on succes
        /// or report a message on error
        let inline update (task: 'a -> Async<'result>)
                          (arg: 'a)
                          (onSuccess: 'result -> 'model -> 'model)
                          (onError: exn -> 'msg) : Cmd<'model, 'msg> =
            OfAsyncWith.update start task arg onSuccess onError

        /// Command that will evaluate an async block and map the result
        /// into success or error (of exception)
        let inline either (task: 'a -> Async<_>)
                          (arg: 'a)
                          (ofSuccess: _ -> 'msg)
                          (ofError: _ -> 'msg) : Cmd<'value, 'msg> =
            OfAsyncWith.either start task arg ofSuccess ofError

        /// Command that will evaluate an async block and map the success
        let inline perform (task: 'a -> Async<_>)
                           (arg: 'a)
                           (ofSuccess: _ -> 'msg) : Cmd<'value, 'msg> =
            OfAsyncWith.perform start task arg ofSuccess

        /// Command that will evaluate an async block and map the error (of exception)
        let inline attempt (task: 'a -> Async<_>)
                           (arg: 'a)
                           (ofError: _ -> 'msg) : Cmd<'value, 'msg> =
            OfAsyncWith.attempt start task arg ofError

        /// Command that will evaluate an async block to the message
        let inline result (task: Async<'msg>) : Cmd<'value, 'msg> =
            OfAsyncWith.result start task


<|start_filename|>samples/FelizSvelte/src/Clock.fs<|end_filename|>
module Clock

open System
open Fable.Core
open Fable.React
open Feliz
open Util
open Lit

let private clockHand (time: Time) =
    let length = time.Length
    let angle = 2.0 * Math.PI * time.ClockPercentage
    let handX = (50.0 + length * cos (angle - Math.PI / 2.0))
    let handY = (50.0 + length * sin (angle - Math.PI / 2.0))
    svg $"""
<line
  x1="50"
  y1="50"
  x2={handX}
  y2={handY}
  stroke={time.Stroke}
  stroke-width={time.StrokeWidth}>
</line>   
"""

let private handTop (time: Time) =
    let length = time.Length
    let revolution = float time.Value
    let angle = 2.0 * Math.PI * (revolution / time.FullRound)
    let handX = (50.0 + length * cos (angle - Math.PI / 2.0))
    let handY = (50.0 + length * sin (angle - Math.PI / 2.0))
    svg $"""
<circle
  cx={handX}
  cy={handY}
  r="2"
  fill={time.Stroke}>
</circle>   
"""

[<ReactComponent>]
let Clock () =
    let time = Hooks.useState DateTime.Now

    Hooks.useEffectDisposable((fun () ->
        let id = JS.setInterval (fun _ -> DateTime.Now |> time.update) 1000
        { new IDisposable with
            member _.Dispose() =
                JS.clearInterval id }
    ), [||])

    React.Lit.useLit time.current <| fun time -> html $"""
<svg viewBox="0 0 100 100"
     width="350px">
  <circle
    cx="50"
    cy="50"
    r="45"
    fill="#0B79CE"></circle>

  {clockHand time.AsHour}
  {handTop time.AsHour}

  {clockHand time.AsMinute}
  {handTop time.AsMinute}

  {clockHand time.AsSecond}
  {handTop time.AsSecond}

  <circle
    cx="50"
    cy="50"
    r="3"
    fill="#0B79CE"
    stroke="#023963"
    stroke-width="1">  
  </circle>   
</svg>
"""

<|start_filename|>samples/FelizSvelte/src/TimeFliesElmish.fs<|end_filename|>
module TimeFliesElmish

open System
open Fable
open Fable.Core
open Fable.Core.JsInterop
open Fable.Reaction
open Fable.SimpleHttp
open FSharp.Control
open ElmishStore
open Browser

type Letter = { char: char; x: int; y: int }

type Model =
    { letters: Map<int, Letter>
      fps: int
      second: float
      count: int
      message: string
      quote: string
      isLoading: bool
      stream: IDisposable option }

type Msg =
    | Letter of index: int * char: char * x: int * y: int
    | Message of string
    | GetTodayQuote
    | OnError of exn

let getOffset (element: Browser.Types.Element) =
    let doc = element.ownerDocument
    let docElem = doc.documentElement
    let clientTop = docElem.clientTop
    let clientLeft = docElem.clientLeft
    let scrollTop = window.pageYOffset
    let scrollLeft = window.pageXOffset

    int (scrollTop - clientTop), int (scrollLeft - clientLeft)

let startStream (text: string) (dispatch: Msg -> unit) =
    let container = document.body
    let top, left = getOffset container

    asyncRx {
        let chars =
            Seq.toList text
            |> Seq.mapi (fun i c -> i, c)

        let! i, c = AsyncRx.ofSeq chars

        yield!
            AsyncRx.ofMouseMove ()
            |> AsyncRx.delay (100 * i)
            |> AsyncRx.requestAnimationFrame
            |> AsyncRx.map (fun m -> Letter(i, c, int m.clientX + i * 10 + 15 - left, int m.clientY - top))
    }
    |> AsyncRx.toObservable
    |> Observable.subscribe dispatch

let disposeStream (model: Model) =
    model.stream |> Option.iter (fun d -> d.Dispose())

let getQuote () = async {
    let! response =
        Http.request "https://quotes.rest/qod.json?category=inspire"
        |> Http.send

    return
        match response.content with
        | ResponseContent.Text res ->
            let res = JS.JSON.parse(res)
            res?contents?quotes?(0)?quote: string
        | _ ->
            failwith "Cannot read response"
}

let update (msg: Msg) (model: Model) =
    match msg with
    | Letter (i, c, x, y) ->
        let second = DateTimeOffset.Now.ToUnixTimeSeconds() |> float

        { model with
              letters = Map.add i { char = c; x = x; y = y } model.letters
              second = second
              fps = if second > model.second then model.count else model.fps
              count = if second > model.second then 0 else model.count + 1 },
        []

    | Message txt ->
        disposeStream model

        { model with
              letters = Map.empty
              message = txt },
        [fun update dispatch ->
            let stream = startStream txt dispatch
            update (fun m -> { m with stream = Some stream })]

    | GetTodayQuote ->
        let onSuccess quote m =
            { m with isLoading = false; quote = quote }

        { model with isLoading = true },
        Cmd.OfAsync.update getQuote () onSuccess OnError

    | OnError exn ->
        console.error(exn)
        { model with isLoading = false }, []

let init msg =
    { letters = Map.empty
      count = 0
      second = 0.
      fps = 0
      message = msg
      quote = ""
      isLoading = false
      stream = None },
    Cmd.OfFunc.result (Message msg)

open Feliz

let theySaidSoAttribution() =
    Html.span [
        prop.style [
            style.zIndex 50
            style.fontSize(length.em 0.9)
            style.fontWeight.bold
        ]
        prop.children [
            Html.img [
                prop.src "https://theysaidso.com/branding/theysaidso.png"
                prop.height 20
                prop.width 20
                prop.alt "theysaidso.com"
            ]
            Html.a [
                prop.href "https://theysaidso.com"
                prop.title "Powered by quotes from theysaidso.com"
                prop.style [
                    style.color "#ccc"
                    style.marginLeft 4
                    style.verticalAlign.middle
                ]
                prop.text "They Said So®"
            ]
        ]
    ]

[<ReactComponent>]
let TimeFliesElmish (text: string) =
    let model, dispatch =
        ReactStore.useElmishStore init update disposeStream text

    Html.div [
        prop.style [
            style.fontFamily "Consolas, monospace"
            style.height 100
        ]
        prop.children [
            for KeyValue(_idx, letter) in model.letters do
                Html.span [
                    prop.style [
                        style.position.fixedRelativeToWindow
                        style.top letter.y
                        style.left letter.x
                    ]
                    prop.text (string letter.char)
                ]
            Html.input [
                prop.style [ style.width 300 ]
                prop.defaultValue model.message
                prop.onChange (fun (ev: Types.Event) ->
                    Message (ev.target :?> Types.HTMLInputElement).value |> dispatch)
            ]
            Html.p ("fps: " + string model.fps)
            Html.button [
                prop.disabled model.isLoading
                prop.children [
                    Html.text "Get today's quote from "
                    theySaidSoAttribution()
                ]
                prop.onClick (fun _ -> dispatch GetTodayQuote)
            ]
            Html.p (if model.isLoading then "Loading..." else model.quote)
        ]
    ]


<|start_filename|>src/Fable.SvelteStore/Fable.SvelteStore.fs<|end_filename|>
[<RequireQualifiedAccess>]
module SvelteStore

open System
open Fable
open Fable.Core
open ElmishStore

type Subscribe<'Value> = 'Value -> unit
type Dispose = delegate of unit -> unit
type IDispatcher<'Msg> = interface end

type IReadable<'Value> =
    abstract subscribe: Subscribe<'Value> -> Dispose

type IWritable<'Value> =
    inherit IReadable<'Value>
    abstract update: ('Value -> 'Value) -> unit
    abstract set: 'Value -> unit

type Initialize<'Props, 'Value> = delegate of 'Props -> IReadable<'Value>

[<Import("readable", from="svelte/store")>]
let private makeReadableStore (init: 'Value) (start: ('Value -> unit) -> Dispose): IReadable<'Value> = jsNative

[<Import("writable", from="svelte/store")>]
let private makeWritableStore (init: 'Value) (start: ('Value -> unit) -> Dispose): IWritable<'Value> = jsNative

let private storeCons value dispose =
    let mutable store = Unchecked.defaultof<IWritable<'Value>>
    store <- makeWritableStore value (fun _set ->
        Dispose(fun () -> store.update(fun model ->
            dispose model
            model)))
    store, store.update

let make init dispose props: IWritable<'Model> =
    Store.makeWithCons init dispose storeCons props

let makeRec (init: IWritable<'Model> -> 'Props -> 'Model * IDisposable) =
    fun (props: 'Props) ->
        let mutable store = Unchecked.defaultof<IWritable<'Model>>
        store <- makeWritableStore Unchecked.defaultof<'Model> (fun set ->
            let v, disp = init store props
            set v
            Dispose(fun () -> disp.Dispose()))
        store

let makeElmish (init: 'Props -> 'Value * Cmd<'Value, 'Msg>)
               (update: 'Msg -> 'Value -> 'Value * Cmd<'Value, 'Msg>)
               (dispose: 'Value -> unit)
               (props: 'Props): IWritable<'Value> * Dispatch<'Msg> =

    Store.makeElmishWithCons init update dispose storeCons props

let makeElmishSimple (init: 'Props -> 'Value)
                     (update: 'Msg -> 'Value -> 'Value)
                     (dispose: 'Value -> unit)
                     (props: 'Props): IWritable<'Value> * Dispatch<'Msg> =

    let init p = init p, []
    let update m v = update m v, []
    makeElmish init update dispose props

[<SveltePlugins.Dispatcher>]
let makeDispatcher (dispatch: 'Msg -> unit): IDispatcher<'Msg> = failwith "never"

let map (f: 'a -> 'b) (store: IReadable<'a>): IReadable<'b> =
    makeReadableStore Unchecked.defaultof<_> (fun set ->
        let disp = store.subscribe(f >> set)
        Dispose(fun () -> disp.Invoke()))
