0.37.10 • Published 12 months ago

@effect/sql v0.37.10

Weekly downloads
-
License
MIT
Repository
github
Last release
12 months ago

Effect SQL

A SQL toolkit for Effect.

Basic example

import { Effect, Struct, pipe } from "effect"
import { PgClient } from "@effect/sql-pg"
import { SqlClient } from "@effect/sql"

const SqlLive = PgClient.layer({
  database: "effect_pg_dev"
})

const program = Effect.gen(function* () {
  const sql = yield* SqlClient.SqlClient

  const people = yield* sql<{
    readonly id: number
    readonly name: string
  }>`SELECT id, name FROM people`

  yield* Effect.log(`Got ${people.length} results!`)
})

pipe(program, Effect.provide(SqlLive), Effect.runPromise)

Alternatively, you can also create the SqlLive layer using the PgClient.layerConfig constructor.

import { Config } from "effect"
import { PgClient } from "@effect/sql-pg"

const SqlLive = PgClient.layerConfig({
  database: Config.string("DATABASE")
})

Migrating from sqlfx

If you are coming from the sqlfx package, here are some differences that should be noted:

All the modules are now re-exported from the top level for easy access

For example, to create the client Layer, instead of:

import { Config } from "effect"
import { PgClient } from "@sqlfx/pg"

const SqlLive = PgClient.makeLayer({
  database: Config.succeed("effect_pg_dev")
})

You now do:

import { PgClient } from "@effect/sql-pg"

const SqlLive = PgClient.layer({
  database: "effect_pg_dev"
})

The default table name for migrations has changed

To continue using your sqlfx migrations table, you can setup your migrator Layer as below:

import { PgMigrator } from "@effect/sql-pg"

const MigratorLive = Layer.provide(
  PgMigrator.layer({
    loader: PgMigrator.fromFileSystem(
      fileURLToPath(new URL("migrations", import.meta.url))
    ),
    table: "sqlfx_migrations"
  }),
  SqlLive
)

Or you can rename the sqlfx_migrations table to effect_sql_migrations.

The resolver & schema apis have moved

  • sql.resolver -> SqlResolver.ordered
  • sql.resolverVoid -> SqlResolver.void
  • sql.resolverId -> SqlResolver.findById
  • sql.resolverIdMany -> SqlResolver.grouped
  • sql.resolverSingle* has been removed in favour of using the effect/Cache module with the schema apis
  • sql.schema -> SqlSchema.findAll
  • sql.schemaSingle -> SqlSchema.single
  • sql.schemaSingleOption -> SqlSchema.findOne
  • sql.schemaVoid -> SqlSchema.void

The array helper has moved

In sqlfx you could pass an array to the sql(array) function to pass an list of items to a SQL IN clause. Now you have to use sql.in(array).

INSERT resolver

import { Effect, Schema, pipe } from "effect"
import { SqlResolver, SqlClient } from "@effect/sql"

class Person extends Schema.Class<Person>("Person")({
  id: Schema.Number,
  name: Schema.String,
  createdAt: Schema.DateFromSelf,
  updatedAt: Schema.DateFromSelf
}) {}

const InsertPersonSchema = Schema.Struct(
  Struct.omit(Person.fields, "id", "createdAt", "updatedAt")
)

export const makePersonService = Effect.gen(function* () {
  const sql = yield* SqlClient.SqlClient

  const InsertPerson = yield* SqlResolver.ordered("InsertPerson", {
    Request: InsertPersonSchema,
    Result: Person,
    execute: (requests) =>
      sql`
        INSERT INTO people
        ${sql.insert(requests)}
        RETURNING people.*
      `
  })

  const insert = InsertPerson.execute

  return { insert }
})

SELECT resolver

import { Effect, Schema, pipe } from "effect"
import { SqlResolver, SqlClient } from "@effect/sql"

class Person extends Schema.Class<Person>("Person")({
  id: Schema.Number,
  name: Schema.String,
  createdAt: Schema.DateFromSelf,
  updatedAt: Schema.DateFromSelf
}) {}

export const makePersonService = Effect.gen(function* () {
  const sql = yield* SqlClient.SqlClient

  const GetById = yield* SqlResolver.findById("GetPersonById", {
    Id: Schema.Number,
    Result: Person,
    ResultId: (_) => _.id,
    execute: (ids) => sql`SELECT * FROM people WHERE ${sql.in("id", ids)}`
  })

  const getById = (id: number) =>
    Effect.withRequestCaching("on")(GetById.execute(id))

  return { getById }
})

Building queries

Safe interpolation

import { Effect } from "effect"
import { SqlClient } from "@effect/sql"

export const make = (limit: number) =>
  Effect.gen(function* () {
    const sql = yield* SqlClient.SqlClient

    const statement = sql`SELECT * FROM people LIMIT ${limit}`
    // e.g. SELECT * FROM people LIMIT ?
  })

Identifiers

import { Effect } from "effect"
import { SqlClient } from "@effect/sql"

const table = "people"

export const make = (limit: number) =>
  Effect.gen(function* () {
    const sql = yield* SqlClient.SqlClient

    const statement = sql`SELECT * FROM ${sql(table)} LIMIT ${limit}`
    // e.g. SELECT * FROM "people" LIMIT ?
  })

Unsafe interpolation

import { Effect } from "effect"
import { SqlClient } from "@effect/sql"

type OrderBy = "id" | "created_at" | "updated_at"
type SortOrder = "ASC" | "DESC"

export const make = (orderBy: OrderBy, sortOrder: SortOrder) =>
  Effect.gen(function* () {
    const sql = yield* SqlClient.SqlClient

    const statement = sql`SELECT * FROM people ORDER BY ${sql(orderBy)} ${sql.unsafe(sortOrder)}`
    // e.g. SELECT * FROM people ORDER BY `id` ASC
  })

Where clause combinators

AND

import { Effect } from "effect"
import { SqlClient } from "@effect/sql"

export const make = (names: string[], cursor: string) =>
  Effect.gen(function* () {
    const sql = yield* SqlClient.SqlClient

    const statement = sql`SELECT * FROM people WHERE ${sql.and([
      sql.in("name", names),
      sql`created_at < ${cursor}`
    ])}`
    // SELECT * FROM people WHERE ("name" IN (?,?,?) AND created_at < ?)
  })

OR

import { Effect } from "effect"
import { SqlClient } from "@effect/sql"

export const make = (names: string[], cursor: Date) =>
  Effect.gen(function* () {
    const sql = yield* SqlClient.SqlClient

    const statement = sql`SELECT * FROM people WHERE ${sql.or([
      sql.in("name", names),
      sql`created_at < ${cursor}`
    ])}`
    // SELECT * FROM people WHERE ("name" IN (?,?,?) OR created_at < ?)
  })

Mixed

import { Effect } from "effect"
import { SqlClient } from "@effect/sql"

export const make = (names: string[], afterCursor: Date, beforeCursor: Date) =>
  Effect.gen(function* () {
    const sql = yield* SqlClient.SqlClient

    const statement = sql`SELECT * FROM people WHERE ${sql.or([
      sql.in("name", names),
      sql.and([`created_at > ${afterCursor}`, `created_at < ${beforeCursor}`])
    ])}`
    // SELECT * FROM people WHERE ("name" IN (?,?,?) OR (created_at > ? AND created_at < ?))
  })

Migrations

A Migrator module is provided, for running migrations.

Migrations are forward-only, and are written in Typescript as Effect's.

Here is an example migration:

// src/migrations/0001_add_users.ts

import { Effect } from "effect"
import { SqlClient } from "@effect/sql"

export default Effect.flatMap(
  SqlClient.SqlClient,
  (sql) => sql`
    CREATE TABLE users (
      id serial PRIMARY KEY,
      name varchar(255) NOT NULL,
      created_at TIMESTAMP NOT NULL DEFAULT NOW(),
      updated_at TIMESTAMP NOT NULL DEFAULT NOW()
    )
  `
)

To run your migrations:

// src/main.ts

import { Effect, Layer, pipe } from "effect"
import { NodeContext, NodeRuntime } from "@effect/platform-node"
import { PgClient, PgMigrator } from "@effect/sql-pg"
import { fileURLToPath } from "node:url"

const program = Effect.gen(function* () {
  // ...
})

const SqlLive = PgClient.layer({
  database: "example_database"
})

const MigratorLive = PgMigrator.layer({
  loader: PgMigrator.fromFileSystem(
    fileURLToPath(new URL("migrations", import.meta.url))
  ),
  // Where to put the `_schema.sql` file
  schemaDirectory: "src/migrations"
}).pipe(Layer.provide(SqlLive))

const EnvLive = Layer.mergeAll(SqlLive, MigratorLive).pipe(
  Layer.provide(NodeContext.layer)
)

pipe(program, Effect.provide(EnvLive), NodeRuntime.runMain)
0.20.1

2 years ago

0.20.0

2 years ago

0.32.4

1 year ago

0.32.3

1 year ago

0.32.2

1 year ago

0.32.1

1 year ago

0.33.21

1 year ago

0.33.20

1 year ago

0.32.0

1 year ago

0.29.0

1 year ago

0.21.3

2 years ago

0.21.2

2 years ago

0.21.1

2 years ago

0.29.1

1 year ago

0.33.10

1 year ago

0.21.0

2 years ago

0.33.7

1 year ago

0.33.6

1 year ago

0.33.5

1 year ago

0.33.4

1 year ago

0.33.3

1 year ago

0.33.2

1 year ago

0.33.1

1 year ago

0.33.19

1 year ago

0.33.0

1 year ago

0.33.18

1 year ago

0.33.17

1 year ago

0.33.16

1 year ago

0.33.15

1 year ago

0.33.14

1 year ago

0.33.13

1 year ago

0.33.12

1 year ago

0.33.9

1 year ago

0.33.11

1 year ago

0.33.8

1 year ago

0.22.7

1 year ago

0.22.6

1 year ago

0.22.5

1 year ago

0.22.4

2 years ago

0.22.3

2 years ago

0.22.2

2 years ago

0.22.1

2 years ago

0.22.0

2 years ago

0.30.7

1 year ago

0.30.6

1 year ago

0.30.4

1 year ago

0.30.3

1 year ago

0.30.2

1 year ago

0.30.1

1 year ago

0.30.0

1 year ago

0.27.0

1 year ago

0.31.1

1 year ago

0.31.0

1 year ago

0.28.1

1 year ago

0.20.9

2 years ago

0.28.0

1 year ago

0.20.8

2 years ago

0.20.7

2 years ago

0.20.6

2 years ago

0.20.5

2 years ago

0.20.4

2 years ago

0.20.3

2 years ago

0.20.2

2 years ago

0.28.4

1 year ago

0.28.3

1 year ago

0.28.2

1 year ago

0.36.0

1 year ago

0.25.2

1 year ago

0.25.1

1 year ago

0.25.0

1 year ago

0.37.3

1 year ago

0.37.2

1 year ago

0.37.1

1 year ago

0.37.0

1 year ago

0.37.9

1 year ago

0.37.8

1 year ago

0.37.7

1 year ago

0.37.6

1 year ago

0.37.5

1 year ago

0.37.4

1 year ago

0.26.1

1 year ago

0.26.0

1 year ago

0.18.15

2 years ago

0.18.16

2 years ago

0.19.0

2 years ago

0.34.1

1 year ago

0.34.0

1 year ago

0.23.3

1 year ago

0.23.2

1 year ago

0.23.1

1 year ago

0.23.0

1 year ago

0.37.10

12 months ago

0.35.5

1 year ago

0.35.4

1 year ago

0.35.3

1 year ago

0.35.2

1 year ago

0.35.1

1 year ago

0.35.0

1 year ago

0.35.8

1 year ago

0.35.7

1 year ago

0.35.6

1 year ago

0.20.11

2 years ago

0.20.12

2 years ago

0.20.10

2 years ago

0.24.3

1 year ago

0.24.2

1 year ago

0.24.1

1 year ago

0.24.0

1 year ago

0.18.11

2 years ago

0.18.10

2 years ago

0.18.13

2 years ago

0.18.12

2 years ago

0.18.14

2 years ago

0.18.9

2 years ago

0.18.8

2 years ago

0.18.7

2 years ago

0.18.4

2 years ago

0.18.5

2 years ago

0.18.6

2 years ago

0.18.1

2 years ago

0.18.3

2 years ago

0.18.0

2 years ago

0.17.0

2 years ago

0.16.6

2 years ago

0.16.5

2 years ago

0.8.5

2 years ago

0.4.9

2 years ago

0.8.4

2 years ago

0.4.8

2 years ago

0.8.7

2 years ago

0.8.6

2 years ago

0.2.17

2 years ago

0.13.0

2 years ago

0.13.1

2 years ago

0.13.2

2 years ago

0.13.3

2 years ago

0.13.4

2 years ago

0.2.16

2 years ago

0.2.15

2 years ago

0.3.0

2 years ago

0.3.6

2 years ago

0.3.5

2 years ago

0.3.8

2 years ago

0.3.7

2 years ago

0.3.2

2 years ago

0.3.1

2 years ago

0.7.0

2 years ago

0.3.4

2 years ago

0.3.3

2 years ago

0.4.20

2 years ago

0.9.7

2 years ago

0.4.21

2 years ago

0.9.4

2 years ago

0.9.3

2 years ago

0.9.6

2 years ago

0.9.5

2 years ago

0.4.26

2 years ago

0.4.27

2 years ago

0.4.24

2 years ago

0.4.25

2 years ago

0.4.22

2 years ago

0.4.23

2 years ago

0.4.19

2 years ago

0.10.1

2 years ago

0.10.2

2 years ago

0.10.3

2 years ago

0.14.0

2 years ago

0.10.4

2 years ago

0.14.1

2 years ago

0.4.10

2 years ago

0.10.0

2 years ago

0.4.17

2 years ago

0.4.18

2 years ago

0.4.15

2 years ago

0.4.16

2 years ago

0.4.13

2 years ago

0.4.14

2 years ago

0.4.11

2 years ago

0.4.12

2 years ago

0.8.1

2 years ago

0.4.5

2 years ago

0.8.0

2 years ago

0.4.4

2 years ago

0.8.3

2 years ago

0.4.7

2 years ago

0.8.2

2 years ago

0.4.6

2 years ago

0.4.1

2 years ago

0.4.0

2 years ago

0.4.3

2 years ago

0.4.2

2 years ago

0.11.0

2 years ago

0.11.1

2 years ago

0.11.2

2 years ago

0.11.3

2 years ago

0.15.0

2 years ago

0.15.1

2 years ago

0.3.18

2 years ago

0.9.0

2 years ago

0.5.3

2 years ago

0.9.2

2 years ago

0.9.1

2 years ago

0.5.0

2 years ago

0.5.2

2 years ago

0.5.1

2 years ago

0.3.9

2 years ago

0.3.17

2 years ago

0.3.16

2 years ago

0.3.15

2 years ago

0.3.14

2 years ago

0.3.13

2 years ago

0.3.12

2 years ago

0.3.11

2 years ago

0.3.10

2 years ago

0.16.3

2 years ago

0.16.4

2 years ago

0.12.0

2 years ago

0.12.1

2 years ago

0.12.2

2 years ago

0.12.3

2 years ago

0.16.0

2 years ago

0.12.4

2 years ago

0.16.1

2 years ago

0.12.5

2 years ago

0.16.2

2 years ago

0.12.6

2 years ago

0.6.3

2 years ago

0.6.2

2 years ago

0.6.1

2 years ago

0.6.0

2 years ago

0.2.14

2 years ago

0.2.13

2 years ago

0.2.12

2 years ago

0.2.11

2 years ago

0.2.10

2 years ago

0.2.9

2 years ago

0.2.8

2 years ago

0.2.7

2 years ago

0.2.6

2 years ago

0.2.5

2 years ago

0.2.4

2 years ago

0.2.3

2 years ago

0.2.2

2 years ago

0.2.1

2 years ago

0.2.0

2 years ago

0.1.17

2 years ago

0.1.16

2 years ago

0.1.15

2 years ago

0.1.13

2 years ago

0.1.14

2 years ago

0.1.11

2 years ago

0.1.12

2 years ago

0.1.10

2 years ago

0.1.9

2 years ago

0.1.8

2 years ago

0.1.7

2 years ago

0.1.4

2 years ago

0.1.6

2 years ago

0.1.5

2 years ago

0.1.2

2 years ago

0.1.1

2 years ago

0.1.3

2 years ago

0.1.0

2 years ago