{"id":15003190,"url":"https://github.com/zaid-ajaj/fable.sqlclient","last_synced_at":"2025-07-23T04:35:04.157Z","repository":{"id":33945785,"uuid":"127658655","full_name":"Zaid-Ajaj/Fable.SqlClient","owner":"Zaid-Ajaj","description":"Fable Node client for Microsoft SQL Server, built around a node-mssql binding ","archived":false,"fork":false,"pushed_at":"2023-01-01T20:09:18.000Z","size":467,"stargazers_count":13,"open_issues_count":0,"forks_count":2,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-06-21T20:37:17.693Z","etag":null,"topics":["database","fable","microsoft-sql-server","node-mssql","sqlclient"],"latest_commit_sha":null,"homepage":null,"language":"F#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Zaid-Ajaj.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-04-01T18:13:57.000Z","updated_at":"2024-05-14T00:17:45.000Z","dependencies_parsed_at":"2023-01-15T03:31:41.211Z","dependency_job_id":null,"html_url":"https://github.com/Zaid-Ajaj/Fable.SqlClient","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/Zaid-Ajaj/Fable.SqlClient","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Zaid-Ajaj%2FFable.SqlClient","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Zaid-Ajaj%2FFable.SqlClient/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Zaid-Ajaj%2FFable.SqlClient/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Zaid-Ajaj%2FFable.SqlClient/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Zaid-Ajaj","download_url":"https://codeload.github.com/Zaid-Ajaj/Fable.SqlClient/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Zaid-Ajaj%2FFable.SqlClient/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":266618882,"owners_count":23957273,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-07-23T02:00:09.312Z","response_time":66,"last_error":null,"robots_txt_status":null,"robots_txt_updated_at":null,"robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["database","fable","microsoft-sql-server","node-mssql","sqlclient"],"created_at":"2024-09-24T18:56:54.089Z","updated_at":"2025-07-23T04:35:04.132Z","avatar_url":"https://github.com/Zaid-Ajaj.png","language":"F#","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Fable.SqlClient [![Nuget](https://img.shields.io/nuget/v/Fable.SqlClient.svg?colorB=green)](https://www.nuget.org/packages/Fable.SqlClient) [![Build status](https://ci.appveyor.com/api/projects/status/n7665851i24yh2d7?svg=true)](https://ci.appveyor.com/project/Zaid-Ajaj/fable-sqlclient)\n\n\n[Fable](https://github.com/fable-compiler/Fable) binding for [node-mssql](https://github.com/tediousjs/node-mssql), Microsoft SQL Server client library with an idiomatic and type-safe F# API to be used from Fable Node applications. \n\n### Installation\nInstall the Fable binding from Nuget\n```bash\n# using nuget\ndotnet add package Fable.SqlClient\n\n# or with paket\npaket add Fable.SqlClient --project /path/to/project.fsproj\n```\nInstall the actual node-mssql from Npm\n```\nnpm install --save node-mssql\n``` \n# Getting started\nFirst of all, configure the connection using `SqlConfig`:\n```fs\nopen Fable.SqlClient\n\nlet connectionConfig = \n    [ SqlConfig.User \"admin\"\n      SqlConfig.Password \"str0ngPa$$word\"\n      SqlConfig.Host \"localhost\"\n      SqlConfig.Database \"AdventuresWorks\"\n      SqlConfog.Port 1433 ]\n```\n\n# `Sql.readRows` querying a tabular result set \n\n```fs\nopen Fable.SqlClient\nopen Fable.SqlClient.OptionWorkflow\n\ntype User = { Id: int; Name: string }\n \nlet getUsers() : Async\u003cOption\u003cUser list\u003e\u003e = \n    async {\n        let! usersResult = \n          connectionConfig\n          |\u003e Sql.connect\n          |\u003e Sql.query \"SELECT id, name from [dbo].[Users]\" \n          |\u003e Sql.readRows (fun row -\u003e \n                option {\n                    let! id = Sql.readInt \"id\" row\n                    let! name = Sql.readString \"name\" row\n                    return { Id = id; Name = name; }   \n                })\n\n        match usersResult with \n        | Ok users -\u003e return Some users \n        | Error sqlError -\u003e return None\n    }\n```\nHere `Sql.readRows` takes a transformer function that maps every row into an `Option\u003c't\u003e`. Because we are using the `option` workflow, the mapping is done safely and if either `id` or `name` is null, then the row is skipped. \n\nIf you want to handle the null values manually, then simply use `let` instead of `let!` in combination with `defaultArg`. For example, if you want to use an empty string as a default for the `name` column, then you simply write the following:  \n\n```fs\nasync {\n    let! usersResult = \n      connectionConfig\n      |\u003e Sql.connect\n      |\u003e Sql.query \"SELECT id, name from [dbo].[Users]\" \n      |\u003e Sql.readRows (fun row -\u003e \n            option {\n                let! id = Sql.readInt \"id\" row\n                // notice: no ! in the let binding\n                let name = Sql.readString \"name\" row\n                let nameOrEmpty = defaultArg name \"\"\n                return { Id = id; Name = nameOrEmpty; }   \n            })\n\n    match usersResult with \n    | Ok users -\u003e return Some users \n    | Error sqlError -\u003e return None\n}\n```\n# `Sql.readRows` from a parameterized query\nQueries can be parameterized with named parameters to avoid SQL injections: \n```fs\nopen Fable.SqlClient\nopen Fable.SqlClient.OptionWorkflow\n\nlet userByUsername (username: string) : Async\u003cUser option\u003e = \n    async {\n        let! results = \n            connectionConfig\n            |\u003e Sql.connect\n            |\u003e Sql.query \"SELECT TOP 1 id, name from [dbo].[Users] where name = @name\"\n            |\u003e Sql.parameters [ SqlParam.From (\"@name\", username) ]\n            |\u003e Sql.readRows (fun row -\u003e\n                option {\n                    let! id = Sql.readInt \"id\" row\n                    let! name = Sql.readString \"name\" row\n                    return { Id = id; Name = name }\n                })\n\n        match results with \n        | Ok (user :: _) -\u003e return Some user \n        | _ -\u003e return None\n    }\n```\n# `Sql.readScalar` querying a scalar value\n```fs\nopen Fable.SqlClient\n\nlet pingDatabase() : Async\u003cDateTime option\u003e = \n    async {\n        let! serverTime = \n            connectionConfig\n            |\u003e Sql.connect\n            |\u003e Sql.query \"SELECT GETDATE()\"\n            |\u003e Sql.readScalar \n\n        match serverTime with \n        | Ok (SqlValue.Date time) -\u003e return Some time\n        | _ -\u003e return None\n    }\n```\n# `Sql.storedProcedure` \nexecuting a stored procedure with parameters\n```fs\nlet userExists (name: string) : Async\u003cbool\u003e = \n    async {\n        let! exists = \n            connectionConfig\n            |\u003e Sql.connect\n            |\u003e Sql.storedProcedure \"user_exists\"\n            |\u003e Sql.parameters [ SqlParam.From(\"@name\", name) ]\n            |\u003e Sql.readScalar \n\n        match exists with \n        | Ok (SqlValue.Bool value) -\u003e return value\n        | _ -\u003e return false\n    }\n```\n# `Sql.readAffectedRows`\nReturns the number of rows affected. For example when you execute a `DELETE`, `INSERT` or `UPDATE` statements, the rows affected will the ones that were deleted, inserted or updated. If you read the rows affected by a `SELECT` statements, the row count is returned. \n```fs\n/// delete events older than 2 months\nlet deleteOldEvents() : Async\u003cResult\u003cint, string\u003e\u003e = \n    async {\n        let! eventsDeleted = \n            connectionConfig\n            |\u003e Sql.connect\n            |\u003e Sql.query \"DELETE FROM [dbo].[Events] WHERE DateAdded \u003c @TwoMonthsAgo\"\n            |\u003e Sql.parameters [ SqlParam.From(\"@TwoMonthsAgo\", DateTime.Now.AddMonths(-2)) ]\n            |\u003e Sql.readAffectedRows \n\n        match eventsDeleted with \n        | Ok count -\u003e return Ok count\n        | Error error -\u003e \n            // Extract info from the SqlError\n            let (errType, errMsg, errStack) = error.Info()\n            return Error errMsg      \n    }\n```\n# `Sql.readJson` \nSince Microsoft SQL Server supports JSON natively, you can query the database and have it return the result set as a single JSON string. `Sql.readJson` is a utility function that extracts the JSON from the scalar value. You can then parse the resulting serialized JSON using your favorite Json library:\n```fs\nasync {\n    let! json =\n        connectionConfig\n        |\u003e Sql.connect\n        |\u003e Sql.query \"SELECT id, name FROM (VALUES(42, N'Fable')) as TableName(id, name) FOR JSON PATH\"\n        |\u003e Sql.readJson \n    \n    match json with \n    | Ok serialized = \n        let values = Json.parseAs\u003c{| id: int; name: string |} array\u003e serialized\n        let value = values.[0] \n        printfn \"Id = %d and Name = %s\" value.id value.name\n    \n    | Error error -\u003e \n        printfn \"Something went wrong...\"\n}\n```\n# API definitions \n```fs\nval Sql.readRows : ((string * SqlValue) list -\u003e Option\u003c't\u003e) -\u003e (props: ISqlProps) -\u003e Async\u003cResult\u003c't list, SqlError\u003e\u003e\n\nval Sql.readAffectedRows (props: ISqlProps) -\u003e Async\u003cResult\u003cint, SqlError\u003e\u003e\n\nval Sql.readJson (props: ISqlProps) -\u003e Async\u003cResult\u003cstring, SqlError\u003e\u003e \n\nval Sql.readScalar (props: ISqlProps) -\u003e Async\u003cResult\u003cSqlValue, SqlError\u003e\u003e\n```\nwhere the important types are defined as follows. The types within `SqlValue` are those that you can read from Sql Server.\n```fs\ntype SqlValue = \n    | TinyInt of uint8\n    | SmallInt of int16\n    | Int of int \n    | Bool of bool\n    | Date of DateTime\n    | UniqueIdentifier of Guid \n    | BigInt of int64\n    | Decimal of decimal  \n    | String of string \n    | Number of float \n    | Binary of byte[]\n    | Null\n\n    \ntype SqlError = \n    | ConnectionError of message: string * stack: string\n    | TransactionError of message: string * stack: string \n    | RequestError of message: string * stack: string \n    | ApplicationError of message: string * stack: string \n    | GenericError of errorType: string * message: string * stack: string\n```\nthe type `ISqlProps` is just a helper type that accumulate the configuration for a query using a fluent syntax\n# Development and Testing\nThe test project is in the `test` directory and includes *integration* tests. To run these tests on your local machine, you will need to setup a couple of environment variables:\n - `SQLCLIENT_DATABASE`: the name of the database to run the tests against. The tests don't require a specific database, you can just use `master`. \n - `SQLCLIENT_SERVER`: the IP address of the hosting machine, if you have a local MSSQL server, then `local/{instance}` will do\n - `SQLCLIENT_USER`: the username to log in with \n - `SQLCLIENT_PASSWORD`: the password of the user\n\nAfter you have set up these variables, you can run the commands:\n```bash\nnpm install \nnpm test\n```\nThis will compile the test the project and runs tests using `Mocha`. \n\n# Known issues\nReading `DateTimeOffset` directly is not supported. It has to be converted to `nvarchar` first and parsed from `SqlValue.String value`. However, a `DateTimeOffset` value can still be used as a parameter value directly. The following test demonstrates what's supported:\n```fs\ntestCaseAsync \"DateTimeOffset round trip works\" \u003c| fun () -\u003e \n    async {\n        let input = DateTimeOffset.UtcNow\n        let! value = \n            defaultConfig\n            |\u003e Sql.connect \n            // convert the value to nvarchar\n            |\u003e Sql.query \"SELECT CONVERT(nvarchar(100), @DateTimeOffset) as [Value]\"\n            |\u003e Sql.parameters [ SqlParam.From(\"@DateTimeOffset\", input) ]\n            |\u003e Sql.readScalar \n\n        match value with \n        // parse the value here\n        | Ok (SqlValue.String serialized) -\u003e \n            let deserialized = DateTimeOffset.Parse serialized \n            areEqual input deserialized\n        \n        | otherwise -\u003e return! failwithf \"Unexpected results:\\n%s\" (Json.stringify otherwise)\n    }\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzaid-ajaj%2Ffable.sqlclient","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fzaid-ajaj%2Ffable.sqlclient","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzaid-ajaj%2Ffable.sqlclient/lists"}