Skip to content

Commit

Permalink
Move sqlfx/sql into the @effect/sql package (#2104)
Browse files Browse the repository at this point in the history
Co-authored-by: Michael Arnaldi <michael.arnaldi@effectful.co>
Co-authored-by: Tim <hello@timsmart.co>
  • Loading branch information
3 people committed Apr 16, 2024
1 parent 25d74f8 commit 1499974
Show file tree
Hide file tree
Showing 147 changed files with 11,506 additions and 412 deletions.
12 changes: 12 additions & 0 deletions .changeset/early-wolves-whisper.md
@@ -0,0 +1,12 @@
---
"@effect/sql-sqlite-react-native": minor
"@effect/sql-sqlite-node": minor
"@effect/sql-sqlite-wasm": minor
"@effect/sql-sqlite-bun": minor
"@effect/sql-mysql2": minor
"@effect/sql-mssql": minor
"@effect/sql-pg": minor
"@effect/sql": minor
---

initial @effect/sql release
5 changes: 5 additions & 0 deletions .changeset/moody-candles-relax.md
@@ -0,0 +1,5 @@
---
"effect": patch
---

don't run resolver if there are no incomplete requests
12 changes: 12 additions & 0 deletions .changeset/serious-glasses-own.md
@@ -0,0 +1,12 @@
---
"effect": patch
---

add String casing transformation apis

- `snakeToCamel`
- `snakeToPascal`
- `snakeToKebab`
- `camelToSnake`
- `pascalToSnake`
- `kebabToSnake`
38 changes: 24 additions & 14 deletions .github/CODEOWNERS
Validating CODEOWNERS rules …
@@ -1,14 +1,24 @@
/packages/effect/ @mikearnaldi
/packages/cli/ @IMax153
/packages/experimental/ @tim-smart
/packages/opentelemetry/ @tim-smart
/packages/platform/ @tim-smart
/packages/platform-browser/ @tim-smart
/packages/platform-bun/ @tim-smart
/packages/platform-node/ @tim-smart
/packages/printer/ @IMax153
/packages/printer-ansi/ @IMax153
/packages/rpc/ @tim-smart
/packages/rpc-http/ @tim-smart
/packages/schema/ @gcanti
/packages/typeclass/ @gcanti
/packages/effect/ @mikearnaldi
/packages/cli/ @IMax153
/packages/experimental/ @tim-smart
/packages/opentelemetry/ @tim-smart
/packages/platform/ @tim-smart
/packages/platform-browser/ @tim-smart
/packages/platform-bun/ @tim-smart
/packages/platform-node/ @tim-smart
/packages/platform-node-shared/ @tim-smart
/packages/printer/ @IMax153
/packages/printer-ansi/ @IMax153
/packages/rpc/ @tim-smart
/packages/rpc-http/ @tim-smart
/packages/schema/ @gcanti
/packages/sql/ @tim-smart
/packages/sql-mssql/ @tim-smart
/packages/sql-mysql2/ @tim-smart
/packages/sql-pg/ @tim-smart
/packages/sql-sqlite-bun/ @tim-smart
/packages/sql-sqlite-node/ @tim-smart
/packages/sql-sqlite-react-native/ @tim-smart
/packages/sql-sqlite-wasm/ @tim-smart
/packages/typeclass/ @gcanti
/packages/vitest/ @mikearnaldi
2 changes: 1 addition & 1 deletion .github/actions/setup/action.yml
Expand Up @@ -18,4 +18,4 @@ runs:
node-version: ${{ inputs.node-version }}
- name: Install dependencies
shell: bash
run: pnpm install --ignore-scripts
run: pnpm install
2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -9,7 +9,7 @@
"clean": "node scripts/clean.mjs",
"codegen": "pnpm --recursive --parallel run codegen",
"build": "tsc -b tsconfig.build.json && pnpm --recursive --parallel run build",
"circular": "madge --extensions ts --circular --no-color --no-spinner packages/*/src",
"circular": "node scripts/circular.mjs",
"test": "vitest",
"coverage": "vitest --coverage",
"check": "tsc -b tsconfig.json",
Expand Down
43 changes: 43 additions & 0 deletions packages/effect/src/String.ts
Expand Up @@ -624,6 +624,49 @@ export const stripMarginWith: {
*/
export const stripMargin = (self: string): string => stripMarginWith(self, "|")

/**
* @since 2.0.0
*/
export const snakeToCamel = (self: string): string => {
let str = self[0]
for (let i = 1; i < self.length; i++) {
str += self[i] === "_" ? self[++i].toUpperCase() : self[i]
}
return str
}

/**
* @since 2.0.0
*/
export const snakeToPascal = (self: string): string => {
let str = self[0].toUpperCase()
for (let i = 1; i < self.length; i++) {
str += self[i] === "_" ? self[++i].toUpperCase() : self[i]
}
return str
}

/**
* @since 2.0.0
*/
export const snakeToKebab = (self: string): string => self.replace(/_/g, "-")

/**
* @since 2.0.0
*/
export const camelToSnake = (self: string): string => self.replace(/([A-Z])/g, "_$1").toLowerCase()

/**
* @since 2.0.0
*/
export const pascalToSnake = (self: string): string =>
(self.slice(0, 1) + self.slice(1).replace(/([A-Z])/g, "_$1")).toLowerCase()

/**
* @since 2.0.0
*/
export const kebabToSnake = (self: string): string => self.replace(/-/g, "_")

class LinesIterator implements IterableIterator<string> {
private index: number
private readonly length: number
Expand Down
36 changes: 18 additions & 18 deletions packages/effect/src/internal/dataSource.ts
Expand Up @@ -28,24 +28,24 @@ export const makeBatched = <A extends Request.Request<any, any>, R>(
run: (requests: Array<A>) => Effect.Effect<void, never, R>
): RequestResolver.RequestResolver<A, R> =>
new core.RequestResolverImpl<A, R>(
(requests) =>
requests.length > 1 ?
core.forEachSequentialDiscard(requests, (block) =>
invokeWithInterrupt(
run(
block
.filter((_) => !_.state.completed)
.map((_) => _.request)
),
block
)) :
(requests.length === 1 ?
run(
requests[0]
.filter((_) => !_.state.completed)
.map((_) => _.request)
) :
core.unit)
(requests) => {
if (requests.length > 1) {
return core.forEachSequentialDiscard(requests, (block) => {
const filtered = block.filter((_) => !_.state.completed).map((_) => _.request)
if (filtered.length === 0) {
return core.unit
}
return invokeWithInterrupt(run(filtered), block)
})
} else if (requests.length === 1) {
const filtered = requests[0].filter((_) => !_.state.completed).map((_) => _.request)
if (filtered.length === 0) {
return core.unit
}
return run(filtered)
}
return core.unit
}
)

/** @internal */
Expand Down
4 changes: 1 addition & 3 deletions packages/platform-node-shared/tsconfig.build.json
Expand Up @@ -16,8 +16,6 @@
"outDir": "build/esm",
"declarationDir": "build/dts",
"stripInternal": true,
"types": [
"node"
]
"types": ["node"]
}
}
4 changes: 2 additions & 2 deletions packages/platform/src/Command.ts
Expand Up @@ -122,8 +122,8 @@ export const isCommand: (u: unknown) => u is Command = internal.isCommand
* @category combinators
*/
export const env: {
(environment: Record<string, string>): (self: Command) => Command
(self: Command, environment: Record<string, string>): Command
(environment: Record<string, string | undefined>): (self: Command) => Command
(self: Command, environment: Record<string, string | undefined>): Command
} = internal.env

/**
Expand Down
16 changes: 11 additions & 5 deletions packages/platform/src/internal/command.ts
Expand Up @@ -21,15 +21,21 @@ export const isCommand = (u: unknown): u is Command.Command => typeof u === "obj

/** @internal */
export const env: {
(environment: Record<string, string>): (self: Command.Command) => Command.Command
(self: Command.Command, environment: Record<string, string>): Command.Command
(environment: Record<string, string | undefined>): (self: Command.Command) => Command.Command
(self: Command.Command, environment: Record<string, string | undefined>): Command.Command
} = dual<
(environment: Record<string, string>) => (self: Command.Command) => Command.Command,
(self: Command.Command, environment: Record<string, string>) => Command.Command
(environment: Record<string, string | undefined>) => (self: Command.Command) => Command.Command,
(self: Command.Command, environment: Record<string, string | undefined>) => Command.Command
>(2, (self, environment) => {
switch (self._tag) {
case "StandardCommand": {
return makeStandard({ ...self, env: HashMap.union(self.env, HashMap.fromIterable(Object.entries(environment))) })
return makeStandard({
...self,
env: HashMap.union(
self.env,
HashMap.fromIterable(Object.entries(environment).filter(([v]) => v !== undefined))
) as HashMap.HashMap<string, string>
})
}
case "PipedCommand": {
return pipeTo(env(self.left, environment), env(self.right, environment))
Expand Down
21 changes: 21 additions & 0 deletions packages/sql-mssql/LICENSE
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2023-present The Contributors

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
5 changes: 5 additions & 0 deletions packages/sql-mssql/README.md
@@ -0,0 +1,5 @@
# Effect SQL - Microsoft SQL Server

An @effect/sql implementation using the mssql `tedious` library.

See here for more information: https://github.com/Effect-TS/effect/tree/main/packages/sql
6 changes: 6 additions & 0 deletions packages/sql-mssql/docgen.json
@@ -0,0 +1,6 @@
{
"$schema": "../../node_modules/@effect/docgen/schema.json",
"exclude": [
"src/internal/**/*.ts"
]
}
22 changes: 22 additions & 0 deletions packages/sql-mssql/examples/docker-compose.yaml
@@ -0,0 +1,22 @@
services:
db:
environment:
ACCEPT_EULA: "Y"
SA_PASSWORD: "Sq1Fx_password"
# mssql server image isn't available for arm64 architecture, so we use azure-sql instead
# image: mcr.microsoft.com/azure-sql-edge:1.0.4
# If you really want to use MS SQL Server, uncomment the following line
image: mcr.microsoft.com/mssql/server
ports:
- 1433:1433
restart: always
healthcheck:
test:
[
"CMD-SHELL",
"/opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P Sq1Fx_password -Q 'SELECT 1' || exit 1",
]
interval: 10s
retries: 10
start_period: 10s
timeout: 3s
106 changes: 106 additions & 0 deletions packages/sql-mssql/examples/migrations.ts
@@ -0,0 +1,106 @@
import * as DevTools from "@effect/experimental/DevTools"
import { NodeFileSystem } from "@effect/platform-node"
import * as Sql from "@effect/sql-mssql"
import { Config, Effect, Layer, Logger, LogLevel, Secret, String } from "effect"
import { pipe } from "effect/Function"
import { fileURLToPath } from "node:url"

const peopleProcedure = pipe(
Sql.procedure.make("people_proc"),
Sql.procedure.param<string>()("name", Sql.types.VarChar),
Sql.procedure.withRows<{ readonly id: number; readonly name: string }>(),
Sql.procedure.compile
)

const program = Effect.gen(function*(_) {
const sql = yield* _(Sql.client.MssqlClient)

yield* _(
sql`
CREATE OR ALTER PROC people_proc
@name VARCHAR(255)
AS
BEGIN
SELECT * FROM people WHERE name = @name
END
`
)

// Insert
const [inserted] = yield* _(
sql`INSERT INTO ${sql("people")} ${
sql.insert({
name: "Tim",
createdAt: new Date()
})
}`
)
console.log(inserted)

console.log(
yield* _(
Effect.all(
[
sql`SELECT TOP 3 * FROM ${sql("people")}`,
sql`SELECT TOP 3 * FROM ${sql("people")}`.values,
sql`SELECT TOP 3 * FROM ${sql("people")}`.withoutTransform,
sql.call(peopleProcedure({ name: "Tim" }))
],
{ concurrency: "unbounded" }
)
)
)

console.log(
yield* _(sql`
UPDATE people
SET name = data.name
OUTPUT inserted.*
FROM ${sql.updateValues([{ ...inserted, name: "New name" }], "data")}
WHERE people.id = data.id
`)
)

console.log(
yield* _(
sql`SELECT TOP 3 * FROM ${sql("people")}`,
Effect.zipRight(
Effect.catchAllCause(
sql.withTransaction(Effect.die("fail")),
(_) => Effect.unit
)
),
Effect.zipRight(
sql.withTransaction(sql`SELECT TOP 3 * FROM ${sql("people")}`)
),
sql.withTransaction
)
)
})

const SqlLive = Sql.migrator.layer({
loader: Sql.migrator.fromFileSystem(
fileURLToPath(new URL("./migrations", import.meta.url))
)
}).pipe(
Layer.provideMerge(
Sql.client.layer({
database: Config.succeed("msdb"),
server: Config.succeed("localhost"),
username: Config.succeed("sa"),
password: Config.succeed(Secret.fromString("Sq1Fx_password")),
transformQueryNames: Config.succeed(String.camelToSnake),
transformResultNames: Config.succeed(String.snakeToCamel)
})
),
Layer.provide(NodeFileSystem.layer),
Layer.provide(DevTools.layer()),
Layer.provide(Logger.minimumLogLevel(LogLevel.All))
)

pipe(
program,
Effect.provide(SqlLive),
Effect.tapErrorCause(Effect.logError),
Effect.runFork
)

0 comments on commit 1499974

Please sign in to comment.