1import {
2 defineContract,
3 enumType,
4 field,
5 member,
6 model,
7} from "@/Prisma/ORM/index.ts";
8import { emitSchemas, makeSchemas, SchemaError } from "@/Prisma/ORM/Schema.ts";
9import { expect, it } from "alchemy-test";
10import * as Effect from "effect/Effect";
11import * as Schema from "effect/Schema";
12import { contract } from "./fixtures/client/contract.ts";
13
14it.effect(
15 "derives row schemas without a client and preserves nullable fields",
16 () =>
17 Effect.gen(function* () {
18 const schemas = makeSchemas(contract);
19 const row = yield* Schema.decodeUnknownEffect(schemas.public.User)({
20 id: 1,
21 email: "a@example.com",
22 name: null,
23 });
24 expect(row).toEqual({ id: 1, email: "a@example.com", name: null });
25 expect(
26 Schema.is(schemas.public.User)({
27 id: "1",
28 email: "a@example.com",
29 name: null,
30 }),
31 ).toBe(false);
32 expect(
33 Schema.is(schemas.public.User)({ id: 1, email: "a@example.com" }),
34 ).toBe(false);
35 expect(
36 Schema.is(schemas.public.User)({
37 id: 1.5,
38 email: "a@example.com",
39 name: null,
40 }),
41 ).toBe(false);
42 }),
43 { tags: ["unit", "provider:prisma", "provider:prisma:orm", "local"] },
44);
45
46it.effect(
47 "maps lists and namespaces without including navigation relations",
48 () =>
49 Effect.sync(() => {
50 const contract = defineContract(
51 { namespaces: ["auth"] },
52 ({ field, model }) => ({
53 models: {
54 Account: model("Account", {
55 namespace: "auth",
56 fields: {
57 id: field.int().id(),
58 tags: field.text().many(),
59 name: field.text().optional(),
60 },
61 }),
62 },
63 }),
64 );
65 const schemas = makeSchemas(contract);
66 expect(
67 Schema.is(schemas.auth.Account)({ id: 1, tags: ["a"], name: null }),
68 ).toBe(true);
69 expect(
70 Schema.is(schemas.auth.Account)({ id: 1, tags: [1], name: null }),
71 ).toBe(false);
72 expect(emitSchemas(contract)).not.toContain("Postgres");
73 expect(emitSchemas(contract)).not.toContain("@prisma");
74 }),
75 { tags: ["unit", "provider:prisma", "provider:prisma:orm", "local"] },
76);
77
78it.effect(
79 "preserves enums, bigint, decimals, bytes, temporal strings, and JSON",
80 () =>
81 Effect.sync(() => {
82 const Status = enumType(
83 "Status",
84 { codecId: "pg/text@1", nativeType: "text" },
85 member("Active", "active"),
86 member("Inactive", "inactive"),
87 );
88 const contract = defineContract({
89 enums: { Status },
90 models: {
91 Value: model("Value", {
92 fields: {
93 id: field
94 .column({ codecId: "pg/int8@1", nativeType: "int8" })
95 .id(),
96 decimal: field.column({
97 codecId: "pg/numeric@1",
98 nativeType: "numeric",
99 }),
100 bytes: field.column({
101 codecId: "pg/bytea@1",
102 nativeType: "bytea",
103 }),
104 uuid: field.column({ codecId: "pg/uuid@1", nativeType: "uuid" }),
105 date: field.column({
106 codecId: "pg/date-string@1",
107 nativeType: "date",
108 }),
109 json: field.column({
110 codecId: "pg/jsonb@1",
111 nativeType: "jsonb",
112 }),
113 status: field.namedType(Status),
114 },
115 }),
116 },
117 });
118 const schema = makeSchemas(contract).public.Value;
119 const row = {
120 id: 9007199254740993n,
121 decimal: "12345678901234567890.123456",
122 bytes: new Uint8Array([1]),
123 date: "2026-09-18",
124 uuid: "00000000-0000-0000-0000-000000000001",
125 json: { nested: [null, 1, true] },
126 status: "active",
127 };
128 expect(Schema.is(schema)(row)).toBe(true);
129 expect(Schema.is(schema)({ ...row, id: 1 })).toBe(false);
130 expect(Schema.is(schema)({ ...row, decimal: 1.5 })).toBe(false);
131 expect(Schema.is(schema)({ ...row, decimal: "invalid" })).toBe(false);
132 expect(Schema.is(schema)({ ...row, json: { invalid: undefined } })).toBe(
133 false,
134 );
135 expect(Schema.is(schema)({ ...row, status: "missing" })).toBe(false);
136 expect(emitSchemas(contract)).toContain(
137 'Schema.Literals(["active","inactive"])',
138 );
139 }),
140 { tags: ["unit", "provider:prisma", "provider:prisma:orm", "local"] },
141);
142
143it.effect(
144 "emits deterministic standalone expressions for the same contract",
145 () =>
146 Effect.sync(() => {
147 const first = emitSchemas(contract);
148 expect(first).toBe(emitSchemas(contract));
149 expect(first).toContain('"name": Schema.NullOr(Schema.String)');
150 expect(first).not.toContain('"posts":');
151 expect(first).not.toContain('"author":');
152 }),
153 { tags: ["unit", "provider:prisma", "provider:prisma:orm", "local"] },
154);
155
156it.effect(
157 "rejects unsupported codecs instead of silently accepting unknown rows",
158 () =>
159 Effect.sync(() => {
160 const custom = {
161 ...contract,
162 domain: {
163 namespaces: {
164 public: {
165 models: {
166 Custom: {
167 fields: {
168 id: {
169 nullable: false,
170 type: { kind: "scalar" as const, codecId: "custom/id@1" },
171 },
172 },
173 relations: {},
174 storage: {},
175 },
176 },
177 },
178 },
179 },
180 };
181 expect(() => makeSchemas(custom)).toThrow(SchemaError);
182 expect(() => emitSchemas(custom)).toThrow(
183 'options.codecs["custom/id@1"]',
184 );
185 const options = {
186 codecs: {
187 "custom/id@1": { schema: Schema.String, expression: "Schema.String" },
188 },
189 };
190 expect(
191 Schema.is(makeSchemas(custom, options).public.Custom)({ id: "custom" }),
192 ).toBe(true);
193 expect(emitSchemas(custom, options)).toContain('"id": Schema.String');
194 }),
195 { tags: ["unit", "provider:prisma", "provider:prisma:orm", "local"] },
196);