From 6ba2bf07437e820c8431fd46964198436364ccf4 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 5 Jul 2026 16:06:26 +0000 Subject: [PATCH 1/2] Use Effect schema and Option idioms Co-authored-by: Julius Marminge --- apps/server/src/cli/config.test.ts | 19 +++++++- apps/server/src/cli/config.ts | 46 +++++++++---------- .../src/state/vcsAction.test.ts | 22 ++++++--- .../client-runtime/src/state/vcsAction.ts | 10 ++-- 4 files changed, 60 insertions(+), 37 deletions(-) diff --git a/apps/server/src/cli/config.test.ts b/apps/server/src/cli/config.test.ts index d4d9d378557..35cbc02a4c5 100644 --- a/apps/server/src/cli/config.test.ts +++ b/apps/server/src/cli/config.test.ts @@ -2,6 +2,7 @@ import * as NodeOS from "node:os"; import { assert, expect, it } from "@effect/vitest"; import * as ConfigProvider from "effect/ConfigProvider"; +import * as Duration from "effect/Duration"; import * as Effect from "effect/Effect"; import * as FileSystem from "effect/FileSystem"; import * as Layer from "effect/Layer"; @@ -16,10 +17,26 @@ import { import * as NetService from "@t3tools/shared/Net"; import * as NodeServices from "@effect/platform-node/NodeServices"; import { deriveServerPaths } from "../config.ts"; -import { resolveServerConfig } from "./config.ts"; +import { DurationFromString, resolveServerConfig } from "./config.ts"; const encodeDesktopBootstrap = Schema.encodeEffect(Schema.fromJsonString(DesktopBackendBootstrap)); +it.effect("decodes compact duration shorthands", () => + Effect.gen(function* () { + const duration = yield* Schema.decodeUnknownEffect(DurationFromString)("5m"); + + assert.strictEqual(Duration.toMillis(duration), Duration.toMillis(Duration.minutes(5))); + }), +); + +it.effect("rejects invalid duration strings", () => + Effect.gen(function* () { + const error = yield* Effect.flip(Schema.decodeUnknownEffect(DurationFromString)("not-a-duration")); + + assert.include(String(error), "Invalid duration"); + }), +); + const makeDesktopBootstrap = ( overrides: Partial = {}, ): DesktopBackendBootstrapValue => ({ diff --git a/apps/server/src/cli/config.ts b/apps/server/src/cli/config.ts index 7a9cd72d526..f20c073ed3f 100644 --- a/apps/server/src/cli/config.ts +++ b/apps/server/src/cli/config.ts @@ -395,9 +395,9 @@ export const resolveCliAuthConfig = ( const DurationShorthandPattern = /^(?\d+)(?ms|s|m|h|d|w)$/i; -const parseDurationInput = (value: string): Duration.Duration | null => { +const parseDurationInput = (value: string): Option.Option => { const trimmed = value.trim(); - if (trimmed.length === 0) return null; + if (trimmed.length === 0) return Option.none(); const shorthand = DurationShorthandPattern.exec(trimmed); const normalizedInput = shorthand?.groups @@ -405,35 +405,32 @@ const parseDurationInput = (value: string): Duration.Duration | null => { const amountText = shorthand.groups.value; const unitText = shorthand.groups.unit; if (typeof amountText !== "string" || typeof unitText !== "string") { - return null; + return Option.none(); } const amount = Number.parseInt(amountText, 10); - if (!Number.isFinite(amount)) return null; + if (!Number.isFinite(amount)) return Option.none(); switch (unitText.toLowerCase()) { case "ms": - return `${amount} millis`; + return Option.some(`${amount} millis` as Duration.Input); case "s": - return `${amount} seconds`; + return Option.some(`${amount} seconds` as Duration.Input); case "m": - return `${amount} minutes`; + return Option.some(`${amount} minutes` as Duration.Input); case "h": - return `${amount} hours`; + return Option.some(`${amount} hours` as Duration.Input); case "d": - return `${amount} days`; + return Option.some(`${amount} days` as Duration.Input); case "w": - return `${amount} weeks`; + return Option.some(`${amount} weeks` as Duration.Input); default: - return null; + return Option.none(); } })() - : (trimmed as Duration.Input); + : Option.some(trimmed as Duration.Input); - if (normalizedInput === null) return null; - - const decoded = Duration.fromInput(normalizedInput as Duration.Input); - return Option.isSome(decoded) ? decoded.value : null; + return Option.flatMap(normalizedInput, (input) => Duration.fromInput(input)); }; export const DurationFromString = Schema.String.pipe( @@ -442,14 +439,15 @@ export const DurationFromString = Schema.String.pipe( SchemaTransformation.transformOrFail({ decode: (value) => { const duration = parseDurationInput(value); - if (duration !== null) { - return Effect.succeed(duration); - } - return Effect.fail( - new SchemaIssue.InvalidValue(Option.some(value), { - message: "Invalid duration. Use values like 5m, 1h, 30d, or 15 minutes.", - }), - ); + return Option.match(duration, { + onSome: Effect.succeed, + onNone: () => + Effect.fail( + new SchemaIssue.InvalidValue(Option.some(value), { + message: "Invalid duration. Use values like 5m, 1h, 30d, or 15 minutes.", + }), + ), + }); }, encode: (duration) => Effect.succeed(Duration.format(duration)), }), diff --git a/packages/client-runtime/src/state/vcsAction.test.ts b/packages/client-runtime/src/state/vcsAction.test.ts index 7e618535ad8..5497a5105e7 100644 --- a/packages/client-runtime/src/state/vcsAction.test.ts +++ b/packages/client-runtime/src/state/vcsAction.test.ts @@ -3,7 +3,7 @@ import { type GitActionProgressEvent, type GitRunStackedActionResult, } from "@t3tools/contracts"; -import { describe, expect, it } from "@effect/vitest"; +import { assert, describe, expect, it } from "@effect/vitest"; import * as Cause from "effect/Cause"; import * as Effect from "effect/Effect"; import * as Layer from "effect/Layer"; @@ -64,7 +64,14 @@ function progress(event: T): T { } describe("vcsActionState", () => { - it("preserves malformed target key diagnostics and the native cause without copying the key", () => { + it("round-trips target keys through a Schema JSON codec", () => { + const key = getVcsActionTargetKey({ environmentId, cwd }); + + assert.isString(key); + assert.deepStrictEqual(parseVcsActionTargetKey(key), { environmentId, cwd }); + }); + + it("preserves malformed target key diagnostics without copying the key", () => { const key = "not-json-with-credential=do-not-log"; let error: unknown; @@ -74,16 +81,17 @@ describe("vcsActionState", () => { error = cause; } - expect(error).toBeInstanceOf(VcsActionTargetKeyParseError); - expect(error).toMatchObject({ keyLength: key.length, cause: expect.any(SyntaxError) }); - expect(error).not.toHaveProperty("key"); - expect((error as Error).message).not.toContain(key); + assert.instanceOf(error, VcsActionTargetKeyParseError); + const parseError = error as VcsActionTargetKeyParseError; + assert.strictEqual(parseError.keyLength, key.length); + assert.notProperty(parseError, "key"); + assert.notInclude(parseError.message, key); }); it("rejects invalid target key shapes", () => { const key = JSON.stringify([environmentId]); - expect(() => parseVcsActionTargetKey(key)).toThrowError(VcsActionTargetKeyParseError); + assert.throws(() => parseVcsActionTargetKey(key), VcsActionTargetKeyParseError); }); it("projects phase and hook progress without owning the async operation", () => { diff --git a/packages/client-runtime/src/state/vcsAction.ts b/packages/client-runtime/src/state/vcsAction.ts index 8ae3219a243..22f499db7f5 100644 --- a/packages/client-runtime/src/state/vcsAction.ts +++ b/packages/client-runtime/src/state/vcsAction.ts @@ -157,9 +157,9 @@ export const EMPTY_VCS_ACTION_STATE = Object.freeze({ const nowMs = (): number => DateTime.toEpochMillis(DateTime.nowUnsafe()); let nextLocalActionId = 0; -const decodeVcsActionTargetKey = Schema.decodeUnknownSync( - Schema.Tuple([EnvironmentId, Schema.String]), -); +const VcsActionTargetKeyJson = Schema.fromJsonString(Schema.Tuple([EnvironmentId, Schema.String])); +const decodeVcsActionTargetKey = Schema.decodeUnknownSync(VcsActionTargetKeyJson); +const encodeVcsActionTargetKey = Schema.encodeSync(VcsActionTargetKeyJson); export const vcsActionStateAtom = Atom.family((key: string) => { return Atom.make(EMPTY_VCS_ACTION_STATE).pipe( @@ -177,12 +177,12 @@ export function getVcsActionTargetKey(target: VcsActionTarget): string | null { if (target.environmentId === null || target.cwd === null) { return null; } - return JSON.stringify([target.environmentId, target.cwd]); + return encodeVcsActionTargetKey([target.environmentId, target.cwd]); } export function parseVcsActionTargetKey(key: string): ResolvedVcsActionTarget { try { - const [environmentId, cwd] = decodeVcsActionTargetKey(JSON.parse(key)); + const [environmentId, cwd] = decodeVcsActionTargetKey(key); return { environmentId, cwd }; } catch (cause) { throw new VcsActionTargetKeyParseError({ keyLength: key.length, cause }); From 311a8f7413927376a7dddcccf9dded5ca1970fc8 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 5 Jul 2026 16:08:19 +0000 Subject: [PATCH 2/2] Fix Effect idiom test validation Co-authored-by: Julius Marminge --- apps/server/src/cli/config.test.ts | 4 +++- packages/client-runtime/src/state/vcsAction.test.ts | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/apps/server/src/cli/config.test.ts b/apps/server/src/cli/config.test.ts index 35cbc02a4c5..565b6a6f467 100644 --- a/apps/server/src/cli/config.test.ts +++ b/apps/server/src/cli/config.test.ts @@ -31,7 +31,9 @@ it.effect("decodes compact duration shorthands", () => it.effect("rejects invalid duration strings", () => Effect.gen(function* () { - const error = yield* Effect.flip(Schema.decodeUnknownEffect(DurationFromString)("not-a-duration")); + const error = yield* Effect.flip( + Schema.decodeUnknownEffect(DurationFromString)("not-a-duration"), + ); assert.include(String(error), "Invalid duration"); }), diff --git a/packages/client-runtime/src/state/vcsAction.test.ts b/packages/client-runtime/src/state/vcsAction.test.ts index 5497a5105e7..8e0f0d0107c 100644 --- a/packages/client-runtime/src/state/vcsAction.test.ts +++ b/packages/client-runtime/src/state/vcsAction.test.ts @@ -67,7 +67,9 @@ describe("vcsActionState", () => { it("round-trips target keys through a Schema JSON codec", () => { const key = getVcsActionTargetKey({ environmentId, cwd }); - assert.isString(key); + if (key === null) { + assert.fail("expected target key to be encoded"); + } assert.deepStrictEqual(parseVcsActionTargetKey(key), { environmentId, cwd }); });