diff --git a/apps/loom-example-app/src/todo-service.ts b/apps/loom-example-app/src/todo-service.ts index 1a617c20..3abfafb3 100644 --- a/apps/loom-example-app/src/todo-service.ts +++ b/apps/loom-example-app/src/todo-service.ts @@ -1,8 +1,8 @@ import * as Data from "effect/Data" +import * as Context from "effect/Context" import * as Effect from "effect/Effect" import * as Layer from "effect/Layer" import * as Ref from "effect/Ref" -import * as ServiceMap from "effect/ServiceMap" export interface TodoItem { readonly id: number @@ -38,14 +38,14 @@ export const initialTodoItems: ReadonlyArray = [ export const cloneInitialTodos = (): Array => initialTodoItems.map((todo) => ({ ...todo })) -export class TodoService extends ServiceMap.Service()("LoomExampleTodoService", { +export class TodoService extends Context.Service()("LoomExampleTodoService", { make: Effect.gen(function*() { const todosRef = yield* Ref.make(cloneInitialTodos()) const nextIdRef = yield* Ref.make(initialTodoItems.length + 1) return { list: () => Ref.get(todosRef), - dispatch: (command) => + dispatch: (command: TodoCommand) => Effect.gen(function*() { switch (command.intent) { case "create": { @@ -59,7 +59,7 @@ export class TodoService extends ServiceMap.Service yield* Ref.update(todosRef, (current) => [...current, nextTodo]) yield* Ref.set(nextIdRef, nextId + 1) - return { intent: command.intent } + return { intent: command.intent } satisfies TodoCommandResult } case "toggle": { const current = yield* Ref.get(todosRef) @@ -71,7 +71,7 @@ export class TodoService extends ServiceMap.Service yield* Ref.update(todosRef, (todos) => todos.map((todo) => todo.id === command.id ? { ...todo, completed: !todo.completed } : todo)) - return { intent: command.intent } + return { intent: command.intent } satisfies TodoCommandResult } case "remove": { const current = yield* Ref.get(todosRef) @@ -86,12 +86,12 @@ export class TodoService extends ServiceMap.Service yield* Ref.update(todosRef, (todos) => todos.filter((todo) => todo.id !== command.id)) - return { intent: command.intent } + return { intent: command.intent } satisfies TodoCommandResult } case "clear-completed": { yield* Ref.update(todosRef, (todos) => todos.filter((todo) => !todo.completed)) - return { intent: command.intent } + return { intent: command.intent } satisfies TodoCommandResult } } }), @@ -100,10 +100,10 @@ export class TodoService extends ServiceMap.Service yield* Ref.set(todosRef, cloneInitialTodos()) yield* Ref.set(nextIdRef, initialTodoItems.length + 1) }), - } + } satisfies TodoServiceApi }), }) { - static readonly layer = Layer.effect(TodoService, this.make) + static readonly layer = Layer.effect(this, this.make) } export const makeTodoService = (): TodoServiceApi => Effect.runSync(TodoService.make) diff --git a/apps/loom-example-app/tests/todo-service.test.ts b/apps/loom-example-app/tests/todo-service.test.ts new file mode 100644 index 00000000..1970cb7b --- /dev/null +++ b/apps/loom-example-app/tests/todo-service.test.ts @@ -0,0 +1,37 @@ +import * as Effect from "effect/Effect" +import { describe, expect, it } from "vitest" +import { makeTodoService, TodoService } from "../src/todo-service.js" + +describe("loom example todo service", () => { + it("creates and resets todos through the Context.Service runtime", async () => { + const service = makeTodoService() + + await Effect.runPromise(service.dispatch({ intent: "create", title: "Verify Context.Service migration" })) + + expect(await Effect.runPromise(service.list())).toEqual([ + { completed: true, id: 1, title: "Sketch the shared Atom shape" }, + { completed: false, id: 2, title: "Wire the composer to shared state" }, + { completed: false, id: 3, title: "Show composition through child components" }, + { completed: false, id: 4, title: "Verify Context.Service migration" }, + ]) + + await Effect.runPromise(service.reset()) + + expect(await Effect.runPromise(service.list())).toEqual([ + { completed: true, id: 1, title: "Sketch the shared Atom shape" }, + { completed: false, id: 2, title: "Wire the composer to shared state" }, + { completed: false, id: 3, title: "Show composition through child components" }, + ]) + }) + + it("fails with TodoNotFoundError for unknown ids", async () => { + await expect( + Effect.runPromise( + Effect.gen(function*() { + const service = yield* TodoService + return yield* service.dispatch({ id: 99, intent: "remove" }) + }).pipe(Effect.provide(TodoService.layer)), + ), + ).rejects.toMatchObject({ _tag: "TodoNotFoundError", id: 99 }) + }) +}) diff --git a/apps/react-remix-example/project.json b/apps/react-remix-example/project.json index d1824cfe..4d3ba6ba 100644 --- a/apps/react-remix-example/project.json +++ b/apps/react-remix-example/project.json @@ -4,6 +4,13 @@ "projectType": "application", "sourceRoot": "apps/react-remix-example/src", "targets": { + "test": { + "executor": "nx:run-commands", + "options": { + "command": "vitest run", + "cwd": "apps/react-remix-example" + } + }, "build": { "executor": "nx:run-script", "outputs": ["{projectRoot}/build"], diff --git a/apps/react-remix-example/tests/unit/routes/api-auth.test.ts b/apps/react-remix-example/tests/unit/routes/api-auth.test.ts new file mode 100644 index 00000000..07776fd3 --- /dev/null +++ b/apps/react-remix-example/tests/unit/routes/api-auth.test.ts @@ -0,0 +1,105 @@ +import type { ActionFunctionArgs, LoaderFunctionArgs } from "@remix-run/node" +import * as Context from "effect/Context" +import * as Effect from "effect/Effect" +import * as Layer from "effect/Layer" +import { describe, expect, it, vi } from "vitest" + +const { authHandlerMock } = vi.hoisted(() => ({ + authHandlerMock: vi.fn(), +})) + +vi.mock("../../../app/lib/better-auth-options.server.js", () => ({ + authOptions: { + baseURL: "http://localhost:3000", + secret: "test-secret", + }, +})) + +vi.mock("@effectify/node-better-auth", () => { + class MockAuthServiceContext extends Context.Service< + MockAuthServiceContext, + { + readonly auth: { + readonly handler: (request: Request) => Promise + } + } + >()("AuthServiceContext") {} + + return { + AuthService: { + AuthServiceContext: Object.assign(MockAuthServiceContext, { + layer: () => + Layer.succeed(MockAuthServiceContext, { + auth: { + handler: authHandlerMock, + }, + }), + }), + }, + } +}) + +vi.mock("@effectify/react-router-better-auth", () => ({ + betterAuthLoader: Effect.succeed( + new Response("loader ok", { + status: 200, + headers: { + "set-cookie": "session=loader", + "x-runtime": "loader", + }, + }), + ), + betterAuthAction: Effect.succeed( + new Response("action ok", { + status: 201, + headers: { + "set-cookie": "session=action", + "x-runtime": "action", + }, + }), + ), +})) + +import { action, loader } from "../../../app/routes/api.auth.$.js" + +const makeLoaderArgs = (request: Request): LoaderFunctionArgs => ({ + context: {}, + params: { "*": "session" }, + request, +}) + +const makeActionArgs = (request: Request): ActionFunctionArgs => ({ + context: {}, + params: { "*": "session" }, + request, +}) + +describe("api.auth route runtime integration", () => { + it("preserves auth loader responses through withLoaderEffect", async () => { + const response = await loader( + makeLoaderArgs(new Request("https://example.com/api/auth/session")), + ).catch((error) => error) + + expect(response).toBeInstanceOf(Response) + expect(response.status).toBe(200) + expect(response.headers.get("set-cookie")).toBe("session=loader") + expect(response.headers.get("x-runtime")).toBe("loader") + await expect(response.text()).resolves.toBe("loader ok") + }) + + it("preserves auth action responses through withActionEffect", async () => { + const response = await action( + makeActionArgs( + new Request("https://example.com/api/auth/session", { + method: "POST", + }), + ), + ).catch((error) => error) + + expect(response).toBeInstanceOf(Response) + expect(response.status).toBe(201) + expect(response.headers.get("set-cookie")).toBe("session=action") + expect(response.headers.get("x-runtime")).toBe("action") + await expect(response.text()).resolves.toBe("action ok") + }) +}) diff --git a/apps/react-remix-example/tests/unit/routes/test-route.test.ts b/apps/react-remix-example/tests/unit/routes/test-route.test.ts new file mode 100644 index 00000000..dbbe4fd0 --- /dev/null +++ b/apps/react-remix-example/tests/unit/routes/test-route.test.ts @@ -0,0 +1,107 @@ +import type { ActionFunctionArgs, LoaderFunctionArgs } from "@remix-run/node" +import * as Context from "effect/Context" +import * as Layer from "effect/Layer" +import { describe, expect, it, vi } from "vitest" + +const { authHandlerMock } = vi.hoisted(() => ({ + authHandlerMock: vi.fn(), +})) + +vi.mock("../../../app/lib/better-auth-options.server.js", () => ({ + authOptions: { + baseURL: "http://localhost:3000", + secret: "test-secret", + }, +})) + +vi.mock("@effectify/node-better-auth", () => { + class MockAuthServiceContext extends Context.Service< + MockAuthServiceContext, + { + readonly auth: { + readonly handler: (request: Request) => Promise + } + } + >()("AuthServiceContext") {} + + return { + AuthService: { + AuthServiceContext: Object.assign(MockAuthServiceContext, { + layer: () => + Layer.succeed(MockAuthServiceContext, { + auth: { + handler: authHandlerMock, + }, + }), + }), + }, + } +}) + +import { action, loader } from "../../../app/routes/test.js" + +const makeLoaderArgs = (request: Request): LoaderFunctionArgs => ({ + context: {}, + params: {}, + request, +}) + +const makeActionArgs = (request: Request): ActionFunctionArgs => ({ + context: {}, + params: {}, + request, +}) + +describe("test route runtime integration", () => { + it("returns loader data through withLoaderEffect", async () => { + const response = await loader(makeLoaderArgs(new Request("https://example.com/test"))) + + expect(response).toEqual({ + ok: true, + data: { + message: "Test route works!", + }, + }) + }) + + it("returns a validation response when the form input is blank", async () => { + const response = await action( + makeActionArgs( + new Request("https://example.com/test", { + method: "POST", + body: new URLSearchParams({ inputValue: "" }), + }), + ), + ) + + expect(response).toBeInstanceOf(Response) + if (!(response instanceof Response)) { + throw new Error("Expected a Remix validation Response") + } + + expect(response.status).toBe(400) + await expect(response.json()).resolves.toEqual({ + ok: false, + errors: ["Input value is required"], + }) + }) + + it("returns processed action data through withActionEffect", async () => { + const response = await action( + makeActionArgs( + new Request("https://example.com/test", { + method: "POST", + body: new URLSearchParams({ inputValue: "Effectify" }), + }), + ), + ) + + expect(response).toEqual({ + ok: true, + response: { + message: "Received successfully!", + inputValue: "Effectify", + }, + }) + }) +}) diff --git a/apps/react-remix-example/vitest.config.ts b/apps/react-remix-example/vitest.config.ts new file mode 100644 index 00000000..8890c157 --- /dev/null +++ b/apps/react-remix-example/vitest.config.ts @@ -0,0 +1,24 @@ +import { dirname, resolve } from "node:path" +import { fileURLToPath } from "node:url" +import { defineConfig } from "vitest/config" + +const __filename = fileURLToPath(import.meta.url) +const __dirname = dirname(__filename) + +export default defineConfig({ + root: __dirname, + resolve: { + alias: { + "~": resolve(__dirname, "app"), + }, + conditions: ["@effectify/source"], + }, + test: { + include: ["tests/**/*.{test,spec}.{ts,tsx}"], + exclude: ["**/node_modules/**", "**/build/**"], + globals: true, + }, + esbuild: { + target: "node22", + }, +}) diff --git a/apps/react-router-example/prisma/schema.prisma b/apps/react-router-example/prisma/schema.prisma index 7f891263..0ce23e6d 100644 --- a/apps/react-router-example/prisma/schema.prisma +++ b/apps/react-router-example/prisma/schema.prisma @@ -9,7 +9,7 @@ generator client { } generator effect { - provider = "node ../../packages/prisma/dist/src/cli.js" + provider = "node ../../packages/prisma/bin/effect-prisma.mjs" output = "./generated/effect" } diff --git a/apps/react-router-example/project.json b/apps/react-router-example/project.json index 0b000430..b8bcb154 100644 --- a/apps/react-router-example/project.json +++ b/apps/react-router-example/project.json @@ -31,7 +31,6 @@ }, "prisma:generate": { "executor": "nx:run-commands", - "dependsOn": ["^build"], "options": { "command": "pnpm dlx prisma generate", "cwd": "apps/react-router-example" diff --git a/apps/react-router-example/tests/unit/lib/runtime.server.test.ts b/apps/react-router-example/tests/unit/lib/runtime.server.test.ts index efefb9fc..dc34e394 100644 --- a/apps/react-router-example/tests/unit/lib/runtime.server.test.ts +++ b/apps/react-router-example/tests/unit/lib/runtime.server.test.ts @@ -27,10 +27,10 @@ vi.mock("../../../app/lib/better-auth-options.server.js", () => ({ })) vi.mock("@effectify/node-better-auth", async () => { + const Context = await import("effect/Context") const Layer = await import("effect/Layer") - const ServiceMap = await import("effect/ServiceMap") - class MockAuthServiceContext extends ServiceMap.Service< + class MockAuthServiceContext extends Context.Service< MockAuthServiceContext, { readonly auth: { @@ -54,11 +54,11 @@ vi.mock("@effectify/node-better-auth", async () => { }) vi.mock("@effectify/hatchet", async () => { + const Context = await import("effect/Context") const Effect = await import("effect/Effect") const Layer = await import("effect/Layer") - const ServiceMap = await import("effect/ServiceMap") - class MockHatchetConfig extends ServiceMap.Service< + class MockHatchetConfig extends Context.Service< MockHatchetConfig, { readonly host: string @@ -67,7 +67,7 @@ vi.mock("@effectify/hatchet", async () => { } >()("HatchetConfig") {} - class MockHatchetClientService extends ServiceMap.Service< + class MockHatchetClientService extends Context.Service< MockHatchetClientService, { readonly initializedWith: { @@ -97,10 +97,10 @@ vi.mock("@effectify/hatchet", async () => { }) vi.mock("../../../prisma/generated/effect/index.js", async () => { + const Context = await import("effect/Context") const Layer = await import("effect/Layer") - const ServiceMap = await import("effect/ServiceMap") - class MockPrismaService extends ServiceMap.Service< + class MockPrismaService extends Context.Service< MockPrismaService, { readonly label: "prisma" diff --git a/apps/react-router-example/vitest.config.ts b/apps/react-router-example/vitest.config.ts index b2918267..dc16dc22 100644 --- a/apps/react-router-example/vitest.config.ts +++ b/apps/react-router-example/vitest.config.ts @@ -1,4 +1,4 @@ -import { dirname } from "node:path" +import { dirname, resolve } from "node:path" import { fileURLToPath } from "node:url" import { defineConfig } from "vitest/config" @@ -7,6 +7,12 @@ const __dirname = dirname(__filename) export default defineConfig({ root: __dirname, + resolve: { + alias: { + "@effectify/hatchet": resolve(__dirname, "../../packages/hatchet/src/index.ts"), + }, + conditions: ["@effectify/source"], + }, test: { include: ["tests/**/*.{test,spec}.{ts,tsx}"], exclude: ["**/node_modules/**", "**/dist/**", "tests/unit/e2e/**"], diff --git a/apps/solid-example/src/routes/atom-demo.tsx b/apps/solid-example/src/routes/atom-demo.tsx index 042e85ee..f855a657 100644 --- a/apps/solid-example/src/routes/atom-demo.tsx +++ b/apps/solid-example/src/routes/atom-demo.tsx @@ -26,7 +26,7 @@ const subscribeAtom = Atom.make(0) const mountAtom = Atom.make(0) function Counter() { - const [count, setCount] = useAtom(counterAtom) + const [count, setCount] = useAtom(() => counterAtom) return (

Counter (useAtom)

@@ -50,7 +50,7 @@ function Counter() { } function DoubledCounter() { - const doubled = useAtomValue(counterAtom, (n: number) => n * 2) + const doubled = useAtomValue(() => counterAtom, (n: number) => n * 2) return (

Doubled (useAtomValue)

@@ -62,7 +62,7 @@ function DoubledCounter() { function InitialValuesDemo() { useAtomInitialValues([[textAtom, "Overridden Initial Value"]]) - const [text] = useAtom(textAtom) + const [text] = useAtom(() => textAtom) return (
@@ -74,7 +74,7 @@ function InitialValuesDemo() { } function RefreshDemo() { - const refresh = useAtomRefresh(counterAtom) + const refresh = useAtomRefresh(() => counterAtom) return (

Refresh Atom

@@ -90,7 +90,7 @@ function RefreshDemo() { } function AtomRefDemo() { - const state = useAtomRef(refAtom) + const state = useAtomRef(() => refAtom) return (
@@ -110,8 +110,8 @@ function AtomRefDemo() { } function SetOnlyDemo() { - const setCount = useAtomSet(setOnlyAtom) - const count = useAtomValue(setOnlyAtom) + const setCount = useAtomSet(() => setOnlyAtom) + const count = useAtomValue(() => setOnlyAtom) return (
@@ -130,11 +130,11 @@ function SetOnlyDemo() { function SubscribeDemo() { const [logs, setLogs] = createSignal([]) - useAtomSubscribe(subscribeAtom, (val: number) => { + useAtomSubscribe(() => subscribeAtom, (val: number) => { setLogs((prev) => [`Value changed to: ${val}`, ...prev].slice(0, 3)) }) - const setCount = useAtomSet(subscribeAtom) + const setCount = useAtomSet(() => subscribeAtom) return (
@@ -156,7 +156,7 @@ function SubscribeDemo() { function MountDemo() { // This atom will log when mounted/unmounted if we added effects to it // For this demo, we just show that useAtomMount can be called - useAtomMount(mountAtom) + useAtomMount(() => mountAtom) return (
diff --git a/package.json b/package.json index b2a0e858..002fca34 100644 --- a/package.json +++ b/package.json @@ -76,7 +76,7 @@ } }, "overrides": { - "@effect/platform-node-shared": "4.0.0-beta.33", + "@effect/platform-node-shared": "4.0.0-beta.57", "@types/react": "19.1.13", "@types/react-dom": "19.1.9" }, diff --git a/packages/hatchet/package.json b/packages/hatchet/package.json index 9ef72314..53bdfa4e 100644 --- a/packages/hatchet/package.json +++ b/packages/hatchet/package.json @@ -14,6 +14,7 @@ "types": "./dist/src/index.d.ts", "exports": { ".": { + "@effectify/source": "./src/index.ts", "types": "./dist/src/index.d.ts", "import": "./dist/src/index.js", "default": "./dist/src/index.js", diff --git a/packages/hatchet/src/core/client.ts b/packages/hatchet/src/core/client.ts index 172ef5a3..126d33cc 100644 --- a/packages/hatchet/src/core/client.ts +++ b/packages/hatchet/src/core/client.ts @@ -1,11 +1,11 @@ /** * @effectify/hatchet - Hatchet Client * - * Hatchet SDK client as a ServiceMap.Service using Effect v4 + * Hatchet SDK client as a Context.Service using Effect v4 */ import * as Effect from "effect/Effect" -import * as ServiceMap from "effect/ServiceMap" +import * as Context from "effect/Context" import * as Layer from "effect/Layer" import { Hatchet as HatchetClientSDK } from "@hatchet-dev/typescript-sdk" import { HatchetConfig } from "./config.js" @@ -13,10 +13,10 @@ import { HatchetConfig } from "./config.js" type HatchetClientType = InstanceType /** - * ServiceMap.Service for the Hatchet SDK client + * Context.Service for the Hatchet SDK client * Renamed to HatchetClientService to avoid conflict with SDK class name */ -export class HatchetClientService extends ServiceMap.Service< +export class HatchetClientService extends Context.Service< HatchetClientService, HatchetClientType >()("HatchetClient") {} diff --git a/packages/hatchet/src/core/config.ts b/packages/hatchet/src/core/config.ts index 965d85c5..665841d7 100644 --- a/packages/hatchet/src/core/config.ts +++ b/packages/hatchet/src/core/config.ts @@ -1,11 +1,11 @@ /** * @effectify/hatchet - Configuration * - * Hatchet configuration using Effect v4 Config and ServiceMap.Service + * Hatchet configuration using Effect v4 Config and Context.Service */ import * as Effect from "effect/Effect" -import * as ServiceMap from "effect/ServiceMap" +import * as Context from "effect/Context" import * as Layer from "effect/Layer" import * as Config from "effect/Config" import * as Schema from "effect/Schema" @@ -26,10 +26,10 @@ const HatchetConfigSchema = Schema.Struct({ export type HatchetConfigType = Schema.Schema.Type /** - * ServiceMap.Service for Hatchet configuration + * Context.Service for Hatchet configuration * This allows injecting config via Effect's dependency injection */ -export class HatchetConfig extends ServiceMap.Service< +export class HatchetConfig extends Context.Service< HatchetConfig, HatchetConfigType >()("HatchetConfig") {} diff --git a/packages/hatchet/src/core/context.ts b/packages/hatchet/src/core/context.ts index fbec4958..bfa0432d 100644 --- a/packages/hatchet/src/core/context.ts +++ b/packages/hatchet/src/core/context.ts @@ -1,10 +1,10 @@ /** * @effectify/hatchet - Hatchet Step Context * - * ServiceMap.Service for injecting Hatchet context into Effect tasks + * Context.Service for injecting Hatchet context into Effect tasks */ -import * as ServiceMap from "effect/ServiceMap" +import * as Context from "effect/Context" import type { Context as SdkContext } from "@hatchet-dev/typescript-sdk" export type HatchetTaskContext< @@ -13,10 +13,10 @@ export type HatchetTaskContext< > = SdkContext /** - * ServiceMap.Service for the Hatchet step context + * Context.Service for the Hatchet step context * This is injected at runtime by the effectifier when executing a task */ -export class HatchetStepContext extends ServiceMap.Service< +export class HatchetStepContext extends Context.Service< HatchetStepContext, HatchetTaskContext >()("HatchetStepContext") {} diff --git a/packages/hatchet/src/effectifier/execute.ts b/packages/hatchet/src/effectifier/execute.ts index 558072ef..015711f8 100644 --- a/packages/hatchet/src/effectifier/execute.ts +++ b/packages/hatchet/src/effectifier/execute.ts @@ -80,11 +80,12 @@ export const createEffectifierFromLayer = ( } /** - * createEffectifierFromServices creates an effectifier from a ServiceMap. + * createEffectifierFromLayer creates an effectifier from a Layer. * - * @param services - The ServiceMap with dependencies + * @param services - The Layer with dependencies * @returns An effectifyTask function bound to that runtime */ +/** @deprecated Use createEffectifierFromLayer instead. */ export const createEffectifierFromServiceMap = ( services: Layer.Layer, ) => { diff --git a/packages/hatchet/src/logging/hatchet-logger.ts b/packages/hatchet/src/logging/hatchet-logger.ts index f21f5e60..c7ca360a 100644 --- a/packages/hatchet/src/logging/hatchet-logger.ts +++ b/packages/hatchet/src/logging/hatchet-logger.ts @@ -6,9 +6,9 @@ */ import * as Effect from "effect/Effect" +import * as Context from "effect/Context" import * as Logger from "effect/Logger" import * as Option from "effect/Option" -import * as ServiceMap from "effect/ServiceMap" import { HatchetStepContext } from "../core/context.js" const defaultFormat = (level: string, message: string): string => `[${level}] ${message}` @@ -34,10 +34,7 @@ const makeConfiguredHatchetLogger = ( stringifyMessage(options.message), ) - const hatchetCtx = ServiceMap.getOption( - options.fiber.services, - HatchetStepContext, - ) + const hatchetCtx = Context.getOption(options.fiber.context, HatchetStepContext) if (Option.isSome(hatchetCtx)) { try { diff --git a/packages/hatchet/tests/unit/effect-beta57-baseline.test.ts b/packages/hatchet/tests/unit/effect-beta57-baseline.test.ts new file mode 100644 index 00000000..c25ab8af --- /dev/null +++ b/packages/hatchet/tests/unit/effect-beta57-baseline.test.ts @@ -0,0 +1,71 @@ +import { readFile } from "node:fs/promises" +import { dirname, join } from "node:path" +import { fileURLToPath } from "node:url" +import { describe, expect, it } from "vitest" + +const workspaceRoot = join( + dirname(fileURLToPath(import.meta.url)), + "../../../..", +) + +const EFFECT_BETA57_VERSION = "4.0.0-beta.57" +const catalogEffectFamilyPackages = [ + "effect", + "@effect/platform", + "@effect/platform-node", + "@effect/vitest", + "@effect/atom-solid", +] as const + +const readWorkspaceFile = async (relativePath: string) => { + return readFile(join(workspaceRoot, relativePath), "utf8") +} + +const escapeForRegExp = (value: string) => value.replaceAll(/[.*+?^${}()|[\]\\]/g, "\\$&") + +describe("Effect beta57 dependency baseline", () => { + it("pins the full Effect family to beta57 in the workspace catalog", async () => { + const workspaceYaml = await readWorkspaceFile("pnpm-workspace.yaml") + + for (const packageName of catalogEffectFamilyPackages) { + expect(workspaceYaml).toMatch( + new RegExp( + `(?:["'])?${escapeForRegExp(packageName)}(?:["'])?:\\s*${escapeForRegExp(EFFECT_BETA57_VERSION)}`, + ), + ) + } + }) + + it("keeps the root pnpm override aligned with the beta57 platform-node-shared package", async () => { + const packageJson = JSON.parse( + await readWorkspaceFile("package.json"), + ) as { + pnpm: { + overrides: Record + } + } + + expect(packageJson.pnpm.overrides["@effect/platform-node-shared"]).toBe( + EFFECT_BETA57_VERSION, + ) + }) + + it("refreshes the lockfile to the same beta57 Effect family baseline", async () => { + const lockfile = await readWorkspaceFile("pnpm-lock.yaml") + + expect(lockfile).not.toContain("4.0.0-beta.33") + expect(lockfile).toContain( + `'@effect/atom-solid':\n specifier: ${EFFECT_BETA57_VERSION}\n version: ${EFFECT_BETA57_VERSION}`, + ) + expect(lockfile).toContain( + `'@effect/platform-node':\n specifier: ${EFFECT_BETA57_VERSION}\n version: ${EFFECT_BETA57_VERSION}`, + ) + expect(lockfile).toContain( + `'@effect/vitest':\n specifier: ${EFFECT_BETA57_VERSION}\n version: ${EFFECT_BETA57_VERSION}`, + ) + expect(lockfile).toContain( + `effect:\n specifier: ${EFFECT_BETA57_VERSION}\n version: ${EFFECT_BETA57_VERSION}`, + ) + expect(lockfile).toContain(`'@effect/platform-node-shared': ${EFFECT_BETA57_VERSION}`) + }) +}) diff --git a/packages/hatchet/tests/unit/effectifier.test.ts b/packages/hatchet/tests/unit/effectifier.test.ts index 26e4c0bb..45a359c9 100644 --- a/packages/hatchet/tests/unit/effectifier.test.ts +++ b/packages/hatchet/tests/unit/effectifier.test.ts @@ -5,10 +5,10 @@ */ import { describe, expect, expectTypeOf, it } from "vitest" +import * as Context from "effect/Context" import * as Effect from "effect/Effect" import * as Layer from "effect/Layer" import * as ManagedRuntime from "effect/ManagedRuntime" -import * as ServiceMap from "effect/ServiceMap" import { HatchetStepContext, type HatchetTaskContext } from "@effectify/hatchet" import { createMockContext } from "@effectify/hatchet" import { @@ -17,8 +17,8 @@ import { effectifyTask, } from "../../src/effectifier/execute.js" -// Sample service for testing using ServiceMap pattern -class TestService extends ServiceMap.Service()( +// Sample service for testing using Context.Service pattern +class TestService extends Context.Service()( "TestService", ) {} @@ -264,7 +264,7 @@ describe("createEffectifierFromLayer", () => { }) describe("createEffectifierFromServiceMap", () => { - it("should create effectifier from ServiceMap (Layer)", async () => { + it("should create effectifier from the deprecated alias", async () => { const services = Layer.succeed(TestService, "from-service-map") const effect = Effect.gen(function*() { diff --git a/packages/hatchet/tests/unit/logging.test.ts b/packages/hatchet/tests/unit/logging.test.ts index 3708d53f..580f9acf 100644 --- a/packages/hatchet/tests/unit/logging.test.ts +++ b/packages/hatchet/tests/unit/logging.test.ts @@ -25,7 +25,7 @@ const runWithLogger = async ( Layer.build(Layer.mergeAll(Logger.layer([logger]), extraLayer)), ).pipe(Effect.runPromise) - return effect.pipe(Effect.provideServices(services), Effect.runPromise) + return effect.pipe(Effect.provideContext(services), Effect.runPromise) } const runWithLoggerCaptureConsole = async ( diff --git a/packages/loom/router/src/action-input.ts b/packages/loom/router/src/action-input.ts index 95f74528..b442d374 100644 --- a/packages/loom/router/src/action-input.ts +++ b/packages/loom/router/src/action-input.ts @@ -25,9 +25,7 @@ export interface Decoder { readonly decode: (input: Normalized) => Result.Result } -export type SchemaDecoder = Schema.Schema & { - readonly DecodingServices: never -} +export type SchemaDecoder = Schema.Decoder export type DecoderLike = Decoder | SchemaDecoder diff --git a/packages/loom/router/src/decode.ts b/packages/loom/router/src/decode.ts index 33766582..184dab1a 100644 --- a/packages/loom/router/src/decode.ts +++ b/packages/loom/router/src/decode.ts @@ -16,9 +16,7 @@ export interface Decoder { readonly decode: (input: Input) => Result.Result } -export type SchemaDecoder = Schema.Schema & { - readonly DecodingServices: never -} +export type SchemaDecoder = Schema.Decoder export type DecoderLike = Decoder | SchemaDecoder diff --git a/packages/loom/router/src/internal/annotations.ts b/packages/loom/router/src/internal/annotations.ts index e2c7292a..911de9e7 100644 --- a/packages/loom/router/src/internal/annotations.ts +++ b/packages/loom/router/src/internal/annotations.ts @@ -1,4 +1,4 @@ -import type * as ServiceMap from "effect/ServiceMap" +import type * as Context from "effect/Context" export type Annotations = ReadonlyMap @@ -6,7 +6,7 @@ export const emptyAnnotations = (): Annotations => new Map() export const mergeAnnotations = (left: Annotations, right: Annotations): Annotations => new Map([...left, ...right]) -export const annotateValue = (annotations: Annotations, tag: ServiceMap.Key, value: S): Annotations => { +export const annotateValue = (annotations: Annotations, tag: Context.Service, value: S): Annotations => { const next = new Map(annotations) next.set(tag.key, value) @@ -14,5 +14,5 @@ export const annotateValue = (annotations: Annotations, tag: ServiceMap.Ke return next } -export const getAnnotation = (annotations: Annotations, tag: ServiceMap.Key): unknown => +export const getAnnotation = (annotations: Annotations, tag: Context.Service): unknown => annotations.get(tag.key) diff --git a/packages/loom/router/src/internal/route-dsl.ts b/packages/loom/router/src/internal/route-dsl.ts index 86e86868..f52435ab 100644 --- a/packages/loom/router/src/internal/route-dsl.ts +++ b/packages/loom/router/src/internal/route-dsl.ts @@ -1,4 +1,4 @@ -import type * as ServiceMap from "effect/ServiceMap" +import type * as Context from "effect/Context" import * as Decode from "../decode.js" import * as Fallback from "../fallback.js" import * as Layout from "../layout.js" @@ -251,7 +251,7 @@ export const annotateRoute = < S, >( self: RouteDefinition, - tag: ServiceMap.Key, + tag: Context.Service, value: S, ): RouteDefinition => ({ ...self, diff --git a/packages/loom/router/src/internal/route-errors.ts b/packages/loom/router/src/internal/route-errors.ts index 9b7da4cb..5aabb084 100644 --- a/packages/loom/router/src/internal/route-errors.ts +++ b/packages/loom/router/src/internal/route-errors.ts @@ -4,9 +4,7 @@ import * as Schema from "effect/Schema" export type RouteSchemaPhase = "loader-output" | "loader-error" | "action-output" | "action-error" -type RouteSchema = Schema.Schema & { - readonly DecodingServices: never -} +type RouteSchema = Schema.Decoder export class RouteModuleExportError extends Data.TaggedError("LoomRouterRouteModuleExportError")<{ readonly exportName: "component" | "loader" | "action" diff --git a/packages/loom/router/src/internal/route-group.ts b/packages/loom/router/src/internal/route-group.ts index 402c20bf..fa849e08 100644 --- a/packages/loom/router/src/internal/route-group.ts +++ b/packages/loom/router/src/internal/route-group.ts @@ -1,4 +1,4 @@ -import type * as ServiceMap from "effect/ServiceMap" +import type * as Context from "effect/Context" import type * as Route from "../route.js" import { annotateValue, emptyAnnotations, mergeAnnotations } from "./annotations.js" import { joinPathnames } from "./path.js" @@ -52,7 +52,7 @@ export const prefixRouteGroup = , I, S>( self: RouteGroupDefinition, - tag: ServiceMap.Key, + tag: Context.Service, value: S, ): RouteGroupDefinition => ({ ...self, diff --git a/packages/loom/router/src/internal/router.ts b/packages/loom/router/src/internal/router.ts index 8c151abe..f697f941 100644 --- a/packages/loom/router/src/internal/router.ts +++ b/packages/loom/router/src/internal/router.ts @@ -2,7 +2,7 @@ import { Diagnostics, Html } from "@effectify/loom" import type * as Loom from "@effectify/loom" import * as Decode from "../decode.js" import * as Result from "effect/Result" -import type * as ServiceMap from "effect/ServiceMap" +import type * as Context from "effect/Context" import { buildUrl, validateHrefInput } from "./path.js" import type * as Fallback from "../fallback.js" import type * as Layout from "../layout.js" @@ -400,7 +400,7 @@ export const prefixRouter = >( export const annotateRouter = , I, S>( self: RouterDefinition, - tag: ServiceMap.Key, + tag: Context.Service, value: S, ): RouterDefinition => makeDefinition({ diff --git a/packages/loom/router/src/route-group.ts b/packages/loom/router/src/route-group.ts index 63dff1d1..2392a5a3 100644 --- a/packages/loom/router/src/route-group.ts +++ b/packages/loom/router/src/route-group.ts @@ -1,5 +1,5 @@ import { dual } from "effect/Function" -import type * as ServiceMap from "effect/ServiceMap" +import type * as Context from "effect/Context" import type * as Fallback from "./fallback.js" import type * as Layout from "./layout.js" import { reflectRoutes } from "./internal/reflection.js" @@ -58,19 +58,19 @@ export const prefix: { ): Definition } = dual(2, internal.prefixRouteGroup) -/** Add a single Effect ServiceMap annotation to a route group. */ +/** Add a single Effect Context annotation to a route group. */ export const annotate: { - (tag: ServiceMap.Key, value: S): >( + (tag: Context.Service, value: S): >( self: Definition, ) => Definition , I, S>( self: Definition, - tag: ServiceMap.Key, + tag: Context.Service, value: S, ): Definition } = dual(3, internal.annotateRouteGroup) -/** Merge multiple Effect ServiceMap annotations into a route group. */ +/** Merge multiple Effect Context annotations into a route group. */ export const annotateMerge: { ( annotations: Annotations, diff --git a/packages/loom/router/src/route.ts b/packages/loom/router/src/route.ts index cb61b0da..2a34a562 100644 --- a/packages/loom/router/src/route.ts +++ b/packages/loom/router/src/route.ts @@ -1,7 +1,7 @@ import * as Effect from "effect/Effect" +import type * as Context from "effect/Context" import { dual } from "effect/Function" import * as Schema from "effect/Schema" -import type * as ServiceMap from "effect/ServiceMap" import type * as Decode from "./decode.js" import type * as Fallback from "./fallback.js" import type * as Layout from "./layout.js" @@ -32,9 +32,7 @@ export interface DecodeOptions } -export type RouteSchema = Schema.Schema & { - readonly DecodingServices: never -} +export type RouteSchema = Schema.Decoder type DecodeOutputOf = Decoder extends Decode.DecoderLike ? Output : Fallback @@ -672,9 +670,9 @@ export const prefix: { path: AbsolutePath, ): Definition => internal.prefixRoute(self, path)) -/** Add a single Effect ServiceMap annotation to a route. */ +/** Add a single Effect Context annotation to a route. */ export const annotate: { - (tag: ServiceMap.Key, value: S): < + (tag: Context.Service, value: S): < Content, ParamsOutput extends Params, SearchOutput extends Search, @@ -693,7 +691,7 @@ export const annotate: { S, >( self: Definition, - tag: ServiceMap.Key, + tag: Context.Service, value: S, ): Definition } = dual(3, < @@ -706,11 +704,11 @@ export const annotate: { S, >( self: Definition, - tag: ServiceMap.Key, + tag: Context.Service, value: S, ): Definition => internal.annotateRoute(self, tag, value)) -/** Merge multiple Effect ServiceMap annotations into a route. */ +/** Merge multiple Effect Context annotations into a route. */ export const annotateMerge: { (annotations: Annotations): < Content, @@ -1102,7 +1100,7 @@ export const hasAction = ( ): self is Self & { readonly action: Exclude, undefined> } => self.action !== undefined /** Read a single annotation value from a merged annotation map. */ -export const getAnnotation = (annotations: Annotations, tag: ServiceMap.Key): unknown => +export const getAnnotation = (annotations: Annotations, tag: Context.Service): unknown => getAnnotationValue(annotations, tag) /** Encode a stable href for an absolute route definition. */ diff --git a/packages/loom/router/src/router.ts b/packages/loom/router/src/router.ts index 08a31e82..507e4ada 100644 --- a/packages/loom/router/src/router.ts +++ b/packages/loom/router/src/router.ts @@ -1,6 +1,6 @@ import type * as Loom from "@effectify/loom" +import type * as EffectContext from "effect/Context" import { dual } from "effect/Function" -import type * as ServiceMap from "effect/ServiceMap" import type * as Decode from "./decode.js" import type * as Fallback from "./fallback.js" import type * as Layout from "./layout.js" @@ -183,19 +183,19 @@ export const prefix: { >(self: Definition, path: Route.AbsolutePath): Definition } = dual(2, internal.prefixRouter) -/** Add a single Effect ServiceMap annotation to a router. */ +/** Add a single Effect Context annotation to a router. */ export const annotate: { - (tag: ServiceMap.Key, value: S): >( + (tag: EffectContext.Service, value: S): >( self: Definition, ) => Definition , I, S>( self: Definition, - tag: ServiceMap.Key, + tag: EffectContext.Service, value: S, ): Definition } = dual(3, internal.annotateRouter) -/** Merge multiple Effect ServiceMap annotations into a router. */ +/** Merge multiple Effect Context annotations into a router. */ export const annotateMerge: { ( annotations: Route.Annotations, diff --git a/packages/loom/router/tests/public-api.types.ts b/packages/loom/router/tests/public-api.types.ts index 779bf367..fe8a3b5a 100644 --- a/packages/loom/router/tests/public-api.types.ts +++ b/packages/loom/router/tests/public-api.types.ts @@ -1,8 +1,9 @@ import type * as Loom from "@effectify/loom" import { Component, View } from "@effectify/loom" +import * as Context from "effect/Context" import * as Effect from "effect/Effect" import * as Schema from "effect/Schema" -import { pipe, ServiceMap } from "effect" +import { pipe } from "effect" import * as Result from "effect/Result" import { Decode, @@ -99,7 +100,7 @@ const resolveResult: Router.ResolveResult = Router.resolve( new URL("https://effectify.dev/users/42?tab=profile"), ) const resolvedOutput: Loom.View.Child | undefined = resolveResult.output -const RouterTitle = ServiceMap.Service<{ readonly title: string }>("RouterTitle") +const RouterTitle = Context.Service<{ readonly title: string }>("RouterTitle") const routeGroup = pipe(RouteGroup.make("users"), RouteGroup.add(route)) const algebraRouter = pipe( Router.make("app"), diff --git a/packages/loom/router/tests/router-algebra.test.ts b/packages/loom/router/tests/router-algebra.test.ts index 68755508..6f1753e2 100644 --- a/packages/loom/router/tests/router-algebra.test.ts +++ b/packages/loom/router/tests/router-algebra.test.ts @@ -1,12 +1,12 @@ -import { pipe, ServiceMap } from "effect" +import { Context, pipe } from "effect" import * as Schema from "effect/Schema" import { describe, expect, it } from "vitest" import * as Decode from "../src/decode.js" import { Fallback, Layout, Route, RouteGroup, Router } from "../src/index.js" -const AppTitle = ServiceMap.Service<{ readonly value: string }>("AppTitle") -const GroupTitle = ServiceMap.Service<{ readonly value: string }>("GroupTitle") -const RouteTitle = ServiceMap.Service<{ readonly value: string }>("RouteTitle") +const AppTitle = Context.Service<{ readonly value: string }>("AppTitle") +const GroupTitle = Context.Service<{ readonly value: string }>("GroupTitle") +const RouteTitle = Context.Service<{ readonly value: string }>("RouteTitle") const readValue = (annotation: unknown): unknown => typeof annotation === "object" && annotation !== null && "value" in annotation ? annotation.value : annotation diff --git a/packages/loom/router/tests/router-runtime.test.ts b/packages/loom/router/tests/router-runtime.test.ts index db3857b5..2dc19a68 100644 --- a/packages/loom/router/tests/router-runtime.test.ts +++ b/packages/loom/router/tests/router-runtime.test.ts @@ -1,3 +1,4 @@ +import * as Effect from "effect/Effect" import { SchemaGetter } from "effect" import * as Schema from "effect/Schema" import { Component, Html, Hydration, Slot, View, Web } from "@effectify/loom" @@ -256,7 +257,7 @@ describe("@effectify/loom-router runtime", () => { tab: Schema.optional(Schema.String), page: Schema.optional(Schema.String).pipe( Schema.decodeTo(Schema.String, { - decode: SchemaGetter.withDefault(() => "1"), + decode: SchemaGetter.withDefault(Effect.succeed("1")), encode: SchemaGetter.required(), }), ), diff --git a/packages/loom/router/tests/schema-contracts.test.ts b/packages/loom/router/tests/schema-contracts.test.ts new file mode 100644 index 00000000..347e695b --- /dev/null +++ b/packages/loom/router/tests/schema-contracts.test.ts @@ -0,0 +1,63 @@ +import * as Cause from "effect/Cause" +import * as Result from "effect/Result" +import * as Schema from "effect/Schema" +import { describe, expect, it } from "vitest" +import { Decode, Submission } from "../src/index.js" +import * as RouteErrors from "../src/internal/route-errors.js" + +describe("@effectify/loom-router schema-backed decoder contracts", () => { + it("decodes transformed router inputs through Decode.schema", () => { + const decoder = Decode.schema(Schema.Struct({ page: Schema.NumberFromString })) + + expect(decoder.decode({ page: "2" })).toEqual(Result.succeed({ page: 2 })) + expect(decoder.decode({})).toMatchObject({ + _tag: "Failure", + failure: { + _tag: "LoomRouterDecodeFailure", + input: {}, + }, + }) + }) + + it("decodes transformed action submissions through Submission.schema", () => { + const decoder = Submission.schema( + Schema.Struct({ + page: Schema.NumberFromString, + title: Schema.String, + }), + ) + + expect(decoder.decode({ page: "3", title: "draft" })).toEqual(Result.succeed({ page: 3, title: "draft" })) + expect(decoder.decode({ page: "3" })).toMatchObject({ + _tag: "Failure", + failure: { + _tag: "LoomRouterActionInputFailure", + input: { page: "3" }, + }, + }) + }) + + it("decodes transformed route loader contracts through RouteSchema validation", () => { + const outputSchema = Schema.Struct({ attempts: Schema.NumberFromString, message: Schema.String }) + const errorSchema = Schema.Struct({ message: Schema.String }) + + expect(RouteErrors.validateLoaderOutput(outputSchema, { attempts: "4", message: "ok" })).toEqual({ + attempts: 4, + message: "ok", + }) + + expect( + RouteErrors.mapLoaderCause(Cause.fail({}), { + error: errorSchema, + }), + ).toEqual( + new RouteErrors.RouteLoaderDefect({ + defect: new RouteErrors.RouteSchemaContractError({ + issue: expect.anything(), + phase: "loader-error", + value: {}, + }), + }), + ) + }) +}) diff --git a/packages/node/better-auth/src/lib/auth-service.ts b/packages/node/better-auth/src/lib/auth-service.ts index 9f05df56..8b0e32e2 100644 --- a/packages/node/better-auth/src/lib/auth-service.ts +++ b/packages/node/better-auth/src/lib/auth-service.ts @@ -1,6 +1,6 @@ import { betterAuth, type BetterAuthOptions, type Session, type User } from "better-auth" -import * as ServiceMap from "effect/ServiceMap" +import * as Context from "effect/Context" import * as Effect from "effect/Effect" import * as Layer from "effect/Layer" import * as Data from "effect/Data" @@ -10,10 +10,10 @@ export namespace AuthService { /** * AuthServiceContext provides access to the better-auth instance. * - * In Effect v4, services are created with ServiceMap.Service and have a .Layer property + * In Effect v4 beta57, services are created with Context.Service and still compose through Layers. * for easy Layer composition. */ - export class AuthServiceContext extends ServiceMap.Service()( + export class AuthServiceContext extends Context.Service()( "AuthServiceContext", { make: (options: BetterAuthOptions) => @@ -55,7 +55,7 @@ export namespace AuthService { * * This is request-scoped context provided at runtime during request handling. */ - export class AuthContext extends ServiceMap.Service< + export class AuthContext extends Context.Service< AuthContext, { readonly user: User; readonly session: Session } >()("AuthContext") {} diff --git a/packages/prisma/bin/effect-prisma.mjs b/packages/prisma/bin/effect-prisma.mjs new file mode 100644 index 00000000..aa90d07b --- /dev/null +++ b/packages/prisma/bin/effect-prisma.mjs @@ -0,0 +1,22 @@ +#!/usr/bin/env node + +import { spawnSync } from "node:child_process" +import { fileURLToPath } from "node:url" +import path from "node:path" + +const cliPath = fileURLToPath(new URL("../src/cli.ts", import.meta.url)) + +const result = spawnSync( + process.execPath, + ["--import", "tsx", cliPath, ...process.argv.slice(2)], + { + cwd: path.resolve(path.dirname(cliPath), ".."), + stdio: "inherit", + }, +) + +if (result.error) { + throw result.error +} + +process.exit(result.status ?? 1) diff --git a/packages/prisma/package.json b/packages/prisma/package.json index e9f2dfaf..25d7d73e 100644 --- a/packages/prisma/package.json +++ b/packages/prisma/package.json @@ -11,7 +11,7 @@ "type": "module", "license": "MIT", "bin": { - "effect-prisma": "./dist/src/cli.js" + "effect-prisma": "./bin/effect-prisma.mjs" }, "exports": { ".": "./dist/src/runtime/index.js", diff --git a/packages/prisma/src/schema-generator/index.ts b/packages/prisma/src/schema-generator/index.ts index 26eefb13..6c31338e 100644 --- a/packages/prisma/src/schema-generator/index.ts +++ b/packages/prisma/src/schema-generator/index.ts @@ -1,6 +1,7 @@ import * as FileSystem from "effect/FileSystem" import * as Path from "effect/Path" import type { DMMF } from "@prisma/generator-helper" +import * as Context from "effect/Context" import * as Effect from "effect/Effect" import * as Layer from "effect/Layer" import { RenderError, RenderService } from "../services/render-service.js" @@ -10,10 +11,9 @@ import * as EffectGenerator from "./effect/generator.js" import * as JoinTableGenerator from "./effect/join-table.js" import * as KyselyGenerator from "./kysely/generator.js" import * as PrismaGenerator from "./prisma/generator.js" -import * as ServiceMap from "effect/ServiceMap" import * as PlatformError from "effect/PlatformError" -export class GenerateSchemnaService extends ServiceMap.Service< +export class GenerateSchemnaService extends Context.Service< GenerateSchemnaService, { generate: ( diff --git a/packages/prisma/src/services/formatter-service.ts b/packages/prisma/src/services/formatter-service.ts index 32d8a178..b247cf43 100644 --- a/packages/prisma/src/services/formatter-service.ts +++ b/packages/prisma/src/services/formatter-service.ts @@ -1,4 +1,4 @@ -import * as ServiceMap from "effect/ServiceMap" +import * as Context from "effect/Context" import * as Effect from "effect/Effect" import * as Layer from "effect/Layer" import { createFromBuffer } from "@dprint/formatter" @@ -14,7 +14,7 @@ export class FormatError extends Data.TaggedError("FormatError")<{ } } -export class FormatterService extends ServiceMap.Service< +export class FormatterService extends Context.Service< FormatterService, { readonly format: (code: string) => Effect.Effect diff --git a/packages/prisma/src/services/generator-context.ts b/packages/prisma/src/services/generator-context.ts index ff7aa248..e6f9283d 100644 --- a/packages/prisma/src/services/generator-context.ts +++ b/packages/prisma/src/services/generator-context.ts @@ -1,7 +1,7 @@ import type { GeneratorOptions } from "@prisma/generator-helper" -import * as ServiceMap from "effect/ServiceMap" +import * as Context from "effect/Context" -export class GeneratorContext extends ServiceMap.Service< +export class GeneratorContext extends Context.Service< GeneratorContext, GeneratorOptions >()("GeneratorContext") {} diff --git a/packages/prisma/src/services/generator-service.ts b/packages/prisma/src/services/generator-service.ts index 476b4d0a..f2d179c9 100644 --- a/packages/prisma/src/services/generator-service.ts +++ b/packages/prisma/src/services/generator-service.ts @@ -1,13 +1,13 @@ import * as FileSystem from "effect/FileSystem" import * as Path from "effect/Path" import type { DMMF, GeneratorOptions } from "@prisma/generator-helper" +import * as Context from "effect/Context" import * as Effect from "effect/Effect" import * as Layer from "effect/Layer" import { GeneratorContext } from "./generator-context.js" import { RenderService } from "./render-service.js" import { FormatterService } from "./formatter-service.js" import { GenerateSchemnaService } from "../schema-generator/index.js" -import * as ServiceMap from "effect/ServiceMap" import { Data } from "effect" class GeneratorError extends Data.TaggedError("GeneratorError")<{ @@ -18,7 +18,7 @@ class GeneratorError extends Data.TaggedError("GeneratorError")<{ } } -export class GeneratorService extends ServiceMap.Service< +export class GeneratorService extends Context.Service< GeneratorService, { readonly generate: Effect.Effect @@ -233,6 +233,7 @@ export class GeneratorService extends ServiceMap.Service< }), }) { static readonly layer = Layer.effect(GeneratorService, this.make).pipe( + Layer.provide(GenerateSchemnaService.layer), Layer.provide(RenderService.layer), Layer.provide(FormatterService.layer), ) diff --git a/packages/prisma/src/services/render-service.ts b/packages/prisma/src/services/render-service.ts index 57173e59..61a891bf 100644 --- a/packages/prisma/src/services/render-service.ts +++ b/packages/prisma/src/services/render-service.ts @@ -1,4 +1,4 @@ -import * as ServiceMap from "effect/ServiceMap" +import * as Context from "effect/Context" import * as Effect from "effect/Effect" import * as Layer from "effect/Layer" import { Eta } from "eta" @@ -15,7 +15,7 @@ export class RenderError extends Data.TaggedError("RenderError")<{ } } -export class RenderService extends ServiceMap.Service< +export class RenderService extends Context.Service< RenderService, { readonly render: ( diff --git a/packages/prisma/src/templates/index-custom-error.eta b/packages/prisma/src/templates/index-custom-error.eta index ca25cfcb..eaf40620 100644 --- a/packages/prisma/src/templates/index-custom-error.eta +++ b/packages/prisma/src/templates/index-custom-error.eta @@ -1,9 +1,9 @@ // This file was generated by prisma-effect-generator, do not edit manually. +import * as Context from "effect/Context" import * as Effect from "effect/Effect" import * as Exit from "effect/Exit" import * as Layer from "effect/Layer" -import * as ServiceMap from "effect/ServiceMap" import { Prisma as PrismaNamespace, PrismaClient as BasePrismaClient } from "<%= it.clientImportPath %>" import { <%= it.customError.className %>, mapPrismaError } from "<%= it.customError.path %>" import * as Model from "./prisma-repository.js" @@ -28,7 +28,7 @@ type TransactionOptions = { * Service tag for the Prisma client instance. * Holds the transaction client (tx) and root client. */ -export class PrismaClient extends ServiceMap.Service< +export class PrismaClient extends Context.Service< PrismaClient, { tx: BasePrismaClient | PrismaNamespace.TransactionClient @@ -114,7 +114,7 @@ const $begin = ( * The main Prisma service with all database operations. * Provides type-safe, effectful access to your Prisma models. */ -export class Prisma extends ServiceMap.Service()("Prisma", { +export class Prisma extends Context.Service()("Prisma", { make: Effect.gen(function* () { return { $transaction: ( @@ -187,4 +187,3 @@ export const makePrismaLayer = PrismaClient.layer export const makePrismaLayerEffect = PrismaClient.layerEffect <%~ it.modelExports %> - diff --git a/packages/prisma/src/templates/index-default.eta b/packages/prisma/src/templates/index-default.eta index fe571700..d29ee21e 100644 --- a/packages/prisma/src/templates/index-default.eta +++ b/packages/prisma/src/templates/index-default.eta @@ -1,10 +1,10 @@ // This file was generated by prisma-effect-generator, do not edit manually. import * as Data from "effect/Data" +import * as Context from "effect/Context" import * as Effect from "effect/Effect" import * as Exit from "effect/Exit" import * as Layer from "effect/Layer" -import * as ServiceMap from "effect/ServiceMap" import { Prisma as PrismaNamespace, PrismaClient as BasePrismaClient } from "<%= it.clientImportPath %>" // Symbol used to identify intentional rollbacks vs actual errors @@ -27,7 +27,7 @@ type TransactionOptions = { * Service tag for the Prisma client instance. * Holds the transaction client (tx) and root client. */ -export class PrismaClient extends ServiceMap.Service< +export class PrismaClient extends Context.Service< PrismaClient, { tx: BasePrismaClient | PrismaNamespace.TransactionClient @@ -289,7 +289,7 @@ const $begin = ( * The main Prisma service with all database operations. * Provides type-safe, effectful access to your Prisma models. */ -export class Prisma extends ServiceMap.Service()("Prisma", { +export class Prisma extends Context.Service()("Prisma", { make: Effect.gen(function* () { return { $transaction: ( @@ -362,4 +362,3 @@ export const makePrismaLayer = PrismaClient.layer export const makePrismaLayerEffect = PrismaClient.layerEffect <%~ it.modelExports %> - diff --git a/packages/prisma/test/effect-beta57-prisma-generator.test.ts b/packages/prisma/test/effect-beta57-prisma-generator.test.ts new file mode 100644 index 00000000..79f8f00d --- /dev/null +++ b/packages/prisma/test/effect-beta57-prisma-generator.test.ts @@ -0,0 +1,147 @@ +import { getDMMF } from "@prisma/internals" +import * as NodeServices from "@effect/platform-node/NodeServices" +import type { GeneratorOptions } from "@prisma/generator-helper" +import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises" +import { tmpdir } from "node:os" +import path from "node:path" +import { spawn } from "node:child_process" +import * as Effect from "effect/Effect" +import * as Layer from "effect/Layer" +import { afterEach, describe, expect, it } from "vitest" + +import { GenerateSchemnaService } from "../src/schema-generator/index.js" +import { GeneratorContext } from "../src/services/generator-context.js" +import { GeneratorService } from "../src/services/generator-service.js" + +const createdDirs: Array = [] + +const makeTempDir = async () => { + const dir = await mkdtemp(path.join(tmpdir(), "effectify-prisma-beta57-")) + createdDirs.push(dir) + return dir +} + +afterEach(async () => { + await Promise.all(createdDirs.splice(0).map((dir) => rm(dir, { force: true, recursive: true }))) +}) + +const baseSchema = ` +datasource db { + provider = "sqlite" +} + +model Todo { + id String @id @default(uuid()) + title String + content String? + published Boolean @default(false) +} +` + +const makeGeneratorOptions = async ( + outputDir: string, + config: Record = {}, +): Promise => { + await rm(outputDir, { force: true, recursive: true }) + await mkdir(outputDir, { recursive: true }) + const schemaPath = path.join(outputDir, "schema.prisma") + await writeFile(schemaPath, baseSchema) + const dmmf = await getDMMF({ datamodel: baseSchema }) + + return { + dmmf, + generator: { + config: { + clientImportPath: "../client.js", + importFileExtension: "js", + ...config, + }, + output: { + value: outputDir, + }, + }, + schemaPath, + } as GeneratorOptions +} + +const generatorLayer = Layer.mergeAll( + GeneratorService.layer, + GenerateSchemnaService.layer, +).pipe(Layer.provideMerge(NodeServices.layer)) + +const runGenerator = (options: GeneratorOptions) => + Effect.runPromise( + Effect.service(GeneratorService).pipe( + Effect.flatMap(({ generate }) => generate), + Effect.provideService(GeneratorContext, options), + Effect.provide(generatorLayer), + ), + ) + +const readGeneratedIndex = async (outputDir: string) => readFile(path.join(outputDir, "index.ts"), "utf8") + +const expectContextBasedRuntime = (source: string) => { + expect(source).toContain('import * as Context from "effect/Context"') + expect(source).toContain("extends Context.Service<") + expect(source).not.toContain("ServiceMap") +} + +const runPnpm = (cwd: string, args: Array) => + new Promise((resolve, reject) => { + const child = spawn("pnpm", args, { cwd, stdio: "pipe" }) + let stderr = "" + + child.stderr.on("data", (chunk) => { + stderr += String(chunk) + }) + + child.on("error", reject) + child.on("close", (code) => { + if (code === 0) { + resolve() + return + } + reject(new Error(stderr || `pnpm ${args.join(" ")} failed with exit code ${code}`)) + }) + }) + +describe("beta57 prisma generator migration", () => { + it("generates the default runtime with Context services", async () => { + const outputDir = path.join(await makeTempDir(), "generated", "effect") + const options = await makeGeneratorOptions(outputDir) + + await runGenerator(options) + + const indexSource = await readGeneratedIndex(outputDir) + + expectContextBasedRuntime(indexSource) + expect(indexSource).toContain("export class PrismaClient") + expect(indexSource).toContain("export class Prisma extends Context.Service()") + }) + + it("generates the custom-error runtime with Context services", async () => { + const outputDir = path.join(await makeTempDir(), "generated", "effect") + const options = await makeGeneratorOptions(outputDir, { + errorImportPath: "../errors/prisma-error#AppPrismaError", + }) + + await runGenerator(options) + + const indexSource = await readGeneratedIndex(outputDir) + + expectContextBasedRuntime(indexSource) + expect(indexSource).toContain('import { AppPrismaError, mapPrismaError } from "../errors/prisma-error.js"') + }) + + it("regenerates the react-router example runtime without the dist CLI build", async () => { + const appDir = path.resolve(import.meta.dirname, "../../../apps/react-router-example") + const generatedIndexPath = path.join(appDir, "prisma", "generated", "effect", "index.ts") + + await runPnpm(appDir, ["dlx", "prisma", "generate", "--schema", "prisma/schema.prisma"]) + + const indexSource = await readFile(generatedIndexPath, "utf8") + + expectContextBasedRuntime(indexSource) + expect(indexSource).toContain("export const PrismaService = Prisma") + }) +}) diff --git a/packages/react/remix/src/lib/context.ts b/packages/react/remix/src/lib/context.ts index 99e50449..35b48c25 100644 --- a/packages/react/remix/src/lib/context.ts +++ b/packages/react/remix/src/lib/context.ts @@ -1,15 +1,15 @@ import type { ActionFunctionArgs, LoaderFunctionArgs } from "@remix-run/node" -import * as ServiceMap from "effect/ServiceMap" +import * as Context from "effect/Context" /** * ActionArgsContext provides access to React Router action arguments. * Used in Effect handlers to access request data. * - * In Effect v4, Context.Tag was replaced by ServiceMap.Service. + * In Effect v4 beta57, request-scoped services use Context.Service. * For request-scoped contexts that are provided at runtime, we use - * ServiceMap.Service with a minimal make constructor. + * Context.Service with a minimal make constructor. */ -export class ActionArgsContext extends ServiceMap.Service< +export class ActionArgsContext extends Context.Service< ActionArgsContext, ActionFunctionArgs >()("ActionArgsContext") {} @@ -18,7 +18,7 @@ export class ActionArgsContext extends ServiceMap.Service< * LoaderArgsContext provides access to React Router loader arguments. * Used in Effect handlers to access request data during route loading. */ -export class LoaderArgsContext extends ServiceMap.Service< +export class LoaderArgsContext extends Context.Service< LoaderArgsContext, LoaderFunctionArgs >()("LoaderArgsContext") {} diff --git a/packages/react/router/src/lib/context.ts b/packages/react/router/src/lib/context.ts index 86f183df..edb73522 100644 --- a/packages/react/router/src/lib/context.ts +++ b/packages/react/router/src/lib/context.ts @@ -1,12 +1,12 @@ import type { ActionFunctionArgs, LoaderFunctionArgs } from "react-router" -import * as ServiceMap from "effect/ServiceMap" +import * as Context from "effect/Context" -export class ActionArgsContext extends ServiceMap.Service< +export class ActionArgsContext extends Context.Service< ActionArgsContext, ActionFunctionArgs >()("ActionArgsContext") {} -export class LoaderArgsContext extends ServiceMap.Service< +export class LoaderArgsContext extends Context.Service< LoaderArgsContext, LoaderFunctionArgs >()("LoaderArgsContext") {} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 394a5b21..be3e4136 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -22,8 +22,8 @@ catalogs: specifier: 0.5.0 version: 0.5.0 '@effect/atom-solid': - specifier: 4.0.0-beta.33 - version: 4.0.0-beta.33 + specifier: 4.0.0-beta.57 + version: 4.0.0-beta.57 '@effect/build-utils': specifier: 0.8.9 version: 0.8.9 @@ -34,14 +34,14 @@ catalogs: specifier: 0.56.0 version: 0.56.0 '@effect/platform-node': - specifier: 4.0.0-beta.33 - version: 4.0.0-beta.33 + specifier: 4.0.0-beta.57 + version: 4.0.0-beta.57 '@effect/tsgo': specifier: 0.0.17 version: 0.0.17 '@effect/vitest': - specifier: 4.0.0-beta.33 - version: 4.0.0-beta.33 + specifier: 4.0.0-beta.57 + version: 4.0.0-beta.57 '@hatchet-dev/typescript-sdk': specifier: 1.21.0 version: 1.21.0 @@ -202,8 +202,8 @@ catalogs: specifier: 0.51.1 version: 0.51.1 effect: - specifier: 4.0.0-beta.33 - version: 4.0.0-beta.33 + specifier: 4.0.0-beta.57 + version: 4.0.0-beta.57 eta: specifier: ^4.5.0 version: 4.6.0 @@ -323,7 +323,7 @@ catalogs: version: 4.3.6 overrides: - '@effect/platform-node-shared': 4.0.0-beta.33 + '@effect/platform-node-shared': 4.0.0-beta.57 '@types/react': 19.1.13 '@types/react-dom': 19.1.9 @@ -335,7 +335,7 @@ importers: dependencies: '@effect/experimental': specifier: 'catalog:' - version: 0.59.0(@effect/platform@0.95.0(effect@4.0.0-beta.33))(effect@4.0.0-beta.33)(ioredis@5.10.1) + version: 0.59.0(@effect/platform@0.95.0(effect@4.0.0-beta.57))(effect@4.0.0-beta.57)(ioredis@5.10.1) '@prisma/client': specifier: 'catalog:' version: 7.3.0(prisma@7.3.0(@types/react@19.1.13)(better-sqlite3@12.9.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(typescript@5.9.3))(typescript@5.9.3) @@ -462,7 +462,7 @@ importers: version: 5.9.3 ultracite: specifier: 'catalog:' - version: 6.3.6(effect@4.0.0-beta.33)(typescript@5.9.3)(valibot@1.3.1(typescript@5.9.3)) + version: 6.3.6(effect@4.0.0-beta.57)(typescript@5.9.3)(valibot@1.3.1(typescript@5.9.3)) verdaccio: specifier: 'catalog:' version: 6.2.2(encoding@0.1.13)(typanion@3.14.0) @@ -522,7 +522,7 @@ importers: version: 2.1.1 effect: specifier: 'catalog:' - version: 4.0.0-beta.33 + version: 4.0.0-beta.57 devDependencies: '@nx/vite': specifier: 'catalog:' @@ -544,7 +544,7 @@ importers: dependencies: '@effect/platform-node': specifier: 'catalog:' - version: 4.0.0-beta.33(effect@4.0.0-beta.33)(ioredis@5.10.1) + version: 4.0.0-beta.57(effect@4.0.0-beta.57)(ioredis@5.10.1) '@effectify/node-better-auth': specifier: workspace:* version: link:../../packages/node/better-auth @@ -556,7 +556,7 @@ importers: version: 12.6.2 effect: specifier: 'catalog:' - version: 4.0.0-beta.33 + version: 4.0.0-beta.57 tslib: specifier: 'catalog:' version: 2.8.1 @@ -569,7 +569,7 @@ importers: dependencies: '@effect/platform-node': specifier: 'catalog:' - version: 4.0.0-beta.33(effect@4.0.0-beta.33)(ioredis@5.10.1) + version: 4.0.0-beta.57(effect@4.0.0-beta.57)(ioredis@5.10.1) '@effectify/node-better-auth': specifier: workspace:* version: link:../../packages/node/better-auth @@ -605,7 +605,7 @@ importers: version: 17.4.2 effect: specifier: 'catalog:' - version: 4.0.0-beta.33 + version: 4.0.0-beta.57 isbot: specifier: ^4.4.0 version: 4.4.0 @@ -699,7 +699,7 @@ importers: version: 17.4.2 effect: specifier: 'catalog:' - version: 4.0.0-beta.33 + version: 4.0.0-beta.57 kysely: specifier: 'catalog:' version: 0.28.11 @@ -744,10 +744,10 @@ importers: dependencies: '@effect-atom/atom': specifier: 'catalog:' - version: 0.5.0(@effect/experimental@0.59.0(@effect/platform@0.95.0(effect@4.0.0-beta.33))(effect@4.0.0-beta.33)(ioredis@5.10.1))(@effect/platform@0.95.0(effect@4.0.0-beta.33))(@effect/rpc@0.73.2(@effect/platform@0.95.0(effect@4.0.0-beta.33))(effect@4.0.0-beta.33))(effect@4.0.0-beta.33) + version: 0.5.0(@effect/experimental@0.59.0(@effect/platform@0.95.0(effect@4.0.0-beta.57))(effect@4.0.0-beta.57)(ioredis@5.10.1))(@effect/platform@0.95.0(effect@4.0.0-beta.57))(@effect/rpc@0.73.2(@effect/platform@0.95.0(effect@4.0.0-beta.57))(effect@4.0.0-beta.57))(effect@4.0.0-beta.57) '@effect/atom-solid': specifier: 'catalog:' - version: 4.0.0-beta.33(effect@4.0.0-beta.33)(solid-js@1.9.12) + version: 4.0.0-beta.57(effect@4.0.0-beta.57)(solid-js@1.9.12) '@tailwindcss/vite': specifier: 'catalog:' version: 4.1.18(vite@8.0.3(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.46.2)(tsx@4.20.6)(yaml@2.8.3)) @@ -768,7 +768,7 @@ importers: version: 1.167.45(solid-js@1.9.12)(vite-plugin-solid@2.11.11(solid-js@1.9.12)(vite@8.0.3(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.46.2)(tsx@4.20.6)(yaml@2.8.3)))(vite@8.0.3(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.46.2)(tsx@4.20.6)(yaml@2.8.3))(webpack@5.106.2(@swc/core@1.15.8(@swc/helpers@0.5.21))(esbuild@0.27.7)) effect: specifier: 'catalog:' - version: 4.0.0-beta.33 + version: 4.0.0-beta.57 lucide-solid: specifier: 'catalog:' version: 0.554.0(solid-js@1.9.12) @@ -805,7 +805,7 @@ importers: version: link:../../shared/domain effect: specifier: 'catalog:' - version: 4.0.0-beta.33 + version: 4.0.0-beta.57 tslib: specifier: 'catalog:' version: 2.8.1 @@ -832,7 +832,7 @@ importers: version: 5.90.10(react@19.2.0) effect: specifier: 'catalog:' - version: 4.0.0-beta.33 + version: 4.0.0-beta.57 react: specifier: 'catalog:' version: 19.2.0 @@ -881,7 +881,7 @@ importers: version: 5.90.23(solid-js@1.9.12) effect: specifier: 'catalog:' - version: 4.0.0-beta.33 + version: 4.0.0-beta.57 lucide-solid: specifier: 'catalog:' version: 0.554.0(solid-js@1.9.12) @@ -896,11 +896,11 @@ importers: version: 1.21.0 effect: specifier: 'catalog:' - version: 4.0.0-beta.33 + version: 4.0.0-beta.57 devDependencies: '@effect/vitest': specifier: 'catalog:' - version: 4.0.0-beta.33(effect@4.0.0-beta.33)(vitest@4.1.2(@opentelemetry/api@1.9.1)(@types/node@20.19.25)(@vitest/ui@4.1.5(vitest@4.1.2))(jsdom@27.2.0)(vite@8.0.3(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@20.19.25)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.46.2)(tsx@4.20.6)(yaml@2.8.3))) + version: 4.0.0-beta.57(effect@4.0.0-beta.57)(vitest@4.1.2(@opentelemetry/api@1.9.1)(@types/node@20.19.25)(@vitest/ui@4.1.5(vitest@4.1.2))(jsdom@27.2.0)(vite@8.0.3(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@20.19.25)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.46.2)(tsx@4.20.6)(yaml@2.8.3))) '@types/node': specifier: 'catalog:' version: 20.19.25 @@ -921,7 +921,7 @@ importers: dependencies: effect: specifier: 'catalog:' - version: 4.0.0-beta.33 + version: 4.0.0-beta.57 tslib: specifier: 'catalog:' version: 2.8.1 @@ -933,7 +933,7 @@ importers: version: link:../web effect: specifier: 'catalog:' - version: 4.0.0-beta.33 + version: 4.0.0-beta.57 tslib: specifier: 'catalog:' version: 2.8.1 @@ -945,7 +945,7 @@ importers: version: link:../web effect: specifier: 'catalog:' - version: 4.0.0-beta.33 + version: 4.0.0-beta.57 tslib: specifier: 'catalog:' version: 2.8.1 @@ -957,7 +957,7 @@ importers: version: link:../core effect: specifier: 'catalog:' - version: 4.0.0-beta.33 + version: 4.0.0-beta.57 tslib: specifier: 'catalog:' version: 2.8.1 @@ -982,7 +982,7 @@ importers: version: link:../web effect: specifier: 'catalog:' - version: 4.0.0-beta.33 + version: 4.0.0-beta.57 tslib: specifier: 'catalog:' version: 2.8.1 @@ -1000,7 +1000,7 @@ importers: version: link:../runtime effect: specifier: 'catalog:' - version: 4.0.0-beta.33 + version: 4.0.0-beta.57 tslib: specifier: 'catalog:' version: 2.8.1 @@ -1031,10 +1031,10 @@ importers: devDependencies: '@effect/platform-node': specifier: 'catalog:' - version: 4.0.0-beta.33(effect@4.0.0-beta.33)(ioredis@5.10.1) + version: 4.0.0-beta.57(effect@4.0.0-beta.57)(ioredis@5.10.1) '@effect/vitest': specifier: 'catalog:' - version: 4.0.0-beta.33(effect@4.0.0-beta.33)(vitest@4.1.2(@opentelemetry/api@1.9.1)(@types/node@20.19.25)(@vitest/ui@4.1.5(vitest@4.1.2))(jsdom@27.2.0)(vite@8.0.3(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@20.19.25)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.46.2)(tsx@4.20.6)(yaml@2.8.3))) + version: 4.0.0-beta.57(effect@4.0.0-beta.57)(vitest@4.1.2(@opentelemetry/api@1.9.1)(@types/node@20.19.25)(@vitest/ui@4.1.5(vitest@4.1.2))(jsdom@27.2.0)(vite@8.0.3(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@20.19.25)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.46.2)(tsx@4.20.6)(yaml@2.8.3))) '@types/better-sqlite3': specifier: 'catalog:' version: 7.6.13 @@ -1046,7 +1046,7 @@ importers: version: 12.6.2 effect: specifier: 'catalog:' - version: 4.0.0-beta.33 + version: 4.0.0-beta.57 vitest: specifier: 'catalog:' version: 4.1.2(@opentelemetry/api@1.9.1)(@types/node@20.19.25)(@vitest/ui@4.1.5(vitest@4.1.2))(jsdom@27.2.0)(vite@8.0.3(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@20.19.25)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.46.2)(tsx@4.20.6)(yaml@2.8.3)) @@ -1061,7 +1061,7 @@ importers: version: 0.95.15 '@effect/platform-node': specifier: 'catalog:' - version: 4.0.0-beta.33(effect@4.0.0-beta.33)(ioredis@5.10.1) + version: 4.0.0-beta.57(effect@4.0.0-beta.57)(ioredis@5.10.1) '@prisma/client': specifier: 'catalog:' version: 7.3.0(prisma@7.3.0(@types/react@19.1.13)(better-sqlite3@12.6.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(typescript@5.9.3))(typescript@5.9.3) @@ -1070,7 +1070,7 @@ importers: version: 7.3.0 effect: specifier: 'catalog:' - version: 4.0.0-beta.33 + version: 4.0.0-beta.57 eta: specifier: 'catalog:' version: 4.6.0 @@ -1083,13 +1083,13 @@ importers: version: 0.8.9 '@effect/experimental': specifier: 'catalog:' - version: 0.59.0(@effect/platform@0.95.0(effect@4.0.0-beta.33))(effect@4.0.0-beta.33)(ioredis@5.10.1) + version: 0.59.0(@effect/platform@0.95.0(effect@4.0.0-beta.57))(effect@4.0.0-beta.57)(ioredis@5.10.1) '@effect/language-service': specifier: 'catalog:' version: 0.56.0 '@effect/vitest': specifier: 'catalog:' - version: 4.0.0-beta.33(effect@4.0.0-beta.33)(vitest@4.1.2(@opentelemetry/api@1.9.1)(@types/node@20.19.25)(@vitest/ui@4.1.5(vitest@4.1.2))(jsdom@27.2.0)(vite@8.0.3(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@20.19.25)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.46.2)(tsx@4.20.6)(yaml@2.8.3))) + version: 4.0.0-beta.57(effect@4.0.0-beta.57)(vitest@4.1.2(@opentelemetry/api@1.9.1)(@types/node@20.19.25)(@vitest/ui@4.1.5(vitest@4.1.2))(jsdom@27.2.0)(vite@8.0.3(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@20.19.25)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.46.2)(tsx@4.20.6)(yaml@2.8.3))) '@prisma/adapter-better-sqlite3': specifier: 'catalog:' version: 7.3.0 @@ -1149,7 +1149,7 @@ importers: version: 5.90.10(react@19.2.0) effect: specifier: 'catalog:' - version: 4.0.0-beta.33 + version: 4.0.0-beta.57 react: specifier: 'catalog:' version: 19.2.0 @@ -1174,13 +1174,13 @@ importers: version: 2.17.4(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(typescript@5.9.3) effect: specifier: 'catalog:' - version: 4.0.0-beta.33 + version: 4.0.0-beta.57 packages/react/router: dependencies: effect: specifier: 'catalog:' - version: 4.0.0-beta.33 + version: 4.0.0-beta.57 react-router: specifier: 'catalog:' version: 7.12.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0) @@ -1195,7 +1195,7 @@ importers: version: link:../router effect: specifier: 'catalog:' - version: 4.0.0-beta.33 + version: 4.0.0-beta.57 packages/react/ui: dependencies: @@ -1268,7 +1268,7 @@ importers: version: 13.15.10 effect: specifier: 'catalog:' - version: 4.0.0-beta.33 + version: 4.0.0-beta.57 tslib: specifier: 'catalog:' version: 2.8.1 @@ -1290,7 +1290,7 @@ importers: version: 5.90.23(solid-js@1.9.12) effect: specifier: 'catalog:' - version: 4.0.0-beta.33 + version: 4.0.0-beta.57 solid-js: specifier: 'catalog:' version: 1.9.12 @@ -2253,10 +2253,10 @@ packages: '@effect/rpc': ^0.73.0 effect: ^3.19.15 - '@effect/atom-solid@4.0.0-beta.33': - resolution: {integrity: sha512-odqtmEhL/Lar9wJXNkgVxGzPyOir8R2S7uGWO42ngjus6CQxCcikFQmBoLBGOy8amFSA8sv2wIV3ukkCz5FL8g==} + '@effect/atom-solid@4.0.0-beta.57': + resolution: {integrity: sha512-DfjciO5E5ohv8sxyqihelKx9jzwdFmsLx3j6QiF5IuFyduZmn/qAzqflBEY0cdYP4pXHIsdE9ArJ0eBQ5IW+iQ==} peerDependencies: - effect: ^4.0.0-beta.33 + effect: ^4.0.0-beta.57 solid-js: '>=1 <2' '@effect/build-utils@0.8.9': @@ -2281,17 +2281,17 @@ packages: resolution: {integrity: sha512-gvJaHoeXMHAoA6+Xyj9Vdq52yDCs+ECLbKpHvxHtdJP/C0D9b3JFEfLjdVuw37zoWcYS856um4rgEYHlW2LSEQ==} hasBin: true - '@effect/platform-node-shared@4.0.0-beta.33': - resolution: {integrity: sha512-jaJnvYz1IiPZyN//fCJsvwnmujJS5KD8noCVVLhb4ZGCWKhQpt0x2iuax6HFzMlPEQSfl04GLU+PVKh0nkzPyA==} + '@effect/platform-node-shared@4.0.0-beta.57': + resolution: {integrity: sha512-C976X6f+qHUtLSqcqImuCrjhAHnJV17NC2RvvybsAuDfkyIWU4MyiO2XwgiBeijeNupyr1M/KPKnyjtkNxV9Hw==} engines: {node: '>=18.0.0'} peerDependencies: - effect: ^4.0.0-beta.33 + effect: ^4.0.0-beta.57 - '@effect/platform-node@4.0.0-beta.33': - resolution: {integrity: sha512-mw/zCuq4bSRP5nm3hPlfjX+veKlG6kC3NleuMhRuVSa8NzlHF08rXptd6S9ks9JuDz5F6dgzIf/beaGAYF8TmA==} + '@effect/platform-node@4.0.0-beta.57': + resolution: {integrity: sha512-la0xxPSAYOsY0d+uVxEBxok3jYB31iPQmIaZZRUj2SNWqcGGHJc6KorKtI8guqSLuv9FGZ255kBWXRbG6hMeeg==} engines: {node: '>=18.0.0'} peerDependencies: - effect: ^4.0.0-beta.33 + effect: ^4.0.0-beta.57 ioredis: ^5.7.0 '@effect/platform@0.95.0': @@ -2344,10 +2344,10 @@ packages: resolution: {integrity: sha512-lolef3DCZq9fSMXZQjHOFBfiR67oGtdvWX4umb11SsHRJDIYleizAMZRiGI9T9Tp6jRTqsiqa8XFO5MANnoC0g==} hasBin: true - '@effect/vitest@4.0.0-beta.33': - resolution: {integrity: sha512-atoJmncSbrKm8Fb1W+09mju6LwWRdhfvBicHpChwoPWCiij5fFrwRD7EBgIAmYUjycikR2/RYsPpeKXi8L26kw==} + '@effect/vitest@4.0.0-beta.57': + resolution: {integrity: sha512-XyGYv1zisrdP/N8+r4qaegyHZK4WS/1xBGlLPWqEoggBhgW7rD48cGUXDLEP7TlHcIJQiIlHtJlQUIwgVx3zWg==} peerDependencies: - effect: ^4.0.0-beta.33 + effect: ^4.0.0-beta.57 vitest: ^3.0.0 || ^4.0.0 '@electric-sql/pglite-socket@0.0.20': @@ -7156,8 +7156,8 @@ packages: effect@3.18.4: resolution: {integrity: sha512-b1LXQJLe9D11wfnOKAk3PKxuqYshQ0Heez+y5pnkd3jLj1yx9QhM72zZ9uUrOQyNvrs2GZZd/3maL0ZV18YuDA==} - effect@4.0.0-beta.33: - resolution: {integrity: sha512-ln9emWPd1SemokSdOV43r2CbH1j8GTe9qbPvttmh9/j2OR0WNmj7UpjbN34llQgF9QV4IdcN6QdV2w8G7B7RyQ==} + effect@4.0.0-beta.57: + resolution: {integrity: sha512-rg32VgXnLKaPRs9tbRDaZ5jxmzNY7ojXt85gSHGUTwdlbWH5Ik+OCUY2q14TXliygPGoHwCAvNWS4bQJOqf00g==} ejs@3.1.10: resolution: {integrity: sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==} @@ -11134,6 +11134,10 @@ packages: toml@3.0.0: resolution: {integrity: sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w==} + toml@4.1.1: + resolution: {integrity: sha512-EBJnVBr3dTXdA89WVFoAIPUqkBjxPMwRqsfuo1r240tKFHXv3zgca4+NJib/h6TyvGF7vOawz0jGuryJCdNHrw==} + engines: {node: '>=20'} + totalist@3.0.1: resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} engines: {node: '>=6'} @@ -11315,6 +11319,10 @@ packages: resolution: {integrity: sha512-xXnp4kTyor2Zq+J1FfPI6Eq3ew5h6Vl0F/8d9XU5zZQf1tX9s2Su1/3PiMmUANFULpmksxkClamIZcaUqryHsQ==} engines: {node: '>=20.18.1'} + undici@8.1.0: + resolution: {integrity: sha512-E9MkTS4xXLnRPYqxH2e6Hr2/49e7WFDKczKcCaFH4VaZs2iNvHMqeIkyUAD9vM8kujy9TjVrRlQ5KkdEJxB2pw==} + engines: {node: '>=22.19.0'} + unicode-canonical-property-names-ecmascript@2.0.1: resolution: {integrity: sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==} engines: {node: '>=4'} @@ -13373,16 +13381,16 @@ snapshots: '@dprint/win32-x64@0.51.1': optional: true - '@effect-atom/atom@0.5.0(@effect/experimental@0.59.0(@effect/platform@0.95.0(effect@4.0.0-beta.33))(effect@4.0.0-beta.33)(ioredis@5.10.1))(@effect/platform@0.95.0(effect@4.0.0-beta.33))(@effect/rpc@0.73.2(@effect/platform@0.95.0(effect@4.0.0-beta.33))(effect@4.0.0-beta.33))(effect@4.0.0-beta.33)': + '@effect-atom/atom@0.5.0(@effect/experimental@0.59.0(@effect/platform@0.95.0(effect@4.0.0-beta.57))(effect@4.0.0-beta.57)(ioredis@5.10.1))(@effect/platform@0.95.0(effect@4.0.0-beta.57))(@effect/rpc@0.73.2(@effect/platform@0.95.0(effect@4.0.0-beta.57))(effect@4.0.0-beta.57))(effect@4.0.0-beta.57)': dependencies: - '@effect/experimental': 0.59.0(@effect/platform@0.95.0(effect@4.0.0-beta.33))(effect@4.0.0-beta.33)(ioredis@5.10.1) - '@effect/platform': 0.95.0(effect@4.0.0-beta.33) - '@effect/rpc': 0.73.2(@effect/platform@0.95.0(effect@4.0.0-beta.33))(effect@4.0.0-beta.33) - effect: 4.0.0-beta.33 + '@effect/experimental': 0.59.0(@effect/platform@0.95.0(effect@4.0.0-beta.57))(effect@4.0.0-beta.57)(ioredis@5.10.1) + '@effect/platform': 0.95.0(effect@4.0.0-beta.57) + '@effect/rpc': 0.73.2(@effect/platform@0.95.0(effect@4.0.0-beta.57))(effect@4.0.0-beta.57) + effect: 4.0.0-beta.57 - '@effect/atom-solid@4.0.0-beta.33(effect@4.0.0-beta.33)(solid-js@1.9.12)': + '@effect/atom-solid@4.0.0-beta.57(effect@4.0.0-beta.57)(solid-js@1.9.12)': dependencies: - effect: 4.0.0-beta.33 + effect: 4.0.0-beta.57 solid-js: 1.9.12 '@effect/build-utils@0.8.9': @@ -13390,47 +13398,47 @@ snapshots: micromatch: 4.0.8 pkg-entry-points: 1.1.1 - '@effect/experimental@0.59.0(@effect/platform@0.95.0(effect@4.0.0-beta.33))(effect@4.0.0-beta.33)(ioredis@5.10.1)': + '@effect/experimental@0.59.0(@effect/platform@0.95.0(effect@4.0.0-beta.57))(effect@4.0.0-beta.57)(ioredis@5.10.1)': dependencies: - '@effect/platform': 0.95.0(effect@4.0.0-beta.33) - effect: 4.0.0-beta.33 + '@effect/platform': 0.95.0(effect@4.0.0-beta.57) + effect: 4.0.0-beta.57 uuid: 11.1.0 optionalDependencies: ioredis: 5.10.1 '@effect/language-service@0.56.0': {} - '@effect/platform-node-shared@4.0.0-beta.33(effect@4.0.0-beta.33)': + '@effect/platform-node-shared@4.0.0-beta.57(effect@4.0.0-beta.57)': dependencies: '@types/ws': 8.18.1 - effect: 4.0.0-beta.33 + effect: 4.0.0-beta.57 ws: 8.20.0 transitivePeerDependencies: - bufferutil - utf-8-validate - '@effect/platform-node@4.0.0-beta.33(effect@4.0.0-beta.33)(ioredis@5.10.1)': + '@effect/platform-node@4.0.0-beta.57(effect@4.0.0-beta.57)(ioredis@5.10.1)': dependencies: - '@effect/platform-node-shared': 4.0.0-beta.33(effect@4.0.0-beta.33) - effect: 4.0.0-beta.33 + '@effect/platform-node-shared': 4.0.0-beta.57(effect@4.0.0-beta.57) + effect: 4.0.0-beta.57 ioredis: 5.10.1 mime: 4.1.0 - undici: 7.25.0 + undici: 8.1.0 transitivePeerDependencies: - bufferutil - utf-8-validate - '@effect/platform@0.95.0(effect@4.0.0-beta.33)': + '@effect/platform@0.95.0(effect@4.0.0-beta.57)': dependencies: - effect: 4.0.0-beta.33 + effect: 4.0.0-beta.57 find-my-way-ts: 0.1.6 msgpackr: 1.11.10 multipasta: 0.2.7 - '@effect/rpc@0.73.2(@effect/platform@0.95.0(effect@4.0.0-beta.33))(effect@4.0.0-beta.33)': + '@effect/rpc@0.73.2(@effect/platform@0.95.0(effect@4.0.0-beta.57))(effect@4.0.0-beta.57)': dependencies: - '@effect/platform': 0.95.0(effect@4.0.0-beta.33) - effect: 4.0.0-beta.33 + '@effect/platform': 0.95.0(effect@4.0.0-beta.57) + effect: 4.0.0-beta.57 msgpackr: 1.11.10 '@effect/tsgo-darwin-arm64@0.0.17': @@ -13464,9 +13472,9 @@ snapshots: '@effect/tsgo-win32-arm64': 0.0.17 '@effect/tsgo-win32-x64': 0.0.17 - '@effect/vitest@4.0.0-beta.33(effect@4.0.0-beta.33)(vitest@4.1.2(@opentelemetry/api@1.9.1)(@types/node@20.19.25)(@vitest/ui@4.1.5(vitest@4.1.2))(jsdom@27.2.0)(vite@8.0.3(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@20.19.25)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.46.2)(tsx@4.20.6)(yaml@2.8.3)))': + '@effect/vitest@4.0.0-beta.57(effect@4.0.0-beta.57)(vitest@4.1.2(@opentelemetry/api@1.9.1)(@types/node@20.19.25)(@vitest/ui@4.1.5(vitest@4.1.2))(jsdom@27.2.0)(vite@8.0.3(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@20.19.25)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.46.2)(tsx@4.20.6)(yaml@2.8.3)))': dependencies: - effect: 4.0.0-beta.33 + effect: 4.0.0-beta.57 vitest: 4.1.2(@opentelemetry/api@1.9.1)(@types/node@20.19.25)(@vitest/ui@4.1.5(vitest@4.1.2))(jsdom@27.2.0)(vite@8.0.3(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@20.19.25)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.46.2)(tsx@4.20.6)(yaml@2.8.3)) '@electric-sql/pglite-socket@0.0.20(@electric-sql/pglite@0.3.15)': @@ -19204,7 +19212,7 @@ snapshots: '@standard-schema/spec': 1.1.0 fast-check: 3.23.2 - effect@4.0.0-beta.33: + effect@4.0.0-beta.57: dependencies: '@standard-schema/spec': 1.1.0 fast-check: 4.7.0 @@ -19213,7 +19221,7 @@ snapshots: kubernetes-types: 1.30.0 msgpackr: 1.11.10 multipasta: 0.2.7 - toml: 3.0.0 + toml: 4.1.1 uuid: 13.0.0 yaml: 2.8.3 @@ -24588,6 +24596,8 @@ snapshots: toml@3.0.0: {} + toml@4.1.1: {} + totalist@3.0.1: {} tough-cookie@5.1.2: @@ -24610,12 +24620,12 @@ snapshots: trough@2.2.0: {} - trpc-cli@0.12.4(@trpc/server@11.16.0(typescript@5.9.3))(effect@4.0.0-beta.33)(valibot@1.3.1(typescript@5.9.3))(zod@4.3.6): + trpc-cli@0.12.4(@trpc/server@11.16.0(typescript@5.9.3))(effect@4.0.0-beta.57)(valibot@1.3.1(typescript@5.9.3))(zod@4.3.6): dependencies: commander: 14.0.3 optionalDependencies: '@trpc/server': 11.16.0(typescript@5.9.3) - effect: 4.0.0-beta.33 + effect: 4.0.0-beta.57 valibot: 1.3.1(typescript@5.9.3) zod: 4.3.6 @@ -24699,7 +24709,7 @@ snapshots: uglify-js@3.19.3: optional: true - ultracite@6.3.6(effect@4.0.0-beta.33)(typescript@5.9.3)(valibot@1.3.1(typescript@5.9.3)): + ultracite@6.3.6(effect@4.0.0-beta.57)(typescript@5.9.3)(valibot@1.3.1(typescript@5.9.3)): dependencies: '@clack/prompts': 0.11.0 '@trpc/server': 11.16.0(typescript@5.9.3) @@ -24707,7 +24717,7 @@ snapshots: glob: 12.0.0 jsonc-parser: 3.3.1 nypm: 0.6.6 - trpc-cli: 0.12.4(@trpc/server@11.16.0(typescript@5.9.3))(effect@4.0.0-beta.33)(valibot@1.3.1(typescript@5.9.3))(zod@4.3.6) + trpc-cli: 0.12.4(@trpc/server@11.16.0(typescript@5.9.3))(effect@4.0.0-beta.57)(valibot@1.3.1(typescript@5.9.3))(zod@4.3.6) zod: 4.3.6 transitivePeerDependencies: - '@orpc/server' @@ -24732,6 +24742,8 @@ snapshots: undici@7.25.0: {} + undici@8.1.0: {} + unicode-canonical-property-names-ecmascript@2.0.1: {} unicode-match-property-ecmascript@2.0.0: diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 47b1c73a..d2dbd0e9 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -13,14 +13,14 @@ catalog: '@dprint/formatter': ^0.5.1 '@dprint/typescript': ^0.95.15 '@effect-atom/atom': 0.5.0 - '@effect/atom-solid': 4.0.0-beta.33 + '@effect/atom-solid': 4.0.0-beta.57 '@effect/build-utils': 0.8.9 '@effect/experimental': 0.59.0 '@effect/language-service': 0.56.0 - '@effect/platform': 4.0.0-beta.33 - '@effect/platform-node': 4.0.0-beta.33 + '@effect/platform': 4.0.0-beta.57 + '@effect/platform-node': 4.0.0-beta.57 '@effect/tsgo': 0.0.17 - '@effect/vitest': 4.0.0-beta.33 + '@effect/vitest': 4.0.0-beta.57 '@hatchet-dev/typescript-sdk': 1.21.0 '@kobalte/core': 0.13.11 '@nx/js': 22.6.2 @@ -99,7 +99,7 @@ catalog: cross-env: 10.1.0 dotenv: ^17.2.3 dprint: 0.51.1 - effect: 4.0.0-beta.33 + effect: 4.0.0-beta.57 effect-prisma-generator: ^0.4.0 eta: ^4.5.0 express: 5.1.0