diff --git a/packages/core/src/schema.ts b/packages/core/src/schema.ts index eb0ae4b07dc..c58a4b381bd 100644 --- a/packages/core/src/schema.ts +++ b/packages/core/src/schema.ts @@ -1,4 +1,3 @@ -import { Schema } from "effect" import { AbsolutePath, DateTimeUtcFromMillis, @@ -38,52 +37,3 @@ export type DeepMutable = T extends string | number | boolean | bigint | symb : T extends object ? { -readonly [K in keyof T]: DeepMutable } : T - -/** - * Nominal wrapper for scalar types. The class itself is a valid schema — - * pass it directly to `Schema.decodeUnknownSync`, `Schema.decodeEffect`, etc. - * - * The runtime value remains an unwrapped primitive. `Schema.brand` supplies - * the primitive schema behavior and constructor validation, while the class - * supplies the nominal TypeScript identity. - * Apply checks and annotations to the underlying schema before wrapping it; - * schema rebuild operations intentionally return the underlying schema shape. - * - * @example - * class QuestionID extends Newtype()("QuestionID", Schema.String) {} - * - * const id = QuestionID.make("question-1") - * Schema.decodeUnknownEffect(QuestionID)(input) - */ -type NewtypeSchema = (abstract new (_: never) => { - readonly _newtype: Tag -}) & - Schema.BottomWithoutNew< - Self, - S["Encoded"], - S["DecodingServices"], - S["EncodingServices"], - S["ast"], - S["Rebuild"], - S["~type.make.in"], - Self, - S["~type.parameters"], - Self, - S["~type.mutability"], - S["~type.optionality"], - S["~type.constructor.default"], - S["~encoded.mutability"], - S["~encoded.optionality"] - > & - Omit - -export function Newtype() { - return (tag: Tag, schema: S): NewtypeSchema => { - abstract class Base { - declare readonly _newtype: Tag - } - - Object.setPrototypeOf(Base, schema.pipe(Schema.brand(tag))) - return Base as unknown as NewtypeSchema - } -} diff --git a/packages/core/test/newtype.test.ts b/packages/core/test/newtype.test.ts deleted file mode 100644 index 1bcba5709db..00000000000 --- a/packages/core/test/newtype.test.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { expect, test } from "bun:test" -import { Effect, Schema } from "effect" -import { Newtype } from "../src/schema" - -class UserID extends Newtype()("Test.UserID", Schema.NonEmptyString) {} -class ProjectID extends Newtype()("Test.ProjectID", Schema.NonEmptyString) {} -class Port extends Newtype()("Test.Port", Schema.FiniteFromString) {} - -const User = Schema.Struct({ id: UserID }) - -test("constructs nominal values from the underlying type", () => { - const id = UserID.make("user-1") - const acceptUserID = (_id: UserID) => undefined - - expect(String(id)).toBe("user-1") - acceptUserID(id) - - if (false) { - // @ts-expect-error distinct newtypes are not interchangeable - acceptUserID(ProjectID.make("project-1")) - } -}) - -test("preserves constructor validation", () => { - expect(() => UserID.make("")).toThrow() -}) - -test("decodes and encodes as a schema", async () => { - const decoded = await Effect.runPromise(Schema.decodeUnknownEffect(User)({ id: "user-1" })) - const encoded = await Effect.runPromise(Schema.encodeEffect(User)(decoded)) - - expect(String(decoded.id)).toBe("user-1") - expect(encoded).toEqual({ id: "user-1" }) -}) - -test("preserves the underlying schema validation", async () => { - const result = await Effect.runPromise(Schema.decodeUnknownEffect(UserID)("").pipe(Effect.result)) - expect(result._tag).toBe("Failure") -}) - -test("preserves transformed encoded and decoded representations", async () => { - const decoded = await Effect.runPromise(Schema.decodeUnknownEffect(Port)("8080")) - const encoded = await Effect.runPromise(Schema.encodeEffect(Port)(decoded)) - - expect(Number(decoded)).toBe(8080) - expect(encoded).toBe("8080") -})