├── glues ├── ServeStatic │ ├── README.md │ ├── tests │ │ ├── files │ │ │ └── users │ │ │ │ ├── tobi.txt │ │ │ │ └── index.html │ │ ├── Tests.ServeStatic.fsproj │ │ └── Tests.ServeStatic.fs │ ├── CHANGELOG.md │ └── src │ │ ├── Glutinum.ServeStatic.fsproj │ │ └── Glutinum.ServeStatic.fs ├── Express │ ├── tests │ │ ├── fixtures │ │ │ ├── .name │ │ │ ├── empty.txt │ │ │ ├── broken.send │ │ │ ├── name.txt │ │ │ ├── % of dogs.txt │ │ │ ├── nums.txt │ │ │ ├── snow ☃ │ │ │ │ └── .gitkeep │ │ │ ├── todo.txt │ │ │ ├── name.tmpl │ │ │ ├── pets │ │ │ │ └── names.txt │ │ │ ├── users │ │ │ │ ├── tobi.txt │ │ │ │ └── index.html │ │ │ ├── blog │ │ │ │ ├── index.html │ │ │ │ └── post │ │ │ │ │ └── index.tmpl │ │ │ ├── todo.html │ │ │ ├── user.html │ │ │ ├── user.tmpl │ │ │ ├── email.tmpl │ │ │ ├── default_layout │ │ │ │ ├── name.tmpl │ │ │ │ └── user.tmpl │ │ │ └── local_layout │ │ │ │ └── user.tmpl │ │ ├── Tests.Express.Port.App.fs │ │ ├── Tests.Express.Port.App.Listen.fs │ │ ├── Tests.Express.Port.App.Del.fs │ │ ├── support │ │ │ └── tmpl.js │ │ ├── Tests.Express.Port.App.Locals.fs │ │ ├── Tests.Express.Port.App.All.fs │ │ ├── Tests.Express.Port.App.Response.fs │ │ ├── Tests.Express.fsproj │ │ ├── Tests.Express.Port.App.Route.fs │ │ ├── Tests.Express.Port.App.Head.fs │ │ ├── Tests.Express.Port.App.Routes.Error.fs │ │ ├── Tests.Express.Port.App.Request.fs │ │ ├── Tests.Express.Port.App.Engine.fs │ │ ├── Tests.Express.Port.App.Options.fs │ │ ├── Tests.Express.Port.App.Render.fs │ │ └── Tests.Express.Port.App.Params.fs │ ├── CHANGELOG.md │ ├── README.md │ └── src │ │ ├── Glutinum.Express.fsproj │ │ └── Glutinum.Express.fs ├── ExpressServeStaticCore │ ├── README.md │ ├── CHANGELOG.md │ └── src │ │ └── Glutinum.ExpressServeStaticCore.fsproj ├── Connect │ ├── src │ │ ├── Glutinum.Connect.Ext.fs │ │ ├── Glutinum.Connect.fsproj │ │ └── Glutinum.Connect.fs │ ├── README.md │ ├── CHANGELOG.md │ └── tests │ │ ├── Tests.Connect.fsproj │ │ └── Tests.Connect.fs ├── After │ ├── src │ │ ├── Glutinum.After.fs │ │ └── Glutinum.After.fsproj │ ├── CHANGELOG.md │ └── README.md ├── RangeParser.Extensions │ ├── CHANGELOG.md │ ├── src │ │ ├── Glutinum.RangeParser.Extensions.fs │ │ └── Glutinum.RangeParser.Extensions.fsproj │ └── tests │ │ ├── Tests.RangeParser.fsproj │ │ └── Tests.RangeParser.fs ├── Chalk │ ├── CHANGELOG.md │ ├── tests │ │ ├── Tests.Chalk.fsproj │ │ ├── Tests.Chalk.Instance.fs │ │ ├── Tests.Chalk.Level.fs │ │ └── Tests.Chalk.ChalkJS.fs │ ├── src │ │ ├── Glutinum.Chalk.fsproj │ │ └── Glutinum.Chalk.fs │ └── README.md ├── Methods │ ├── CHANGELOG.md │ ├── README.md │ └── src │ │ ├── Glutinum.Methods.fsproj │ │ └── Glutinum.Methods.fs ├── SuperAgent │ ├── CHANGELOG.md │ ├── README.md │ └── src │ │ ├── Glutinum.SuperAgent.fsproj │ │ └── Glutinum.SuperAgent.fs ├── SuperTest │ ├── CHANGELOG.md │ ├── README.md │ └── src │ │ ├── Glutinum.SuperTest.fsproj │ │ └── Glutinum.SuperTest.fs ├── BodyParser │ ├── README.md │ ├── CHANGELOG.md │ ├── tests │ │ ├── Tests.BodyParser.fsproj │ │ ├── Tests.BodyParser.Text.fs │ │ └── Tests.BodyParser.Json.fs │ └── src │ │ ├── Glutinum.BodyParser.fsproj │ │ └── Glutinum.BodyParser.fs ├── Qs │ ├── README.md │ ├── CHANGELOG.md │ ├── tests │ │ ├── Tests.Qs.fsproj │ │ └── Tests.Qs.fs │ └── src │ │ ├── Glutinum.Qs.fsproj │ │ └── Glutinum.Qs.fs ├── Mime │ ├── README.md │ ├── CHANGELOG.md │ ├── tests │ │ ├── Tests.Mime.fsproj │ │ └── Tests.Mime.fs │ └── src │ │ ├── Glutinum.Mime.fsproj │ │ └── Glutinum.Mime.fs └── RangeParser │ ├── src │ ├── Glutinum.RangeParser.fsproj │ └── Glutinum.RangeParser.fs │ ├── README.md │ └── CHANGELOG.md ├── tests-shared ├── mocha.env.js ├── Fable.Core.JsInterop.fs ├── Mocha.fs ├── Tests.Shared.props ├── Globals.fs └── Node.Assert.fs ├── global.json ├── .editorconfig ├── Settings.FSharpLint ├── .config └── dotnet-tools.json ├── .github ├── ISSUE_TEMPLATE │ └── typescript-to-f--structure-conversion.md └── FUNDING.yml ├── scripts ├── await-spawn.js └── init-glue-templates.js ├── LICENSE ├── package.json ├── README.md └── .gitignore /glues/ServeStatic/README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /glues/Express/tests/fixtures/.name: -------------------------------------------------------------------------------- 1 | tobi -------------------------------------------------------------------------------- /glues/Express/tests/fixtures/empty.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /glues/ExpressServeStaticCore/README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /glues/Express/tests/fixtures/broken.send: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /glues/Express/tests/fixtures/name.txt: -------------------------------------------------------------------------------- 1 | tobi -------------------------------------------------------------------------------- /glues/Express/tests/fixtures/% of dogs.txt: -------------------------------------------------------------------------------- 1 | 20% -------------------------------------------------------------------------------- /glues/Express/tests/fixtures/nums.txt: -------------------------------------------------------------------------------- 1 | 123456789 -------------------------------------------------------------------------------- /glues/Express/tests/fixtures/snow ☃/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /glues/Express/tests/fixtures/todo.txt: -------------------------------------------------------------------------------- 1 | - groceries -------------------------------------------------------------------------------- /glues/Express/tests/fixtures/name.tmpl: -------------------------------------------------------------------------------- 1 |

$name

-------------------------------------------------------------------------------- /glues/Express/tests/fixtures/pets/names.txt: -------------------------------------------------------------------------------- 1 | tobi,loki -------------------------------------------------------------------------------- /glues/Express/tests/fixtures/users/tobi.txt: -------------------------------------------------------------------------------- 1 | ferret -------------------------------------------------------------------------------- /glues/ServeStatic/tests/files/users/tobi.txt: -------------------------------------------------------------------------------- 1 | ferret -------------------------------------------------------------------------------- /glues/Express/tests/fixtures/blog/index.html: -------------------------------------------------------------------------------- 1 | index -------------------------------------------------------------------------------- /glues/Express/tests/fixtures/todo.html: -------------------------------------------------------------------------------- 1 |
  • groceries
  • -------------------------------------------------------------------------------- /glues/Express/tests/fixtures/user.html: -------------------------------------------------------------------------------- 1 |

    {{user.name}}

    -------------------------------------------------------------------------------- /glues/Express/tests/fixtures/user.tmpl: -------------------------------------------------------------------------------- 1 |

    $user.name

    -------------------------------------------------------------------------------- /glues/Express/tests/fixtures/email.tmpl: -------------------------------------------------------------------------------- 1 |

    This is an email

    -------------------------------------------------------------------------------- /tests-shared/mocha.env.js: -------------------------------------------------------------------------------- 1 | process.env.NODE_ENV = 'test'; 2 | -------------------------------------------------------------------------------- /glues/Express/tests/fixtures/blog/post/index.tmpl: -------------------------------------------------------------------------------- 1 |

    blog post

    -------------------------------------------------------------------------------- /glues/Express/tests/fixtures/default_layout/name.tmpl: -------------------------------------------------------------------------------- 1 |

    $name

    -------------------------------------------------------------------------------- /glues/Express/tests/fixtures/users/index.html: -------------------------------------------------------------------------------- 1 |

    tobi, loki, jane

    -------------------------------------------------------------------------------- /glues/Express/tests/fixtures/default_layout/user.tmpl: -------------------------------------------------------------------------------- 1 |

    $user.name

    -------------------------------------------------------------------------------- /glues/ServeStatic/tests/files/users/index.html: -------------------------------------------------------------------------------- 1 |

    tobi, loki, jane

    -------------------------------------------------------------------------------- /glues/Express/tests/fixtures/local_layout/user.tmpl: -------------------------------------------------------------------------------- 1 | $user.name -------------------------------------------------------------------------------- /global.json: -------------------------------------------------------------------------------- 1 | { 2 | "sdk": { 3 | "version": "8.0.100", 4 | "rollForward": "minor" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | indent_style = space 7 | indent_size = 4 8 | -------------------------------------------------------------------------------- /tests-shared/Fable.Core.JsInterop.fs: -------------------------------------------------------------------------------- 1 | module Fable.Core.JsInterop 2 | 3 | open Fable.Core 4 | 5 | [] 6 | let inline jsDelete<'T> (v : 'T) : unit = jsNative 7 | -------------------------------------------------------------------------------- /Settings.FSharpLint: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | False 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /glues/Connect/src/Glutinum.Connect.Ext.fs: -------------------------------------------------------------------------------- 1 | [] 2 | module Glutinum.Connect.Ext 3 | 4 | open Fable.Core 5 | 6 | type Node.Http.IExports with 7 | [] 8 | member __.createServer(_ : Connect.CreateServer.Server) : Node.Http.Server = jsNative 9 | -------------------------------------------------------------------------------- /.config/dotnet-tools.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "isRoot": true, 4 | "tools": { 5 | "fable": { 6 | "version": "4.19.3", 7 | "commands": [ 8 | "fable" 9 | ] 10 | }, 11 | "femto": { 12 | "version": "0.9.0", 13 | "commands": [ 14 | "femto" 15 | ] 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /glues/Connect/README.md: -------------------------------------------------------------------------------- 1 | # Glutinum.Connect 2 | 3 | Binding for [connect](https://www.npmjs.com/package/connect) 4 | 5 | # Usage 6 | 7 | ```fs 8 | open Connect 9 | let app = connect() 10 | 11 | // We need to help the compiler with a type hint 12 | app.``use``(fun req res -> 13 | res.``end``("Hello, world!") 14 | ) |> ignore 15 | ``` 16 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/typescript-to-f--structure-conversion.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: TypeScript to F# structure conversion 3 | about: Use this template to ask supporting a new TypeScript to F# conversion 4 | title: '' 5 | labels: TS to F# conversion 6 | assignees: '' 7 | 8 | --- 9 | 10 | ```ts 11 | 12 | ``` 13 | 14 | should translates to 15 | 16 | ```fs 17 | 18 | ``` 19 | -------------------------------------------------------------------------------- /glues/After/src/Glutinum.After.fs: -------------------------------------------------------------------------------- 1 | // ts2fable 0.8.0 2 | module rec Glutinum.After 3 | 4 | open Fable.Core 5 | 6 | [] 7 | let after : IExports = jsNative 8 | 9 | type [] IExports = 10 | [] 11 | abstract Invoke : count : int * callback : 'Callback * ?errCallback : (obj option -> unit) -> 'T 12 | -------------------------------------------------------------------------------- /tests-shared/Mocha.fs: -------------------------------------------------------------------------------- 1 | module Mocha 2 | 3 | open Fable.Core 4 | 5 | let [] describe (name: string) (f: unit->unit) : unit = jsNative 6 | let [] it (msg: string) (f: unit->unit) : unit = jsNative 7 | let [] itAsync (msg: string) (f: (obj->unit)->unit) : unit = jsNative 8 | let [] beforeEach (f: unit->unit) : unit = jsNative 9 | 10 | -------------------------------------------------------------------------------- /glues/RangeParser.Extensions/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## Unreleased 8 | 9 | ## 1.0.0 - 2021-03-17 10 | 11 | ### Added 12 | 13 | * Initial release 14 | -------------------------------------------------------------------------------- /glues/Chalk/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## Unreleased 8 | 9 | ## 1.0.0 - 2021-07-26 10 | 11 | ### Added 12 | 13 | * Release 1.0 14 | 15 | ## 0.1.0-alpha-001 - 2021-03-17 16 | 17 | * Initial release 18 | -------------------------------------------------------------------------------- /glues/After/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## Unreleased 9 | 10 | ## 1.0.0 - 2021-07-26 11 | 12 | ### Added 13 | 14 | * Release 1.0 15 | 16 | ## 0.1.0-alpha-001 - 2021-03-17 17 | 18 | * Initial release 19 | -------------------------------------------------------------------------------- /glues/Connect/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## Unreleased 8 | 9 | ## 0.1.0-alpha-002 - 2021-03-17 10 | 11 | * Fix package description 12 | 13 | ## 0.1.0-alpha-001 - 2021-03-17 14 | 15 | * Initial release 16 | -------------------------------------------------------------------------------- /glues/Methods/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## Unreleased 8 | 9 | ## 0.1.0-alpha-002 - 2021-03-17 10 | 11 | * Fix package description 12 | 13 | ## 0.1.0-alpha-001 - 2021-03-17 14 | 15 | * Initial release 16 | -------------------------------------------------------------------------------- /glues/ServeStatic/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## Unreleased 8 | 9 | ## 1.0.0 - 2021-07-26 10 | 11 | ### Added 12 | 13 | * Release 1.0 14 | 15 | ## 0.1.0-alpha-001 - 2021-03-17 16 | 17 | * Initial release 18 | -------------------------------------------------------------------------------- /glues/SuperAgent/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## Unreleased 8 | 9 | ## 0.1.0-alpha-002 - 2021-03-17 10 | 11 | * Fix package description 12 | 13 | ## 0.1.0-alpha-001 - 2021-03-17 14 | 15 | * Initial release 16 | -------------------------------------------------------------------------------- /glues/SuperTest/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## Unreleased 8 | 9 | ## 0.1.0-alpha-002 - 2021-03-17 10 | 11 | * Fix package description 12 | 13 | ## 0.1.0-alpha-001 - 2021-03-17 14 | 15 | * Initial release 16 | -------------------------------------------------------------------------------- /glues/After/README.md: -------------------------------------------------------------------------------- 1 | # BodyParser 2 | 3 | Binding for [body-parser](https://www.npmjs.com/package/body-parser) 4 | 5 | # Usage 6 | 7 | ```fs 8 | open BodyParser 9 | 10 | let bodyParser = 11 | let options = 12 | jsOptions(fun o -> 13 | o.limit <- !^ "1kb" 14 | ) 15 | 16 | bodyParser.json(options) 17 | 18 | // If used with Express 19 | open Express 20 | 21 | let app = express.express () 22 | app.``use``(bodyParser) 23 | ``` 24 | -------------------------------------------------------------------------------- /glues/BodyParser/README.md: -------------------------------------------------------------------------------- 1 | # BodyParser 2 | 3 | Binding for [body-parser](https://www.npmjs.com/package/body-parser) 4 | 5 | # Usage 6 | 7 | ```fs 8 | open BodyParser 9 | 10 | let bodyParser = 11 | let options = 12 | jsOptions(fun o -> 13 | o.limit <- !^ "1kb" 14 | ) 15 | 16 | bodyParser.json(options) 17 | 18 | // If used with Express 19 | open Express 20 | 21 | let app = express.express () 22 | app.``use``(bodyParser) 23 | ``` 24 | -------------------------------------------------------------------------------- /glues/Methods/README.md: -------------------------------------------------------------------------------- 1 | # BodyParser 2 | 3 | Binding for [body-parser](https://www.npmjs.com/package/body-parser) 4 | 5 | # Usage 6 | 7 | ```fs 8 | open BodyParser 9 | 10 | let bodyParser = 11 | let options = 12 | jsOptions(fun o -> 13 | o.limit <- !^ "1kb" 14 | ) 15 | 16 | bodyParser.json(options) 17 | 18 | // If used with Express 19 | open Express 20 | 21 | let app = express.express () 22 | app.``use``(bodyParser) 23 | ``` 24 | -------------------------------------------------------------------------------- /glues/SuperAgent/README.md: -------------------------------------------------------------------------------- 1 | # BodyParser 2 | 3 | Binding for [body-parser](https://www.npmjs.com/package/body-parser) 4 | 5 | # Usage 6 | 7 | ```fs 8 | open BodyParser 9 | 10 | let bodyParser = 11 | let options = 12 | jsOptions(fun o -> 13 | o.limit <- !^ "1kb" 14 | ) 15 | 16 | bodyParser.json(options) 17 | 18 | // If used with Express 19 | open Express 20 | 21 | let app = express.express () 22 | app.``use``(bodyParser) 23 | ``` 24 | -------------------------------------------------------------------------------- /glues/SuperTest/README.md: -------------------------------------------------------------------------------- 1 | # BodyParser 2 | 3 | Binding for [body-parser](https://www.npmjs.com/package/body-parser) 4 | 5 | # Usage 6 | 7 | ```fs 8 | open BodyParser 9 | 10 | let bodyParser = 11 | let options = 12 | jsOptions(fun o -> 13 | o.limit <- !^ "1kb" 14 | ) 15 | 16 | bodyParser.json(options) 17 | 18 | // If used with Express 19 | open Express 20 | 21 | let app = express.express () 22 | app.``use``(bodyParser) 23 | ``` 24 | -------------------------------------------------------------------------------- /glues/Qs/README.md: -------------------------------------------------------------------------------- 1 | # Qs 2 | 3 | Binding for [qs](https://www.npmjs.com/package/qs) 4 | 5 | # Usage 6 | 7 | ```fs 8 | open Qs 9 | 10 | let res = qs.parse("0=foo") 11 | 12 | // You can access the value store at index 0 like that 13 | res.[0] 14 | // Return: Some (U4.Case "foo") 15 | 16 | // You can also pass options to the 'parse' function 17 | qs.parse( 18 | "a[0]=b&a[1]=c", 19 | jsOptions(fun o -> 20 | o.arrayFormat <- Qs.IArrayFormat.Brackets 21 | ) 22 | ) 23 | ``` 24 | -------------------------------------------------------------------------------- /tests-shared/Tests.Shared.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /glues/Mime/README.md: -------------------------------------------------------------------------------- 1 | # Mime 2 | 3 | Binding for [mime](https://www.npmjs.com/package/mime) 4 | 5 | # Usage 6 | 7 | ```fs 8 | open Mime 9 | 10 | // You can create a custom Mime instance 11 | let typeMap = createEmpty 12 | typeMap.["text/a"] <- ResizeArray(["a"; "a1"]) 13 | typeMap.["text/b"] <- ResizeArray(["b"; "b1"]) 14 | 15 | let myMime = mime.Mime(typeMap) 16 | 17 | // You can also use the default settings 18 | mime.getType("txt") 19 | // Returns: Some "text/plain" 20 | 21 | mime.getExtension("text/html") 22 | // Returns: Some "html" 23 | ``` 24 | -------------------------------------------------------------------------------- /glues/Mime/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## Unreleased 8 | 9 | ## 2.0.0 - 2024-08-14 10 | 11 | ### Changed 12 | 13 | * Updated to support `Mime@4` 14 | 15 | ## 1.0.0 - 2021-07-26 16 | 17 | ### Added 18 | 19 | * Release 1.0 20 | 21 | ## 0.1.0-alpha-001 - 2021-03-17 22 | 23 | ### Added 24 | 25 | * Initial release 26 | -------------------------------------------------------------------------------- /glues/Qs/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## Unreleased 8 | 9 | ## 1.0.0 - 2021-07-26 10 | 11 | ### Added 12 | 13 | * Release 1.0 14 | 15 | ## 0.1.0-alpha-002 - 2021-03-17 16 | 17 | ### Changed 18 | 19 | * Fix package description 20 | 21 | ## 0.1.0-alpha-001 - 2021-03-17 22 | 23 | ### Added 24 | 25 | * Initial release 26 | -------------------------------------------------------------------------------- /glues/Express/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## Unreleased 8 | 9 | ## 0.2.0 - 2024-08-14 10 | 11 | * Release to force update of `ExpressServeStaticCore` dependency to `0.2.0` 12 | 13 | ## 0.1.0-alpha-002 - 2021-03-17 14 | 15 | * Fix package description 16 | 17 | ## 0.1.0-alpha-001 - 2021-03-17 18 | 19 | * Initial release 20 | -------------------------------------------------------------------------------- /glues/Express/tests/Tests.Express.Port.App.fs: -------------------------------------------------------------------------------- 1 | module Tests.Express.Port.Express.App.App 2 | 3 | open Mocha 4 | open Glutinum.Express 5 | 6 | describe "app" (fun _ -> 7 | 8 | itAsync "should inherit from event emitter" (fun d -> 9 | let app = express.express () 10 | 11 | app.on("foo", fun _ -> d()) |> ignore 12 | app.emit("foo") |> ignore 13 | ) 14 | 15 | itAsync "should be callable" (fun d -> 16 | request(express.express()) 17 | .get("/") 18 | .expect(404, d) 19 | |> ignore 20 | ) 21 | 22 | ) 23 | -------------------------------------------------------------------------------- /glues/Express/README.md: -------------------------------------------------------------------------------- 1 | # Glutinum.Express 2 | 3 | Binding for [Express](https://github.com/expressjs/express) 4 | 5 | # Usage 6 | 7 | ```fs 8 | open Fable.Core 9 | open Glutinum.Express 10 | open Glutinum.ExpressServeStaticCore 11 | 12 | let port = 2700 13 | let app = express.express () 14 | 15 | app.get ( 16 | "/", 17 | fun (req: Request) (res: Response) -> 18 | JS.console.log ($"New request, %s{req.hostname}") 19 | res.send ("Hello world") 20 | ) 21 | 22 | app.listen ( 23 | port, 24 | fun () -> JS.console.log $"Started listening on http://127.0.0.1:%i{port}" 25 | ) 26 | |> ignore 27 | ``` 28 | -------------------------------------------------------------------------------- /glues/ExpressServeStaticCore/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## Unreleased 8 | 9 | ## 0.2.0 - 2024-08-14 10 | 11 | * add deprecation message to Request#param (by @joprice) 12 | * fix return type of 'is' function (by @joprice) 13 | 14 | ## 0.1.0-alpha-002 - 2021-03-17 15 | 16 | * Fix package description 17 | 18 | ## 0.1.0-alpha-001 - 2021-03-17 19 | 20 | * Initial release 21 | -------------------------------------------------------------------------------- /scripts/await-spawn.js: -------------------------------------------------------------------------------- 1 | import { spawn } from 'child_process' 2 | 3 | export default (...args) => { 4 | const child = spawn(...args) 5 | 6 | const promise = new Promise((resolve, reject) => { 7 | child.on('error', reject) 8 | 9 | child.on('exit', code => { 10 | if (code === 0) { 11 | resolve() 12 | } else { 13 | const err = new Error(`child exited with code ${code}`) 14 | err.code = code 15 | reject(err) 16 | } 17 | }) 18 | }) 19 | 20 | promise.child = child 21 | 22 | return promise 23 | } 24 | -------------------------------------------------------------------------------- /glues/BodyParser/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## Unreleased 8 | 9 | ## 1.0.0 - 2021-07-26 10 | 11 | ### Added 12 | 13 | * Release 1.0 14 | 15 | ### Fixed 16 | 17 | * Fix #8: body-parser import doesn't work when targeting es module in node 18 | 19 | ## 0.1.0-alpha-002 - 2021-03-17 20 | 21 | * Fix package description 22 | 23 | ## 0.1.0-alpha-001 - 2021-03-17 24 | 25 | * Initial release 26 | 27 | ## 1.0.0 - 2021-07-26 28 | -------------------------------------------------------------------------------- /glues/Express/tests/Tests.Express.Port.App.Listen.fs: -------------------------------------------------------------------------------- 1 | module Tests.Express.Port.App.Listen 2 | 3 | open Node 4 | open Mocha 5 | open Glutinum.ExpressServeStaticCore 6 | open Glutinum.Express 7 | 8 | #nowarn "40" 9 | 10 | describe "app.listen()" (fun _ -> 11 | 12 | itAsync "should wrap with an HTTP server" (fun d -> 13 | let app = express.express () 14 | 15 | app.del("/tobi", fun req (res : Response<_,_>) next -> 16 | res.``end``("deleted tobi") 17 | ) 18 | 19 | let rec server : Http.Server = 20 | app.listen(9999, fun _ -> 21 | server.close() |> ignore 22 | d() 23 | ) 24 | 25 | () 26 | ) 27 | 28 | ) 29 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: MangelMaxime 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /glues/RangeParser/src/Glutinum.RangeParser.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 1.0.1 5 | netstandard2.0 6 | true 7 | Maxime Mangel 8 | 9 | Fable bindings for npm https://github.com/jshttp/range-parser package 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /glues/RangeParser/README.md: -------------------------------------------------------------------------------- 1 | # RangeParser 2 | 3 | Binding for [range-parser](https://www.npmjs.com/package/range-parser) 4 | 5 | # Usage 6 | 7 | ```fs 8 | open RangeParser 9 | 10 | let range = rangeParser.rangeParser(1000, "bytes=0-499") 11 | 12 | // The binding include a custom active pattern making it easier/safer to work with the library 13 | 14 | match range with 15 | | ParseRangeResult.UnkownError _ 16 | | ParseRangeResult.ResultInvalid 17 | | ParseRangeResult.ResultUnsatisfiable -> 18 | // Handle error case 19 | 20 | | ParseRangeResult.Range range -> 21 | // Here you can access your successful result 22 | range.``type`` // Returns: "bytes" 23 | range.Count // Returns: 1 24 | range.[0].start // Returns: 0 25 | range.[0].``end`` // Returns: 499 26 | ``` 27 | -------------------------------------------------------------------------------- /glues/RangeParser/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## Unreleased 8 | 9 | ## 1.0.1 - 2021-07-26 10 | 11 | ### Fixed 12 | 13 | * Do not publish the code under `.fable` directory, this package is now no code. 14 | 15 | ## 1.0.0 - 2021-07-26 16 | 17 | ### Changed 18 | 19 | * Make this package no-code, the `Ext` module has been extracted to from `Glutinum.RangeParser.Extensions` 20 | 21 | ## 0.1.0-alpha-002 - 2021-03-17 22 | 23 | ### Changed 24 | 25 | * Fix package description 26 | 27 | ## 0.1.0-alpha-001 - 2021-03-17 28 | 29 | ### Added 30 | 31 | * Initial release 32 | -------------------------------------------------------------------------------- /glues/Qs/tests/Tests.Qs.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | true 6 | ../../../tests-shared 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /glues/Mime/tests/Tests.Mime.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | true 6 | ../../../tests-shared 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /glues/RangeParser.Extensions/src/Glutinum.RangeParser.Extensions.fs: -------------------------------------------------------------------------------- 1 | namespace Glutinum.RangeParser 2 | 3 | open Fable.Core 4 | 5 | module internal Interop = 6 | 7 | [] 8 | let internal jsTypeOf _ : string = jsNative 9 | 10 | [] 11 | module ParseRangeResult = 12 | 13 | let (|UnkownError|ResultInvalid|ResultUnsatisfiable|Range|) (result : RangeParser.ParseRangeResult) : Choice = 14 | if Interop.jsTypeOf result = "number" then 15 | let error = unbox result 16 | 17 | if error = -1 then 18 | ResultUnsatisfiable 19 | else if error = -2 then 20 | ResultInvalid 21 | else 22 | UnkownError (unbox result) 23 | else 24 | Range (unbox result) 25 | -------------------------------------------------------------------------------- /glues/Connect/tests/Tests.Connect.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | true 6 | ../../../tests-shared 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /glues/Chalk/tests/Tests.Chalk.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | true 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /glues/ServeStatic/tests/Tests.ServeStatic.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | true 6 | ../../../tests-shared 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /glues/RangeParser.Extensions/tests/Tests.RangeParser.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | true 6 | ../../../tests-shared 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /tests-shared/Globals.fs: -------------------------------------------------------------------------------- 1 | [] 2 | module Globals 3 | 4 | open Fable.Core 5 | open Fable.Core.JS 6 | open Glutinum.SuperTest 7 | 8 | [] 9 | let Assert: Node.Assert.IExports = jsNative 10 | 11 | let request (app : #obj) = supertest.supertest app 12 | 13 | type Node.Http.ServerResponse with 14 | [] 15 | member __.setHeader(name: string, value: string) : unit = jsNative 16 | [] 17 | member __.setHeader(name: string, value: ResizeArray) : unit = jsNative 18 | 19 | [] 20 | let inline jsUndefined<'T> : 'T = jsNative 21 | 22 | [] 23 | let inline returnNothingHack<'T> : 'T = jsNative 24 | 25 | type NumberConstructor with 26 | [] 27 | member __.Create(v : obj) = jsNative 28 | 29 | [] 30 | let internal jsTypeOf _ : string = jsNative 31 | -------------------------------------------------------------------------------- /glues/Qs/src/Glutinum.Qs.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 1.0.0 5 | netstandard2.0 6 | true 7 | Maxime Mangel 8 | 9 | Fable bindings for npm https://github.com/ljharb/qs package 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 20 | 23 | 24 | -------------------------------------------------------------------------------- /glues/Chalk/src/Glutinum.Chalk.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 1.0.0 5 | netstandard2.0 6 | true 7 | Maxime Mangel 8 | 9 | Fable bindings for npm https://github.com/chalk/chalk package 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 20 | 23 | 24 | -------------------------------------------------------------------------------- /glues/Mime/src/Glutinum.Mime.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 2.0.0 5 | netstandard2.0 6 | true 7 | Maxime Mangel 8 | 9 | Fable bindings for npm https://github.com/broofa/mime package 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 20 | 23 | 24 | -------------------------------------------------------------------------------- /glues/After/src/Glutinum.After.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 1.0.0 5 | netstandard2.0 6 | true 7 | Maxime Mangel 8 | 9 | Fable bindings for https://www.npmjs.com/package/after package 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 20 | 23 | 24 | -------------------------------------------------------------------------------- /glues/BodyParser/tests/Tests.BodyParser.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | true 6 | ../../../tests-shared 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /glues/Methods/src/Glutinum.Methods.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 0.1.0-alpha-002 5 | netstandard2.0 6 | true 7 | Maxime Mangel 8 | 9 | Fable bindings for npm https://github.com/jshttp/methods package 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 20 | 23 | 24 | -------------------------------------------------------------------------------- /glues/Express/tests/Tests.Express.Port.App.Del.fs: -------------------------------------------------------------------------------- 1 | module Tests.Express.Port.App.Del 2 | 3 | open Mocha 4 | open Glutinum.ExpressServeStaticCore 5 | open Glutinum.Express 6 | 7 | 8 | describe "app.delete()" (fun _ -> 9 | 10 | itAsync "app.delete() works" (fun d -> 11 | let app = express.express () 12 | 13 | app.delete("/tobi", fun req (res : Response<_,_>) next -> 14 | res.``end``("deleted tobi") 15 | ) 16 | 17 | request(app) 18 | .del("/tobi") 19 | .expect("deleted tobi", d) 20 | |> ignore 21 | ) 22 | 23 | itAsync "app.del() should alias app.delete()" (fun d -> 24 | let app = express.express () 25 | 26 | app.del("/tobi", fun req (res : Response<_,_>) next -> 27 | res.``end``("deleted tobi") 28 | ) 29 | 30 | request(app) 31 | .del("/tobi") 32 | .expect("deleted tobi", d) 33 | |> ignore 34 | ) 35 | 36 | ) 37 | -------------------------------------------------------------------------------- /glues/Express/tests/support/tmpl.js: -------------------------------------------------------------------------------- 1 | import { readFile } from 'fs'; 2 | 3 | var variableRegExp = /\$([0-9a-zA-Z\.]+)/g; 4 | 5 | export default function renderFile(fileName, options, callback) { 6 | function onReadFile(err, str) { 7 | if (err) { 8 | callback(err); 9 | return; 10 | } 11 | 12 | try { 13 | str = str.replace(variableRegExp, generateVariableLookup(options)); 14 | } catch (e) { 15 | err = e; 16 | err.name = 'RenderError' 17 | } 18 | 19 | callback(err, str); 20 | } 21 | 22 | readFile(fileName, 'utf8', onReadFile); 23 | }; 24 | 25 | function generateVariableLookup(data) { 26 | return function variableLookup(str, path) { 27 | var parts = path.split('.'); 28 | var value = data; 29 | 30 | for (var i = 0; i < parts.length; i++) { 31 | value = value[parts[i]]; 32 | } 33 | 34 | return value; 35 | }; 36 | } 37 | -------------------------------------------------------------------------------- /glues/RangeParser.Extensions/src/Glutinum.RangeParser.Extensions.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 1.0.0 5 | netstandard2.0 6 | true 7 | Maxime Mangel 8 | 9 | Extensions for Glutinum.RangeParser glues 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /glues/BodyParser/src/Glutinum.BodyParser.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 1.0.0 5 | netstandard2.0 6 | true 7 | Maxime Mangel 8 | 9 | Fable bindings for npm https://github.com/expressjs/body-parser package 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 21 | 24 | 25 | -------------------------------------------------------------------------------- /glues/Connect/src/Glutinum.Connect.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 0.1.0-alpha-002 5 | netstandard2.0 6 | true 7 | Maxime Mangel 8 | 9 | Fable bindings for npm https://github.com/senchalabs/connect package 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 22 | 25 | 26 | -------------------------------------------------------------------------------- /glues/SuperAgent/src/Glutinum.SuperAgent.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 0.1.0-alpha-002 5 | netstandard2.0 6 | true 7 | Maxime Mangel 8 | 9 | Fable bindings for npm https://github.com/visionmedia/superagent package 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 22 | 25 | 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Mangel Maxime 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /glues/Mime/src/Glutinum.Mime.fs: -------------------------------------------------------------------------------- 1 | module rec Glutinum.Mime 2 | 3 | // Exported from: https://github.com/DefinitelyTyped/DefinitelyTyped/blob/0893371fea43bfdf1777b6d835424961ba0d1dbb/types/mime/index.d.ts 4 | 5 | open Fable.Core 6 | 7 | [] 8 | let mime : Mime.IExports = jsNative 9 | 10 | module Mime = 11 | 12 | type [] IExports = 13 | [] 14 | abstract Mime: mimes: TypeMap -> Mime 15 | abstract getType: path: string -> string option 16 | abstract getExtension: mime: string -> string option 17 | abstract define: mimes: TypeMap * ?force: bool -> unit 18 | 19 | type [] Mime = 20 | abstract getType: path: string -> string option 21 | abstract getExtension: mime: string -> string option 22 | abstract define: mimes: TypeMap * ?force: bool -> unit 23 | 24 | type [] MimeStatic = 25 | [] abstract Create: mimes: TypeMap -> Mime 26 | 27 | type [] TypeMap = 28 | [] abstract Item: key: string -> ResizeArray with get, set 29 | -------------------------------------------------------------------------------- /glues/ServeStatic/src/Glutinum.ServeStatic.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 1.0.0 5 | netstandard2.0 6 | true 7 | Maxime Mangel 8 | 9 | Fable bindings for npm https://github.com/expressjs/serve-static package 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 24 | 27 | 28 | -------------------------------------------------------------------------------- /glues/ExpressServeStaticCore/src/Glutinum.ExpressServeStaticCore.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 0.2.0 5 | netstandard2.0 6 | true 7 | Maxime Mangel 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 23 | 26 | 27 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fable-express", 3 | "version": "0.0.0", 4 | "private": true, 5 | "description": "This repo is a meta repo for working on the binding for Express ecosystem", 6 | "author": "Mangel Maxime ", 7 | "license": "MIT", 8 | "type": "module", 9 | "scripts": { 10 | "postinstall": "dotnet tool restore" 11 | }, 12 | "dependencies": { 13 | "after": "^0.8.2", 14 | "body-parser": "^1.19.0", 15 | "connect": "^3.7.0", 16 | "cookie-parser": "^1.4.5", 17 | "dirname-filename-esm": "^1.1.2", 18 | "express": "^4.17.1", 19 | "mime": "^4.0.4", 20 | "qs": "^6.9.6", 21 | "range-parser": "^1.2.1", 22 | "serve-static": "^1.14.1", 23 | "supertest": "6.0.1" 24 | }, 25 | "devDependencies": { 26 | "chalk": "^4.1.0", 27 | "changelog-parser": "^2.8.0", 28 | "concurrently": "^6.0.0", 29 | "del-cli": "^3.0.1", 30 | "esm": "^3.2.25", 31 | "fast-glob": "^3.3.2", 32 | "mocha": "^10.7.3", 33 | "nodemon": "^2.0.7", 34 | "prompts": "^2.4.0", 35 | "yargs": "^16.2.0" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /glues/Chalk/tests/Tests.Chalk.Instance.fs: -------------------------------------------------------------------------------- 1 | module Tests.Chalk.Instance 2 | 3 | open Mocha 4 | open Fable.Core 5 | open Fable.Core.JsInterop 6 | open Glutinum.Chalk 7 | 8 | // Test ported from https://github.com/chalk/chalk/blob/4dab5e1fb6f42c6c9fdacbe34b9dafd24359208e/test/instance.js 9 | 10 | [] 11 | let Assert: Node.Assert.IExports = jsNative 12 | 13 | describe "Chalk" (fun _ -> 14 | 15 | describe "Instance" (fun _ -> 16 | 17 | it "create an isolated context where colors can be disabled (by level)" (fun _ -> 18 | let instance = chalk.Instance(jsOptions(fun o -> 19 | o.level <- Some Chalk.Level.N0 20 | )) 21 | 22 | Assert.strictEqual( 23 | instance.red.Invoke("foo"), 24 | "foo" 25 | ) 26 | 27 | Assert.strictEqual( 28 | chalk.red.Invoke("foo"), 29 | "\u001B[31mfoo\u001B[39m" 30 | ) 31 | 32 | instance.level <- Chalk.Level.N2 33 | 34 | 35 | Assert.strictEqual( 36 | instance.red.Invoke("foo"), 37 | "\u001B[31mfoo\u001B[39m" 38 | ) 39 | 40 | ) 41 | 42 | ) 43 | 44 | ) 45 | -------------------------------------------------------------------------------- /glues/SuperTest/src/Glutinum.SuperTest.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 0.1.0-alpha-002 5 | netstandard2.0 6 | true 7 | Maxime Mangel 8 | 9 | Fable bindings for npm https://github.com/visionmedia/supertest package 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 25 | 28 | 29 | -------------------------------------------------------------------------------- /glues/Express/tests/Tests.Express.Port.App.Locals.fs: -------------------------------------------------------------------------------- 1 | module Tests.Express.Port.App.Locals 2 | 3 | open Fable.Core.JS 4 | open Fable.Core.JsInterop 5 | open Mocha 6 | open Glutinum.ExpressServeStaticCore 7 | open Glutinum.Express 8 | 9 | describe "app" (fun _ -> 10 | 11 | describe ".locals(obj" (fun _ -> 12 | 13 | it "should merge locals" (fun _ -> 14 | let app = express.express () 15 | 16 | Assert.deepStrictEqual(Constructors.Object.keys(app.locals), ResizeArray ["settings"]) 17 | app.locals.["user"] <- "tobi" 18 | app.locals.["age"] <- 2 19 | 20 | Assert.deepStrictEqual(Constructors.Object.keys(app.locals), ResizeArray ["settings"; "user"; "age"]) 21 | Assert.strictEqual(app.locals.["user"], box "tobi") 22 | Assert.strictEqual(app.locals.["age"], box 2) 23 | ) 24 | 25 | ) 26 | 27 | describe ".locals.settings" (fun _ -> 28 | it "should expose app settings" (fun _ -> 29 | let app = express.express () 30 | app.set("title", "House of Manny") |> ignore 31 | 32 | let o = app.locals.["settings"] 33 | Assert.strictEqual(o?env, "test") 34 | Assert.strictEqual(o?title, "House of Manny") 35 | ) 36 | ) 37 | 38 | ) 39 | -------------------------------------------------------------------------------- /glues/Express/tests/Tests.Express.Port.App.All.fs: -------------------------------------------------------------------------------- 1 | module Tests.Express.Port.App.All 2 | 3 | open Mocha 4 | open Glutinum.ExpressServeStaticCore 5 | open Glutinum.Express 6 | 7 | describe "app.all()" (fun _ -> 8 | 9 | itAsync "should add a router per method" (fun d -> 10 | let app = express.express () 11 | 12 | app.all("/tobi", fun (req : Request) (res : Response) next -> 13 | res.``end``(req.``method``) 14 | ) 15 | 16 | request(app) 17 | .put("/tobi") 18 | .expect( 19 | "PUT", 20 | fun _ -> 21 | request(app) 22 | .get("/tobi") 23 | .expect("GET", d) 24 | |> ignore 25 | ) 26 | |> ignore 27 | ) 28 | 29 | itAsync "should run the callback for a method just once" (fun d -> 30 | let app = express.express () 31 | let mutable n = 0 32 | 33 | app.all("/*", fun (req : Request) (res : Response) (next : NextFunction) -> 34 | if n > 0 then 35 | d(System.Exception("DELETE called several times")) 36 | n <- n + 1 37 | next.Invoke() 38 | ) 39 | 40 | request(app) 41 | .del("/tobi") 42 | .expect(404, d) 43 | |> ignore 44 | ) 45 | 46 | ) 47 | -------------------------------------------------------------------------------- /glues/Chalk/tests/Tests.Chalk.Level.fs: -------------------------------------------------------------------------------- 1 | module Tests.Chalk.Level 2 | 3 | open Mocha 4 | open Fable.Core 5 | open Glutinum.Chalk 6 | 7 | // Test ported from https://github.com/chalk/chalk/blob/4dab5e1fb6f42c6c9fdacbe34b9dafd24359208e/test/instance.js 8 | 9 | [] 10 | let Assert: Node.Assert.IExports = jsNative 11 | 12 | describe "Chalk" (fun _ -> 13 | 14 | describe "Level" (fun _ -> 15 | 16 | it "don't output colors when manually disabled" (fun _ -> 17 | let oldLevel = chalk.level 18 | chalk.level <- Chalk.Level.N0 19 | 20 | Assert.strictEqual( 21 | chalk.red.Invoke("foo"), 22 | "foo" 23 | ) 24 | 25 | chalk.level <- oldLevel 26 | ) 27 | 28 | it "enable/disable colors based on overall chalk .level property, not individual instances" (fun _ -> 29 | let oldLevel = chalk.level 30 | 31 | chalk.level <- Chalk.Level.N1 32 | let red = chalk.red 33 | 34 | Assert.strictEqual( 35 | red.level, 36 | Chalk.Level.N1 37 | ) 38 | 39 | chalk.level <- Chalk.Level.N0 40 | 41 | Assert.strictEqual( 42 | red.level, 43 | chalk.level 44 | ) 45 | 46 | chalk.level <- oldLevel 47 | ) 48 | ) 49 | ) 50 | -------------------------------------------------------------------------------- /glues/Express/src/Glutinum.Express.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 0.2.0 5 | netstandard2.0 6 | true 7 | Maxime Mangel 8 | 9 | Fable bindings for npm https://github.com/expressjs/express package 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 27 | 30 | 31 | -------------------------------------------------------------------------------- /glues/Mime/tests/Tests.Mime.fs: -------------------------------------------------------------------------------- 1 | module Tests.Mime 2 | 3 | open Mocha 4 | open Fable.Core 5 | open Fable.Core.Testing 6 | open Fable.Core.JsInterop 7 | open Glutinum.Mime 8 | 9 | // Code adapted from: https://github.com/broofa/mime/blob/9847c9f9ee077a8d6e17d0b738b1b28c030a9a89/src/test.js 10 | 11 | 12 | describe "Mime" (fun _ -> 13 | 14 | it "Mime new constructor works" (fun _ -> 15 | let typeMap = createEmpty 16 | typeMap.["text/a"] <- ResizeArray(["a"; "a1"]) 17 | typeMap.["text/b"] <- ResizeArray(["b"; "b1"]) 18 | 19 | let mime = mime.Mime(typeMap) 20 | 21 | Assert.AreEqual(mime.getType("a"), Some "text/a") 22 | ) 23 | 24 | it "define works" (fun _ -> 25 | let typeMap = createEmpty 26 | typeMap.["text/a"] <- ResizeArray(["a"; "a1"]) 27 | typeMap.["text/b"] <- ResizeArray(["b"; "b1"]) 28 | 29 | let mime = mime.Mime(typeMap) 30 | 31 | let subTypeMap = createEmpty 32 | subTypeMap.["text/c"] <- ResizeArray(["c"]) 33 | 34 | mime.define(subTypeMap) 35 | 36 | Assert.AreEqual(mime.getType("c"), Some "text/c") 37 | ) 38 | 39 | it "getType() works" (fun _ -> 40 | Assert.AreEqual(mime.getType("txt"), Some "text/plain") 41 | ) 42 | 43 | it "getExtension() works" (fun _ -> 44 | Assert.AreEqual(mime.getExtension("text/html"), Some "html") 45 | ) 46 | 47 | ) 48 | -------------------------------------------------------------------------------- /glues/Express/tests/Tests.Express.Port.App.Response.fs: -------------------------------------------------------------------------------- 1 | module Tests.Express.Port.App.Response 2 | 3 | open Glutinum.ExpressServeStaticCore 4 | open Glutinum.Express 5 | open Fable.Core.JsInterop 6 | open Mocha 7 | 8 | describe "app" (fun _ -> 9 | describe ".response" (fun _ -> 10 | itAsync "should extend the response prototype" (fun d -> 11 | let app = express.express () 12 | 13 | emitJsStatement app """ 14 | $0.response.shout = function(str){ 15 | this.send(str.toUpperCase()); 16 | }; 17 | """ 18 | 19 | app.``use``(fun req res -> 20 | res?shout("hey") 21 | ) 22 | 23 | request(app) 24 | .get("/") 25 | .expect("HEY", d) 26 | |> ignore 27 | ) 28 | 29 | itAsync "should not be influenced by other app protos" (fun d -> 30 | let app = express.express () 31 | let app2 = express.express() 32 | 33 | emitJsStatement app """ 34 | $0.response.shout = function(str){ 35 | this.send(str.toUpperCase()); 36 | }; 37 | """ 38 | 39 | emitJsStatement app2 """ 40 | $0.response.shout = function(str){ 41 | this.send(str); 42 | }; 43 | """ 44 | 45 | app.``use``(fun req res -> 46 | res?shout("hey") 47 | ) 48 | 49 | request(app) 50 | .get("/") 51 | .expect("HEY", d) 52 | |> ignore 53 | ) 54 | ) 55 | ) 56 | -------------------------------------------------------------------------------- /glues/Chalk/README.md: -------------------------------------------------------------------------------- 1 | # Glutinum.Chalk 2 | 3 | Binding for [https://github.com/chalk/chalk](chalk) 4 | 5 | ## Limitations 6 | 7 | This package doesn't support template literal style notation from chalk. 8 | 9 | ## Usage 10 | 11 | *If you want to compare the F# code with JavaScript the section below is a port of [Chalk - Usage](https://github.com/chalk/chalk/tree/4dab5e1fb6f42c6c9fdacbe34b9dafd24359208e#usage)* 12 | 13 | ```fs 14 | open Glutinum.Chalk 15 | open Fable.Core 16 | 17 | let log x = JS.console.log(x) 18 | 19 | // Combine styled and normal strings 20 | log(chalk.blue.Invoke("Hello") + " World" + chalk.red.Invoke("!")) 21 | 22 | // Compose multiples styles using the chainable API 23 | log(chalk.blue.bgRed.bold.Invoke("Hello world!")) 24 | 25 | // Pass in multiple arguments 26 | log(chalk.blue.Invoke("Hello", "World!", "Foo", "bar", "biz", "baz")) 27 | 28 | // Nest styles 29 | log(chalk.red.Invoke("Hello", chalk.underline.bgBlue.Invoke("world") + "!")); 30 | 31 | // Nest styles of the same type even (color, underline, background) 32 | log( 33 | chalk.green.Invoke( 34 | "I am a green line " + 35 | chalk.blue.underline.bold.Invoke("with a blue substring") + 36 | " that becomes green again!" 37 | )); 38 | 39 | // Use RGB colors in terminal emulators that support it. 40 | log(chalk.keyword("orange").Invoke("Yay for orange colored text!")); 41 | log(chalk.rgb(123, 45, 67).underline.Invoke("Underlined reddish color")); 42 | log(chalk.hex("#DEADED").bold.Invoke("Bold gray!")); 43 | ``` 44 | -------------------------------------------------------------------------------- /glues/Connect/tests/Tests.Connect.fs: -------------------------------------------------------------------------------- 1 | module Tests.Connect 2 | 3 | open Mocha 4 | open Fable.Core.JsInterop 5 | open Node 6 | open Glutinum.Connect 7 | 8 | // Code adapted from: https://github.com/senchalabs/connect/blob/52cf21b211272519caeef3bb5064c3430f4feb43/test/server.js 9 | 10 | describe "Connect" (fun _ -> 11 | 12 | let mutable app : Connect.CreateServer.Server = Unchecked.defaultof<_> 13 | 14 | beforeEach (fun _ -> 15 | app <- connect() 16 | ) 17 | 18 | itAsync "should inherit from event emitter" (fun ok -> 19 | app.on("foo", ok) |> ignore 20 | app.emit("foo") |> ignore 21 | ) 22 | 23 | itAsync "should work in http.createServer" (fun ok -> 24 | let app = connect() 25 | 26 | // We need to help the compiler with a type hint 27 | app.``use``(fun req res -> 28 | res.``end``("Hello, world!") 29 | ) |> ignore 30 | 31 | let server = http.createServer(app) 32 | 33 | request(box server) 34 | .get("/") 35 | .expect(200, "Hello, world!", fun err _ -> ok err) 36 | |> ignore 37 | ) 38 | 39 | describe "error handler" (fun _ -> 40 | 41 | itAsync "should use custom error code" (fun ok -> 42 | let app = connect() 43 | 44 | app.``use``(fun req res next -> 45 | let err = new System.Exception("boom!") 46 | err?status <- 503 47 | raise err |> ignore 48 | ) |> ignore 49 | 50 | request(box app) 51 | .get("/") 52 | .expect(503, fun err _ -> ok err) 53 | |> ignore 54 | ) 55 | 56 | ) 57 | 58 | ) 59 | -------------------------------------------------------------------------------- /glues/ServeStatic/tests/Tests.ServeStatic.fs: -------------------------------------------------------------------------------- 1 | module Tests.ServeStatic 2 | 3 | open Mocha 4 | open Fable.Core 5 | open Fable.Core.Testing 6 | open Fable.Core.JsInterop 7 | open Glutinum.ServeStatic 8 | open Node 9 | open Glutinum.Connect 10 | 11 | // Code adapted from: https://github.com/expressjs/serve-static/blob/94feedb81682f4503ed9f8dc6d51a5c1b9bfa091/test/test.js 12 | 13 | let __dirname = 14 | emitJsExpr () """ 15 | import { dirname } from 'dirname-filename-esm'; 16 | dirname(import.meta); 17 | """ 18 | 19 | let private createServer opts = 20 | let dir = path.join(__dirname, "files") 21 | 22 | let serve = serveStatic.serveStatic(dir, opts) 23 | 24 | http.createServer(fun req res -> 25 | // let req = req :?> Types.Connect.CreateServer.IncomingMessage 26 | 27 | let next = 28 | Connect.CreateServer.NextFunction(fun error -> 29 | res.statusCode <- 30 | if isNull error then 31 | 404 32 | else 33 | if isNull error?status then 34 | 500 35 | else 36 | error?status |> unbox 37 | if isNull error then 38 | res.``end``("sorry!") 39 | else 40 | res.``end``(error?``stack``) 41 | ) 42 | 43 | serve.Invoke(req, res, next) 44 | |> ignore 45 | 46 | ) 47 | 48 | 49 | describe "ServeStatic" (fun _ -> 50 | 51 | itAsync "should support nesting" (fun d -> 52 | request(box (createServer null)) 53 | .get("/users/tobi.txt") 54 | .expect(200, "ferret", d) 55 | |> ignore 56 | ) 57 | 58 | itAsync "should support index.html" (fun d -> 59 | request(box (createServer null)) 60 | .get("/users/") 61 | .expect(200, "

    tobi, loki, jane

    ", d) 62 | |> ignore 63 | ) 64 | 65 | ) 66 | -------------------------------------------------------------------------------- /glues/Express/tests/Tests.Express.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | true 6 | ../../../tests-shared 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /glues/Express/tests/Tests.Express.Port.App.Route.fs: -------------------------------------------------------------------------------- 1 | module Tests.Express.Port.App.Route 2 | 3 | open Glutinum.ExpressServeStaticCore 4 | open Glutinum.Express 5 | open Mocha 6 | 7 | describe "app.route" (fun _ -> 8 | itAsync "should return a new route" (fun d -> 9 | let app = express.express () 10 | 11 | app.route("/foo") 12 | .get(fun (req : Request) (res : Response) -> 13 | res.send("get") 14 | ) 15 | .post(fun (req : Request) (res : Response) -> 16 | res.send("post") 17 | ) 18 | |> ignore 19 | 20 | request(app) 21 | .post("/foo") 22 | .expect("post", d) 23 | |> ignore 24 | ) 25 | 26 | itAsync "should all .VERB after .all" (fun d -> 27 | let app = express.express () 28 | 29 | app.route("/foo") 30 | .all(fun req res (next : NextFunction) -> 31 | next.Invoke() 32 | ) 33 | .get(fun req (res : Response) -> 34 | res.send("get") 35 | ) 36 | .post(fun (req : Request) (res : Response) -> 37 | res.send("post") 38 | ) 39 | |> ignore 40 | 41 | request(app) 42 | .post("/foo") 43 | .expect("post", d) 44 | |> ignore 45 | ) 46 | 47 | itAsync "should support dynamic routes" (fun d -> 48 | let app = express.express () 49 | 50 | app.route("/:foo") 51 | .get(fun (req : Request) (res : Response) -> 52 | res.send(req.``params``.["foo"]) 53 | ) 54 | |> ignore 55 | 56 | request(app) 57 | .get("/test") 58 | .expect("test", d) 59 | |> ignore 60 | ) 61 | 62 | itAsync "should not error on empty routes" (fun d -> 63 | let app = express.express () 64 | 65 | app.route("/:foo") |> ignore 66 | 67 | request(app) 68 | .get("/test") 69 | .expect(404, d) 70 | |> ignore 71 | ) 72 | 73 | ) 74 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Glutinum 2 | 3 | Glutinum is a tentative to bring an equivalent to [DefinitelyTyped](https://github.com/DefinitelyTyped/DefinitelyTyped) for Fable ecosystem. 4 | 5 | Right now Glutinum contains bindings generated using [ts2fable](https://github.com/fable-compiler/ts2fable/) and improved manually. 6 | 7 | In the future, Glutinum will try to use it's own converter to experiment with a new way to write bindings. 8 | 9 | ## Can I use the packages from this repository? 10 | 11 | Yes, you can. 12 | 13 | Most of the package already have a battery of tests to check regression and if they works. 14 | 15 | ## Structure of a glue 16 | 17 | A glue, is always no-code bindings between F# and JavaScript code. 18 | 19 | If there are code addition made to improve the user experience when working with the package then it goes into another package `Glutinum.XXX.Extensions`. 20 | 21 | Example: `Glutinum.RangeParser.Extensions` 22 | 23 | This is so we don't have to make all the packages includes their source code under `.fable` directory and should improve Fable memory consumption and performance. 24 | 25 | ## Tests status 26 | 27 | 28 | 29 | | Binding | Number of tests | 30 | |---------|-----------------| 31 | | BodyParser | 5 | 32 | | Chalk | 30 | 33 | | Connect | 3 | 34 | | Express | 424 | 35 | | Mime | 4 | 36 | | Qs | 5 | 37 | | RangeParser | 5 | 38 | | ServeStatic | 2 | 39 | 40 | 41 | 42 | ## How to use this repository? 43 | 44 | If you are on Windows using the standard terminal you need to run `node build.js`. 45 | 46 | If you are on Linux, OSX or Windows using a bash-like terminal you can run `build.js` directly. 47 | 48 | From now, I will always use `build.js` in the command as it is shorter. 49 | 50 | Run `build.js --help` for more information about which command are supported. 51 | 52 | ### Getting completion from Linux, OSX, Windows bash-like 53 | 54 | It is possible to have `TAB` completion support from your terminal. 55 | 56 | Run `./build.js completion` and follow the instruction. 57 | 58 | ### Why this name? 59 | 60 | It comes from the ideas that this project is trying to **glue** together F# and TypeScript. 61 | 62 | Glutinum is from the Latin word gluten ("glue") with the suffix -um. 63 | -------------------------------------------------------------------------------- /glues/Methods/src/Glutinum.Methods.fs: -------------------------------------------------------------------------------- 1 | // ts2fable 0.8.0 2 | module rec Glutinum.Methods 3 | 4 | open System 5 | open Fable.Core 6 | open Fable.Core.JS 7 | 8 | let [] methods: ResizeArray = jsNative 9 | 10 | type [] [] Method = 11 | | [] ACL 12 | | [] BIND 13 | | [] CHECKOUT 14 | | [] CONNECT 15 | | [] COPY 16 | | [] DELETE 17 | | [] GET 18 | | [] HEAD 19 | | [] LINK 20 | | [] LOCK 21 | | [] MSEARCH 22 | | [] MERGE 23 | | [] MKACTIVITY 24 | | [] MKCALENDAR 25 | | [] MKCOL 26 | | [] MOVE 27 | | [] NOTIFY 28 | | [] OPTIONS 29 | | [] PATCH 30 | | [] POST 31 | | [] PROPFIND 32 | | [] PROPPATCH 33 | | [] PURGE 34 | | [] PUT 35 | | [] REBIND 36 | | [] REPORT 37 | | [] SEARCH 38 | | [] SOURCE 39 | | [] SUBSCRIBE 40 | | [] TRACE 41 | | [] UNBIND 42 | | [] UNLINK 43 | | [] UNLOCK 44 | | [] UNSUBSCRIBE 45 | | Acl 46 | | Bind 47 | | Checkout 48 | | Connect 49 | | Copy 50 | | Delete 51 | | Get 52 | | Head 53 | | Link 54 | | Lock 55 | | [] MSearch 56 | | Merge 57 | | Mkactivity 58 | | Mkcalendar 59 | | Mkcol 60 | | Move 61 | | Notify 62 | | Options 63 | | Patch 64 | | Post 65 | | Propfind 66 | | Proppatch 67 | | Purge 68 | | Put 69 | | Rebind 70 | | Report 71 | | Search 72 | | Source 73 | | Subscribe 74 | | Trace 75 | | Unbind 76 | | Unlink 77 | | Unlock 78 | | Unsubscribe 79 | -------------------------------------------------------------------------------- /glues/BodyParser/tests/Tests.BodyParser.Text.fs: -------------------------------------------------------------------------------- 1 | module Tests.BodyParser.Text 2 | 3 | open Mocha 4 | open Node 5 | open Fable.Core 6 | open Fable.Core.JsInterop 7 | open Glutinum.BodyParser 8 | open Glutinum.Connect 9 | open Glutinum.SuperTest 10 | 11 | // Code adapted from: https://github.com/expressjs/body-parser/blob/480b1cfe29af19c070f4ae96e0d598c099f42a12/test/text.js 12 | 13 | let private createServer (opts : BodyParser.OptionsText) = 14 | let bodyParser = bodyParser.text(opts) 15 | 16 | http.createServer(fun req res -> 17 | let req = req :?> Connect.CreateServer.IncomingMessage 18 | 19 | let next = 20 | Connect.CreateServer.NextFunction(fun error -> 21 | res.statusCode <- 22 | if isNull error then 23 | 200 24 | else 25 | if isNull error?status then 26 | 500 27 | else 28 | error?status |> unbox 29 | if isNull error then 30 | res.``end``(JS.JSON.stringify(req?body)) 31 | else 32 | res.``end``(error?message) 33 | ) 34 | 35 | bodyParser.Invoke(req, res, next) 36 | ) 37 | 38 | 39 | 40 | describe "bodyParser.text()" (fun _ -> 41 | 42 | itAsync "should parse text/plain" (fun d -> 43 | let test = 44 | request(box (createServer null)) 45 | .post("/") 46 | .set("Content-Type", "text/plain") 47 | .send("user is tobi") 48 | :?> SuperTest.Test 49 | 50 | test.expect(200, "\"user is tobi\"", d) 51 | |> ignore 52 | ) 53 | 54 | describe "with limit option" (fun _ -> 55 | 56 | itAsync "should 413 when over limit with Content-Length" (fun d -> 57 | let buf = buffer.Buffer.alloc(1028, ".") 58 | 59 | let server = 60 | createServer(jsOptions(fun o -> 61 | o.limit <- !^ "1kb" 62 | )) 63 | 64 | let test = 65 | request(box server) 66 | .post("/") 67 | .set("Content-Type", "text/plain") 68 | .set("Content-Length", "1028") 69 | .send(buf.toString()) 70 | :?> SuperTest.Test 71 | 72 | test.expect(413, d) 73 | |> ignore 74 | ) 75 | ) 76 | ) 77 | 78 | -------------------------------------------------------------------------------- /glues/Express/tests/Tests.Express.Port.App.Head.fs: -------------------------------------------------------------------------------- 1 | module Tests.Express.Port.App.Head 2 | 3 | open Mocha 4 | open Glutinum.ExpressServeStaticCore 5 | open Glutinum.Express 6 | open Fable.Core.JsInterop 7 | 8 | describe "HEAD" (fun _ -> 9 | 10 | itAsync "should default to GET" (fun d -> 11 | let app = express.express () 12 | 13 | app.get("/tobi", fun (req : Request) (res : Response) (next : NextFunction) -> 14 | res.send("tobi") 15 | ) 16 | 17 | request(app) 18 | .head("/tobi") 19 | .expect(200, d) 20 | |> ignore 21 | ) 22 | 23 | itAsync "should output the same headers as GET requests" (fun d -> 24 | let app = express.express () 25 | 26 | app.get("/tobi", fun req (res : Response) next -> 27 | res.send("tobi") 28 | ) 29 | 30 | request(app) 31 | .get("/tobi") 32 | .expect( 33 | 200, 34 | (fun err res -> 35 | if err.IsSome then 36 | d err 37 | 38 | let headers = res.headers 39 | 40 | request(app) 41 | .get("/tobi") 42 | .expect( 43 | 200, 44 | (fun err res -> 45 | if err.IsSome then 46 | d err 47 | 48 | jsDelete headers.Value?date 49 | jsDelete res.headers.Value?date 50 | 51 | Assert.deepStrictEqual(res.headers, headers) 52 | d() 53 | ) 54 | ) 55 | |> ignore 56 | ) 57 | ) 58 | |> ignore 59 | ) 60 | 61 | ) 62 | 63 | describe "app.head()" (fun _ -> 64 | 65 | itAsync "should override" (fun d -> 66 | 67 | let app = express.express () 68 | let mutable called = false 69 | 70 | app.head("/tobi", fun req (res : Response<_,_>) next -> 71 | called <- true 72 | res.``end``() 73 | ) 74 | 75 | app.get("/tobi", fun req (res : Response) next -> 76 | Assert.fail("should not call GET") 77 | res.send("tobi") 78 | ) 79 | 80 | request(app) 81 | .head("/tobi") 82 | .expect( 83 | 200, 84 | (fun err _ -> 85 | Assert.strictEqual(called, true) 86 | d () 87 | ) 88 | ) 89 | |> ignore 90 | 91 | ) 92 | 93 | ) 94 | -------------------------------------------------------------------------------- /glues/BodyParser/tests/Tests.BodyParser.Json.fs: -------------------------------------------------------------------------------- 1 | module Tests.BodyParser.Json 2 | 3 | open Mocha 4 | open Node 5 | open Fable.Core 6 | open Fable.Core.JsInterop 7 | open Glutinum.BodyParser 8 | open Glutinum.Connect 9 | open Glutinum.SuperTest 10 | 11 | // Code adapted from: https://github.com/expressjs/body-parser/blob/480b1cfe29af19c070f4ae96e0d598c099f42a12/test/json.js 12 | 13 | let private createServer opts = 14 | let bodyParser = bodyParser.json(opts) 15 | 16 | http.createServer(fun req res -> 17 | let req = req :?> Connect.CreateServer.IncomingMessage 18 | 19 | let next = 20 | Connect.CreateServer.NextFunction(fun error -> 21 | res.statusCode <- 22 | if isNull error then 23 | 200 24 | else 25 | if isNull error?status then 26 | 500 27 | else 28 | error?status |> unbox 29 | if isNull error then 30 | res.``end``(JS.JSON.stringify(req?body)) 31 | else 32 | res.``end``(error?message) 33 | ) 34 | 35 | bodyParser.Invoke(req, res, next) 36 | ) 37 | 38 | 39 | 40 | describe "bodyParser.json()" (fun _ -> 41 | 42 | itAsync "should default to {}" (fun d -> 43 | request(box (createServer null)) 44 | .post("/") 45 | .expect(200, "{}", d) 46 | |> ignore 47 | ) 48 | 49 | itAsync "should parse JSON" (fun ok -> 50 | let test = 51 | request(box (createServer null)) 52 | .post("/") 53 | .set("Content-Type", "application/json") 54 | .send("""{"user":"tobi"}""") 55 | :?> SuperTest.Test 56 | 57 | test.expect(200, """{"user":"tobi"}""", ok) 58 | |> ignore 59 | ) 60 | 61 | describe "with limit option" (fun _ -> 62 | 63 | itAsync "should 413 when over limit with Content-Length" (fun d -> 64 | let buf = buffer.Buffer.alloc(1024, ".") 65 | 66 | let server = 67 | createServer(jsOptions(fun o -> 68 | o.limit <- !^ "1kb" 69 | )) 70 | 71 | let test = 72 | request(box server) 73 | .post("/") 74 | .set("Content-Type", "application/json") 75 | .set("Content-Length", "1034") 76 | .send(JS.JSON.stringify( 77 | createObj [ 78 | "str" ==> buf.toString() 79 | ] 80 | ) 81 | ) 82 | :?> SuperTest.Test 83 | 84 | test.expect(413, d) 85 | |> ignore 86 | ) 87 | ) 88 | ) 89 | -------------------------------------------------------------------------------- /glues/Express/tests/Tests.Express.Port.App.Routes.Error.fs: -------------------------------------------------------------------------------- 1 | module Tests.Express.Port.App.Routes.Error 2 | 3 | open System.Text.RegularExpressions 4 | open Glutinum.ExpressServeStaticCore 5 | open Glutinum.Express 6 | open Fable.Core.JsInterop 7 | open Mocha 8 | open Node 9 | open Fable.Core 10 | 11 | describe "app" (fun () -> 12 | 13 | describe ".VERB()" (fun () -> 14 | 15 | itAsync "should not get invoked without error handler on error" (fun d -> 16 | let app = express.express () 17 | 18 | app.``use``(fun (req : Request) (res : Response) (next : NextFunction) -> 19 | next.Invoke(System.Exception("boom!")) 20 | ) 21 | 22 | app.get("/bar", fun (req : Request) (res : Response) -> 23 | res.send("hello, world!") 24 | ) 25 | 26 | request(app) 27 | .post("/bar") 28 | .expect(500, JS.Constructors.RegExp.Create("Error: boom!"), d) 29 | |> ignore 30 | ) 31 | 32 | itAsync "should only call an error handling routing callback when an error is propagated" (fun ``done`` -> 33 | let app = express.express () 34 | 35 | let mutable a = false 36 | let mutable b = false 37 | let mutable c = false 38 | let mutable d = false 39 | 40 | app.get("/", 41 | fun (req : Request) (res : Response) (next : NextFunction) -> 42 | next.Invoke(System.Exception("fabricated error")) 43 | |> Adapter.RequestHandler, 44 | fun (req : Request) (res : Response) (next : NextFunction) -> 45 | a <- true 46 | next.Invoke() 47 | |> Adapter.RequestHandler, 48 | fun (err : Error option) (req : Request) (res : Response) (next : NextFunction) -> 49 | b <- true 50 | Assert.strictEqual(err.Value.Message, "fabricated error") 51 | next.Invoke(err) 52 | |> Adapter.RequestHandler, 53 | fun (err : Error option) (req : Request) (res : Response) (next : NextFunction) -> 54 | c <- true 55 | Assert.strictEqual(err.Value.Message, "fabricated error") 56 | next.Invoke() 57 | |> Adapter.RequestHandler, 58 | fun (err : Error option) (req : Request) (res : Response) (next : NextFunction) -> 59 | d <- true 60 | next.Invoke() 61 | |> Adapter.RequestHandler, 62 | fun (req : Request) (res : Response) -> 63 | Assert.strictEqual(a, false) 64 | Assert.strictEqual(b, true) 65 | Assert.strictEqual(c, true) 66 | Assert.strictEqual(d, false) 67 | res.send(204) 68 | |> Adapter.RequestHandler 69 | ) 70 | 71 | request(app) 72 | .get("/") 73 | .expect(204, ``done``) 74 | |> ignore 75 | ) 76 | ) 77 | ) 78 | -------------------------------------------------------------------------------- /scripts/init-glue-templates.js: -------------------------------------------------------------------------------- 1 | export const initialChangelog = () => { 2 | return ` 3 | # Changelog 4 | All notable changes to this project will be documented in this file. 5 | 6 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 7 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 8 | 9 | ## Unreleased 10 | `.trimStart() 11 | } 12 | 13 | export const initialReadme = (glueName, npmPackageName, npmPackageUrl) => { 14 | return ` 15 | # Glutinum.${glueName} 16 | 17 | Binding for [${npmPackageName}](${npmPackageUrl}) 18 | 19 | ## Usage 20 | `.trimStart() 21 | } 22 | 23 | export const initialGlueFsproj = (glueName, authors, npmPackageUrl) => { 24 | return ` 25 | 26 | 27 | 28 | 0.0.0 29 | netstandard2.0 30 | true 31 | ${authors} 32 | 33 | Fable bindings for npm ${npmPackageUrl} package 34 | 35 | 36 | 37 | 38 | 39 | 41 | 44 | 45 | `.trimStart() 46 | } 47 | 48 | const lowerFirstLetter = (txt) => { 49 | return txt.charAt(0).toLowerCase() + txt.slice(1) 50 | } 51 | 52 | export const initialGlueFsharpFile = (glueName, npmPackageName) => { 53 | return ` 54 | module rec Glutinum.${glueName} 55 | 56 | open Fable.Core 57 | 58 | [] 59 | let ${lowerFirstLetter(glueName)} : ${glueName}.IExports = jsNative 60 | 61 | module ${glueName} = 62 | 63 | type [] IExports = 64 | class end 65 | `.trimStart() 66 | } 67 | 68 | export const initialGlueTestFsproj = (glueName) => { 69 | return ` 70 | 71 | 72 | 73 | netstandard2.0 74 | true 75 | ../../../tests-shared 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | `.trimStart() 86 | } 87 | 88 | export const initialGlueTestFsharpFile = (glueName) => { 89 | return ` 90 | module Tests.${glueName} 91 | 92 | open Mocha 93 | open Fable.Core 94 | open Glutinum.${glueName} 95 | 96 | describe "${glueName}" (fun _ -> 97 | 98 | it "Mime new constructor works" (fun _ -> 99 | failwith "Tests should go here" 100 | ) 101 | ) 102 | `.trimStart() 103 | } 104 | -------------------------------------------------------------------------------- /glues/SuperTest/src/Glutinum.SuperTest.fs: -------------------------------------------------------------------------------- 1 | module rec Glutinum.SuperTest 2 | 3 | open System 4 | open Fable.Core 5 | open Fable.Core.JS 6 | 7 | [] 8 | let supertest : IExports = jsNative 9 | 10 | type IExports = 11 | [] 12 | abstract supertest : app : obj -> SuperTest.SuperTest 13 | abstract agent: ?app: obj * ?options: SuperTest.AgentOptions -> SuperTest.SuperAgentTest 14 | 15 | //let supertest (_app : obj) : Supertest.Test = jsNative 16 | // 17 | //type Supertest() = 18 | // [] 19 | // abstract agent: ?app: obj * ?options: Supertest.AgentOptions -> Supertest.SuperAgentTest 20 | 21 | 22 | type RegExp = System.Text.RegularExpressions.Regex 23 | 24 | module SuperTest = 25 | 26 | // type [] IExports = 27 | // abstract agent: ?app: obj * ?options: AgentOptions -> SuperAgentTest 28 | 29 | type [] Response = 30 | inherit SuperAgent.Request.Response 31 | 32 | type [] Request = 33 | inherit SuperAgent.Request.SuperAgentRequest 34 | 35 | type CallbackHandler = 36 | Action 37 | // [] abstract Invoke: err: obj option * res: Response -> unit 38 | 39 | type [] Test = 40 | inherit SuperAgent.Request.SuperAgentRequest 41 | abstract app: obj option with get, set 42 | abstract url: string with get, set 43 | abstract serverAddress: app: obj option * path: string -> string 44 | abstract expect: status: int -> Test 45 | abstract expect: body : string -> Test 46 | abstract expect: status: int * callback: Func -> Test 47 | // abstract expect: status: int * body: obj * callback: CallbackHandler -> Test 48 | abstract expect: status: int * body: obj -> Test 49 | abstract expect: status : int * callback: Func -> Test 50 | abstract expect: status: int * body: obj * callback: Func -> Test 51 | abstract expect: status: int * body: obj * callback: Func -> Test 52 | abstract expect: status: string * body: obj * callback: Func -> Test 53 | abstract expect: body: obj * callback: Func -> Test 54 | abstract expect: body: obj * callback: Func -> Test 55 | 56 | abstract expect: checker: (Response -> obj option) * ?callback: CallbackHandler -> Test 57 | abstract expect: body: string * ?callback: CallbackHandler -> Test 58 | abstract expect: body: RegExp * ?callback: CallbackHandler -> Test 59 | abstract expect: body: Object * ?callback: CallbackHandler -> Test 60 | abstract expect: field: string * ``val``: string -> Test 61 | abstract expect: field: string * ``val``: string * callback: Func -> Test 62 | abstract expect: field: string * ``val``: RegExp * ?callback: Func -> Test 63 | abstract ``end``: ?callback: CallbackHandler -> Test 64 | 65 | type [] AgentOptions = 66 | abstract ca: obj option with get, set 67 | 68 | type [] SuperAgentTest = 69 | interface end 70 | 71 | type [] SuperTest<'T when 'T :> SuperAgent.Request.SuperAgentRequest> = 72 | inherit SuperAgent.Request.SuperAgent<'T> 73 | -------------------------------------------------------------------------------- /glues/Express/tests/Tests.Express.Port.App.Request.fs: -------------------------------------------------------------------------------- 1 | module Tests.Express.Port.App.Request 2 | 3 | open Glutinum.Express 4 | open Fable.Core.JsInterop 5 | open Mocha 6 | open Glutinum.SuperTest 7 | open Fable.Core 8 | 9 | // this ensures the two types are the same to asset that the is method below has the expected return type 10 | let expectEqual (actual: 'T, expected: 'T) = 11 | Assert.strictEqual(actual, expected) 12 | 13 | let expectEqualNonStrict (actual: 'T, expected: 'T) = 14 | Assert.notStrictEqual(actual, expected) 15 | 16 | let url : obj = import "*" "url" 17 | 18 | describe "app" (fun _ -> 19 | describe ".request" (fun _ -> 20 | itAsync "should extend the request prototype" (fun d -> 21 | let app = express.express () 22 | 23 | emitJsStatement (app, url) """ 24 | $0.request.querystring = function(){ 25 | return $1.parse(this.url).query; 26 | }; 27 | """ 28 | 29 | app.``use``(fun req res -> 30 | res.``end``(req?querystring()) 31 | ) 32 | 33 | request(app) 34 | .get("/foo?name=tobi") 35 | .expect("name=tobi", d) 36 | |> ignore 37 | ) 38 | ) 39 | 40 | describe ".is"(fun _ -> 41 | 42 | itAsync "should return null when body is missing" (fun d -> 43 | let app = express.express () 44 | app.``use``(fun req res -> 45 | let json: U2 = req.is(!^"json") 46 | //NOTE: not testing strict equality here since the string option returns a null, whereas 47 | // the runtime representation of None is undefined 48 | expectEqualNonStrict(json, !^None) 49 | //NOTE: converting this to a boolean, since providing the option to the send method 50 | // will result in `some` checking for null, and the response body being {}, 51 | // which is indistinguishable from some other empty object 52 | let json = 53 | match json with 54 | | U2.Case1 (Some value) -> false 55 | | U2.Case1 None -> true 56 | | U2.Case2 _ -> false 57 | res.``send``(json) 58 | ) 59 | request(app) 60 | .get("/") 61 | .expect("true", d) 62 | |> ignore 63 | ) 64 | 65 | itAsync "should return false when missing application/json content type" (fun d -> 66 | let app = express.express () 67 | app.``use``(fun req res -> 68 | let json = req.is(!^"json") 69 | expectEqual(json, !^false) 70 | res.``send``(json) 71 | ) 72 | let test = 73 | request(app) 74 | .post("/") 75 | .send "{}" 76 | :?> SuperTest.Test 77 | test.expect("false", d) 78 | |> ignore 79 | ) 80 | 81 | itAsync "should return 'json' when body and content type is present" (fun d -> 82 | let app = express.express () 83 | app.``use``(fun req res -> 84 | let json = req.is(!^"json") 85 | expectEqual(json, !^(Some "json")) 86 | res.``send``(json) 87 | ) 88 | let test = 89 | request(app) 90 | .post("/") 91 | .set("Content-Type", "application/json") 92 | .send "{}" 93 | :?> SuperTest.Test 94 | test.expect("json", d) 95 | |> ignore 96 | ) 97 | ) 98 | ) 99 | -------------------------------------------------------------------------------- /glues/Express/tests/Tests.Express.Port.App.Engine.fs: -------------------------------------------------------------------------------- 1 | module Tests.Express.Port.App.Engine 2 | 3 | open Mocha 4 | open Glutinum.ExpressServeStaticCore 5 | open Glutinum.Express 6 | open Node 7 | open Fable.Core.JsInterop 8 | 9 | let __dirname = 10 | emitJsExpr () """ 11 | import { dirname } from 'dirname-filename-esm'; 12 | dirname(import.meta); 13 | """ 14 | 15 | type RenderOption = 16 | { 17 | user : 18 | {| 19 | name : string 20 | |} 21 | } 22 | 23 | let render path (options : RenderOption) (fn : EngineRenderFunc) = 24 | fs.readFile(path, "utf8", fun (err : Base.ErrnoException option) (str : string) -> 25 | if err.IsSome then 26 | fn.Invoke(err, None) 27 | else 28 | let str = str.Replace("{{user.name}}", options.user.name) 29 | fn.Invoke(null, Some str) 30 | ) 31 | 32 | 33 | describe "app.engine(ext, fn)" (fun _ -> 34 | 35 | itAsync "should map a template engine" (fun d -> 36 | let app = express.express () 37 | 38 | app.set("views", path.join(__dirname, "fixtures")) |> ignore 39 | app.engine(".html", render) |> ignore 40 | app.locals.["user"] <- 41 | {| 42 | name = "tobi" 43 | |} 44 | 45 | app.render("user.html", fun err str -> 46 | if err.IsSome then 47 | d err 48 | else 49 | Assert.strictEqual(str, "

    tobi

    ") 50 | d() 51 | ) 52 | ) 53 | 54 | it "should throw when the callback is missing" (fun _ -> 55 | let app = express.express () 56 | 57 | Assert.throws( 58 | (fun () -> 59 | app.engine(".html", unbox null) 60 | ) 61 | ) 62 | ) 63 | 64 | itAsync """should work without leading "." """ (fun d -> 65 | let app = express.express () 66 | 67 | app.set("views", path.join(__dirname, "fixtures")) |> ignore 68 | app.engine("html", render) |> ignore 69 | app.locals.["user"] <- 70 | {| 71 | name = "tobi" 72 | |} 73 | 74 | app.render("user.html", fun err str -> 75 | if err.IsSome then 76 | d err 77 | else 78 | Assert.strictEqual(str, "

    tobi

    ") 79 | d() 80 | ) 81 | 82 | ) 83 | 84 | itAsync """should work "view engine" setting""" (fun d -> 85 | let app = express.express () 86 | 87 | app.set("views", path.join(__dirname, "fixtures")) |> ignore 88 | app.engine("html", render) |> ignore 89 | app.set("view engine","html") |> ignore 90 | app.locals.["user"] <- 91 | {| 92 | name = "tobi" 93 | |} 94 | 95 | app.render("user.html", fun err str -> 96 | if err.IsSome then 97 | d err 98 | else 99 | Assert.strictEqual(str, "

    tobi

    ") 100 | d() 101 | ) 102 | ) 103 | 104 | itAsync """should work "view engine" with leading "." """ (fun d -> 105 | let app = express.express () 106 | 107 | app.set("views", path.join(__dirname, "fixtures")) |> ignore 108 | app.engine(".html", render) |> ignore 109 | app.set("view engine",".html") |> ignore 110 | app.locals.["user"] <- 111 | {| 112 | name = "tobi" 113 | |} 114 | 115 | app.render("user.html", fun err str -> 116 | if err.IsSome then 117 | d err 118 | else 119 | Assert.strictEqual(str, "

    tobi

    ") 120 | d() 121 | ) 122 | 123 | ) 124 | 125 | ) 126 | -------------------------------------------------------------------------------- /tests-shared/Node.Assert.fs: -------------------------------------------------------------------------------- 1 | module rec Node.Assert 2 | 3 | open System 4 | open Fable.Core 5 | open Fable.Core.JS 6 | 7 | type Error = System.Exception 8 | type Function = System.Action 9 | type RegExp = System.Text.RegularExpressions.Regex 10 | 11 | type [] IExports = 12 | abstract AssertionError: AssertionErrorStatic 13 | abstract fail: ?message: Error -> unit 14 | abstract fail: ?message: string -> unit 15 | [] 16 | abstract fail: actual: obj * expected: obj option * ?message: U2 * ?operator: string * ?stackStartFn: Function -> unit 17 | abstract ok: value: obj * ?message: U2 -> unit 18 | [] 19 | abstract equal: actual: obj * expected: obj option * ?message: U2 -> unit 20 | [] 21 | abstract notEqual: actual: obj * expected: obj option * ?message: U2 -> unit 22 | [] 23 | abstract deepEqual: actual: obj * expected: obj option * ?message: U2 -> unit 24 | [] 25 | abstract notDeepEqual: actual: obj * expected: obj option * ?message: U2 -> unit 26 | abstract strictEqual: actual: obj * expected: 'T * ?message: U2 -> unit 27 | abstract notStrictEqual: actual: obj * expected: 'T * ?message: U2 -> unit 28 | abstract deepStrictEqual: actual: obj * expected: 'T * ?message: U2 -> unit 29 | abstract notDeepStrictEqual: actual: obj * expected: obj option * ?message: U2 -> unit 30 | abstract throws: block: (unit -> 'T) * ?message: string -> unit 31 | abstract throws: block: (unit -> obj) * ?message: Error -> unit 32 | abstract throws: block: (unit -> obj) -> unit 33 | abstract throws: block: (unit -> obj option) * error: AssertPredicate * ?message: U2 -> unit 34 | abstract doesNotThrow: block: (unit -> obj option) * ?message: U2 -> unit 35 | abstract doesNotThrow: block: (unit -> obj option) * error: U2 * ?message: U2 -> unit 36 | abstract ifError: value: obj option -> bool 37 | abstract rejects: block: U2<(unit -> Promise), Promise> * ?message: U2 -> Promise 38 | abstract rejects: block: U2<(unit -> Promise), Promise> * error: AssertPredicate * ?message: U2 -> Promise 39 | abstract doesNotReject: block: U2<(unit -> Promise), Promise> * ?message: U2 -> Promise 40 | abstract doesNotReject: block: U2<(unit -> Promise), Promise> * error: U2 * ?message: U2 -> Promise 41 | abstract ``match``: value: string * regExp: RegExp * ?message: U2 -> unit 42 | abstract doesNotMatch: value: string * regExp: RegExp * ?message: U2 -> unit 43 | abstract strict: obj 44 | 45 | type [] AssertionError = 46 | inherit Error 47 | abstract name: string with get, set 48 | abstract message: string with get, set 49 | abstract actual: obj with get, set 50 | abstract expected: obj option with get, set 51 | abstract operator: string with get, set 52 | abstract generatedMessage: bool with get, set 53 | abstract code: string with get, set 54 | 55 | type [] AssertionErrorStatic = 56 | [] abstract Create: ?options: AssertionErrorStaticOptions -> AssertionError 57 | 58 | type [] AssertionErrorStaticOptions = 59 | abstract message: string option with get, set 60 | abstract actual: obj with get, set 61 | abstract expected: obj option with get, set 62 | abstract operator: string option with get, set 63 | abstract stackStartFn: Function option with get, set 64 | 65 | type AssertPredicate = 66 | U5 bool), obj, Error> 67 | -------------------------------------------------------------------------------- /glues/RangeParser/src/Glutinum.RangeParser.fs: -------------------------------------------------------------------------------- 1 | namespace rec Glutinum.RangeParser 2 | 3 | open System 4 | open Fable.Core 5 | 6 | [] 7 | module Api = 8 | [] 9 | let rangeParser : IExports = jsNative 10 | 11 | type IExports = 12 | /// 13 | /// When ranges are returned, the array has a "type" property which is the type of 14 | /// range that is required (most commonly, "bytes"). Each array element is an object 15 | /// with a "start" and "end" property for the portion of the range. 16 | /// 17 | /// Use ParseRangeResult pattern matcher against the result of parseString.Invoke to get a typed result. 18 | /// 19 | /// 20 | /// let range = parseRange.Invoke(1000, "bytes=0-499") 21 | /// 22 | /// match range with 23 | /// | ParseRangeResult.UnkownError error -> 24 | /// printfn "Result of parseRange.Invoke is an unkown error value... If this happen a fix is probably needed in the binding" 25 | /// 26 | /// | ParseRangeResult.ResultInvalid error -> 27 | /// printfn "Result of parseRange.Invoke is an error of type ResultInvalid" 28 | /// 29 | /// | ParseRangeResult.ResultUnsatisfiable error -> 30 | /// printfn "Result of parseRange.Invoke is an error of type ResultUnsatisfiable" 31 | /// 32 | /// | ParseRangeResult.Range range -> 33 | /// // Here you can access your successful result 34 | /// Expect.equal range.``type "bytes" "" 35 | /// Expect.equal range.Count 1 "" 36 | /// Expect.equal range.[0].start 0 "" 37 | /// Expect.equal range.[0].``end 499 "" 38 | /// 39 | /// 40 | [] 41 | abstract rangeParser : size: int * str: string * ?options: RangeParser.Options -> RangeParser.ParseRangeResult 42 | 43 | /// 44 | /// Equivalent to 45 | /// 46 | /// npm.parseRange( 47 | /// size, 48 | /// str, 49 | /// jsOptions<RangeParser.Types.Options>(fun o -> 50 | /// o.combine <- true 51 | /// ) 52 | /// ) 53 | /// 54 | /// 55 | [] 56 | abstract rangeParser : size: int * str: string * combine : bool -> RangeParser.ParseRangeResult 57 | 58 | module RangeParser = 59 | 60 | type Array<'T> = Collections.Generic.IList<'T> 61 | 62 | type [] Ranges = 63 | inherit Array 64 | abstract ``type``: string with get, set 65 | 66 | type [] Range = 67 | abstract start: int with get, set 68 | abstract ``end``: int with get, set 69 | 70 | type [] Options = 71 | /// The "combine" option can be set to true and overlapping & adjacent ranges 72 | /// will be combined into a single range. 73 | abstract combine: bool with get, set 74 | 75 | /// 76 | /// Alias type representing an error case. The runtime representation of this type is always equal to -1 77 | /// 78 | /// Please use ParseRangeResult.ResultUnsatisfiable pattern matcher againt the result of parseString.Invoke to get a typed result 79 | /// 80 | type ResultUnsatisfiable = 81 | int 82 | 83 | /// 84 | /// Alias type representing an error case. The runtime representation of this type is always equal to -2 85 | /// 86 | /// Please use ParseRangeResult.ResultUnsatisfiable pattern matcher againt the result of parseString.Invoke to get a typed result 87 | /// 88 | type ResultInvalid = 89 | int 90 | 91 | /// 92 | /// Alias type representing an error case 93 | /// 94 | type Errored = 95 | U2 96 | 97 | /// 98 | /// Alias type representing the result of parseString.Invoke 99 | /// 100 | type ParseRangeResult = 101 | U2 102 | -------------------------------------------------------------------------------- /glues/Qs/src/Glutinum.Qs.fs: -------------------------------------------------------------------------------- 1 | module rec Glutinum.Qs 2 | 3 | open System 4 | open Fable.Core 5 | open Fable.Core.JS 6 | 7 | [] 8 | let qs : Qs.IExports = jsNative 9 | 10 | module Qs = 11 | 12 | type Array<'T> = Collections.Generic.IList<'T> 13 | type RegExp = Text.RegularExpressions.Regex 14 | 15 | type [] IExports = 16 | abstract stringify: obj: obj * ?options: IStringifyOptions -> string 17 | // abstract parse: str: string -> ParsedQs 18 | abstract parse: str: string * ?options: IParseOptions -> ParsedQs 19 | // abstract parse: str: string * ?options: IParseOptions -> ParseReturn 20 | 21 | type [] ParseReturn = 22 | [] abstract Item: key: string -> obj with get, set 23 | 24 | type [] defaultEncoder = 25 | [] abstract Invoke: str: obj * ?defaultEncoder: obj * ?charset: string -> string 26 | 27 | type [] defaultDecoder = 28 | [] abstract Invoke: str: string * ?decoder: obj * ?charset: string -> string 29 | 30 | type [] IStringifyOptions = 31 | abstract delimiter: string with get, set 32 | abstract strictNullHandling: bool with get, set 33 | abstract skipNulls: bool with get, set 34 | abstract encode: bool with get, set 35 | abstract encoder: Func with get, set 36 | abstract filter: U2>, (string -> obj option -> obj option)> with get, set 37 | abstract arrayFormat: IArrayFormat with get, set 38 | abstract indices: bool with get, set 39 | abstract sort: (obj option -> obj option -> float) with get, set 40 | abstract serializeDate: (DateTime -> string) with get, set 41 | abstract format: IStringifyOptionsFormat with get, set 42 | abstract encodeValuesOnly: bool with get, set 43 | abstract addQueryPrefix: bool with get, set 44 | abstract allowDots: bool with get, set 45 | abstract charset: IStringifyOptionsCharset with get, set 46 | abstract charsetSentinel: bool with get, set 47 | 48 | type [] IParseOptions = 49 | abstract comma: bool with get, set 50 | abstract delimiter: U2 with get, set 51 | abstract depth: float with get, set 52 | abstract decoder: Func with get, set 53 | abstract arrayLimit: float with get, set 54 | abstract arrayFormat: IArrayFormat with get, set 55 | abstract parseArrays: bool with get, set 56 | abstract allowDots: bool with get, set 57 | abstract plainObjects: bool with get, set 58 | abstract allowPrototypes: bool with get, set 59 | abstract parameterLimit: float with get, set 60 | abstract strictNullHandling: bool with get, set 61 | abstract ignoreQueryPrefix: bool with get, set 62 | abstract charset: IStringifyOptionsCharset with get, set 63 | abstract charsetSentinel: bool with get, set 64 | abstract interpretNumericEntities: bool with get, set 65 | 66 | type [] ParsedQs = 67 | [] abstract Item: key: string -> U4, ParsedQs, ResizeArray> option with get, set 68 | [] abstract Item: key: int -> U4, ParsedQs, ResizeArray> option with get, set 69 | 70 | type [] [] IStringifyOptionsEncoder = 71 | | Key 72 | | Value 73 | 74 | type [] [] IArrayFormat = 75 | | Indices 76 | | Brackets 77 | | Repeat 78 | | Comma 79 | 80 | type [] [] IStringifyOptionsFormat = 81 | | [] RFC1738 82 | | [] RFC3986 83 | 84 | type [] [] IStringifyOptionsCharset = 85 | | [] Utf8 86 | | [] Iso88591 87 | -------------------------------------------------------------------------------- /glues/Express/tests/Tests.Express.Port.App.Options.fs: -------------------------------------------------------------------------------- 1 | module Tests.Express.Port.App.Options 2 | 3 | open Mocha 4 | open Glutinum.ExpressServeStaticCore 5 | open Glutinum.Express 6 | 7 | describe "OPTIONS" (fun _ -> 8 | 9 | itAsync "should default to the routes defined" (fun d -> 10 | let app = express.express () 11 | 12 | app.del("/", fun (_ : Request) (_ : Response) -> ()) 13 | app.get("/users", fun _ _ -> ()) 14 | app.put("/users", fun _ _ -> ()) 15 | 16 | request(app) 17 | .options("/users") 18 | .expect("Allow", "GET,HEAD,PUT") 19 | .expect(200, "GET,HEAD,PUT", d) 20 | |> ignore 21 | ) 22 | 23 | itAsync "should only include each method once" (fun d -> 24 | let app = express.express () 25 | 26 | app.del("/", fun (_ : Request) (_ : Response) -> ()) 27 | app.get("/users", fun _ _ -> ()) 28 | app.put("/users", fun _ _ -> ()) 29 | app.get("/users", fun _ _ -> ()) 30 | 31 | request(app) 32 | .options("/users") 33 | .expect("Allow", "GET,HEAD,PUT") 34 | .expect(200, "GET,HEAD,PUT", d) 35 | |> ignore 36 | 37 | ) 38 | 39 | itAsync "should not be affected by app.all" (fun d -> 40 | let app = express.express () 41 | 42 | app.del("/", fun (_ : Request) (_ : Response) -> ()) 43 | app.get("/users", fun _ _ -> ()) 44 | app.put("/users", fun _ _ -> ()) 45 | app.all("/users", fun (req : Request) (res : Response) (next : NextFunction) -> 46 | res.setHeader("x-hit", "1") 47 | next.Invoke() 48 | ) 49 | 50 | request(app) 51 | .options("/users") 52 | .expect("x-hit", "1") 53 | .expect("Allow", "GET,HEAD,PUT") 54 | .expect(200, "GET,HEAD,PUT", d) 55 | |> ignore 56 | 57 | ) 58 | 59 | itAsync "should not respond if the path is not defined" (fun d -> 60 | let app = express.express () 61 | 62 | app.get("/users", fun _ _ -> ()) 63 | 64 | request(app) 65 | .options("/other") 66 | .expect(404, d) 67 | |> ignore 68 | ) 69 | 70 | itAsync "should forward requests down the middleware chain" (fun d -> 71 | let app = express.express () 72 | let router = express.Router() 73 | 74 | router.get("/users", fun _ _ -> ()) 75 | app.``use``(router) 76 | app.get("/other", fun _ _ -> ()) 77 | 78 | request(app) 79 | .options("/other") 80 | .expect("Allow", "GET,HEAD") 81 | .expect(200, "GET,HEAD", d) 82 | |> ignore 83 | 84 | ) 85 | 86 | describe "when error occurs in response handler" (fun _ -> 87 | 88 | itAsync "should pass error to callback" (fun d -> 89 | let app = express.express () 90 | let router = express.Router() 91 | 92 | router.get("/users", fun _ _ -> ()) 93 | 94 | app.``use``(fun (req : Request) (res : Response) (next : NextFunction) -> 95 | res.writeHead(200) 96 | next.Invoke() 97 | ) 98 | 99 | app.``use``(router) 100 | app.``use``(fun err req (res : Response) next -> 101 | res.``end``("true") 102 | ) 103 | 104 | request(app) 105 | .options("/users") 106 | .expect(200, "true", d) 107 | |> ignore 108 | ) 109 | 110 | ) 111 | ) 112 | 113 | describe "app.options()" (fun _ -> 114 | 115 | itAsync "should override the default behaviour" (fun d -> 116 | let app = express.express () 117 | 118 | app.options("/users", fun (req : Request) (res : Response) -> 119 | res.set("Allow", "GET") |> ignore 120 | res.send("GET") 121 | ) 122 | 123 | app.get("/users", fun _ _ -> ()) 124 | app.put("/users", fun _ _ -> ()) 125 | 126 | request(app) 127 | .options("/users") 128 | .expect("GET") 129 | .expect("Allow", "GET", d) 130 | |> ignore 131 | 132 | ) 133 | 134 | ) 135 | -------------------------------------------------------------------------------- /glues/Qs/tests/Tests.Qs.fs: -------------------------------------------------------------------------------- 1 | module Tests.Qs 2 | 3 | open Mocha 4 | open Fable.Core 5 | open Fable.Core.Testing 6 | open Fable.Core.JsInterop 7 | open Glutinum.Qs 8 | 9 | // Code adapted from: https://github.com/ljharb/qs/tree/b04febd9cb1c94b466aa2bd81b6452b44712414e 10 | 11 | 12 | [] 13 | let jsTypeOf _ = jsNative 14 | 15 | [] 16 | let JsBigInt _ : bigint = jsNative 17 | 18 | let private parseApi () = 19 | describe "qs.parse()" (fun _ -> 20 | 21 | it "parses a simple string" (fun _ -> 22 | let res = qs.parse("0=foo") 23 | 24 | Assert.AreEqual(res.[0], Some (!^ "foo")) 25 | ) 26 | 27 | it "arrayFormat: brackets allows only explicit arrays" (fun _ -> 28 | let res = 29 | qs.parse( 30 | "a[0]=b&a[1]=c", 31 | jsOptions(fun o -> 32 | o.arrayFormat <- Qs.IArrayFormat.Brackets 33 | ) 34 | ) 35 | 36 | Assert.AreEqual(res.["a"], Some (!^ ResizeArray(["b"; "c"]))) 37 | ) 38 | 39 | it "allows for decoding keys and values differently" (fun _ -> 40 | let decoder = 41 | // Code copied from Qs test but it seems typ is always undefined so it doesn't work /shrug 42 | // So for now, I am using some stupid decoder just to test the API 43 | // System.Func<_,_,_,_,_>( 44 | // fun (str : string) (defaultDecoder : Types.defaultDecoder) (charset : string) (typ : Types.IStringifyOptionsEncoder) -> 45 | 46 | // if typ = Types.IStringifyOptionsEncoder.Key then 47 | // defaultDecoder.Invoke(str, defaultDecoder, charset).ToLower() |> box 48 | 49 | // else if typ = Types.IStringifyOptionsEncoder.Value then 50 | // defaultDecoder.Invoke(str, defaultDecoder, charset).ToUpper() |> box 51 | 52 | // else 53 | // failwithf "this should never happen! type: %A" typ 54 | // ) 55 | 56 | System.Func<_,_,_,_,_>( 57 | fun (str : string) (defaultDecoder : Qs.defaultDecoder) (charset : string) (_: Qs.IStringifyOptionsEncoder) -> 58 | if str = "KeY" then 59 | box "key" 60 | 61 | else if str = "vAlUe" then 62 | box "VALUE" 63 | 64 | else 65 | failwithf "this should never happen! type: %A" str 66 | ) 67 | 68 | let res = 69 | qs.parse( 70 | "KeY=vAlUe", 71 | jsOptions(fun o -> 72 | o.decoder <- decoder 73 | ) 74 | ) 75 | 76 | Assert.AreEqual(res.["key"], Some (!^ "VALUE")) 77 | 78 | ) 79 | 80 | ) 81 | 82 | let private stringifyApi () = 83 | describe "qs.stringify()" (fun _ -> 84 | it "stringifies a querystring object" (fun _ -> 85 | let res = 86 | qs.stringify({| a = "b" |}) 87 | 88 | Assert.AreEqual(res, "a=b") 89 | 90 | ) 91 | 92 | it "stringifies using encoder" (fun _-> 93 | let encoder = 94 | System.Func<_,Qs.defaultEncoder,_,_,_> (fun value defaultEncoder charset _ -> 95 | let result = defaultEncoder.Invoke(value, defaultEncoder, charset) 96 | 97 | if jsTypeOf value = "bigint" then 98 | result + "n" 99 | else 100 | result 101 | ) 102 | 103 | let res = 104 | qs.stringify( 105 | createObj [ 106 | "a" ==> JsBigInt 2 107 | ], 108 | jsOptions(fun o -> 109 | o.encoder <- encoder 110 | ) 111 | ) 112 | 113 | Assert.AreEqual(res, "a=2n") 114 | ) 115 | ) 116 | 117 | 118 | describe "Qs" (fun _ -> 119 | parseApi () 120 | stringifyApi () 121 | ) 122 | -------------------------------------------------------------------------------- /glues/RangeParser.Extensions/tests/Tests.RangeParser.fs: -------------------------------------------------------------------------------- 1 | module Tests.RangeParser 2 | 3 | open Mocha 4 | open Fable.Core.Testing 5 | open Fable.Core.JsInterop 6 | open Glutinum.RangeParser 7 | 8 | // Code adapted from: https://github.com/jshttp/range-parser/tree/5f48dfc7996b18242dfa1fbddcc03f39b42a4554 9 | 10 | 11 | describe "RangeParser" (fun _ -> 12 | 13 | describe "parseRange(len, str)" (fun _ -> 14 | 15 | itAsync "should return -2 (aka ResultInvalid) for invalid str" (fun ok -> 16 | let range = rangeParser.rangeParser(200, "malformed") 17 | 18 | match range with 19 | | ParseRangeResult.ResultInvalid -> 20 | ok() 21 | 22 | | ParseRangeResult.UnkownError _ 23 | | ParseRangeResult.ResultUnsatisfiable 24 | | ParseRangeResult.Range _ -> 25 | failwith "Should not happen" 26 | ) 27 | 28 | itAsync "should return -1 if all specified ranges are invalid" (fun ok -> 29 | let range = rangeParser.rangeParser(200, "bytes=500-20") 30 | 31 | match range with 32 | | ParseRangeResult.ResultUnsatisfiable -> 33 | ok() 34 | 35 | | ParseRangeResult.UnkownError _ 36 | | ParseRangeResult.ResultInvalid 37 | | ParseRangeResult.Range _ -> 38 | failwith "Should not happen" 39 | ) 40 | 41 | it "should parse str" (fun _ -> 42 | let range = rangeParser.rangeParser(1000, "bytes=0-499") 43 | 44 | match range with 45 | | ParseRangeResult.UnkownError _ 46 | | ParseRangeResult.ResultInvalid 47 | | ParseRangeResult.ResultUnsatisfiable -> 48 | failwith "Should not happen" 49 | 50 | | ParseRangeResult.Range range -> 51 | // Here you can access your successful result 52 | Assert.AreEqual(range.``type``, "bytes") 53 | Assert.AreEqual(range.Count, 1) 54 | Assert.AreEqual(range.[0].start, 0) 55 | Assert.AreEqual(range.[0].``end``, 499) 56 | ) 57 | 58 | ) 59 | 60 | describe "when combine: true" (fun _ -> 61 | 62 | it "should combine overlapping ranges" (fun _ -> 63 | let range = rangeParser.rangeParser( 64 | 150, 65 | "bytes=0-4,90-99,5-75,100-199,101-102", 66 | jsOptions(fun o -> 67 | o.combine <- true 68 | ) 69 | ) 70 | 71 | match range with 72 | | ParseRangeResult.UnkownError _ 73 | | ParseRangeResult.ResultInvalid 74 | | ParseRangeResult.ResultUnsatisfiable -> 75 | failwith "Should not happen" 76 | 77 | | ParseRangeResult.Range range -> 78 | // Here you can access your successful result 79 | Assert.AreEqual(range.``type``, "bytes") 80 | Assert.AreEqual(range.Count, 2) 81 | Assert.AreEqual(range.[0].start, 0) 82 | Assert.AreEqual(range.[0].``end``, 75 ) 83 | Assert.AreEqual(range.[1].start, 90) 84 | Assert.AreEqual(range.[1].``end``, 149) 85 | ) 86 | 87 | it "overload npm.rangeParser with direct combine argument works" (fun _ -> 88 | let range = 89 | rangeParser.rangeParser( 90 | 150, 91 | "bytes=0-4,90-99,5-75,100-199,101-102", 92 | true 93 | ) 94 | 95 | match range with 96 | | ParseRangeResult.UnkownError _ 97 | | ParseRangeResult.ResultInvalid 98 | | ParseRangeResult.ResultUnsatisfiable -> 99 | failwith "Should not happen" 100 | 101 | | ParseRangeResult.Range range -> 102 | // Here you can access your successful result 103 | Assert.AreEqual(range.``type``, "bytes") 104 | Assert.AreEqual(range.Count, 2) 105 | Assert.AreEqual(range.[0].start, 0) 106 | Assert.AreEqual(range.[0].``end``, 75 ) 107 | Assert.AreEqual(range.[1].start, 90) 108 | Assert.AreEqual(range.[1].``end``, 149) 109 | ) 110 | 111 | ) 112 | 113 | ) 114 | -------------------------------------------------------------------------------- /glues/BodyParser/src/Glutinum.BodyParser.fs: -------------------------------------------------------------------------------- 1 | module rec Glutinum.BodyParser 2 | 3 | open System 4 | open Fable.Core 5 | open Node 6 | 7 | [] 8 | let bodyParser : BodyParser.IExports = jsNative 9 | 10 | module BodyParser = 11 | 12 | type [] IExports = 13 | /// 14 | /// Returns middleware that only parses json and only looks at requests 15 | /// where the Content-Type header matches the type option. 16 | /// 17 | abstract json: ?options: OptionsJson -> Func, unit> 18 | /// 19 | /// Returns middleware that parses all bodies as a Buffer and only looks at requests 20 | /// where the Content-Type header matches the type option. 21 | /// 22 | abstract raw: ?options: Options -> Func, unit> 23 | /// 24 | /// Returns middleware that parses all bodies as a string and only looks at requests 25 | /// where the Content-Type header matches the type option. 26 | /// 27 | abstract text: ?options: OptionsText -> Func, unit> 28 | /// 29 | /// Returns middleware that only parses urlencoded bodies and only looks at requests 30 | /// where the Content-Type header matches the type option 31 | /// 32 | abstract urlencoded: ?options: OptionsUrlencoded -> Func, unit> 33 | 34 | type [] Options = 35 | /// 36 | /// When set to true, then deflated (compressed) bodies will be inflated; when false, deflated bodies are rejected. Defaults to true. 37 | /// 38 | abstract inflate: bool with get, set 39 | /// 40 | /// Controls the maximum request body size. If this is a number, 41 | /// then the value specifies the number of bytes; if it is a string, 42 | /// the value is passed to the bytes library for parsing. Defaults to '100kb'. 43 | /// 44 | abstract limit: U2 with get, set 45 | /// 46 | /// The type option is used to determine what media type the middleware will parse 47 | /// 48 | abstract ``type``: U3, (Http.IncomingMessage -> obj option)> with get, set 49 | /// 50 | /// The verify option, if supplied, is called as verify(req, res, buf, encoding), 51 | /// where buf is a Buffer of the raw request body and encoding is the encoding of the request. 52 | /// 53 | abstract verify: req: Http.IncomingMessage * res: Http.ServerResponse * buf: Buffer * encoding: string -> unit 54 | 55 | type [] OptionsJson = 56 | inherit Options 57 | /// 58 | /// The reviver option is passed directly to JSON.parse as the second argument. 59 | /// 60 | abstract reviver: key: string * value: obj option -> obj option 61 | /// 62 | /// When set to true, will only accept arrays and objects; 63 | /// when `false` will accept anything JSON.parse accepts. Defaults to true. 64 | /// 65 | abstract strict: bool with get, set 66 | 67 | type [] OptionsText = 68 | inherit Options 69 | /// 70 | /// Specify the default character set for the text content if the charset 71 | /// is not specified in the Content-Type header of the request. 72 | /// Defaults to utf-8. 73 | /// 74 | abstract defaultCharset: string with get, set 75 | 76 | type [] OptionsUrlencoded = 77 | inherit Options 78 | /// 79 | /// The extended option allows to choose between parsing the URL-encoded data 80 | /// with the querystring library (when `false`) or the qs library (when true). 81 | /// 82 | abstract extended: bool with get, set 83 | /// 84 | /// The parameterLimit option controls the maximum number of parameters 85 | /// that are allowed in the URL-encoded data. If a request contains more parameters than this value, 86 | /// a 413 will be returned to the client. Defaults to 1000. 87 | /// 88 | abstract parameterLimit: float with get, set 89 | -------------------------------------------------------------------------------- /glues/ServeStatic/src/Glutinum.ServeStatic.fs: -------------------------------------------------------------------------------- 1 | module rec Glutinum.ServeStatic 2 | 3 | // Exported from: https://github.com/DefinitelyTyped/DefinitelyTyped/blob/0893371fea43bfdf1777b6d835424961ba0d1dbb/types/mime/index.d.ts 4 | 5 | open Fable.Core 6 | open Node 7 | open Mime 8 | open Glutinum.Connect 9 | 10 | [] 11 | let serveStatic : IExports = jsNative 12 | 13 | type [] IExports = 14 | /// Create a new middleware function to serve files from within a given root directory. 15 | /// The file to serve will be determined by combining req.url with the provided root directory. 16 | /// When a file is not found, instead of sending a 404 response, this module will instead call next() to move on to the next middleware, allowing for stacking and fall-backs. 17 | [] 18 | abstract serveStatic: root: string * ?options: ServeStatic.ServeStaticOptions<'R> -> ServeStatic.RequestHandler<'R> when 'R :> Http.ServerResponse 19 | 20 | abstract mime : Mime.IExports 21 | 22 | type ServeStaticOptions = 23 | ServeStaticOptions 24 | 25 | type [] ServeStaticOptions<'R when 'R :> Http.ServerResponse> = 26 | /// Enable or disable setting Cache-Control response header, defaults to true. 27 | /// Disabling this will ignore the immutable and maxAge options. 28 | abstract cacheControl: bool with get, set 29 | /// Set how "dotfiles" are treated when encountered. A dotfile is a file or directory that begins with a dot ("."). 30 | /// Note this check is done on the path itself without checking if the path actually exists on the disk. 31 | /// If root is specified, only the dotfiles above the root are checked (i.e. the root itself can be within a dotfile when when set to "deny"). 32 | /// The default value is 'ignore'. 33 | /// 'allow' No special treatment for dotfiles 34 | /// 'deny' Send a 403 for any request for a dotfile 35 | /// 'ignore' Pretend like the dotfile does not exist and call next() 36 | abstract dotfiles: string with get, set 37 | /// Enable or disable etag generation, defaults to true. 38 | abstract etag: bool with get, set 39 | /// Set file extension fallbacks. When set, if a file is not found, the given extensions will be added to the file name and search for. 40 | /// The first that exists will be served. Example: ['html', 'htm']. 41 | /// The default value is false. 42 | abstract extensions: ResizeArray with get, set 43 | /// Let client errors fall-through as unhandled requests, otherwise forward a client error. 44 | /// The default value is true. 45 | abstract fallthrough: bool with get, set 46 | /// Enable or disable the immutable directive in the Cache-Control response header. 47 | /// If enabled, the maxAge option should also be specified to enable caching. The immutable directive will prevent supported clients from making conditional requests during the life of the maxAge option to check if the file has changed. 48 | abstract immutable: bool with get, set 49 | /// By default this module will send "index.html" files in response to a request on a directory. 50 | /// To disable this set false or to supply a new index pass a string or an array in preferred order. 51 | abstract index: U3> with get, set 52 | /// Enable or disable Last-Modified header, defaults to true. Uses the file system's last modified value. 53 | abstract lastModified: bool with get, set 54 | /// Provide a max-age in milliseconds for http caching, defaults to 0. This can also be a string accepted by the ms module. 55 | abstract maxAge: U2 with get, set 56 | /// Redirect to trailing "/" when the pathname is a dir. Defaults to true. 57 | abstract redirect: bool with get, set 58 | /// Function to set custom headers on response. Alterations to the headers need to occur synchronously. 59 | /// The function is called as fn(res, path, stat), where the arguments are: 60 | /// res the response object 61 | /// path the file path that is being sent 62 | /// stat the stat object of the file that is being sent 63 | abstract setHeaders: ('R -> string -> obj option -> obj option) with get, set 64 | 65 | type [] RequestHandler<'R when 'R :> Http.ServerResponse> = 66 | [] abstract Invoke: request: Http.IncomingMessage * response: 'R * next: Connect.CreateServer.NextFunction -> obj option 67 | 68 | type [] RequestHandlerConstructor<'R when 'R :> Http.ServerResponse> = 69 | [] abstract Invoke: root: string * ?options: ServeStaticOptions<'R> -> RequestHandler<'R> 70 | abstract mime: obj with get, set 71 | -------------------------------------------------------------------------------- /glues/Connect/src/Glutinum.Connect.fs: -------------------------------------------------------------------------------- 1 | namespace rec Glutinum.Connect 2 | 3 | open System 4 | open Fable.Core 5 | open Fable.Core.JS 6 | open Node 7 | 8 | [] 9 | module Api = 10 | [] 11 | let connect () : Connect.CreateServer.Server = jsNative 12 | 13 | type Function = System.Action 14 | 15 | module Connect = 16 | module CreateServer = 17 | 18 | type ServerHandle = 19 | U2 20 | 21 | type [] IncomingMessage = 22 | inherit Http.IncomingMessage 23 | abstract originalUrl: Http.IncomingMessage option with get, set 24 | 25 | type [] IncomingMessageStatic = 26 | [] abstract Create: unit -> IncomingMessage 27 | 28 | // Try with private constructor ? 29 | // type NextFunction private (?err : obj) = 30 | // class end 31 | 32 | type NextFunction = 33 | Func 34 | type SimpleHandleFunction = 35 | Func 36 | 37 | type NextHandleFunction = 38 | Func, unit> 39 | 40 | type ErrorHandleFunction = 41 | Func 42 | 43 | // type [] NextFunction = 44 | // [] abstract Invoke: ?err: obj -> unit 45 | 46 | // type [] SimpleHandleFunction = 47 | // [] abstract Invoke: req: IncomingMessage * res: Http.ServerResponse -> unit 48 | 49 | // type [] NextHandleFunction = 50 | // [] abstract Invoke: req: IncomingMessage * res: Http.ServerResponse * next: NextFunction -> unit 51 | 52 | // type [] ErrorHandleFunction = 53 | // [] abstract Invoke: err: obj option * req: IncomingMessage * res: Http.ServerResponse * next: NextFunction -> unit 54 | 55 | type HandleFunction = 56 | U3 57 | 58 | type [] ServerStackItem = 59 | abstract route: string with get, set 60 | abstract handle: ServerHandle with get, set 61 | 62 | type [] Server = 63 | inherit Events.EventEmitter 64 | [] abstract Invoke: req: Http.IncomingMessage * res: Http.ServerResponse * ?next: Function -> unit 65 | abstract route: string with get, set 66 | abstract stack: ResizeArray with get, set 67 | /// Utilize the given middleware `handle` to the given route, 68 | /// defaulting to _/_. This "route" is the mount-point for the 69 | /// middleware, when given a value other than _/_ the middleware 70 | /// is only effective when that segment is present in the request's 71 | /// pathname. 72 | /// 73 | /// For example if we were to mount a function at _/admin_, it would 74 | /// be invoked on _/admin_, and _/admin/settings_, however it would 75 | /// not be invoked for _/_, or _/posts_. 76 | // abstract ``use``: fn: NextHandleFunction -> Server 77 | abstract ``use``: fn: SimpleHandleFunction -> Server 78 | abstract ``use``: fn: NextHandleFunction -> Server 79 | abstract ``use``: fn: ErrorHandleFunction -> Server 80 | abstract ``use``: fn: HandleFunction -> Server 81 | // abstract ``use``: route: string * fn: NextHandleFunction -> Server 82 | abstract ``use``: route: string * fn: HandleFunction -> Server 83 | abstract ``use``: route: string * fn: SimpleHandleFunction -> Server 84 | abstract ``use``: route: string * fn: NextHandleFunction -> Server 85 | abstract ``use``: route: string * fn: ErrorHandleFunction -> Server 86 | /// Handle server requests, punting them down 87 | /// the middleware stack. 88 | abstract handle: req: Http.IncomingMessage * res: Http.ServerResponse * next: Function -> unit 89 | /// Listen for connections. 90 | /// 91 | /// This method takes the same arguments 92 | /// as node's http.Server#listen(). 93 | /// 94 | /// HTTP and HTTPS: 95 | /// 96 | /// If you run your application both as HTTP 97 | /// and HTTPS you may wrap them individually, 98 | /// since your Connect "server" is really just 99 | /// a JavaScript Function. 100 | /// 101 | /// var connect = require('connect') 102 | /// , http = require('http') 103 | /// , https = require('https'); 104 | /// 105 | /// var app = connect(); 106 | /// 107 | /// http.createServer(app).listen(80); 108 | /// https.createServer(options, app).listen(443); 109 | abstract listen: port: int * ?hostname: string * ?backlog: float * ?callback: Function -> Http.Server 110 | abstract listen: port: int * ?hostname: string * ?callback: Function -> Http.Server 111 | abstract listen: path: string * ?callback: Function -> Http.Server 112 | abstract listen: handle: obj option * ?listeningListener: Function -> Http.Server 113 | -------------------------------------------------------------------------------- /glues/Express/src/Glutinum.Express.fs: -------------------------------------------------------------------------------- 1 | // ts2fable 0.8.0 2 | module rec Glutinum.Express 3 | 4 | open Fable.Core 5 | open Qs 6 | 7 | module ServeStatic = Glutinum.ServeStatic 8 | module Core = ExpressServeStaticCore 9 | 10 | let [] express : Express.IExports = jsNative 11 | 12 | module Express = 13 | 14 | type [] IExports = 15 | abstract json: obj 16 | abstract raw: obj 17 | abstract text: obj 18 | abstract application: Application 19 | abstract request: Request 20 | abstract response: Response 21 | abstract ``static``: ServeStatic.RequestHandlerConstructor 22 | abstract urlencoded: obj 23 | /// This is a built-in middleware function in Express. It parses incoming request query parameters. 24 | abstract query: options: U2 -> Handler 25 | abstract Router: ?options: RouterOptions -> Core.Router 26 | /// 27 | /// Creates an Express application. The express() function is a top-level function exported by the express module. 28 | /// 29 | [] 30 | abstract express : unit -> Express 31 | 32 | type [] RouterOptions = 33 | /// Enable case sensitivity. 34 | abstract caseSensitive: bool with get, set 35 | /// 36 | /// Preserve the req.params values from the parent router. 37 | /// If the parent and the child have conflicting param names, the child’s value take precedence. 38 | /// 39 | /// false 40 | abstract mergeParams: bool with get, set 41 | /// Enable strict routing. 42 | abstract strict: bool with get, set 43 | 44 | type [] Application = 45 | inherit Core.Application 46 | 47 | type [] CookieOptions = 48 | inherit Core.CookieOptions 49 | 50 | type [] Errback = 51 | inherit Core.Errback 52 | 53 | type ErrorRequestHandler = 54 | ErrorRequestHandler> 55 | 56 | type ErrorRequestHandler<'P> = 57 | ErrorRequestHandler<'P, obj option, obj option, Core.Query, Core.Dictionary> 58 | 59 | type ErrorRequestHandler<'P, 'ResBody> = 60 | ErrorRequestHandler<'P, 'ResBody, obj option, Core.Query, Core.Dictionary> 61 | 62 | type ErrorRequestHandler<'P, 'ResBody, 'ReqBody> = 63 | ErrorRequestHandler<'P, 'ResBody, 'ReqBody, Core.Query, Core.Dictionary> 64 | 65 | type ErrorRequestHandler<'P, 'ResBody, 'ReqBody, 'ReqQuery> = 66 | ErrorRequestHandler<'P, 'ResBody, 'ReqBody, 'ReqQuery, Core.Dictionary> 67 | 68 | type ErrorRequestHandler<'P, 'ResBody, 'ReqBody, 'ReqQuery, 'Locals when 'Locals :> Core.Dictionary> = 69 | Core.ErrorRequestHandler<'P, 'ResBody, 'ReqBody, 'ReqQuery, 'Locals> 70 | 71 | type Express = 72 | Core.Express 73 | 74 | type Handler = 75 | Core.Handler 76 | 77 | type [] IRoute = 78 | inherit Core.IRoute 79 | 80 | type [] IRouter = 81 | inherit Core.IRouter 82 | 83 | type [] IRouterHandler<'T> = 84 | inherit Core.IRouterHandler<'T> 85 | 86 | type [] IRouterMatcher<'T> = 87 | inherit Core.IRouterMatcher<'T> 88 | 89 | type [] MediaType = 90 | inherit Core.MediaType 91 | 92 | type [] NextFunction = 93 | inherit Core.NextFunction 94 | 95 | type Request = 96 | Request> 97 | 98 | type Request<'P> = 99 | Request<'P, obj option, obj option, Core.Query, Core.Dictionary> 100 | 101 | type Request<'P, 'ResBody> = 102 | Request<'P, 'ResBody, obj option, Core.Query, Core.Dictionary> 103 | 104 | type Request<'P, 'ResBody, 'ReqBody> = 105 | Request<'P, 'ResBody, 'ReqBody, Core.Query, Core.Dictionary> 106 | 107 | type Request<'P, 'ResBody, 'ReqBody, 'ReqQuery> = 108 | Request<'P, 'ResBody, 'ReqBody, 'ReqQuery, Core.Dictionary> 109 | 110 | type [] Request<'P, 'ResBody, 'ReqBody, 'ReqQuery, 'Locals when 'Locals :> Core.Dictionary> = 111 | inherit Core.Request<'P, 'ResBody, 'ReqBody, 'ReqQuery, 'Locals> 112 | 113 | type RequestHandler = 114 | RequestHandler> 115 | 116 | type RequestHandler<'P> = 117 | RequestHandler<'P, obj option, obj option, Core.Query, Core.Dictionary> 118 | 119 | type RequestHandler<'P, 'ResBody> = 120 | RequestHandler<'P, 'ResBody, obj option, Core.Query, Core.Dictionary> 121 | 122 | type RequestHandler<'P, 'ResBody, 'ReqBody> = 123 | RequestHandler<'P, 'ResBody, 'ReqBody, Core.Query, Core.Dictionary> 124 | 125 | type RequestHandler<'P, 'ResBody, 'ReqBody, 'ReqQuery> = 126 | RequestHandler<'P, 'ResBody, 'ReqBody, 'ReqQuery, Core.Dictionary> 127 | 128 | type RequestHandler<'P, 'ResBody, 'ReqBody, 'ReqQuery, 'Locals when 'Locals :> Core.Dictionary> = 129 | Core.RequestHandler<'P, 'ResBody, 'ReqBody, 'ReqQuery, 'Locals> 130 | 131 | type RequestParamHandler = 132 | Core.RequestParamHandler 133 | 134 | type Response = 135 | Response> 136 | 137 | type Response<'ResBody> = 138 | Response<'ResBody, Core.Dictionary> 139 | 140 | type [] Response<'ResBody, 'Locals when 'Locals :> Core.Dictionary> = 141 | inherit Core.Response<'ResBody, 'Locals> 142 | 143 | type [] Router = 144 | inherit Core.Router 145 | 146 | // type [] Send = 147 | // inherit Core.Send 148 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/ 2 | .ionide/ 3 | build/ 4 | npm/ 5 | tests/**/out 6 | 7 | ## Ignore Visual Studio temporary files, build results, and 8 | ## files generated by popular Visual Studio add-ons. 9 | ## 10 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 11 | 12 | # User-specific files 13 | *.suo 14 | *.user 15 | *.userosscache 16 | *.sln.docstates 17 | 18 | # User-specific files (MonoDevelop/Xamarin Studio) 19 | *.userprefs 20 | 21 | # Build results 22 | [Dd]ebug/ 23 | [Dd]ebugPublic/ 24 | [Rr]elease/ 25 | [Rr]eleases/ 26 | x64/ 27 | x86/ 28 | bld/ 29 | [Bb]in/ 30 | [Oo]bj/ 31 | [Ll]og/ 32 | 33 | # Visual Studio 2015/2017 cache/options directory 34 | .vs/ 35 | # Uncomment if you have tasks that create the project's static files in wwwroot 36 | #wwwroot/ 37 | 38 | # Visual Studio 2017 auto generated files 39 | Generated\ Files/ 40 | 41 | # MSTest test Results 42 | [Tt]est[Rr]esult*/ 43 | [Bb]uild[Ll]og.* 44 | 45 | # NUNIT 46 | *.VisualState.xml 47 | TestResult.xml 48 | 49 | # Build Results of an ATL Project 50 | [Dd]ebugPS/ 51 | [Rr]eleasePS/ 52 | dlldata.c 53 | 54 | # Benchmark Results 55 | BenchmarkDotNet.Artifacts/ 56 | 57 | # .NET Core 58 | project.lock.json 59 | project.fragment.lock.json 60 | artifacts/ 61 | **/Properties/launchSettings.json 62 | 63 | # StyleCop 64 | StyleCopReport.xml 65 | 66 | # Files built by Visual Studio 67 | *_i.c 68 | *_p.c 69 | *_i.h 70 | *.ilk 71 | *.meta 72 | *.obj 73 | *.iobj 74 | *.pch 75 | *.pdb 76 | *.ipdb 77 | *.pgc 78 | *.pgd 79 | *.rsp 80 | *.sbr 81 | *.tlb 82 | *.tli 83 | *.tlh 84 | *.tmp 85 | *.tmp_proj 86 | *.log 87 | *.vspscc 88 | *.vssscc 89 | .builds 90 | *.pidb 91 | *.svclog 92 | *.scc 93 | 94 | # Chutzpah Test files 95 | _Chutzpah* 96 | 97 | # Visual C++ cache files 98 | ipch/ 99 | *.aps 100 | *.ncb 101 | *.opendb 102 | *.opensdf 103 | *.sdf 104 | *.cachefile 105 | *.VC.db 106 | *.VC.VC.opendb 107 | 108 | # Visual Studio profiler 109 | *.psess 110 | *.vsp 111 | *.vspx 112 | *.sap 113 | 114 | # Visual Studio Trace Files 115 | *.e2e 116 | 117 | # TFS 2012 Local Workspace 118 | $tf/ 119 | 120 | # Guidance Automation Toolkit 121 | *.gpState 122 | 123 | # ReSharper is a .NET coding add-in 124 | _ReSharper*/ 125 | *.[Rr]e[Ss]harper 126 | *.DotSettings.user 127 | 128 | # JustCode is a .NET coding add-in 129 | .JustCode 130 | 131 | # TeamCity is a build add-in 132 | _TeamCity* 133 | 134 | # DotCover is a Code Coverage Tool 135 | *.dotCover 136 | 137 | # AxoCover is a Code Coverage Tool 138 | .axoCover/* 139 | !.axoCover/settings.json 140 | 141 | # Visual Studio code coverage results 142 | *.coverage 143 | *.coveragexml 144 | 145 | # NCrunch 146 | _NCrunch_* 147 | .*crunch*.local.xml 148 | nCrunchTemp_* 149 | 150 | # MightyMoose 151 | *.mm.* 152 | AutoTest.Net/ 153 | 154 | # Web workbench (sass) 155 | .sass-cache/ 156 | 157 | # DocProject is a documentation generator add-in 158 | DocProject/buildhelp/ 159 | DocProject/Help/*.HxT 160 | DocProject/Help/*.HxC 161 | DocProject/Help/*.hhc 162 | DocProject/Help/*.hhk 163 | DocProject/Help/*.hhp 164 | DocProject/Help/Html2 165 | DocProject/Help/html 166 | 167 | # Click-Once directory 168 | publish/ 169 | 170 | # Publish Web Output 171 | *.[Pp]ublish.xml 172 | *.azurePubxml 173 | # Note: Comment the next line if you want to checkin your web deploy settings, 174 | # but database connection strings (with potential passwords) will be unencrypted 175 | *.pubxml 176 | *.publishproj 177 | 178 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 179 | # checkin your Azure Web App publish settings, but sensitive information contained 180 | # in these scripts will be unencrypted 181 | PublishScripts/ 182 | 183 | # NuGet Packages 184 | *.nupkg 185 | # The packages folder can be ignored because of Package Restore 186 | **/[Pp]ackages/* 187 | # except build/, which is used as an MSBuild target. 188 | !**/[Pp]ackages/build/ 189 | # Uncomment if necessary however generally it will be regenerated when needed 190 | #!**/[Pp]ackages/repositories.config 191 | # NuGet v3's project.json files produces more ignorable files 192 | *.nuget.props 193 | *.nuget.targets 194 | 195 | # Microsoft Azure Build Output 196 | csx/ 197 | *.build.csdef 198 | 199 | # Microsoft Azure Emulator 200 | ecf/ 201 | rcf/ 202 | 203 | # Windows Store app package directories and files 204 | AppPackages/ 205 | BundleArtifacts/ 206 | Package.StoreAssociation.xml 207 | _pkginfo.txt 208 | *.appx 209 | 210 | # Visual Studio cache files 211 | # files ending in .cache can be ignored 212 | *.[Cc]ache 213 | # but keep track of directories ending in .cache 214 | !*.[Cc]ache/ 215 | 216 | # Others 217 | ClientBin/ 218 | ~$* 219 | *~ 220 | *.dbmdl 221 | *.dbproj.schemaview 222 | *.jfm 223 | *.pfx 224 | *.publishsettings 225 | orleans.codegen.cs 226 | 227 | # Including strong name files can present a security risk 228 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 229 | #*.snk 230 | 231 | # Since there are multiple workflows, uncomment next line to ignore bower_components 232 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 233 | #bower_components/ 234 | 235 | # RIA/Silverlight projects 236 | Generated_Code/ 237 | 238 | # Backup & report files from converting an old project file 239 | # to a newer Visual Studio version. Backup files are not needed, 240 | # because we have git ;-) 241 | _UpgradeReport_Files/ 242 | Backup*/ 243 | UpgradeLog*.XML 244 | UpgradeLog*.htm 245 | ServiceFabricBackup/ 246 | *.rptproj.bak 247 | 248 | # SQL Server files 249 | *.mdf 250 | *.ldf 251 | *.ndf 252 | 253 | # Business Intelligence projects 254 | *.rdl.data 255 | *.bim.layout 256 | *.bim_*.settings 257 | *.rptproj.rsuser 258 | 259 | # Microsoft Fakes 260 | FakesAssemblies/ 261 | 262 | # GhostDoc plugin setting file 263 | *.GhostDoc.xml 264 | 265 | # Node.js Tools for Visual Studio 266 | .ntvs_analysis.dat 267 | node_modules/ 268 | 269 | # Visual Studio 6 build log 270 | *.plg 271 | 272 | # Visual Studio 6 workspace options file 273 | *.opt 274 | 275 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 276 | *.vbw 277 | 278 | # Visual Studio LightSwitch build output 279 | **/*.HTMLClient/GeneratedArtifacts 280 | **/*.DesktopClient/GeneratedArtifacts 281 | **/*.DesktopClient/ModelManifest.xml 282 | **/*.Server/GeneratedArtifacts 283 | **/*.Server/ModelManifest.xml 284 | _Pvt_Extensions 285 | 286 | # Paket dependency manager 287 | .paket/paket.exe 288 | paket-files/ 289 | 290 | # FAKE - F# Make 291 | .fake/ 292 | 293 | # JetBrains Rider 294 | .idea/ 295 | *.sln.iml 296 | 297 | # CodeRush 298 | .cr/ 299 | 300 | # Python Tools for Visual Studio (PTVS) 301 | __pycache__/ 302 | *.pyc 303 | 304 | # Cake - Uncomment if you are using it 305 | # tools/** 306 | # !tools/packages.config 307 | 308 | # Tabs Studio 309 | *.tss 310 | 311 | # Telerik's JustMock configuration file 312 | *.jmconfig 313 | 314 | # BizTalk build output 315 | *.btp.cs 316 | *.btm.cs 317 | *.odx.cs 318 | *.xsd.cs 319 | 320 | # OpenCover UI analysis results 321 | OpenCover/ 322 | 323 | # Azure Stream Analytics local run output 324 | ASALocalRun/ 325 | 326 | # MSBuild Binary and Structured Log 327 | *.binlog 328 | 329 | # NVidia Nsight GPU debugger configuration file 330 | *.nvuser 331 | 332 | # MFractors (Xamarin productivity tool) working folder 333 | .mfractor/ 334 | 335 | *.fs.js 336 | -------------------------------------------------------------------------------- /glues/Chalk/tests/Tests.Chalk.ChalkJS.fs: -------------------------------------------------------------------------------- 1 | module Tests.Chalk.ChalkJS 2 | 3 | open Mocha 4 | open Fable.Core 5 | open Fable.Core.JsInterop 6 | open Glutinum.Chalk 7 | 8 | // Test ported from https://github.com/chalk/chalk/blob/4dab5e1fb6f42c6c9fdacbe34b9dafd24359208e/test/chalk.js 9 | 10 | [] 11 | let Assert: Node.Assert.IExports = jsNative 12 | 13 | describe "Chalk" (fun _ -> 14 | 15 | it "don't add any styling when called as the base function" (fun _ -> 16 | Assert.strictEqual(chalk.Invoke("foo"), "foo") 17 | ) 18 | 19 | it "support multiple arguments in base function" (fun _ -> 20 | Assert.strictEqual(chalk.Invoke("hello", "there"), "hello there") 21 | ) 22 | 23 | it "support automatic casting to string" (fun _ -> 24 | Assert.strictEqual(chalk.Invoke(ResizeArray([ "hello"; "there" ])), "hello,there") 25 | Assert.strictEqual(chalk.Invoke(123), "123") 26 | 27 | Assert.strictEqual(chalk.bold.Invoke(ResizeArray([ "foo"; "bar" ])), "\u001B[1mfoo,bar\u001B[22m") 28 | Assert.strictEqual(chalk.green.Invoke(98765), "\u001B[32m98765\u001B[39m") 29 | ) 30 | 31 | it "style string" (fun _ -> 32 | Assert.strictEqual(chalk.underline.Invoke("foo"), "\u001B[4mfoo\u001B[24m") 33 | Assert.strictEqual(chalk.red.Invoke("foo"), "\u001B[31mfoo\u001B[39m") 34 | Assert.strictEqual(chalk.bgRed.Invoke("foo"), "\u001B[41mfoo\u001B[49m") 35 | ) 36 | 37 | it "support applying multiple styles at once" (fun _ -> 38 | Assert.strictEqual(chalk.red.bgGreen.underline.Invoke("foo"), "\u001B[31m\u001B[42m\u001B[4mfoo\u001B[24m\u001B[49m\u001B[39m") 39 | Assert.strictEqual(chalk.underline.red.bgGreen.Invoke("foo"), "\u001B[4m\u001B[31m\u001B[42mfoo\u001B[49m\u001B[39m\u001B[24m") 40 | ) 41 | 42 | it "support nesting styles" (fun _ -> 43 | Assert.strictEqual( 44 | chalk.red.Invoke( 45 | "foo" + 46 | chalk.underline.bgBlue.Invoke("bar") + 47 | "!" 48 | ), 49 | "\u001B[31mfoo\u001B[4m\u001B[44mbar\u001B[49m\u001B[24m!\u001B[39m" 50 | ) 51 | ) 52 | 53 | it "support nesting styles of the same type (color, underline, bg)" (fun _ -> 54 | Assert.strictEqual( 55 | chalk.red.Invoke( 56 | "a" + 57 | chalk.yellow.Invoke( 58 | "b" + 59 | chalk.green.Invoke("c") 60 | + "b" 61 | ) + 62 | "c" 63 | ), 64 | "\u001B[31ma\u001B[33mb\u001B[32mc\u001B[39m\u001B[31m\u001B[33mb\u001B[39m\u001B[31mc\u001B[39m" 65 | ) 66 | ) 67 | 68 | it "reset all styles with '.reset()'" (fun _ -> 69 | Assert.strictEqual( 70 | chalk.reset.Invoke( 71 | chalk.red.bgGreen.underline.Invoke("foo") + "foo" 72 | ), 73 | "\u001B[0m\u001B[31m\u001B[42m\u001B[4mfoo\u001B[24m\u001B[49m\u001B[39mfoo\u001B[0m" 74 | ) 75 | ) 76 | 77 | it "support caching multiple styles" (fun _ -> 78 | let red = chalk.red.red 79 | let green = chalk.red.green 80 | let redBold = red.bold 81 | let greenBold = green.bold 82 | 83 | Assert.notStrictEqual(red.Invoke("foo"), green.Invoke("foo")) 84 | Assert.notStrictEqual(redBold.Invoke("bar"), greenBold.Invoke("bar")) 85 | Assert.notStrictEqual(green.Invoke("baz"), greenBold.Invoke("baz")) 86 | ) 87 | 88 | it "alias gray to grey" (fun _ -> 89 | Assert.strictEqual( 90 | chalk.grey.Invoke("foo"), 91 | "\u001B[90mfoo\u001B[39m" 92 | ) 93 | ) 94 | 95 | it "support variable number of arguments" (fun _ -> 96 | Assert.strictEqual( 97 | chalk.red.Invoke("foo", "bar"), 98 | "\u001B[31mfoo bar\u001B[39m" 99 | ) 100 | ) 101 | 102 | it "support falsy value" (fun _ -> 103 | Assert.strictEqual( 104 | chalk.red.Invoke(0), 105 | "\u001B[31m0\u001B[39m" 106 | ) 107 | ) 108 | 109 | it "don't output escape codes if the input is empty" (fun _ -> 110 | Assert.strictEqual( 111 | chalk.red.Invoke(), 112 | "" 113 | ) 114 | Assert.strictEqual( 115 | chalk.red.blue.black.Invoke(), 116 | "" 117 | ) 118 | ) 119 | 120 | it "keep Function.prototype methods" (fun _ -> 121 | // Not ported because it test JavaScript specific behaviour 122 | // test('keep Function.prototype methods', t => { 123 | // t.is(Reflect.apply(chalk.grey, null, ['foo']), '\u001B[90mfoo\u001B[39m'); 124 | // t.is(chalk.reset(chalk.red.bgGreen.underline.bind(null)('foo') + 'foo'), '\u001B[0m\u001B[31m\u001B[42m\u001B[4mfoo\u001B[24m\u001B[49m\u001B[39mfoo\u001B[0m'); 125 | // t.is(chalk.red.blue.black.call(null), ''); 126 | // }); 127 | () 128 | ) 129 | 130 | it "line breaks should open and close colors" (fun _ -> 131 | Assert.strictEqual( 132 | chalk.grey.Invoke("hello\nworld"), 133 | "\u001B[90mhello\u001B[39m\n\u001B[90mworld\u001B[39m" 134 | ) 135 | ) 136 | 137 | it "line breaks should open and close colors with CRLF" (fun _ -> 138 | Assert.strictEqual( 139 | chalk.grey.Invoke("hello\r\nworld"), 140 | "\u001B[90mhello\u001B[39m\r\n\u001B[90mworld\u001B[39m" 141 | ) 142 | ) 143 | 144 | it "properly convert RGB to 16 colors on basic color terminals" (fun _ -> 145 | Assert.strictEqual( 146 | chalk.Instance(jsOptions(fun o -> 147 | o.level <- Some Chalk.Level.N1 148 | )).hex("#FF0000").Invoke("hello"), 149 | "\u001B[91mhello\u001B[39m" 150 | ) 151 | 152 | Assert.strictEqual( 153 | chalk.Instance(jsOptions(fun o -> 154 | o.level <- Some Chalk.Level.N1 155 | )).bgHex("#FF0000").Invoke("hello"), 156 | "\u001B[101mhello\u001B[49m" 157 | ) 158 | ) 159 | 160 | it "properly convert RGB to 256 colors on basic color terminals" (fun _ -> 161 | Assert.strictEqual( 162 | chalk.Instance(jsOptions(fun o -> 163 | o.level <- Some Chalk.Level.N2 164 | )).hex("#FF0000").Invoke("hello"), 165 | "\u001B[38;5;196mhello\u001B[39m" 166 | ) 167 | 168 | Assert.strictEqual( 169 | chalk.Instance(jsOptions(fun o -> 170 | o.level <- Some Chalk.Level.N2 171 | )).bgHex("#FF0000").Invoke("hello"), 172 | "\u001B[48;5;196mhello\u001B[49m" 173 | ) 174 | 175 | Assert.strictEqual( 176 | chalk.Instance(jsOptions(fun o -> 177 | o.level <- Some Chalk.Level.N3 178 | )).bgHex("#FF0000").Invoke("hello"), 179 | "\u001B[48;2;255;0;0mhello\u001B[49m" 180 | ) 181 | ) 182 | 183 | it "don't emit RGB codes if level is 0" (fun _ -> 184 | Assert.strictEqual( 185 | chalk.Instance(jsOptions(fun o -> 186 | o.level <- Some Chalk.Level.N0 187 | )).hex("#FF0000").Invoke("hello"), 188 | "hello" 189 | ) 190 | 191 | Assert.strictEqual( 192 | chalk.Instance(jsOptions(fun o -> 193 | o.level <- Some Chalk.Level.N0 194 | )).bgHex("#FF0000").Invoke("hello"), 195 | "hello" 196 | ) 197 | ) 198 | 199 | it "supports blackBright color" (fun _ -> 200 | Assert.strictEqual( 201 | chalk.blackBright.Invoke("foo"), 202 | "\u001B[90mfoo\u001B[39m" 203 | ) 204 | ) 205 | 206 | it "sets correct level for chalk.stderr and respects it" (fun _ -> 207 | Assert.strictEqual( 208 | chalk.stderr.level, 209 | 3 210 | ) 211 | 212 | Assert.strictEqual( 213 | chalk.stderr.red.bold.Invoke("foo"), 214 | "\u001B[31m\u001B[1mfoo\u001B[22m\u001B[39m" 215 | ) 216 | ) 217 | 218 | it "supports rbg colors" (fun _ -> 219 | Assert.strictEqual( 220 | chalk.rgb(123, 45, 67).underline.Invoke("foo"), 221 | "\u001B[38;2;123;45;67m\u001B[4mfoo\u001B[24m\u001B[39m" 222 | ) 223 | ) 224 | 225 | it "supports hsl colors" (fun _ -> 226 | Assert.strictEqual( 227 | chalk.hsl(32, 100, 50).bold.Invoke("foo"), 228 | "\u001B[38;2;255;136;0m\u001B[1mfoo\u001B[22m\u001B[39m" 229 | ) 230 | ) 231 | 232 | it "supports hsv colors" (fun _ -> 233 | Assert.strictEqual( 234 | chalk.hsv(32, 100, 100).bold.Invoke("foo"), 235 | "\u001B[38;2;255;136;0m\u001B[1mfoo\u001B[22m\u001B[39m" 236 | ) 237 | ) 238 | 239 | it "supports hwb colors" (fun _ -> 240 | Assert.strictEqual( 241 | chalk.hwb(32, 0, 50).bold.Invoke("foo"), 242 | "\u001B[38;2;128;68;0m\u001B[1mfoo\u001B[22m\u001B[39m" 243 | ) 244 | ) 245 | 246 | 247 | it "supports ansi colors" (fun _ -> 248 | Assert.strictEqual( 249 | chalk.ansi(31).bgAnsi(93).Invoke("foo"), 250 | "\u001B[38;2;128;0;0m\u001B[48;2;255;255;0mfoo\u001B[49m\u001B[39m" 251 | ) 252 | ) 253 | 254 | 255 | it "supports ansi256 colors" (fun _ -> 256 | Assert.strictEqual( 257 | chalk.bgAnsi256(194).Invoke("foo"), 258 | "\u001B[48;2;204;255;204mfoo\u001B[49m" 259 | ) 260 | ) 261 | ) 262 | -------------------------------------------------------------------------------- /glues/SuperAgent/src/Glutinum.SuperAgent.fs: -------------------------------------------------------------------------------- 1 | module rec Glutinum.SuperAgent 2 | 3 | open System 4 | open Fable.Core 5 | open Fable.Core.JS 6 | open Node 7 | open Browser 8 | 9 | [] 10 | let superagent : SuperAgent.Request.SuperAgentStatic = jsNative 11 | 12 | type Error = Exception 13 | 14 | type [] CallbackHandler = 15 | [] abstract Invoke: err: obj option * res: Request.Response -> unit 16 | 17 | type [] Serializer = 18 | [] abstract Invoke: obj: obj option -> string 19 | 20 | type [] BrowserParser = 21 | [] abstract Invoke: str: string -> obj option 22 | 23 | type [] NodeParser = 24 | [] abstract Invoke: res: Request.Response * callback: (Error option -> obj option -> unit) -> unit 25 | 26 | type Parser = 27 | U2 28 | 29 | type MultipartValueSingle = 30 | U6, string, bool, float> 31 | 32 | type MultipartValue = 33 | U2> 34 | 35 | module Request = 36 | 37 | type [] SuperAgentRequest = 38 | inherit Request 39 | abstract agent: ?agent: Http.Agent -> SuperAgentRequest 40 | abstract cookies: string with get, set 41 | abstract ``method``: string with get, set 42 | abstract url: string with get, set 43 | 44 | type [] SuperAgentStatic = 45 | inherit SuperAgent 46 | [] abstract Invoke: url: string -> SuperAgentRequest 47 | [] abstract Invoke: ``method``: string * url: string -> SuperAgentRequest 48 | abstract agent: unit -> obj 49 | abstract serialize: SuperAgentStaticSerialize with get, set 50 | abstract parse: SuperAgentStaticParse with get, set 51 | 52 | type [] SuperAgent<'Req when 'Req :> SuperAgentRequest> = 53 | inherit Stream.Stream 54 | // abstract jar: Cookiejar.CookieJar with get, set 55 | abstract attachCookies: req: 'Req -> unit 56 | abstract checkout: url: string * ?callback: CallbackHandler -> 'Req 57 | abstract connect: url: string * ?callback: CallbackHandler -> 'Req 58 | abstract copy: url: string * ?callback: CallbackHandler -> 'Req 59 | abstract del: url: string * ?callback: CallbackHandler -> 'Req 60 | abstract delete: url: string * ?callback: CallbackHandler -> 'Req 61 | abstract get: url: string * ?callback: CallbackHandler -> 'Req 62 | abstract head: url: string * ?callback: CallbackHandler -> 'Req 63 | abstract lock: url: string * ?callback: CallbackHandler -> 'Req 64 | abstract merge: url: string * ?callback: CallbackHandler -> 'Req 65 | abstract mkactivity: url: string * ?callback: CallbackHandler -> 'Req 66 | abstract mkcol: url: string * ?callback: CallbackHandler -> 'Req 67 | abstract move: url: string * ?callback: CallbackHandler -> 'Req 68 | abstract notify: url: string * ?callback: CallbackHandler -> 'Req 69 | abstract options: url: string * ?callback: CallbackHandler -> 'Req 70 | abstract patch: url: string * ?callback: CallbackHandler -> 'Req 71 | abstract post: url: string * ?callback: CallbackHandler -> 'Req 72 | abstract propfind: url: string * ?callback: CallbackHandler -> 'Req 73 | abstract proppatch: url: string * ?callback: CallbackHandler -> 'Req 74 | abstract purge: url: string * ?callback: CallbackHandler -> 'Req 75 | abstract put: url: string * ?callback: CallbackHandler -> 'Req 76 | abstract report: url: string * ?callback: CallbackHandler -> 'Req 77 | abstract saveCookies: res: Response -> unit 78 | abstract search: url: string * ?callback: CallbackHandler -> 'Req 79 | abstract subscribe: url: string * ?callback: CallbackHandler -> 'Req 80 | abstract trace: url: string * ?callback: CallbackHandler -> 'Req 81 | abstract unlock: url: string * ?callback: CallbackHandler -> 'Req 82 | abstract unsubscribe: url: string * ?callback: CallbackHandler -> 'Req 83 | 84 | type [] ResponseError = 85 | inherit Error 86 | abstract status: float option with get, set 87 | abstract response: Response option with get, set 88 | 89 | type [] HTTPError = 90 | inherit Error 91 | abstract status: float with get, set 92 | abstract text: string with get, set 93 | abstract ``method``: string with get, set 94 | abstract path: string with get, set 95 | 96 | type [] Response = 97 | inherit Stream.Stream 98 | abstract accepted: bool with get, set 99 | abstract badRequest: bool with get, set 100 | abstract body: obj option with get, set 101 | abstract charset: string with get, set 102 | abstract clientError: bool with get, set 103 | abstract error: HTTPError with get, set 104 | abstract files: obj option with get, set 105 | abstract forbidden: bool with get, set 106 | abstract get: header: string -> string 107 | [] abstract ``get_Set-Cookie``: unit -> ResizeArray 108 | abstract header: obj option with get, set 109 | abstract headers: obj option with get, set 110 | abstract info: bool with get, set 111 | abstract links: obj with get, set 112 | abstract noContent: bool with get, set 113 | abstract notAcceptable: bool with get, set 114 | abstract notFound: bool with get, set 115 | abstract ok: bool with get, set 116 | abstract redirect: bool with get, set 117 | abstract serverError: bool with get, set 118 | abstract status: float with get, set 119 | abstract statusType: float with get, set 120 | abstract text: string with get, set 121 | abstract ``type``: string with get, set 122 | abstract unauthorized: bool with get, set 123 | // abstract xhr: XMLHttpRequest with get, set 124 | abstract redirects: ResizeArray with get, set 125 | 126 | type [] Request = 127 | inherit Promise 128 | abstract abort: unit -> unit 129 | abstract accept: ``type``: string -> Request 130 | abstract attach: field: string * file: MultipartValueSingle * ?options: U2 -> Request 131 | abstract auth: user: string * pass: string * ?options: RequestAuthOptions -> Request 132 | abstract auth: token: string * options: RequestAuthOptions_ -> Request 133 | abstract buffer: ?``val``: bool -> Request 134 | abstract ca: cert: U4, Buffer, ResizeArray> -> Request 135 | abstract cert: cert: U4, Buffer, ResizeArray> -> Request 136 | abstract clearTimeout: unit -> Request 137 | abstract disableTLSCerts: unit -> Request 138 | abstract ``end``: ?callback: CallbackHandler -> unit 139 | abstract field: name: string * ``val``: MultipartValue -> Request 140 | abstract field: fields: RequestFieldFields -> Request 141 | abstract get: field: string -> string 142 | abstract http2: ?enable: bool -> Request 143 | abstract key: cert: U4, Buffer, ResizeArray> -> Request 144 | abstract ok: callback: (Response -> bool) -> Request 145 | [] abstract on_error: handler: (obj option -> unit) -> Request 146 | [] abstract on_progress: handler: (ProgressEvent -> unit) -> Request 147 | [] abstract on_response: handler: (Response -> unit) -> Request 148 | abstract on: name: string * handler: (obj option -> unit) -> Request 149 | abstract parse: parser: Parser -> Request 150 | abstract part: unit -> Request 151 | abstract pfx: cert: U5, Buffer, ResizeArray, RequestPfx> -> Request 152 | abstract pipe: stream: Stream.Stream * ?options: obj -> Stream.Stream 153 | abstract query: ``val``: U2 -> Request 154 | abstract redirects: n: float -> Request 155 | abstract responseType: ``type``: string -> Request 156 | abstract retry: ?count: float * ?callback: CallbackHandler -> Request 157 | abstract send: ?data: U2 -> Request 158 | abstract send: ?data: string -> Request 159 | abstract send: ?data: obj -> Request 160 | abstract serialize: serializer: Serializer -> Request 161 | abstract set: field: obj -> Request 162 | abstract set: field: string * ``val``: string -> Request 163 | [] abstract set_Cookie: ``val``: ResizeArray -> Request 164 | abstract timeout: ms: U2 -> Request 165 | abstract trustLocalhost: ?enabled: bool -> Request 166 | abstract ``type``: ``val``: string -> Request 167 | abstract unset: field: string -> Request 168 | abstract ``use``: fn: Plugin -> Request 169 | abstract withCredentials: unit -> Request 170 | abstract write: data: U2 * ?encoding: string -> bool 171 | abstract maxResponseSize: size: float -> Request 172 | 173 | type [] RequestAuthOptions = 174 | abstract ``type``: RequestAuthOptionsType with get, set 175 | 176 | type [] RequestAuthOptions_ = 177 | abstract ``type``: string with get, set 178 | 179 | type [] RequestFieldFields = 180 | [] abstract Item: fieldName: string -> MultipartValue with get, set 181 | 182 | type [] Plugin = 183 | [] abstract Invoke: req: SuperAgentRequest -> unit 184 | 185 | type [] ProgressEvent = 186 | abstract direction: ProgressEventDirection with get, set 187 | abstract loaded: float with get, set 188 | abstract percent: float option with get, set 189 | abstract total: float option with get, set 190 | 191 | type [] SuperAgentStaticSerialize = 192 | [] abstract Item: ``type``: string -> Serializer with get, set 193 | 194 | type [] SuperAgentStaticParse = 195 | [] abstract Item: ``type``: string -> Parser with get, set 196 | 197 | type [] RequestAttach = 198 | abstract filename: string option with get, set 199 | abstract contentType: string option with get, set 200 | 201 | type [] RequestPfx = 202 | abstract pfx: U2 with get, set 203 | abstract passphrase: string with get, set 204 | 205 | type [] RequestTimeout = 206 | abstract deadline: float option with get, set 207 | abstract response: float option with get, set 208 | 209 | type [] [] RequestAuthOptionsType = 210 | | Basic 211 | | Auto 212 | 213 | type [] [] ProgressEventDirection = 214 | | Download 215 | | Upload 216 | -------------------------------------------------------------------------------- /glues/Chalk/src/Glutinum.Chalk.fs: -------------------------------------------------------------------------------- 1 | module rec Glutinum.Chalk 2 | 3 | open Fable.Core 4 | open System 5 | 6 | type TemplateStringsArray = System.Collections.Generic.IReadOnlyList 7 | 8 | /// 9 | /// Main Chalk object that allows to chain styles together. 10 | /// Call the last one as a method with a string argument. 11 | /// Order doesn't matter, and later styles take precedent in case of a conflict. 12 | /// This simply means that chalk.red.yellow.green is equivalent to chalk.green. 13 | /// 14 | [] 15 | let chalk : Chalk.IExports = jsNative 16 | 17 | module Chalk = 18 | 19 | type IExports = 20 | inherit Chalk 21 | abstract supportsColor: ColorSupport with get, set 22 | abstract Level: Level with get, set 23 | abstract Color: Color with get, set 24 | abstract ForegroundColor: ForegroundColor with get, set 25 | abstract BackgroundColor: BackgroundColor with get, set 26 | abstract Modifiers: Modifiers with get, set 27 | abstract stderr: Stderr with get, set 28 | 29 | type Stderr = 30 | inherit Chalk 31 | abstract supportsColor: ColorSupport with get, set 32 | 33 | /// 34 | /// Basic foreground colors. 35 | /// 36 | /// More colors here. 37 | /// 38 | type [] [] ForegroundColor = 39 | | Black 40 | | Red 41 | | Green 42 | | Yellow 43 | | Blue 44 | | Magenta 45 | | Cyan 46 | | White 47 | | Gray 48 | | Grey 49 | | BlackBright 50 | | RedBright 51 | | GreenBright 52 | | YellowBright 53 | | BlueBright 54 | | MagentaBright 55 | | CyanBright 56 | | WhiteBright 57 | 58 | /// 59 | /// Basic background colors. 60 | /// 61 | /// More colors here. 62 | /// 63 | type [] [] BackgroundColor = 64 | | BgBlack 65 | | BgRed 66 | | BgGreen 67 | | BgYellow 68 | | BgBlue 69 | | BgMagenta 70 | | BgCyan 71 | | BgWhite 72 | | BgGray 73 | | BgGrey 74 | | BgBlackBright 75 | | BgRedBright 76 | | BgGreenBright 77 | | BgYellowBright 78 | | BgBlueBright 79 | | BgMagentaBright 80 | | BgCyanBright 81 | | BgWhiteBright 82 | 83 | /// 84 | /// Basic colors. 85 | /// 86 | /// More colors here. 87 | /// 88 | type [] [] Color = 89 | | Black 90 | | Red 91 | | Green 92 | | Yellow 93 | | Blue 94 | | Magenta 95 | | Cyan 96 | | White 97 | | Gray 98 | | Grey 99 | | BlackBright 100 | | RedBright 101 | | GreenBright 102 | | YellowBright 103 | | BlueBright 104 | | MagentaBright 105 | | CyanBright 106 | | WhiteBright 107 | | BgBlack 108 | | BgRed 109 | | BgGreen 110 | | BgYellow 111 | | BgBlue 112 | | BgMagenta 113 | | BgCyan 114 | | BgWhite 115 | | BgGray 116 | | BgGrey 117 | | BgBlackBright 118 | | BgRedBright 119 | | BgGreenBright 120 | | BgYellowBright 121 | | BgBlueBright 122 | | BgMagentaBright 123 | | BgCyanBright 124 | | BgWhiteBright 125 | 126 | type [] [] Modifiers = 127 | | Reset 128 | | Bold 129 | | Dim 130 | | Italic 131 | | Underline 132 | | Inverse 133 | | Hidden 134 | | Strikethrough 135 | | Visible 136 | 137 | /// 138 | /// Levels: 139 | /// - 0 - All colors disabled. 140 | /// - 1 - Basic 16 colors support. 141 | /// - 2 - ANSI 256 colors support. 142 | /// - 3 - Truecolor 16 million colors support. 143 | /// 144 | type [] Level = 145 | | N0 = 0 146 | | N1 = 1 147 | | N2 = 2 148 | | N3 = 3 149 | 150 | type [] Options = 151 | /// 152 | /// Specify the color support for Chalk. 153 | /// 154 | /// By default, color support is automatically detected based on the environment. 155 | /// 156 | /// Levels: 157 | /// - 0 - All colors disabled. 158 | /// - 1 - Basic 16 colors support. 159 | /// - 2 - ANSI 256 colors support. 160 | /// - 3 - Truecolor 16 million colors support. 161 | /// 162 | abstract level: Level option with get, set 163 | 164 | /// Detect whether the terminal supports color. 165 | type [] ColorSupport = 166 | /// The color level used by Chalk. 167 | abstract level: Level with get, set 168 | /// Return whether Chalk supports basic 16 colors. 169 | abstract hasBasic: bool with get, set 170 | /// Return whether Chalk supports ANSI 256 colors. 171 | abstract has256: bool with get, set 172 | /// Return whether Chalk supports Truecolor 16 million colors. 173 | abstract has16m: bool with get, set 174 | 175 | type [] ChalkFunction = 176 | /// Use a template string. 177 | /// Template literals are unsupported for nested calls (see issue #341 178 | /// 179 | /// 180 | /// import chalk = require('chalk'); 181 | /// log(chalk` 182 | /// CPU: {red ${cpu.totalPercent}%} 183 | /// RAM: {green ${ram.used / ram.total * 100}%} 184 | /// DISK: {rgb(255,131,0) ${disk.used / disk.total * 100}%} 185 | /// `); 186 | /// 187 | /// 188 | /// 189 | /// 190 | /// import chalk = require('chalk'); 191 | /// log(chalk.red.bgBlack`2 + 3 = {bold ${2 + 3}}`) 192 | /// 193 | /// 194 | [] abstract Invoke: text: TemplateStringsArray * [] placeholders: obj[] -> string 195 | [] abstract Invoke: [] text: #obj[] -> string 196 | 197 | /// 198 | /// Main Chalk object that allows to chain styles together. 199 | /// Call the last one as a method with a string argument. 200 | /// Order doesn't matter, and later styles take precedent in case of a conflict. 201 | /// This simply means that chalk.red.yellow.green is equivalent to chalk.green. 202 | /// 203 | type [] Chalk = 204 | inherit ChalkFunction 205 | /// Return a new Chalk instance. 206 | [] 207 | abstract Instance: ?options : Options -> Chalk 208 | /// 209 | /// The color support for Chalk. 210 | /// 211 | /// By default, color support is automatically detected based on the environment. 212 | /// 213 | /// Levels: 214 | /// - 0 - All colors disabled. 215 | /// - 1 - Basic 16 colors support. 216 | /// - 2 - ANSI 256 colors support. 217 | /// - 3 - Truecolor 16 million colors support. 218 | /// 219 | abstract level: Level with get, set 220 | /// Use HEX value to set text color. 221 | /// Hexadecimal value representing the desired color. 222 | /// 223 | /// 224 | /// import chalk = require('chalk'); 225 | /// chalk.hex('#DEADED'); 226 | /// 227 | /// 228 | abstract hex: color : string -> Chalk 229 | /// Use keyword color value to set text color. 230 | /// Keyword value representing the desired color. 231 | /// 232 | /// 233 | /// import chalk = require('chalk'); 234 | /// chalk.keyword('orange'); 235 | /// 236 | /// 237 | abstract keyword: color : string -> Chalk 238 | /// Use RGB values to set text color. 239 | abstract rgb: int * int * int -> Chalk 240 | /// Use HSL values to set text color. 241 | abstract hsl: int * int * int -> Chalk 242 | /// Use HSV values to set text color. 243 | abstract hsv: int * int * int -> Chalk 244 | /// Use HWB values to set text color. 245 | abstract hwb: int * int * int -> Chalk 246 | /// 247 | /// Use a Select/Set Graphic Rendition color code number to set text color. 248 | /// 249 | /// 30 <= code && code < 38 || 90 <= code && code < 98 250 | /// For example, 31 for red, 91 for redBright. 251 | /// 252 | abstract ansi: int -> Chalk 253 | /// Use a 8-bit unsigned number to set text color. 254 | abstract ansi256: int -> Chalk 255 | /// Use HEX value to set background color. 256 | /// Hexadecimal value representing the desired color. 257 | /// 258 | /// 259 | /// import chalk = require('chalk'); 260 | /// chalk.bgHex('#DEADED'); 261 | /// 262 | /// 263 | abstract bgHex: color : string -> Chalk 264 | /// Use keyword color value to set background color. 265 | /// Keyword value representing the desired color. 266 | /// 267 | /// 268 | /// import chalk = require('chalk'); 269 | /// chalk.bgKeyword('orange'); 270 | /// 271 | /// 272 | abstract bgKeyword: color : string -> Chalk 273 | /// Use RGB values to set background color. 274 | abstract bgRgb: (float -> float -> float -> Chalk) 275 | /// Use HSL values to set background color. 276 | abstract bgHsl: (float -> float -> float -> Chalk) 277 | /// Use HSV values to set background color. 278 | abstract bgHsv: (float -> float -> float -> Chalk) 279 | /// Use HWB values to set background color. 280 | abstract bgHwb: (float -> float -> float -> Chalk) 281 | /// 282 | /// Use a Select/Set Graphic Rendition color code number to set background color. 283 | /// 284 | /// 30 <= code && code < 38 || 90 <= code && code < 98 285 | /// For example, 31 for red, 91 for redBright. 286 | /// Use the foreground code, not the background code (for example, not 41, nor 101). 287 | /// 288 | abstract bgAnsi: int -> Chalk 289 | /// Use a 8-bit unsigned number to set background color. 290 | abstract bgAnsi256: int -> Chalk 291 | /// Modifier: Resets the current color chain. 292 | abstract reset: Chalk 293 | /// Modifier: Make text bold. 294 | abstract bold: Chalk 295 | /// Modifier: Emitting only a small amount of light. 296 | abstract dim: Chalk 297 | /// Modifier: Make text italic. (Not widely supported) 298 | abstract italic: Chalk 299 | /// Modifier: Make text underline. (Not widely supported) 300 | abstract underline: Chalk 301 | /// Modifier: Inverse background and foreground colors. 302 | abstract inverse: Chalk 303 | /// Modifier: Prints the text, but makes it invisible. 304 | abstract hidden: Chalk 305 | /// Modifier: Puts a horizontal line through the center of the text. (Not widely supported) 306 | abstract strikethrough: Chalk 307 | /// Modifier: Prints the text only when Chalk has a color support level > 0. 308 | /// Can be useful for things that are purely cosmetic. 309 | abstract visible: Chalk 310 | abstract black: Chalk 311 | abstract red: Chalk 312 | abstract green: Chalk 313 | abstract yellow: Chalk 314 | abstract blue: Chalk 315 | abstract magenta: Chalk 316 | abstract cyan: Chalk 317 | abstract white: Chalk 318 | abstract gray: Chalk 319 | abstract grey: Chalk 320 | abstract blackBright: Chalk 321 | abstract redBright: Chalk 322 | abstract greenBright: Chalk 323 | abstract yellowBright: Chalk 324 | abstract blueBright: Chalk 325 | abstract magentaBright: Chalk 326 | abstract cyanBright: Chalk 327 | abstract whiteBright: Chalk 328 | abstract bgBlack: Chalk 329 | abstract bgRed: Chalk 330 | abstract bgGreen: Chalk 331 | abstract bgYellow: Chalk 332 | abstract bgBlue: Chalk 333 | abstract bgMagenta: Chalk 334 | abstract bgCyan: Chalk 335 | abstract bgWhite: Chalk 336 | abstract bgGray: Chalk 337 | abstract bgGrey: Chalk 338 | abstract bgBlackBright: Chalk 339 | abstract bgRedBright: Chalk 340 | abstract bgGreenBright: Chalk 341 | abstract bgYellowBright: Chalk 342 | abstract bgBlueBright: Chalk 343 | abstract bgMagentaBright: Chalk 344 | abstract bgCyanBright: Chalk 345 | abstract bgWhiteBright: Chalk 346 | -------------------------------------------------------------------------------- /glues/Express/tests/Tests.Express.Port.App.Render.fs: -------------------------------------------------------------------------------- 1 | module Tests.Express.Port.App.Render 2 | 3 | open Glutinum.ExpressServeStaticCore 4 | open Glutinum.Express 5 | open Fable.Core.JsInterop 6 | open Mocha 7 | open Node 8 | open Fable.Core.JsInterop 9 | 10 | let __dirname = 11 | emitJsExpr () """ 12 | import { dirname } from 'dirname-filename-esm'; 13 | dirname(import.meta); 14 | """ 15 | 16 | let tmpl (_ : string) (_ : obj) _ = import "default" "./support/tmpl.js" 17 | 18 | let createApp() = 19 | let app = express.express () 20 | app.engine(".tmpl", tmpl); 21 | app 22 | 23 | 24 | describe "app" (fun _ -> 25 | describe ".render(name, fn)" (fun _ -> 26 | 27 | itAsync "should support absolute paths" (fun d -> 28 | let app = createApp() 29 | 30 | app.locals.["user"] <- {| name = "tobi" |} 31 | 32 | app.render(path.join(__dirname, "fixtures", "user.tmpl"), fun err str -> 33 | if err.IsSome then 34 | d err 35 | else 36 | Assert.strictEqual(str, "

    tobi

    ") 37 | d () 38 | ) 39 | ) 40 | 41 | itAsync "should support absolute paths with 'view engine'" (fun d -> 42 | let app = createApp() 43 | 44 | app.set("view engine", "tmpl") 45 | app.locals.["user"] <- {| name = "tobi" |} 46 | 47 | app.render(path.join(__dirname, "fixtures", "user"), fun err str -> 48 | if err.IsSome then 49 | d err 50 | else 51 | Assert.strictEqual(str, "

    tobi

    ") 52 | d () 53 | ) 54 | ) 55 | 56 | itAsync "should expose app.locals" (fun d -> 57 | let app = createApp() 58 | 59 | app.set("views", path.join(__dirname, "fixtures")) 60 | app.locals.["user"] <- {| name = "tobi" |} 61 | 62 | app.render("user.tmpl", fun err str -> 63 | if err.IsSome then 64 | d err 65 | else 66 | Assert.strictEqual(str, "

    tobi

    ") 67 | d () 68 | ) 69 | ) 70 | 71 | itAsync "should support index." (fun d -> 72 | let app = createApp() 73 | 74 | app.set("views", path.join(__dirname, "fixtures")) 75 | app.set("view engine", "tmpl") 76 | 77 | app.render("blog/post", fun err str -> 78 | if err.IsSome then 79 | d err 80 | else 81 | Assert.strictEqual(str, "

    blog post

    ") 82 | d () 83 | ) 84 | ) 85 | 86 | itAsync "should handle render error throws" (fun d -> 87 | let app = express.express () 88 | 89 | emitJsStatement () """ 90 | function View(name, options){ 91 | this.name = name; 92 | this.path = 'fale'; 93 | } 94 | 95 | View.prototype.render = function(options, fn){ 96 | throw new Error('err!'); 97 | }; 98 | """ 99 | 100 | emitJsStatement app """ 101 | $0.set('view', View); 102 | """ 103 | 104 | app.render("something", fun err str -> 105 | Assert.ok(box err.IsSome) 106 | Assert.strictEqual(err.Value.Message, "err!") 107 | d() 108 | ) 109 | ) 110 | 111 | describe "when the file does not exist" (fun _ -> 112 | itAsync "should provide a helpful error" (fun d -> 113 | let app = createApp() 114 | 115 | app.set("views", path.join(__dirname, "fixtures")) 116 | 117 | app.render("rawr.tmpl", fun err _ -> 118 | Assert.ok(err) 119 | Assert.strictEqual(err.Value.Message, "Failed to lookup view \"rawr.tmpl\" in views directory \"" + path.join(__dirname, "fixtures") + "\"") 120 | d() 121 | ) 122 | ) 123 | ) 124 | 125 | describe "when an error occurs" (fun _ -> 126 | itAsync "should invoke the callback" (fun d -> 127 | let app = createApp() 128 | 129 | app.set("views", path.join(__dirname, "fixtures")) 130 | 131 | app.render("user.tmpl", fun err _ -> 132 | Assert.ok(err) 133 | Assert.strictEqual(err.Value?name, "RenderError") 134 | d () 135 | ) 136 | ) 137 | ) 138 | 139 | describe "when an extension is given" (fun _ -> 140 | itAsync "should render the template" (fun d -> 141 | let app = createApp() 142 | 143 | app.set("views", path.join(__dirname, "fixtures")) 144 | 145 | app.render("email.tmpl", fun err str -> 146 | if err.IsSome then 147 | d err 148 | else 149 | Assert.strictEqual(str, "

    This is an email

    ") 150 | d () 151 | ) 152 | ) 153 | ) 154 | 155 | describe "when 'view engine' is given" (fun _ -> 156 | itAsync "should render the template" (fun d -> 157 | let app = createApp() 158 | 159 | app.set("view engine", "tmpl") 160 | app.set("views", path.join(__dirname, "fixtures")) 161 | 162 | app.render("email", fun err str -> 163 | if err.IsSome then 164 | d err 165 | else 166 | Assert.strictEqual(str, "

    This is an email

    ") 167 | d () 168 | ) 169 | ) 170 | ) 171 | 172 | describe "when 'views' is given" (fun _ -> 173 | itAsync "should lookup the file in the path" (fun d -> 174 | let app = createApp() 175 | 176 | app.set("views", path.join(__dirname, "fixtures", "default_layout")) 177 | app.locals.["user"] <- {| name = "tobi" |} 178 | 179 | app.render("user.tmpl", fun err str -> 180 | if err.IsSome then 181 | d err 182 | else 183 | Assert.strictEqual(str, "

    tobi

    ") 184 | d () 185 | ) 186 | 187 | ) 188 | 189 | describe "when array of paths" (fun _ -> 190 | itAsync "should lookup the file in the path" (fun d -> 191 | let app = createApp() 192 | let views = 193 | [| 194 | path.join(__dirname, "fixtures", "local_layout") 195 | path.join(__dirname, "fixtures", "default_layout") 196 | |] 197 | 198 | app.set("views", views) 199 | app.locals.["user"] <- {| name = "tobi" |} 200 | 201 | app.render("user.tmpl", fun err str -> 202 | if err.IsSome then 203 | d err 204 | else 205 | Assert.strictEqual(str, "tobi") 206 | d () 207 | ) 208 | ) 209 | 210 | itAsync "should lookup in later paths until found" (fun d -> 211 | let app = createApp() 212 | let views = 213 | [| 214 | path.join(__dirname, "fixtures", "local_layout") 215 | path.join(__dirname, "fixtures", "default_layout") 216 | |] 217 | 218 | app.set("views", views) 219 | app.locals.["name"] <- "tobi" 220 | 221 | app.render("name.tmpl", fun err str -> 222 | if err.IsSome then 223 | d err 224 | else 225 | Assert.strictEqual(str, "

    tobi

    ") 226 | d () 227 | ) 228 | ) 229 | 230 | itAsync "should error if file does not exist" (fun d -> 231 | let app = createApp() 232 | let views = 233 | [| 234 | path.join(__dirname, "fixtures", "local_layout") 235 | path.join(__dirname, "fixtures", "default_layout") 236 | |] 237 | 238 | app.set("views", views) 239 | app.locals.["name"] <- "tobi" 240 | 241 | app.render("pet.tmpl", fun err str -> 242 | Assert.ok(err) 243 | Assert.strictEqual(err.Value.Message, "Failed to lookup view \"pet.tmpl\" in views directories \"" + views.[0] + "\" or \"" + views.[1] + "\"") 244 | d() 245 | ) 246 | ) 247 | ) 248 | ) 249 | 250 | describe "caching" (fun _ -> 251 | itAsync "should always lookup view without cache" (fun d -> 252 | let app = express.express () 253 | let mutable count = 0 254 | 255 | 256 | emitJsStatement () """ 257 | function View(name, options){ 258 | this.name = name; 259 | this.path = 'fake'; 260 | count++; 261 | } 262 | 263 | View.prototype.render = function(options, fn){ 264 | fn(null, 'abstract engine'); 265 | }; 266 | """ 267 | 268 | app.set("view cache", false) 269 | app.set("view", emitJsExpr () "View") 270 | 271 | app.render("something", fun err str -> 272 | if err.IsSome then 273 | d err 274 | else 275 | Assert.strictEqual(count, 1) 276 | Assert.strictEqual(str, "abstract engine") 277 | app.render("something", fun err str -> 278 | if err.IsSome then 279 | d err 280 | else 281 | Assert.strictEqual(count, 2) 282 | Assert.strictEqual(str, "abstract engine") 283 | d() 284 | ) 285 | ) 286 | ) 287 | 288 | itAsync "should cache with 'view cache' setting" (fun d -> 289 | let app = express.express () 290 | let mutable count = 0 // Force a "unique" name because we emit raw JavaScript 291 | 292 | emitJsStatement count """ 293 | function View(name, options){ 294 | this.name = name; 295 | this.path = 'fake'; 296 | $0++; 297 | } 298 | 299 | View.prototype.render = function(options, fn){ 300 | fn(null, 'abstract engine'); 301 | }; 302 | """ 303 | 304 | app.set("view cache", true) 305 | app.set("view", emitJsExpr () "View") 306 | 307 | app.render("something", fun err str -> 308 | if err.IsSome then 309 | d err 310 | else 311 | Assert.strictEqual(count, 1) 312 | Assert.strictEqual(str, "abstract engine") 313 | app.render("something", fun err str -> 314 | if err.IsSome then 315 | d err 316 | else 317 | Assert.strictEqual(count, 1) 318 | Assert.strictEqual(str, "abstract engine") 319 | d () 320 | ) 321 | ) 322 | ) 323 | ) 324 | 325 | describe ".render(name, options, fn)" (fun _ -> 326 | itAsync "should render the template" (fun d -> 327 | let app = createApp() 328 | app.set("views", path.join(__dirname, "fixtures")) 329 | 330 | let user = {| name = "tobi" |} 331 | 332 | app.render("user.tmpl", {| user = user |}, fun err str -> 333 | if err.IsSome then 334 | d err 335 | else 336 | Assert.strictEqual(str, "

    tobi

    ") 337 | d () 338 | ) 339 | ) 340 | 341 | itAsync "should expose app.locals" (fun d -> 342 | let app = createApp() 343 | 344 | app.set("views", path.join(__dirname, "fixtures")) 345 | app.locals.["user"] <- {| name = "tobi" |} 346 | 347 | app.render("user.tmpl", createEmpty, fun err str -> 348 | if err.IsSome then 349 | d err 350 | else 351 | Assert.strictEqual(str, "

    tobi

    ") 352 | d () 353 | ) 354 | ) 355 | 356 | itAsync "should give precedence to app.render() locals" (fun d -> 357 | let app = createApp() 358 | 359 | app.set("views", path.join(__dirname, "fixtures")) 360 | app.locals.["user"] <- {| name = "tobi" |} 361 | let jane = {| name = "jane" |} 362 | 363 | app.render("user.tmpl", {| user = jane |}, fun err str -> 364 | if err.IsSome then 365 | d err 366 | else 367 | Assert.strictEqual(str, "

    jane

    ") 368 | d () 369 | ) 370 | ) 371 | 372 | describe "caching" (fun _ -> 373 | itAsync "should cache with cache option" (fun d -> 374 | let app = express.express () 375 | let mutable count = 0 // Force a "unique" name because we emit raw JavaScript 376 | 377 | // Abuse emit helpers to mimic the JavaScript code as Fable emit flat arrows functions 378 | // which don't have a prototype and neither is a constructor 379 | emitJsStatement count """ 380 | function View(name, options){ 381 | this.name = name; 382 | this.path = 'fake'; 383 | $0++; 384 | } 385 | 386 | View.prototype.render = function(options, fn){ 387 | fn(null, 'abstract engine'); 388 | }; 389 | """ 390 | 391 | app.set("view cache", true) 392 | app.set("view", emitJsExpr () "View") 393 | 394 | app.render("something", {| cache = true |}, fun err str -> 395 | if err.IsSome then 396 | d err 397 | else 398 | Assert.strictEqual(count, 1) 399 | Assert.strictEqual(str, "abstract engine") 400 | app.render("something", fun err str -> 401 | if err.IsSome then 402 | d err 403 | else 404 | Assert.strictEqual(count, 1) 405 | Assert.strictEqual(str, "abstract engine") 406 | d () 407 | ) 408 | ) 409 | ) 410 | ) 411 | ) 412 | ) 413 | ) 414 | -------------------------------------------------------------------------------- /glues/Express/tests/Tests.Express.Port.App.Params.fs: -------------------------------------------------------------------------------- 1 | module Tests.Express.Port.App.Params 2 | 3 | open System 4 | open Fable.Core.JS 5 | open Fable.Core.JsInterop 6 | open Mocha 7 | open Glutinum.ExpressServeStaticCore 8 | open Glutinum.Express 9 | open Node 10 | 11 | describe "app" (fun _ -> 12 | 13 | describe ".param(fn)" (fun _ -> 14 | itAsync "should map app.param(name, ...) logic" (fun d -> 15 | let app = express.express () 16 | 17 | app.param(fun name regexp -> 18 | if Constructors.Object?prototype?toString?call(regexp) = "[object RegExp]" then 19 | RequestParamHandler(fun req res next value _ -> 20 | let captures = regexp.Match(string value) 21 | 22 | if captures.Success then 23 | req.``params``.[name] <- captures.Groups.[0].Value 24 | next.Invoke() 25 | else 26 | next.Invoke("route") 27 | ) 28 | else 29 | // In F# else needs to be balance not as in JavaScript 30 | // so try to hack around this "problem" as I don't know what should be the behaviour when 31 | // the condition is not meat 32 | // Source: https://github.com/expressjs/express/blob/508936853a6e311099c9985d4c11a4b1b8f6af07/test/app.param.js 33 | returnNothingHack 34 | ) 35 | 36 | app.param(":name", RegExp("^([a-zA-Z]+)$")) 37 | 38 | app.get("/user/:name", fun (req : Request) (res : Response) -> 39 | res.send(req.``params``?("name")) 40 | ) 41 | 42 | request(app) 43 | .get("/user/tj") 44 | .expect( 45 | 200, 46 | "tj", 47 | fun err _ -> 48 | if err.IsSome then 49 | d err 50 | else 51 | request(app) 52 | .get("/user/123") 53 | .expect(404, d) 54 | |> ignore 55 | ) 56 | |> ignore 57 | ) 58 | ) 59 | 60 | describe ".params(names, fn)" (fun _ -> 61 | itAsync "should map the array" (fun d -> 62 | let app = express.express () 63 | 64 | app.param(ResizeArray(["id"; "uid"]), fun (req : Request) res next id -> 65 | let id = Constructors.Number.Create(id) 66 | 67 | if isNaN(id) then 68 | next.Invoke("route") 69 | else 70 | req.``params``.["id"] <- unbox id 71 | next.Invoke() 72 | ) 73 | 74 | app.get("/post/:id", fun (req : Request) (res : Response) -> 75 | let id = req.``params``.["id"] 76 | if jsTypeOf id <> "number" then 77 | d (Exception("Should be of type number")) 78 | 79 | res.send("" + unbox id) // id is a Number in JavaScript force a conversion to string 80 | ) 81 | 82 | app.get("/user/:uid", fun (req : Request) (res : Response) -> 83 | let id = req.``params``.["id"] 84 | if jsTypeOf id <> "number" then 85 | d (Exception("Should be of type number")) 86 | 87 | res.send("" + unbox id) // id is a Number in JavaScript force a conversion to string 88 | ) 89 | 90 | request(app) 91 | .get("/user/123") 92 | .expect( 93 | 200, 94 | "123", 95 | fun err _ -> 96 | if err.IsSome then 97 | d err 98 | else 99 | request(app) 100 | .get("/post/123") 101 | .expect( 102 | 200,"123", d) 103 | |> ignore 104 | ) 105 | |> ignore 106 | ) 107 | ) 108 | 109 | describe ".params(name, fn)" (fun _ -> 110 | itAsync "should map logic for a single param" (fun d -> 111 | let app = express.express () 112 | 113 | app.param("id", fun req res next id -> 114 | let id = Constructors.Number.Create(id) 115 | 116 | if isNaN(id) then 117 | next.Invoke("route") 118 | else 119 | req.``params``.["id"] <- unbox id 120 | next.Invoke() 121 | ) 122 | 123 | app.get("/user/:id", fun (req : Request) (res : Response) -> 124 | let id = req.``params``.["id"] 125 | 126 | if jsTypeOf id <> "number" then 127 | d (Exception("Should be of type number")) 128 | 129 | res.send("" + unbox id) // id is a Number in JavaScript force a conversion to string 130 | ) 131 | 132 | request(app) 133 | .get("/user/123") 134 | .expect("123", d) 135 | |> ignore 136 | ) 137 | 138 | itAsync "should only call once per request" (fun d -> 139 | let app = express.express () 140 | let mutable called = 0 141 | let mutable count = 0 142 | 143 | app.param("user", fun req res next user -> 144 | called <- called + 1 145 | req?user <- user 146 | next.Invoke() 147 | ) 148 | 149 | app.get("/foo/:user", fun (req : Request) (res : Response) (next : NextFunction) -> 150 | count <- count + 1 151 | next.Invoke() 152 | ) 153 | 154 | app.get("/foo/:user", fun (req : Request) (res : Response) (next : NextFunction) -> 155 | count <- count + 1 156 | next.Invoke() 157 | ) 158 | 159 | app.``use``(fun req res -> 160 | let text = String.Join(" ", [string count; string called; req?user]) 161 | res.``end``(text) 162 | ) 163 | 164 | request(app) 165 | .get("/foo/bob") 166 | .expect("2 1 bob", d) 167 | |> ignore 168 | ) 169 | 170 | 171 | itAsync "should call when values differ" (fun d -> 172 | let app = express.express () 173 | let mutable called = 0 174 | let mutable count = 0 175 | 176 | app.param("user", fun req res next user -> 177 | called <- called + 1 178 | req?users <- 179 | if isNull req?users then 180 | [ user ] 181 | else 182 | req?users @ [ user ] 183 | 184 | next.Invoke() 185 | ) 186 | 187 | app.get("/:user/bob", fun (req : Request) (res : Response) (next : NextFunction) -> 188 | count <- count + 1 189 | next.Invoke() 190 | ) 191 | 192 | app.get("/foo/:user", fun (req : Request) (res : Response) (next : NextFunction) -> 193 | count <- count + 1 194 | next.Invoke() 195 | ) 196 | 197 | app.``use``(fun req res -> 198 | let users = String.Join(",", unbox req?users) 199 | let text = String.Join(" ", [string count; string called; users]) 200 | res.``end``(text) 201 | ) 202 | 203 | request(app) 204 | .get("/foo/bob") 205 | .expect("2 2 foo,bob", d) 206 | |> ignore 207 | ) 208 | 209 | itAsync "should support altering req.params across routes" (fun d -> 210 | let app = express.express () 211 | 212 | app.param("user", fun req res next user -> 213 | req.``params``.["user"] <- "loki" 214 | next.Invoke() 215 | ) 216 | 217 | app.get("/:user", fun (req : Request) (res : Response) (next : NextFunction) -> 218 | next.Invoke("route") 219 | ) 220 | 221 | app.get("/:user", fun (req : Request) (res : Response) next -> 222 | res.send(req.``params``.["user"]) 223 | ) 224 | 225 | request(app) 226 | .get("/bob") 227 | .expect("loki", d) 228 | |> ignore 229 | ) 230 | 231 | itAsync "should not invoke without route handler" (fun d -> 232 | let app = express.express () 233 | 234 | app.param("thing", fun req res next thing -> 235 | req?thing <- thing 236 | next.Invoke() 237 | ) 238 | 239 | app.param("user", fun req res next user -> 240 | next.Invoke(Exception("invalid invocation")) 241 | ) 242 | 243 | app.post("/:user", fun (req : Request) (res : Response) (next : NextFunction) -> 244 | res.send(req.``params``.["user"]) 245 | ) 246 | 247 | app.get("/:thing", fun (req : Request) (res : Response) (next : NextFunction) -> 248 | res.send(req?thing) 249 | ) 250 | 251 | request(app) 252 | .get("/bob") 253 | .expect(200, "bob", d) 254 | |> ignore 255 | ) 256 | 257 | itAsync "should work with encoded values" (fun d -> 258 | let app = express.express () 259 | 260 | app.param("name", fun req res next name -> 261 | req.``params``.["name"] <- string name 262 | next.Invoke() 263 | ) 264 | 265 | app.get("/user/:name", fun (req : Request) (res : Response) -> 266 | let name = req.``params``.["name"] 267 | res.send(name) 268 | ) 269 | 270 | request(app) 271 | .get("/user/foo%25bar") 272 | .expect("foo%bar", d) 273 | |> ignore 274 | ) 275 | 276 | itAsync "should catch thrown error" (fun d -> 277 | let app = express.express () 278 | 279 | app.param("id", fun req res next id -> 280 | raise (Exception("err!")) 281 | ) 282 | 283 | app.get("/user/:id", fun (req : Request) (res : Response) -> 284 | let id = req.``params``.["id"] 285 | res.send(id) 286 | ) 287 | 288 | request(app) 289 | .get("/user/123") 290 | .expect(500, d) 291 | |> ignore 292 | ) 293 | 294 | itAsync "should catch thrown secondary error" (fun d -> 295 | let app = express.express () 296 | 297 | app.param("id", fun req res next value -> 298 | ``process``.nextTick(unbox next) 299 | ) 300 | 301 | app.param("id", fun req res next id -> 302 | raise (Exception("err!")) 303 | ) 304 | 305 | app.get("/user/:id", fun (req : Request) (res : Response) -> 306 | let id = req.``params``.["id"] 307 | res.send(string id) 308 | ) 309 | 310 | request(app) 311 | .get("/user/123") 312 | .expect(500, d) 313 | |> ignore 314 | ) 315 | 316 | itAsync "should defer to next route" (fun d -> 317 | let app = express.express () 318 | 319 | app.param("id", fun req res next id -> 320 | next.Invoke("route") 321 | ) 322 | 323 | app.get("/user/:id", fun (req : Request) (res : Response) -> 324 | let id = req.``params``.["id"] 325 | res.send(string id) 326 | ) 327 | 328 | app.get("/:name/123", fun (req : Request) (res : Response) -> 329 | res.send("name") 330 | ) 331 | 332 | request(app) 333 | .get("/user/123") 334 | .expect("name", d) 335 | |> ignore 336 | ) 337 | 338 | itAsync "should defer all the param routes" (fun d -> 339 | let app = express.express () 340 | 341 | app.param("id", fun req res next value -> 342 | if value = box "new" then 343 | next.Invoke("route") 344 | else 345 | next.Invoke() 346 | ) 347 | 348 | app.all("/user/:id", fun (req : Request) (res : Response) -> 349 | res.send("all.id") 350 | ) 351 | 352 | app.get("/user/:id", fun (req : Request) (res : Response) -> 353 | res.send("get.id") 354 | ) 355 | 356 | app.get("/user/new", fun (req : Request) (res : Response) -> 357 | res.send("get.new") 358 | ) 359 | 360 | request(app) 361 | .get("/user/new") 362 | .expect("get.new", d) 363 | |> ignore 364 | ) 365 | 366 | itAsync "should not call when values differ on error" (fun d -> 367 | let app = express.express () 368 | let mutable called = 0 369 | let mutable count = 0 370 | 371 | app.param("user", fun req res next user -> 372 | called <- called + 1 373 | if (user = box "foo") then 374 | raise (Exception("err!")) 375 | 376 | req?user <- user 377 | next.Invoke() 378 | ) 379 | 380 | app.get("/:user/bob", fun (req : Request) (res : Response) (next : NextFunction) -> 381 | count <- count + 1 382 | next.Invoke() 383 | ) 384 | 385 | app.get("/foo/:user", fun (req : Request) (res : Response) (next : NextFunction) -> 386 | count <- count + 1 387 | next.Invoke() 388 | ) 389 | 390 | app.``use``(fun err req (res : Response) next -> 391 | res.status(500) 392 | let text = String.Join(" ", [string count; string called; err.Value.Message]) 393 | res.send(text) 394 | ) 395 | 396 | request(app) 397 | .get("/foo/bob") 398 | .expect(500, "0 1 err!", d) 399 | |> ignore 400 | ) 401 | 402 | itAsync "should call when values differ when using 'next'" (fun d -> 403 | let app = express.express () 404 | let mutable called = 0 405 | let mutable count = 0 406 | 407 | app.param("user", fun req res next user -> 408 | called <- called + 1 409 | if (user = box "foo") then 410 | next.Invoke("route") 411 | req?user <- user 412 | next.Invoke() 413 | ) 414 | 415 | app.get("/:user/bob", fun (req : Request) (res : Response) (next : NextFunction) -> 416 | count <- count + 1 417 | next.Invoke() 418 | ) 419 | 420 | app.get("/foo/:user", fun (req : Request) (res : Response) (next : NextFunction) -> 421 | count <- count + 1 422 | next.Invoke() 423 | ) 424 | 425 | app.``use``(fun req res -> 426 | let text = String.Join(" ", [string count; string called; req?user]) 427 | res.``end``(text) 428 | ) 429 | 430 | request(app) 431 | .get("/foo/bob") 432 | .expect("1 2 bob", d) 433 | |> ignore 434 | ) 435 | 436 | ) 437 | ) 438 | --------------------------------------------------------------------------------