{"id":15658354,"url":"https://github.com/fredx87/effect-kysely","last_synced_at":"2025-05-05T03:44:10.006Z","repository":{"id":221768098,"uuid":"741609806","full_name":"Fredx87/effect-kysely","owner":"Fredx87","description":"kysely adapter for effect","archived":false,"fork":false,"pushed_at":"2024-04-21T19:45:47.000Z","size":116,"stargazers_count":24,"open_issues_count":3,"forks_count":0,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-05-05T03:44:00.183Z","etag":null,"topics":["effect","kysely","query","schema","sql"],"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/Fredx87.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","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-01-10T18:43:15.000Z","updated_at":"2025-03-24T06:32:51.000Z","dependencies_parsed_at":null,"dependency_job_id":"4147a62e-b8b3-4c8a-9b93-7153bf9ebb40","html_url":"https://github.com/Fredx87/effect-kysely","commit_stats":null,"previous_names":["fredx87/effect-kysely"],"tags_count":3,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Fredx87%2Feffect-kysely","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Fredx87%2Feffect-kysely/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Fredx87%2Feffect-kysely/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Fredx87%2Feffect-kysely/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Fredx87","download_url":"https://codeload.github.com/Fredx87/effect-kysely/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252436240,"owners_count":21747467,"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":["effect","kysely","query","schema","sql"],"created_at":"2024-10-03T13:12:04.025Z","updated_at":"2025-05-05T03:44:09.989Z","avatar_url":"https://github.com/Fredx87.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# effect-kysely\n\nIntegrate [kysely](https://kysely.dev/) with [effect](https://www.effect.website/). Define your database tables with `@effect/schema` and use `effect-kysely` to query them with encoding and decoding support, or just use `kysely` as a query builder for [sqlfx](https://github.com/tim-smart/sqlfx).\n\n⚠️ **Warning: This library is still in development and the API is subject to change.**\n\nThis library is currently published only as an ES module.\n\n## Getting started\n\nInstall `effect-kysely`\n\n```sh\nnpm install effect-kysely\n```\n\n```sh\nyarn add effect-kysely\n```\n\n```sh\npnpm add effect-kysely\n```\n\nInstall all peer dependencies if not already installed\n\n```sh\nnpm install kysely effect @effect/schema\n```\n\n```sh\nyarn add kysely effect @effect/schema\n```\n\n```sh\npnpm add kysely effect @effect/schema\n```\n\n## Define your database tables\n\n`effect-kysely` provides some utility functions to define your database tables with `@effect/schema`. As in kysely, you can define a schema specifying a different type for select, insert, and update operations.\n\n```ts\nimport * as S from \"@effect/schema/Schema\";\nimport { columnType } from \"effect-kysely/Schema.js\";\n\nconst TodoId = S.number.pipe(S.brand(\"TodoId\"));\n\nconst BooleanFromNumber = S.transform(\n  S.number,\n  S.boolean,\n  (n) =\u003e (n === 1 ? true : false),\n  (b) =\u003e (b ? 1 : 0),\n);\n\nconst _Todo = S.struct({\n  // as in kysely, you can define a schema specifying a different type for select, insert, and update operations\n  id: columnType(TodoId, S.never, S.never),\n  content: S.string,\n  completed: BooleanFromNumber,\n  user_id: S.number,\n  created_at: columnType(S.DateFromString, S.never, S.never),\n  updated_at: columnType(S.DateFromString, S.never, S.DateFromString),\n});\n```\n\nAt the moment, `effect-kysely` provides only the `columnType` and `generated` functions to define different schemas for a column. They have the same meaning as in kysely.\n\n**Note:** A schema that uses these helpers is not meant to be used directly, but it can be used to derive different schemas for select, insert and update operations.\nIf you try to decode/encode something with this schema, you will get an error.\n\n### Derive a static type to be used with kysely\n\nYou can derive a static type to be used with kysely from a schema using the `S.Schema.Encoded` utility. The schema can be used to decode _from_ the database and encode data _to_ the database, so the type used with kysely is the the schema `Encoded` type.\n\n```ts\n/*\ntype TodoTable = {\n    readonly id: ColumnType\u003cnumber, never, never\u003e;\n    readonly content: string;\n    readonly completed: number;\n    readonly user_id: number;\n    readonly created_at: ColumnType\u003cstring, never, never\u003e;\n    readonly updated_at: ColumnType\u003cstring, never, string\u003e;\n}\n*/\ntype TodoTable = S.Schema.Encoded\u003ctypeof _Todo\u003e;\n```\n\n### Derive select, insert and update schemas\n\nYou can derive the select, insert and update schemas from a schema using the `getSchemas` function. It returns an object with the `Selectable`, `Insertable`, and `Updateable` schemas.\n\n```ts\nimport { getSchemas } from \"effect-kysely/Schema.js\";\n\n/*\nTodo.Selectable has id, content, completed, user_id, created_at, updated_at\nTodo.Insertable has content, completed, user_id\nTodo.Updateable has content, completed, user_id, updated_at\n*/\nconst Todo = getSchemas(_Todo);\n```\n\nYou can also derive static types for the different schemas using the `GetTypes` utility.\n\n```ts\nimport { GetTypes } from \"effect-kysely/Schema.js\";\n\n/*\nTodo[\"Selectable\"] = S.Schema.Type\u003cTodo.Selectable\u003e\nTodo[\"Insertable\"] = S.Schema.Type\u003cTodo.Insertable\u003e\nTodo[\"Updateable\"] = S.Schema.Type\u003cTodo.Updateable\u003e\n*/\ntype Todo = GetTypes\u003ctypeof Todo\u003e;\n```\n\n### Define database tables and database service\n\nDefine your database tables to be used with kysely and a tag to be used as an effect service:\n\n```ts\nimport { Context } from \"effect\";\n\ninterface DbTables {\n  todo: TodoTable;\n}\n\nclass DbTag extends Context.Tag(\"DbTag\")\u003cDbTag, Kysely\u003cDbTables\u003e\u003e() {}\n```\n\n## Query your database with kysely\n\nYou can now create queries using `effect-kysely`, with encoding and decoding support.\n\n### withEncoder\n\nIf you need to create a query encoding some data, you can use the `withEncoder` function:\n\n```ts\nimport { Effect } from \"effect\";\nimport { withEncoder } from \"effect-kysely/Query.js\";\n\nconst program = Effect.gen(function* (_) {\n  const db = yield* _(DbTag);\n\n  const insertQuery = withEncoder({\n    encoder: Todo.Insertable,\n    query: (todo) =\u003e db.insertInto(\"todo\").values(todo).executeTakeFirstOrThrow(),\n  });\n\n  const result = yield* _(insertQuery({ content: \"Buy milk\", completed: false, user_id: 1 }));\n\n  return result;\n});\n\nconst DbLive = new Kysely\u003cDbTables\u003e({ dialect: ... });\n\nconst runnable = program.pipe(Effect.provideService(DbTag, DbLive));\n```\n\nThe value passed to the `insertQuery` function will be encoded using the `Todo.Insertable` schema (in this example, completed is encoded as a number). Kysely will type-check that the encoded value passed to the query is compatible with the `Insertable` static type defined for the table.\n\nIn this case, `result` will be an `InsertResult` type from `kysely`, and we are not interested in decoding it.\n\n### withDecoder\n\nIf you need to create a query decoding the result, you can use the `withDecoder` function:\n\n```ts\nconst selectAllTodos = withDecoder({\n  decoder: S.array(Todo.Selectable),\n  query: () =\u003e db.selectFrom(\"todo\").selectAll().execute(),\n});\n\nconst todos = yield * _(selectAllTodos());\n```\n\nIn this case, the query does not take any parameter and we don't need an encoder. The result of the query will be decoded using the provided `decoder` schema (`id` is decoded as `TodoId`, `completed` is decoded as a boolean, `created_at` and `updated_at` are decoded as dates). Kysely generates a type for the result of the query, and `withDecoder` checks that the input schema of the decoder is compatible with the query result.\n\n### withCodec\n\nIf you need to create a query encoding some data and decoding the result, you can use the `withCodec` function:\n\n```ts\nconst insertTodo = withCodec({\n  encoder: Todo.Insertable,\n  decoder: S.struct({ id: TodoId }),\n  query: (todo) =\u003e\n    db\n      .insertInto(\"todo\")\n      .values(todo)\n      .returning(\"id\")\n      .executeTakeFirstOrThrow(),\n});\n\nconst { id } =\n  yield * _(insertTodo({ content: \"Buy milk\", completed: false, user_id: 1 }));\n```\n\n### Errors\n\nThe effect returned by a query execution can fail with different errors:\n\n- `QueryParseError`, if the encoding or decoding fails\n- `QueryError`, if the query execution fails. It contains the error message returned by Kysely.\n- `NotFoundError`, if you used `executeTakeFirstOrThrow()` and the query execution returns no result\n\n### Transactions\n\n`effect-kysely` doesn't provide a specific way to handle transactions. Since the query passed to `withEncoder`, `withDecoder` or `withCodec` is just a function that returns a Promise, you can write a query with a transaction using the method provided by Kysely.\n\n```ts\nconst insertTodos = withEncoder({\n  encoder: S.tuple(Todo.Insertable, Todo.Insertable),\n  query: ([todo1, todo2]) =\u003e\n    db.transaction().execute(async (trx) =\u003e {\n      await trx.insertInto(\"todo\").values(todo1).executeTakeFirstOrThrow();\n\n      await trx.insertInto(\"todo\").values(todo2).executeTakeFirstOrThrow();\n    }),\n});\n```\n\n## Use kysely as a query builder for sqlfx\n\nYou need to:\n\n- Define your database tables as described above\n- create a `sqlfx` client\n- create a [cold Kysely instance](https://kysely.dev/docs/recipes/splitting-query-building-and-execution#cold-kysely-instances)\n\nAt this point you can use `createQuery` from `effect-kysely/sqlfx.js` to create a query using `kysely` as a query builder,\npassing the `sqlfx` client and a compilable `kysely` query.\n\n```ts\nimport { Config, Context, Effect } from \"effect\";\nimport {\n  DummyDriver,\n  Kysely,\n  SqliteAdapter,\n  SqliteIntrospector,\n  SqliteQueryCompiler,\n} from \"kysely\";\nimport * as Sql from \"@sqlfx/sqlite/node\";\nimport { createQuery } from \"effect-kysely/sqlfx.js\";\n\nconst program = Effect.gen(function* (_) {\n  const db = yield* _(DbTag);\n  const sql = yield* _(Sql.tag);\n\n  const InsertTodo = sql.resolver(\"InsertTodo\", {\n    request: Todo.Insertable,\n    result: S.struct({ id: TodoId }),\n    run: (todo) =\u003e\n      createQuery(sql, db.insertInto(\"todo\").values(todo).returning(\"id\")),\n  });\n\n  const GetTodoById = sql.resolverId(\"GetTodoById\", {\n    id: S.number,\n    result: Todo.Selectable,\n    resultId: (_) =\u003e _.id,\n    run: (ids) =\u003e\n      createQuery(\n        sql,\n        db.selectFrom(\"todo\").selectAll().where(\"id\", \"in\", ids),\n      ),\n  });\n\n  const insertedTodos = yield* _(\n    Effect.all(\n      [\n        InsertTodo.execute({\n          content: \"user1 todo1\",\n          completed: false,\n          user_id: 1,\n        }),\n        InsertTodo.execute({\n          content: \"user2 todo1\",\n          completed: false,\n          user_id: 2,\n        }),\n      ],\n      { batching: true },\n    ),\n  );\n\n  const todoIds = insertedTodos.map((t) =\u003e t.id);\n\n  const res = yield* _(\n    Effect.all(todoIds.map(GetTodoById.execute), { batching: true }),\n  );\n\n  return res;\n});\n\nconst DbLive = new Kysely\u003cDbTables\u003e({\n  dialect: {\n    createAdapter: () =\u003e new SqliteAdapter(),\n    createDriver: () =\u003e new DummyDriver(),\n    createIntrospector: (db) =\u003e new SqliteIntrospector(db),\n    createQueryCompiler: () =\u003e new SqliteQueryCompiler(),\n  },\n});\n\nconst SqlLive = Sql.makeLayer({\n  filename: Config.succeed(\"example.db\"),\n});\n\nconst runnable = program.pipe(\n  Effect.provideService(DbTag, DbLive),\n  Effect.provide(SqlLive),\n);\n```\n\n## FAQ\n\n### What is the difference between using only this library and using it with `sqlfx`?\n\nIf you use only `effect-kysely`:\n\n- You can use any database that has a [Kysely dialect](https://kysely.dev/docs/dialects) available\n- The results of the queries are type-checked using the schemas you defined\n- There is no support for batching and caching\n\nIf you use `effect-kysely` with `sqlfx`:\n\n- You can use batching and caching\n- You can use only the databases supported by `sqlfx`\n- The results of the queries are not type-checked using the schemas you defined\n\n## Examples\n\nYou can find more examples in the `examples` folder.\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffredx87%2Feffect-kysely","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffredx87%2Feffect-kysely","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffredx87%2Feffect-kysely/lists"}