{"id":32118694,"url":"https://github.com/tureluren/refql","last_synced_at":"2026-02-21T03:02:03.383Z","repository":{"id":40633618,"uuid":"491588871","full_name":"tureluren/refql","owner":"tureluren","description":"A library for composing and running database queries with rich IntelliSense and type safety","archived":false,"fork":false,"pushed_at":"2025-08-13T19:26:03.000Z","size":8650,"stargazers_count":17,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-10-20T17:43:14.830Z","etag":null,"topics":["fantasy-land","functional-programming","nodejs","orm","query-builder","query-language","semigroup","sql","typescript"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","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/tureluren.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2022-05-12T16:26:09.000Z","updated_at":"2025-10-06T09:14:26.000Z","dependencies_parsed_at":"2025-01-25T22:29:37.430Z","dependency_job_id":"06ccb38c-b646-4afd-84a9-6e01538d38c4","html_url":"https://github.com/tureluren/refql","commit_stats":{"total_commits":266,"total_committers":2,"mean_commits":133.0,"dds":"0.26315789473684215","last_synced_commit":"f471e555c87ecd404c62db90ca4ec29184b6b305"},"previous_names":[],"tags_count":19,"template":false,"template_full_name":null,"purl":"pkg:github/tureluren/refql","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tureluren%2Frefql","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tureluren%2Frefql/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tureluren%2Frefql/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tureluren%2Frefql/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tureluren","download_url":"https://codeload.github.com/tureluren/refql/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tureluren%2Frefql/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29672256,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-21T00:11:43.526Z","status":"online","status_checked_at":"2026-02-21T02:00:07.432Z","response_time":107,"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":["fantasy-land","functional-programming","nodejs","orm","query-builder","query-language","semigroup","sql","typescript"],"created_at":"2025-10-20T17:32:18.337Z","updated_at":"2026-02-21T03:02:03.377Z","avatar_url":"https://github.com/tureluren.png","language":"TypeScript","readme":"# RefQL\nA library for composing and running database queries with rich IntelliSense and type safety.\n\n\u003cimg width=\"682\" height=\"412\" alt=\"RefQL example\" src=\"https://raw.githubusercontent.com/tureluren/refql/main/example.gif\"\u003e\n\n## Installation\n```bash\nnpm install refql \n```\n\n## Create RefQL instance\n```ts\n// refql.ts\nimport { Pool } from \"pg\";\nimport RefQL from \"refql\";\n\nconst pool = new Pool ({\n  // ...pool options\n});\n\nconst querier = (query: string, values: any[]) =\u003e\n  pool.query (query, values).then(({ rows }) =\u003e rows)\n\nconst refql = RefQL ({ \n  querier\n});\n\nexport default refql;\n```\n\n## Introspect the database schema (PostgreSQL only; should be run separately from the main project)\n\n```ts\nimport refql from \"./refql\";\n\nrefql.introspect().then(() =\u003e {\n  console.log(\"RefQL Tables stored inside node_modules/.refql\");\n})\n```\n\n## Easily read referenced data using an `RQLTag`\n\n```ts\nimport refql from \"./refql\";\n\nconst { Player, Team, League, Rating, Goal, Assist, Game } = refql.tables.public;\n\nconst { id } = Team.props;\n\n// select components to create an RQLTag\nconst readTeamById = Team ([\n  Player ([\n    Rating,\n    Goal,\n    Assist\n  ]),\n  League,\n  Game,\n  id.eq\u003c{ id: number }\u003e (p =\u003e p.id)\n]);\n\n// run the RQLTag\nreadTeamById ({ id: 1 }).then(console.log);\n\n// [\n//   {\n//     name: \"FC Horgawid\",\n//     players: [\n//       {\n//         firstName: \"Clifford\",\n//         lastName: \"Morton\",\n//         rating: { acceleration: 71, finishing: 41, positioning: 83 },\n//         goals: [{  ownGoal: false, minute: 74 }, ...],\n//         assists: [{ goalId: 13, playerId: 9 }, ...]\n//       },\n//       ...\n//     ],\n//     league: { name: \"Falkland Islands league\" },\n//     games: [\n//       {\n//         homeTeamId: 1,\n//         awayTeamId: 8,\n//         result: \"0 - 2\"\n//       },\n//       ...\n//     ]\n//   }\n// ];\n```\n\n## Table of contents\n* [Fantasy Land interoperability](#fantasy-land-interoperability)\n* [Options](#options)\n* [Querier](#querier)\n* [Tables and references](#tables-and-references)\n* [Operator mix](#operator-mix)\n* [Insert, update and delete](#insert-update-and-delete)\n* [SQLTag](#sqltag)\n\n## Fantasy Land interoperability\n\u003ca href=\"https://github.com/fantasyland/fantasy-land\"\u003e\u003cimg width=\"82\" height=\"82\" alt=\"Fantasy Land\" src=\"https://raw.github.com/puffnfresh/fantasy-land/master/logo.png\"\u003e\u003c/a\u003e\n\nBoth `RQLTag` and [`SQLTag`](#sqltag) are `Semigroup` structures.\n\n```ts\nimport refql from \"./refql\";\n\nconst { Player, Team } = refql.tables.public;\n\nconst readPart1 = Player ([\n  \"id\",\n  \"firstName\",\n  Team ([\"id\"])\n]);\n\nconst readPart2 = Player ([\n  \"lastName\",\n  Team ([\"name\"])\n]);\n\nconst readPage =\n  readPart1\n    .concat (readPart2)\n    .concat (Player ([\n      Limit\u003c{ limit: number }\u003e (p =\u003e p.limit),\n      Offset\u003c{ offset: number }\u003e (p =\u003e p.offset)\n    ]));\n\nreadPage ({ limit: 1, offset: 0 }).then (console.log);\n\n// [\n//   {\n//     id: 1,\n//     firstName: \"Christine\",\n//     lastName: \"Hubbard\",\n//     team: { id: 1, name: \"FC Agecissak\" }\n//   }\n// ];\n```\n\n\n## Options\n```ts\nimport { Pool } from \"pg\";\nimport RefQL from \"refql\";\n\nconst pool = new Pool ({\n  // ...pool options\n});\n\nconst pgQuerier = (query: string, values: any[]) =\u003e\n  pool.query (query, values).then(({ rows }) =\u003e rows)\n\nconst refql = RefQL ({\n  // querier\n  querier: pgQuerier,\n\n  // database case type - optional\n  casing: \"snake_case\"\n\n  // sign used for parameterized queries - optional\n  parameterSign: \"$\",\n\n  // using indexed parameters or not ($1, $2, ...) - optional\n  indexedParameters: true,\n\n  // run tag and transform result - optional\n  runner: (tag, params) =\u003e tag.run (params)\n});\n```\n\n## Querier\nThe querier should have the type signature `\u003cT\u003e(query: string, values: any[]) =\u003e Promise\u003cT[]\u003e`. This function is a necessary in-between piece to make RefQL independent from database clients. This allows you to choose your own client. This is also the place where you can debug or transform a query before it goes to the database or when the result is obtained. Example of a querier for mySQL:\n\n```ts\nimport mySQL from \"mysql2\";\nimport RefQL from \"refql\";\n\nconst mySQLPool = mySQL.createPool ({\n  // ...pool options\n});\n\nconst mySQLQuerier = \u003cT\u003e(query: string, values: any[]): Promise\u003cT[]\u003e =\u003e\n  new Promise ((res, rej) =\u003e {\n    mySQLPool.query (query, values, (error, rows) =\u003e {\n      if (error) {\n        rej (error);\n        return;\n      }\n      res (rows as T[]);\n    });\n  });\n\nconst refql = RefQL ({\n  querier: mySQLQuerier,\n  parameterSign: \"?\",\n  indexedParameters: false\n});\n```\n\n### Convert Promise output to something else using the runner \nU can use [Module augmentation](https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation) in TypeScript to register another container type.\n\n```ts\nimport RefQL, { Selectable } from \"refql\";\n\ndeclare module \"refql\" {\n  interface RQLTag\u003cTableId extends string = any, Props = any, Components extends Selectable\u003cTableId, Props\u003e[] = Selectable\u003cTableId, Props\u003e[], Params = any, Output = any\u003e {\n    (params?: Params): Task\u003cOutput\u003e;\n  }\n}\n\nclass Task\u003cOutput\u003e {\n  fork: (rej: (e: any) =\u003e void, res: (x: Output[]) =\u003e void) =\u003e void;\n\n  constructor(fork: (rej: (e: any) =\u003e void, res: (x: Output[]) =\u003e void) =\u003e void) {\n    this.fork = fork;\n  }\n}\n\n// transformation function\nconst promiseToTask = \u003cOutput\u003e(p: Promise\u003cOutput\u003e) =\u003e\n  new Task\u003cOutput\u003e ((rej, res) =\u003e p.then (res).catch (rej));\n\nconst { tables }  = RefQL ({\n  // ...refql options\n  runner: (tag, params) =\u003e promiseToTask (tag.run (params))\n});\n\nconst { Player } = tables;\n\nconst readFirstTen = Player ([\n  id.asc(),\n  \"firstName\",\n  \"lastName\",\n  Limit (10),\n]);\n\n// `fork` instead of `then`\nreadFirstTen ().fork (console.error, console.log);\n\n// [\n//   { id: 1, firstName: \"Christine\", lastName: \"Hubbard\" },\n//   { id: 2, firstName: \"Emily\", lastName: \"Mendez\" },\n//   { id: 3, firstName: \"Stella\", lastName: \"Kubo\" },\n//   { id: 4, firstName: \"Celia\", lastName: \"Misuri\" },\n//   { id: 5, firstName: \"Herbert\", lastName: \"Okada\" },\n//   { id: 6, firstName: \"Terry\", lastName: \"Bertrand\" },\n//   { id: 7, firstName: \"Fannie\", lastName: \"Guerrero\" },\n//   { id: 8, firstName: \"Lottie\", lastName: \"Warren\" },\n//   { id: 9, firstName: \"Leah\", lastName: \"Kennedy\" },\n//   { id: 10, firstName: \"Lottie\", lastName: \"Giraud\" }\n// ];\n```\n\n## Tables and references\nFor now, introspection only works for PostgreSQL databases. The example below shows how u can define tables and describe their references to other tables. Relationships are created by passing the table name as a string instead of passing a `Table` object. This is to avoid circular dependency problems. `Tables` are uniquely identifiable by the combination schema and tableName `(\u003cschema\u003e.\u003ctableName\u003e)`.\n\n```ts\nimport { \n  BelongsTo, BelongsToMany, HasMany, HasOne, \n  NumberProp, Offset, StringProp,\n} from \"refql\";\nimport refql from \"./refql\";;\n\nconst { Table } = refql;\n\nconst Player = Table (\"player\", [\n  NumberProp (\"id\"),\n  StringProp (\"firstName\", \"first_name\"),\n  StringProp (\"lastName\", \"last_name\"),\n  BelongsTo (\"team\", \"public.team\"),\n  HasOne (\"rating\", \"rating\"),\n  HasMany (\"goals\", \"goal\"),\n  BelongsToMany (\"games\", \"game\")\n]);\n```\n\n### Ref info\nRefQL tries to link 2 tables based on logical column names, using the \"casing\" option. You can always point RefQL in the right direction if this doesn't work for you by specifying refs yourself.\n\n```ts\nconst playerBelongsToManyGames = BelongsToMany (\"games\", \"game\", {\n  lRef: [\"id\"],\n  rRef: [\"id\"],\n  lxRef: [\"player_id\"],\n  rxRef: [\"game_id\"],\n  xTable: \"game_player\"\n});\n```\n\n## Operator mix\n\n```ts\nimport { NumberProp } from \"refql\";\nimport refql from \"./refql\";\n\nconst { sql, tables } = refql;\n\nconst { Player } = tables.public;\n\n// subselect\nconst goalCount = NumberProp (\"goalCount\", sql`\n  select cast(count(*) as int) from goal\n  where goal.player_id = player.id\n`);\n\nconst { teamId, firstName, lastName } = Player.props;\n\nconst readStrikers = Player ([\n  goalCount.gt (7),\n  teamId.eq (1)\n]);\n\nconst searchStrikers = Player ([\n  lastName\n    .iLike\u003c{ q: string }\u003e (p =\u003e p.q)\n    .or (firstName.iLike\u003c{ q: string }\u003e (p =\u003e p.q))\n    // order by lastName asc\n    .asc ()\n]);\n\nconst readPlayerPage = Player ([\n  Limit (5),\n  Offset (0)\n]);\n\nconst readStrikersPage =\n  readStrikers\n    .concat (searchStrikers)\n    .concat (readPlayerPage);\n\n// run\nreadStrikersPage ({ q: \"Gra%\" }).then (console.log);\n\n// [\n//   {\n//     id: 14,\n//     firstName: \"Arthur\",\n//     lastName: \"Graham\",\n//     goalCount: 14,\n//     teamId: 1\n//   }\n// ];\n```\n\n## Insert, update and delete\n\n### Insert\n\n```ts\nimport { Values } from \"refql\";\nimport refql from \"./refql\";\n\nconst { sql, tables } = refql;\n\nconst { Team } = tables.public;\n\n// `rows` contains the inserted teams\nconst byIds = sql\u003c{rows: { id: number }[]}\u003e`\n  and id in ${Values(({ rows }) =\u003e rows.map(r =\u003e r.id))} \n`;\n\n// `RQLTags` created from `Team` will be concatenated and used for the return value.\n// If there are no `RQLTags`, all fields of the team will be returned.\nconst insertTeam = Team.insert([\n  Team([\n    \"id\",\n    \"name\",\n    byIds\n  ]),\n\n  Team([\n    \"active\",\n    \"leagueId\"\n  ])\n]);\n\n// Fields that are not nullable and don't have a default value are required\ninsertTeam({ data: [{ name: \"New Team\", leagueId: 1 }] })\n  .then(console.log);\n\n// [{\n//   id: 84,\n//   name: \"New Team\",\n//   active: true,\n//   leagueId: 1\n// }];\n```\n\n### Update\n```ts\nimport { Values } from \"refql\";\nimport refql from \"./refql\";\n\nconst { sql, tables } = refql;\n\nconst { Team } = tables.public;\n\n// `rows` contains the inserted teams\nconst byIds = sql\u003c{rows: { id: number }[]}\u003e`\n  and id in ${Values (({ rows }) =\u003e rows.map (r =\u003e r.id))} \n`;\n\nconst updateTeamById = Team.update ([\n  // filter teams\n  id.eq\u003c{ id: number }\u003e (p =\u003e p.id),\n\n  // return value\n  Team ([byIds])\n]);\n\nupdateTeamById ({ data: { name: \"Updated Team\" }, id: 86 })\n  .then (console.log);\n\n// [{\n//   id: 84,\n//   name: \"Updated Team\",\n//   active: true,\n//   leagueId: 1\n// }];\n```\n\n### Delete\n```ts\nimport refql from \"./refql\";\n\nconst { Team } = refql.tables.public;\n\n// all team's fields will be returned\nconst deleteTeam = Team.delete ([\n  id.eq\u003c{ id: number }\u003e (p =\u003e p.id)\n]);\n\ndeleteTeam ({ id: 84 })\n  .then (console.log);\n\n// [{\n//   id: 84,\n//   name: \"Updated Team\",\n//   active: true,\n//   leagueId: 1\n// }];\n```\n\n## SQLTag\nIf something can't be done by using the functions provided by RefQL, use `sql`.\n\n```ts\nimport { NumberProp } from \"refql\";\nimport refql from \"./refql\";\n\nconst { sql, tables } = refql;\n\nconst { Player } = tables.public;\n\nconst goalCount = NumberProp (\"goalCount\", sql`\n  select count(*) from goal\n  where goal.player_id = player.id\n`);\n\nconst readTopScorers = Player ([\n  \"id\",\n  \"firstName\",\n  \"lastName\",\n  goalCount,\n  sql`\n    and (\n      select count(*) from goal\n      where goal.player_id = player.id\n    ) \u003e 15\n  `\n]);\n\nreadTopScorers ().then (console.log);\n\n// [\n//   { id: 44, firstName: \"Lester\", lastName: \"Rhodes\", goalCount: 16 },\n//   { id: 373, firstName: \"Lucinda\", lastName: \"Moss\", goalCount: 17 }\n// ];\n\n```\n\n### Raw\nWith the Raw data type it's possible to inject values as raw text into the query.\n\n```ts\nimport { Raw } from \"refql\";\nimport refql from \"./refql\";\n\nconst { sql, tables } = refql;\n\nconst { Player } = tables.public;\n\n// dynamic properties\nconst idField = \"id\";\nconst bdField = \"birthday\";\n\nconst readPlayerById = sql\u003c{ id: number }\u003e`\n  select id, last_name, age (${Raw (bdField)})::text\n  from ${Player} where ${Raw (idField)} = ${p =\u003e p.id}\n`;\n\n// query: select id, last_name, age (birthday)::text from player where id = $1\n// values: [1]\n\nreadPlayerById ({ id: 1 }).then (console.log);\n\n// [ { id: 1, last_name: \"Hubbard\", age: \"26 years 1 mon 15 days\" } ];\n```\n\n### Values\nUseful when you want to create dynamic queries, such as inserts or queries with the `in` operator.\n\n```ts\nimport { Values } from \"refql\";\nimport refql from \"./refql\";\n\nconst { sql, tables } = refql;\n\nconst { Player } = tables.public;\n\n// select id, last_name from player where id in ($1, $2, $3)\nconst selectPlayers = sql\u003c{ ids: number[]}\u003e`\n  select id, last_name\n  from ${Player}\n  where id in ${Values (p =\u003e p.ids)}\n`;\n\nselectPlayers ({ ids: [1, 2, 3] }).then (console.log);\n\n// [\n//   { id: 1, last_name: \"Hubbard\" },\n//   { id: 2, last_name: \"Mendez\" },\n//   { id: 3, last_name: \"Kubo\" }\n// ];\n\n```\n\n### Values2D\nUseful for batch inserts.\n\n```ts\nimport { Raw, Values, Values2D } from \"refql\";\nimport refql from \"./refql\";\n\nconst { sql, tables } = refql;\n\nconst { Player } = tables.public;\n\ninterface Player {\n  first_name: string;\n  last_name: string;\n}\n\nconst insertBatch = sql\u003c{ fields: (keyof Player)[]; data: Player[] }, Player[]\u003e`\n  insert into ${Player} (${Raw (p =\u003e p.fields.join (\", \"))})\n  values ${Values2D (p =\u003e p.data.map (x =\u003e p.fields.map (f =\u003e x[f])))}\n  returning *\n`;\n\ninsertBatch ({\n  fields: [\"first_name\", \"last_name\"],\n  data: [\n    { first_name: \"John\", last_name: \"Doe\" },\n    { first_name: \"Jane\", last_name: \"Doe\" },\n    { first_name: \"Jimmy\", last_name: \"Doe\" }\n  ]\n}).then (console.log);\n\n// [\n//   { id: 1020, first_name: \"John\", last_name: \"Doe\" },\n//   { id: 1021, first_name: \"Jane\", last_name: \"Doe\" },\n//   { id: 1022, first_name: \"Jimmy\", last_name: \"Doe\" }\n// ];\n```","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftureluren%2Frefql","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftureluren%2Frefql","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftureluren%2Frefql/lists"}