{"id":17159132,"url":"https://github.com/scull7/bs-sql-common","last_synced_at":"2025-08-19T02:42:35.617Z","repository":{"id":28997964,"uuid":"119855232","full_name":"scull7/bs-sql-common","owner":"scull7","description":"A backend wrapper for SQL-based Node.js drivers.","archived":false,"fork":false,"pushed_at":"2022-12-07T10:33:20.000Z","size":340,"stargazers_count":19,"open_issues_count":14,"forks_count":5,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-08-09T02:46:46.340Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"OCaml","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/scull7.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-02-01T15:34:51.000Z","updated_at":"2023-03-24T11:14:06.000Z","dependencies_parsed_at":"2023-01-14T14:00:41.484Z","dependency_job_id":null,"html_url":"https://github.com/scull7/bs-sql-common","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/scull7/bs-sql-common","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/scull7%2Fbs-sql-common","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/scull7%2Fbs-sql-common/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/scull7%2Fbs-sql-common/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/scull7%2Fbs-sql-common/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/scull7","download_url":"https://codeload.github.com/scull7/bs-sql-common/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/scull7%2Fbs-sql-common/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":271092128,"owners_count":24697903,"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-08-19T02:00:09.176Z","response_time":63,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","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":[],"created_at":"2024-10-14T22:13:24.509Z","updated_at":"2025-08-19T02:42:35.546Z","avatar_url":"https://github.com/scull7.png","language":"OCaml","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![NPM](https://nodei.co/npm/bs-sql-common.png)](https://nodei.co/npm/bs-sql-common/)\n[![Build Status](https://www.travis-ci.org/scull7/bs-sql-common.svg?branch=master)](https://www.travis-ci.org/scull7/bs-sql-common)\n[![Coverage Status](https://coveralls.io/repos/github/scull7/bs-sql-common/badge.svg?branch=master)](https://coveralls.io/github/scull7/bs-sql-common?branch=master)\n\n# bs-sql-common\nA common interface for SQL-based Node.js drivers.\n\n## Why?\n\nTo provide a common interface for MySQL, PostgreSQL and sqlite\nimplementations.  \n\n### Version 3\nA rewrite of the entire package to expose it as a Functor that can accept\nany module which implements the [`Queryable`](#Queryable) interface.\n\n* Use [Belt.Result][belt-result] for responses so to better integrate with then\n  BuckleScript ecosystem.\n\n* Provide [response decoding and inspection](#Sql.Response) functions so that\n  the user has a consistent view into responses from any library.\n\n* Provide an [ID type](#Sql.Id) that properly encodes large integers as strings.\n\n* Provide batch inserts and queries\n\n## Status\n\nThe standard things are there and this library is being used live within\nseveral production projects.\n\n- [x] Query parameter substitution\n- [x] Named parameters\n- [x] Promise based interface.\n- [ ] Connection pooling\n- [ ] [Custom Streams][mysql2-custom-streams]\n\n## Installation\n\nInside of a BuckleScript project:\n```sh\nyarn add bs-sql-common\n```\n\nThen add `bs-sql-common` to your `bs-dependencies` in your `bsconfig.json`\n```json\n{\n  \"bs-dependencies\": [ \"bs-sql-common\" ]\n}\n```\n\nThen add a `bs-sql-common` compatible package to your repository or create your\nown. All of the examples use the [`bs-mysql2`][bs-mysql2] package, here are the\nrequirements to use that package:\n\n```sh\nyarn add bs-mysql2\n```\n```json\n{\n  \"bs-dependencies\": [ \"bs-sql-common\", \"bs-mysql2\" ]\n}\n```\n```ocaml\nmodule Sql = SqlCommon.Make(MySql2)\n\nlet db = Sql.Connection.connect\n  ~host=\"127.0.0.1\"\n  ~port=3306\n  ~user=\"root\"\n  ()\n\nSql.query ~db ~sql:\"SHOW DATABASES\" (fun res -\u003e\n  match res with\n  | Belt.Result.Error e -\u003e raise e\n  | Belt.Result.Ok select -\u003e\n    select\n    |. Sql.Response.Select.flatMap (Json.Decode.dict Json.Decode.string)\n    |. Belt.Array.map (fun x -\u003e Js.dict.unsafeGet x \"Database\")\n    |. Expect.expect\n    |\u003e Expect.toContain @@ \"test\"\n)\n```\n\n## Usage\n\n***Note:*** All of the examples use the [`bs-mysql2`][bs-mysql2] package as the\nconnection provider. Any other provider should have the same behavior with\ndiffering connection creation requirements.\n\n### Create a connection and customized module\n\nThe following connection and module will be use within the rest of the examples.\n```reason\nmodule Sql = SqlCommon.Make(MySql2);\n\nlet db = Sql.Connection.connect(~host=\"127.0.0.1\", ~port=3306, ~user=\"root\", ());\n```\nAssume the following statement occurs at the end of each example.\n```reason\nSql.Connection.close(conn);\n```\n\n### Standard Callback Interface\n\n#### Standard Query Method\n\n```reason\nSql.query(~db, ~sql=\"SHOW DATABASES\",\n  fun\n  | Belt.Result.Error e =\u003e Js.log2(\"ERROR: \", e)\n  | Belt.Result.Ok select =\u003e\n    select\n    |. Sql.Response.Select.rows\n    |. Js.log2(\"RESPONSE ROWS: \", _)\n);\n\nSql.mutate(\n  ~db,\n  ~sql=\"INSERT INTO test (foo) VALUES (?)\",\n  ~params=Sql.Params.positional(Json.Encode.([|string(\"bar\")|] |. array)),\n  (res) =\u003e\n    fun\n    | Belt.Result.Error =\u003e Js.log2(\"ERROR: \", e)\n    | Belt.Result.Ok mutation =\u003e\n      mutation\n      |. Sql.Response.Mutation.insertId\n      |. Js.log2(\"INSERT ID: \", _)\n);\n```\n\n#### Prepared Statements - Named Placeholders\n\n```reason\nlet json = Sql.Params.named(\n  Json.Encode.(object_([\n  (\"x\", int(1)),\n  (\"y\", int(2)),\n  ]))\n));\n\nlet decoder = Json.Encode.array(Json.Encode.int)\n\nSql.query(~db, ~sql:\"SELECT :x + :y AS z\", ~params, (res) =\u003e\n  switch res {\n  | Belt.Result.Error =\u003e Js.log2(\"ERROR: \", e)\n  | Belt.Result.Ok select =\u003e\n    select\n    |. Sql.Response.flatMap(decoder)\n    |. Js.log2(\"DECODED ROWS: \", _)\n  }\n);\n\nSql.mutate(~db, ~sql:\"INSERT INTO test (foo, bar) VALUES (:x, :y)\", ~params, (res) =\u003e\n  switch res {\n  | Belt.Result.Error =\u003e Js.log2(\"ERROR: \", e)\n  | Belt.Result.Ok mutation =\u003e\n    mutation\n    |. Sql.Response.Mutation.insertId\n    |. Js.log2(\"INSERT ID: \", _)\n  }\n);\n```\n\n#### Prepared Statements - Positional Placeholders\n\n```reason\nlet params = Sql.Params.positional(\n  Json.Encode.(array(int, [|5,6|]))\n));\n\nSql.query(~db, ~sql:\"SELECT 1 + ? + ? AS result\", ~params, (res) =\u003e\n  switch res {\n  | Belt.Result.Error =\u003e Js.log2(\"ERROR: \", e)\n  | Belt.Result.Ok select =\u003e\n    select\n    |. Sql.Response.rows\n    |. Js.log2(\"RAW ROWS: \", _)\n  }\n);\n\nSql.mutate(~db, ~sql:\"INSERT INTO test (foo, bar) VALUES (?, ?)\", ~params, (res) =\u003e\n  switch res {\n  | Belt.Result.Error =\u003e Js.log2(\"ERROR: \", e)\n  | Belt.Result.Ok mutation =\u003e\n    mutation\n    |. Sql.Response.Mutation.insertId\n    |. Js.log2(\"INSERT ID: \", _)\n  }\n);\n```\n\n### Promise Interface\n\n```reason\nlet params = Sql.Params.positional(\n  Json.Encode.(array(int, [|\"%schema\"|]))\n));\n\nSql.query(~db, ~params, ~sql=\"SELECT ? AS search\")\n|\u003e Js.Promise.then_(select =\u003e\n  select\n  |. Sql.Response.rows\n  |. Js.log2(\"RAW ROWS: \", _)\n  |. ignore\n)\n|\u003e Js.Promise.catch(err =\u003e\n  Js.log2(\"Failure!!!\", err)\n  |. ignore\n)\n```\n\n## Sql.Id\n```ocaml\nmodule Id: sig\n  type t = Driver.Id.t\n\n  val fromJson : Js.Json.t -\u003e Driver.Id.t\n\n  val toJson : Driver.Id.t -\u003e Js.Json.t\n\n  val toString : Driver.Id.t -\u003e string\nend\n```\n\n## Sql.Response\n```ocaml\nmodule Response: sig\n  module Mutation: sig\n    val insertId : Driver.Mutation.t -\u003e Id.t option\n\n    val affectedRows: Driver.Mutation.t -\u003e int\n  end\n\n  module Select: sig\n    module Meta : sig\n      val schema : Driver.Select.Meta.t -\u003e string\n\n      val name : Driver.Select.Meta.t -\u003e string\n\n      val table : Driver.Select.Meta.t -\u003e string\n    end\n\n    val meta : Driver.Select.t -\u003e Driver.Select.Meta.t array\n\n    val concat : Driver.Select.t -\u003e Driver.Select.t -\u003e Driver.Select.t\n\n    val count : Driver.Select.t -\u003e int\n\n    val flatMap :\n      Driver.Select.t -\u003e\n      (Js.Json.t -\u003e Driver.Select.Meta.t array -\u003e 'a) -\u003e\n      'a array\n\n    val flatMap : Driver.Select.t -\u003e (Js.Json.t -\u003e 'a) -\u003e 'a array\n\n    val rows : Driver.Select.t -\u003e Js.Json.t array\n  end\nend\n```\n\n## Queryable Interface\n```ocaml\nmodule type Queryable = sig\n  module Connection : sig\n    type t\n\n    val connect :\n      ?host:string -\u003e\n      ?port:int -\u003e\n      ?user:string -\u003e\n      ?password:string -\u003e\n      ?database:string -\u003e\n      unit -\u003e t\n\n    val close : t -\u003e unit\n  end\n\n  module Exn : sig\n    val fromJs : Js.Json.t -\u003e exn\n  end\n\n  module Id : sig\n    type t\n\n    val fromJson : Js.Json.t -\u003e t\n\n    val toJson : t -\u003e Js.Json.t\n\n    val toString : t -\u003e string\n  end\n\n  module Mutation : sig\n    type t\n\n    val insertId : t -\u003e Id.t option\n\n    val affectedRows : t -\u003e int\n  end\n\n  module Params : sig\n    type t\n\n    val named : Js.Json.t -\u003e t\n\n    val positional : Js.Json.t -\u003e t\n  end\n\n  module Select : sig\n    type t\n\n    module Meta : sig\n      type t\n\n      val schema : t -\u003e string\n\n      val name : t -\u003e string\n\n      val table : t -\u003e string\n    end\n\n    val meta : t -\u003e Meta.t array\n\n    val concat : t -\u003e t -\u003e t\n\n    val count : t -\u003e int\n\n    val flatMapWithMeta : t -\u003e (Js.Json.t -\u003e Meta.t array -\u003e 'a) -\u003e 'a array\n\n    val flatMap : t -\u003e (Js.Json.t -\u003e 'a) -\u003e 'a array\n\n    val rows : t -\u003e Js.Json.t array\n  end\n\n  type response =\n    [\n    | `Error of exn\n    | `Mutation of Mutation.t\n    | `Select of Select.t\n    ]\n\n  type callback = response -\u003e unit\n\n  val execute : Connection.t -\u003e string -\u003e Params.t option -\u003e callback -\u003e unit\nend\n```\n\n[belt-result]: https://bucklescript.github.io/bucklescript/api/Belt.Result.html\n[bs-mysql2]: https://github.com/scull7/bs-mysql2\n[mysql2-custom-streams]: https://github.com/sidorares/node-mysql2/tree/master/documentation/Extras.md\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fscull7%2Fbs-sql-common","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fscull7%2Fbs-sql-common","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fscull7%2Fbs-sql-common/lists"}