{"id":17436200,"url":"https://github.com/a2lix/schemql","last_synced_at":"2026-04-02T01:45:23.017Z","repository":{"id":257827488,"uuid":"873138284","full_name":"a2lix/schemql","owner":"a2lix","description":"A lightweight TypeScript library that enhances your SQL workflow by combining raw SQL with targeted type safety and schema validation","archived":false,"fork":false,"pushed_at":"2024-10-17T21:00:10.000Z","size":48,"stargazers_count":4,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2024-10-19T12:07:01.853Z","etag":null,"topics":["autocomplete","database","query","schema","sql","type-safe","typescript","validation","zod"],"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/a2lix.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,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-10-15T17:00:25.000Z","updated_at":"2024-10-19T11:59:09.000Z","dependencies_parsed_at":"2024-10-19T08:54:15.646Z","dependency_job_id":null,"html_url":"https://github.com/a2lix/schemql","commit_stats":null,"previous_names":["a2lix/schemql"],"tags_count":7,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/a2lix%2Fschemql","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/a2lix%2Fschemql/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/a2lix%2Fschemql/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/a2lix%2Fschemql/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/a2lix","download_url":"https://codeload.github.com/a2lix/schemql/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":249191900,"owners_count":21227674,"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","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":["autocomplete","database","query","schema","sql","type-safe","typescript","validation","zod"],"created_at":"2024-10-17T10:06:40.803Z","updated_at":"2026-04-02T01:45:17.979Z","avatar_url":"https://github.com/a2lix.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![npm version](https://img.shields.io/npm/v/@a2lix/schemql.svg)](https://www.npmjs.com/package/@a2lix/schemql)\n[![npm downloads](https://img.shields.io/npm/dt/@a2lix/schemql.svg)](https://www.npmjs.com/package/@a2lix/schemql)\n[![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)\n![CI](https://github.com/a2lix/schemql/actions/workflows/ci.yml/badge.svg)\n\n# SchemQl\n\n**SchemQl** simplifies database interactions by allowing you to write advanced SQL queries that fully leverage the features of your DBMS, while providing type safety through the use of schemas and offering convenient execution methods.\n\n**Key features:**\n\n- **Database agnostic**: Compatible with any DBMS.\n- **SQL-first**: Write SQL with precise type checks on literals like tables, columns (JSON fields \u0026 some JSONPath included), and parameters.\n- **Flexible parameters** Supports single objects, arrays of objects, and asynchronous generators for parameters.\n- **Schema-agnostic**: Use any validation library implementing [Standard Schema](https://standardschema.dev/) (Zod, ArkType, Effect, etc.) to validate and parse parameters and query results.\n- **Iterative Execution** Process large datasets efficiently using asynchronous generators.\n\n\n![Screenshot](https://github.com/user-attachments/assets/86b1c3cd-2393-4914-b943-b249d6dad59a)\n\n## Installation\n\nTo install SchemQl, use:\n\n```bash\nnpm i @a2lix/schemql\n```\n\n## Usage\n\nHere's a basic example of how to use SchemQl:\n\n\u003cdetails\u003e\n\u003csummary\u003e1. Create your database schema and expose it with a DB interface\u003c/summary\u003e\n\u003cbr\u003e\nTip: Use your favorite AI to generate a schema from your SQL.\n\nIf using JSON data, leverage the built-in `parseJsonPreprocessor`.\n\n**With Zod:**\n```typescript\nimport { parseJsonPreprocessor } from '@a2lix/schemql'\nimport { z } from 'zod/v4'\n\nexport const zUserDb = z.object({\n  id: z.string(),\n  email: z.string(),\n  metadata: z.preprocess(\n    parseJsonPreprocessor,   // ! Zod handles JSON parsing for this JSON columns 'metadata'\n    z.object({\n      role: z.enum(['user', 'admin']).default('user'),\n    })\n  ),\n  created_at: z.int(),\n  disabled_at: z.int().nullable(),\n})\n\ntype UserDb = z.infer\u003ctypeof zUserDb\u003e\n```\n\n**With ArkType:**\n```typescript\nimport { type } from 'arktype'\n\nexport const userDb = type({\n  id: 'string',\n  email: 'string',\n  metadata: type(\"string.json.parse\").to({\n    role: \"'user' | 'admin' = 'user'\",\n  }),\n  created_at: 'number.epoch',\n  disabled_at: 'number.epoch | null',\n})\n\ntype UserDb = typeof userDb.infer\n```\n\n// ...\n\n```typescript\nexport interface DB {\n  users: UserDb\n  // ...other mappings\n}\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e2. Initialize your instance of SchemQl with the DB interface typing\u003c/summary\u003e\n\u003cbr\u003e\nExample with better-sqlite3 adapter.\n\n```typescript\nimport { SchemQl } from '@a2lix/schemql'\nimport { BetterSqlite3Adapter } from '@a2lix/schemql/adapters/better-sqlite3'\nimport type { DB } from '@/schema'\n\nconst schemQl = new SchemQl\u003cDB\u003e({\n  adapter: new BetterSqlite3Adapter('sqlite.db'),\n  stringifyObjectParams: true,   // Optional. Automatically stringify objects (useful for JSON)\n})\n```\n\u003c/details\u003e\n\n\u003cdetails open\u003e\n\u003csummary\u003e3. Use your instance of SchemQl with `.first()` / `.firstOrThrow()` / `.all()` / `.iterate()`\u003c/summary\u003e\n\u003cbr\u003e\nSimple use with resultSchema only and no SQL literal string\n\n```typescript\nconst allUsers = await schemQl.all({\n  resultSchema: zUserDb.array(),\n})(`\n  SELECT *\n  FROM users\n`)\n```\n\nMore advanced example\n\n```typescript\nconst firstUser = await schemQl.first({\n  params: { id: 'uuid-1' },\n  paramsSchema: zUserDb.pick({ id: true }),\n  resultSchema: z.object({ user_id: zUserDb.shape.id, length_id: z.number() }),\n})((s) =\u003e s.sql`\n  SELECT\n    ${'@users.id'} AS ${'$user_id'},\n    LENGTH(${'@users.id'}) AS ${'$length_id'}\n  FROM ${'@users'}\n  WHERE\n    ${'@users.id'} = ${':id'}\n`);\n\nconst allUsersLimit = await schemQl.all({\n  params: { limit: 10 },\n  resultSchema: zUserDb.array(),   // ! Note the array() use for .all() case\n})((s) =\u003e s.sql`\n  SELECT\n    ${'@users.*'}\n  FROM ${'@users'}\n  LIMIT ${':limit'}\n`)\n\nconst allUsersPaginated = await schemQl.all({\n  params: {\n    limit: data.query.limit + 1,\n    cursor: data.query.cursor,\n    dir: data.query.dir,\n  },\n  paramsSchema: zRequestQuery,\n  resultSchema: zUserDb.array(),   // ! Note the array() use for .all() case\n})((s) =\u003e s.sql`\n  SELECT\n    ${'@users.*'}\n  FROM ${'@users'}\n  ${s.sqlCond(\n    !!data.query.cursor,\n    s.sql`WHERE ${'@users.id'} ${s.sqlRaw(data.query.dir === 'next' ? '\u003e' : '\u003c')} ${':cursor'}`\n  )}\n  ORDER BY ${'@users.id'} ${s.sqlCond(data.query.dir === 'prev', 'DESC', 'ASC')}\n  LIMIT ${':limit'}\n`)\n```\n\nAutomatically stringify JSON params 'metadata' (by schemQl if enabled)\nand get parsed JSON metadata, as well (if your schema preprocess is set rightly)\n\n```typescript\nconst firstSession = await schemQl.firstOrThrow({\n  params: {\n    id: uuidv4(),\n    user_id: 'uuid-1',\n    metadata: {\n      account: 'credentials',\n    },\n    expiresAtAdd: 10000,\n  },\n  paramsSchema: zSessionDb.pick({ id: true, user_id: true, metadata: true }).and(z.object({\n    expiresAtAdd: z.number().int(),\n  })),\n  resultSchema: zSessionDb,\n})((s) =\u003e s.sql`\n  INSERT INTO\n    ${{ sessions: ['id', 'user_id', 'metadata', 'expires_at'] }}\n  VALUES\n    (\n      ${':id'}\n      , ${':user_id'}\n      , JSON(${':metadata'})\n      , STRFTIME('%s', 'now') + ${':expiresAtAdd'}\n    )\n  RETURNING *\n`)\n```\n\nHandle iteration when required\n\n```typescript\nconst iterResults = await schemQl.first({\n  params: [\n    { id: 'uuid-1' },\n    { id: 'uuid-2' }\n  ],\n  paramsSchema: zUserDb.pick({ id: true }).array(),  // ! Note the array() use when array of params\n  resultSchema: zUserDb,\n})((s) =\u003e s.sql`\n  SELECT *\n  FROM ${'@users'}\n  WHERE\n    ${'@users.id'} = ${':id'}\n`)\n\nconst iterResults = await schemQl.first({\n  params: function* () {\n    yield { id: 'uuid-1' }\n    yield { id: 'uuid-2' }\n  },\n  paramsSchema: zUserDb.pick({ id: true }),\n  resultSchema: zUserDb,\n})((s) =\u003e s.sql`\n  SELECT *\n  FROM ${'@users'}\n  WHERE\n    ${'@users.id'} = ${':id'}\n`)\n\nconst iterResults = await schemQl.iterate({\n  resultSchema: zUserDb,\n})((s) =\u003e s.sql`\n  SELECT *\n  FROM ${'@users'}\n  LIMIT 10\n`)\n```\n\u003c/details\u003e\n\n## Literal String SQL Helpers\n\n| **Helper Syntax**                  | **Raw SQL Result**           | **Description** |\n|:-----------------------------------|:-----------------------------|:----------------|\n| `${'@table1'}`                     | `table1`                     | **Table Selection**: Prefix `@` eases table selection/validation |\n| `${'@table1.col1'}`                | `table1.col1`                | **Column Selection**: Use `@` for table and column validation |\n| `${'@table1.col1-'}`               | `col1`                       | Final `-` excludes table name (useful if table is aliased) |\n| `${'@table1.col1 -\u003ejsonpath1'}`    | `table1.col1 -\u003e'jsonpath1'`  | **JSON Field Selection**: Use `-\u003e` for JSON paths |\n| `${'@table1.col1 -\u003e\u003ejsonpath1'}`   | `table1.col1 -\u003e\u003e'jsonpath1'` | JSON field (raw) with `-\u003e\u003e` syntax |\n| `${'@table1.col1 $.jsonpath1'}`    | `'$.jsonpath1'`              | JSONPath with `$` prefix |\n| `${'$resultCol1'}`                 | `resultCol1`                 | **Result Selection**: `$` prefix targets resultSchema fields |\n| `${':param1'}`                     | `:param1`                    | **Parameter Selection**: `:` prefix eases parameter validation |\n| `${{ table1: ['col1', 'col2'] }}`  | `table1 (col1, col2)`        | **Batch Column Selection**: Object syntax useful for INSERT |\n| `${s.sqlCond(1, 'ASC', 'DESC')}`   | `ASC`                        | **Conditional SQL**: `s.sqlCond` for conditional clauses |\n| `${s.sqlRaw(var)}`                 | `var`                        | **Raw SQL**: Use `s.sqlRaw` for unprocessed SQL fragments |\n\n\n## Contributing\n\nContributions are welcome! This library aims to remain lightweight and focused, so please keep PRs concise and aligned with this goal.\n\n## License\n\nThis project is licensed under the MIT License.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fa2lix%2Fschemql","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fa2lix%2Fschemql","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fa2lix%2Fschemql/lists"}