prisma-effect-schema 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Frontcore
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,176 @@
1
+ # prisma-effect-schema
2
+
3
+ A Prisma generator that creates [Effect Schema](https://effect.website/docs/schema/introduction) definitions from your Prisma models.
4
+
5
+ ## Features
6
+
7
+ - Generates Effect Schemas for all Prisma models and enums
8
+ - Creates branded ID types for type-safe entity references
9
+ - Handles all Prisma scalar types (String, Int, Float, Boolean, DateTime, Json, Bytes, BigInt, Decimal)
10
+ - Deterministic output (sorted fields/models) to minimize git diffs
11
+ - No timestamps in generated files to avoid unnecessary churn
12
+ - Configurable via Prisma schema
13
+
14
+ ## Installation
15
+
16
+ ```bash
17
+ npm install prisma-effect-schema
18
+ # or
19
+ pnpm add prisma-effect-schema
20
+ ```
21
+
22
+ ## Usage
23
+
24
+ Add the generator to your `schema.prisma`:
25
+
26
+ ```prisma
27
+ generator client {
28
+ provider = "prisma-client-js"
29
+ }
30
+
31
+ generator effectSchema {
32
+ provider = "prisma-effect-schema"
33
+ output = "./generated/effect-schemas.ts"
34
+ }
35
+
36
+ datasource db {
37
+ provider = "postgresql"
38
+ url = env("DATABASE_URL")
39
+ }
40
+
41
+ model User {
42
+ id String @id @default(cuid())
43
+ email String @unique
44
+ name String?
45
+ posts Post[]
46
+ createdAt DateTime @default(now())
47
+ }
48
+
49
+ model Post {
50
+ id String @id @default(cuid())
51
+ title String
52
+ content String?
53
+ published Boolean @default(false)
54
+ authorId String
55
+ author User @relation(fields: [authorId], references: [id])
56
+ }
57
+ ```
58
+
59
+ Then run:
60
+
61
+ ```bash
62
+ npx prisma generate
63
+ ```
64
+
65
+ This will generate `effect-schemas.ts` with:
66
+
67
+ ```typescript
68
+ import { Schema } from "effect";
69
+
70
+ // Branded IDs
71
+ export const UserId = Schema.String.pipe(Schema.brand("UserId"));
72
+ export type UserId = typeof UserId.Type;
73
+
74
+ export const PostId = Schema.String.pipe(Schema.brand("PostId"));
75
+ export type PostId = typeof PostId.Type;
76
+
77
+ // Models
78
+ export const User = Schema.Struct({
79
+ id: UserId,
80
+ email: Schema.String,
81
+ name: Schema.NullOr(Schema.String),
82
+ createdAt: Schema.Date,
83
+ });
84
+ export type User = typeof User.Type;
85
+
86
+ export const Post = Schema.Struct({
87
+ id: PostId,
88
+ title: Schema.String,
89
+ content: Schema.NullOr(Schema.String),
90
+ published: Schema.Boolean,
91
+ authorId: UserId, // References User's branded ID
92
+ });
93
+ export type Post = typeof Post.Type;
94
+ ```
95
+
96
+ ## Configuration Options
97
+
98
+ ```prisma
99
+ generator effectSchema {
100
+ provider = "prisma-effect-schema"
101
+ output = "./generated/effect-schemas.ts"
102
+
103
+ // Include relation fields (uses Schema.suspend for circular refs)
104
+ // Default: false
105
+ includeRelations = "true"
106
+
107
+ // Generate branded ID types (UserId, PostId, etc.)
108
+ // Default: true
109
+ useBrandedIds = "true"
110
+
111
+ // Sort fields alphabetically for deterministic output
112
+ // Default: true
113
+ sortFields = "true"
114
+ }
115
+ ```
116
+
117
+ ## Type Mapping
118
+
119
+ | Prisma Type | Effect Schema |
120
+ | -------------- | ----------------------------- |
121
+ | `String` | `Schema.String` |
122
+ | `Int` | `Schema.Int` |
123
+ | `Float` | `Schema.Number` |
124
+ | `Boolean` | `Schema.Boolean` |
125
+ | `DateTime` | `Schema.Date` |
126
+ | `Json` | `JsonValueSchema` (recursive) |
127
+ | `Bytes` | `Schema.Uint8Array` |
128
+ | `BigInt` | `Schema.BigInt` |
129
+ | `Decimal` | `Schema.String` |
130
+ | `Enum` | `Schema.Literal(...)` |
131
+ | Optional (`?`) | `Schema.NullOr(...)` |
132
+ | List (`[]`) | `Schema.Array(...)` |
133
+
134
+ ## Date Handling
135
+
136
+ By default, the generator uses `Schema.Date` which expects JavaScript `Date` objects. This matches what Prisma returns from database queries.
137
+
138
+ If you're validating API input where dates come as ISO strings, you can compose the schema:
139
+
140
+ ```typescript
141
+ import { Schema } from "effect";
142
+ import { User } from "./generated/effect-schemas";
143
+
144
+ // For API input validation
145
+ const UserInput = User.pipe(
146
+ Schema.transform(User, {
147
+ decode: (input) => ({
148
+ ...input,
149
+ createdAt: new Date(input.createdAt),
150
+ }),
151
+ encode: (user) => ({
152
+ ...user,
153
+ createdAt: user.createdAt.toISOString(),
154
+ }),
155
+ }),
156
+ );
157
+ ```
158
+
159
+ ## Programmatic API
160
+
161
+ You can also use the generator programmatically:
162
+
163
+ ```typescript
164
+ import { generate, resolveConfig } from "prisma-effect-schema";
165
+ import { getDMMF } from "@prisma/sdk";
166
+
167
+ const dmmf = await getDMMF({ datamodelPath: "./prisma/schema.prisma" });
168
+ const config = resolveConfig({ useBrandedIds: true });
169
+
170
+ const { content, stats } = generate({ dmmf, config });
171
+ console.log(`Generated ${stats.modelCount} models`);
172
+ ```
173
+
174
+ ## License
175
+
176
+ MIT
package/dist/bin.d.ts ADDED
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
package/dist/bin.js ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+ import "./chunk-AV5EXPPU.js";
3
+ import "./chunk-25MM3WYP.js";
4
+ //# sourceMappingURL=bin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,416 @@
1
+ // src/config.ts
2
+ import { Array as Arr, Option, pipe, Schema } from "effect";
3
+ var firstString = (value) => pipe(value, Arr.ensure, Arr.head);
4
+ var BooleanFromString = Schema.transform(
5
+ Schema.Union(Schema.String, Schema.Array(Schema.String)),
6
+ Schema.Boolean,
7
+ {
8
+ decode: (value) => pipe(value, firstString, Option.map((s) => s === "true"), Option.getOrElse(() => false)),
9
+ encode: (b) => b ? "true" : "false"
10
+ }
11
+ );
12
+ var DateTimeHandling = Schema.transform(
13
+ Schema.Union(Schema.String, Schema.Array(Schema.String)),
14
+ Schema.Literal("Date", "DateTimeString"),
15
+ {
16
+ decode: (value) => pipe(
17
+ value,
18
+ firstString,
19
+ Option.filter((s) => s === "DateTimeString"),
20
+ Option.map(() => "DateTimeString"),
21
+ Option.getOrElse(() => "Date")
22
+ ),
23
+ encode: (s) => s
24
+ }
25
+ );
26
+ var GeneratorConfigSchema = Schema.Struct({
27
+ /**
28
+ * Whether to include relation fields in the generated schemas.
29
+ * Relations use Schema.suspend() for lazy evaluation to handle circular deps.
30
+ * @default false
31
+ */
32
+ includeRelations: Schema.optionalWith(BooleanFromString, {
33
+ default: () => false
34
+ }),
35
+ /**
36
+ * Whether to generate branded ID types for models with string IDs.
37
+ * When true, generates `UserId`, `PostId`, etc. and uses them in model schemas.
38
+ * @default true
39
+ */
40
+ useBrandedIds: Schema.optionalWith(BooleanFromString, {
41
+ default: () => true
42
+ }),
43
+ /**
44
+ * How to handle DateTime fields.
45
+ * - 'Date': Use Schema.Date (expects Date objects, for Prisma results)
46
+ * - 'DateTimeString': Use Schema.Date with dateTime annotation (for API validation)
47
+ * @default 'Date'
48
+ */
49
+ dateTimeHandling: Schema.optionalWith(DateTimeHandling, {
50
+ default: () => "Date"
51
+ }),
52
+ /**
53
+ * Whether to sort fields alphabetically for deterministic output.
54
+ * @default true
55
+ */
56
+ sortFields: Schema.optionalWith(BooleanFromString, {
57
+ default: () => true
58
+ }),
59
+ /**
60
+ * Custom header to prepend to the generated file.
61
+ * If not provided, uses a default header without timestamps.
62
+ */
63
+ customHeader: Schema.optionalWith(
64
+ Schema.transform(
65
+ Schema.Union(Schema.String, Schema.Array(Schema.String)),
66
+ Schema.NullOr(Schema.String),
67
+ {
68
+ decode: (value) => pipe(value, firstString, Option.getOrElse(() => null)),
69
+ encode: (s) => s ?? ""
70
+ }
71
+ ),
72
+ { default: () => null }
73
+ )
74
+ });
75
+ var parseConfig = (options) => Schema.decodeUnknown(GeneratorConfigSchema)(options.generator.config);
76
+
77
+ // src/errors.ts
78
+ import dedent from "dedent";
79
+ import { Schema as Schema2 } from "effect";
80
+ var AppTag = "[prisma-effect-schema]";
81
+ var UnsupportedTypeError = class extends Schema2.TaggedError()(
82
+ "UnsupportedTypeError",
83
+ {
84
+ typeName: Schema2.String,
85
+ fieldName: Schema2.String,
86
+ modelName: Schema2.String
87
+ }
88
+ ) {
89
+ get message() {
90
+ return dedent`
91
+ ${AppTag} Unsupported Prisma type "${this.typeName}" for field "${this.fieldName}" in model "${this.modelName}".
92
+ Please open an issue at https://github.com/frontcore/prisma-effect-schema/issues
93
+ `;
94
+ }
95
+ };
96
+ var NoOutputConfiguredError = class extends Schema2.TaggedError()(
97
+ "NoOutputConfiguredError",
98
+ {
99
+ cause: Schema2.Unknown,
100
+ details: Schema2.String
101
+ }
102
+ ) {
103
+ static message = `${AppTag} No output path specified in generator config`;
104
+ };
105
+
106
+ // src/emit.ts
107
+ import { Match } from "effect";
108
+ var emitBaseType = (base) => Match.value(base).pipe(
109
+ Match.tag(
110
+ "Primitive",
111
+ ({ schema }) => schema === "Json" ? "JsonValueSchema" : `Schema.${schema}`
112
+ ),
113
+ Match.tag("BrandedId", ({ name }) => name),
114
+ Match.tag("Enum", ({ name }) => name),
115
+ Match.tag("Relation", ({ modelName }) => `Schema.suspend(() => ${modelName})`),
116
+ Match.exhaustive
117
+ );
118
+ var applyWrapper = (inner, wrapper) => {
119
+ switch (wrapper) {
120
+ case "Array":
121
+ return `Schema.Array(${inner})`;
122
+ case "NullOr":
123
+ return `Schema.NullOr(${inner})`;
124
+ }
125
+ };
126
+ var emit = (type) => type.wrappers.reduce(applyWrapper, emitBaseType(type.base));
127
+
128
+ // src/resolver.ts
129
+ import {
130
+ Array as Arr2,
131
+ Data,
132
+ HashMap,
133
+ Option as Option2,
134
+ pipe as pipe2,
135
+ Record
136
+ } from "effect";
137
+ import { capitalize } from "effect/String";
138
+ var Primitive = class extends Data.TaggedClass("Primitive") {
139
+ };
140
+ var BrandedId = class extends Data.TaggedClass("BrandedId") {
141
+ };
142
+ var Enum = class extends Data.TaggedClass("Enum") {
143
+ };
144
+ var Relation = class extends Data.TaggedClass("Relation") {
145
+ };
146
+ var ResolvedType = class extends Data.Class {
147
+ };
148
+ var ScalarTypeMap = {
149
+ Int: "Int",
150
+ Float: "Number",
151
+ Boolean: "Boolean",
152
+ Json: "Json",
153
+ Bytes: "Uint8Array",
154
+ BigInt: "BigInt",
155
+ Decimal: "Decimal"
156
+ };
157
+ var collectBrandedIds = (models) => pipe2(
158
+ models,
159
+ Arr2.filterMap((model) => {
160
+ const pkField = model.fields.find((f) => f.isId && f.type === "String");
161
+ if (!pkField) return Option2.none();
162
+ const suffix = capitalize(pkField.name);
163
+ return Option2.some([model.name, `${model.name}${suffix}`]);
164
+ }),
165
+ HashMap.fromIterable
166
+ );
167
+ var buildForeignKeyMap = (models) => pipe2(
168
+ models,
169
+ Arr2.flatMap(
170
+ (model) => pipe2(
171
+ model.fields,
172
+ Arr2.filter((field) => field.kind === "object"),
173
+ Arr2.flatMap((relationField) => {
174
+ const fkFields = relationField.relationFromFields ?? [];
175
+ const targetModel = relationField.type;
176
+ return fkFields.map((fkField) => [fkField, targetModel]);
177
+ })
178
+ )
179
+ ),
180
+ HashMap.fromIterable
181
+ );
182
+ var SchemaResolver = {
183
+ make: (resolverConfig) => {
184
+ const { modelName, brandedIds, foreignKeys, config } = resolverConfig;
185
+ const resolveBrandedId = (field) => {
186
+ if (!config.useBrandedIds) return Option2.none();
187
+ if (field.isId) {
188
+ return HashMap.get(brandedIds, modelName);
189
+ }
190
+ return pipe2(
191
+ HashMap.get(foreignKeys, field.name),
192
+ Option2.flatMap((targetModel) => HashMap.get(brandedIds, targetModel))
193
+ );
194
+ };
195
+ const resolveScalarType = (field) => {
196
+ if (field.type === "DateTime") {
197
+ return new Primitive({
198
+ schema: config.dateTimeHandling === "DateTimeString" ? "DateTimeUtc" : "Date"
199
+ });
200
+ }
201
+ const mapping = Record.get(ScalarTypeMap, field.type);
202
+ if (Option2.isSome(mapping)) {
203
+ return new Primitive({ schema: mapping.value });
204
+ }
205
+ if (field.type === "String") {
206
+ return pipe2(
207
+ resolveBrandedId(field),
208
+ Option2.match({
209
+ onNone: () => new Primitive({ schema: "String" }),
210
+ onSome: (name) => new BrandedId({ name })
211
+ })
212
+ );
213
+ }
214
+ throw new UnsupportedTypeError({
215
+ typeName: field.type,
216
+ fieldName: field.name,
217
+ modelName
218
+ });
219
+ };
220
+ const resolveBaseType = (field) => {
221
+ switch (field.kind) {
222
+ case "enum":
223
+ return new Enum({ name: field.type });
224
+ case "object":
225
+ return new Relation({ modelName: field.type });
226
+ default:
227
+ return resolveScalarType(field);
228
+ }
229
+ };
230
+ const resolve = (field) => {
231
+ const wrappers = [];
232
+ if (field.isList) {
233
+ wrappers.push("Array");
234
+ }
235
+ if (!field.isRequired) {
236
+ wrappers.push("NullOr");
237
+ }
238
+ return new ResolvedType({
239
+ base: resolveBaseType(field),
240
+ wrappers
241
+ });
242
+ };
243
+ const fieldToSchema = (field) => emit(resolve(field));
244
+ return {
245
+ resolve,
246
+ fieldToSchema,
247
+ brandedIds
248
+ };
249
+ }
250
+ };
251
+
252
+ // src/templates.ts
253
+ import dedent2 from "dedent";
254
+ import { Array as Arr3, HashMap as HashMap2, Order, pipe as pipe3 } from "effect";
255
+ var DEFAULT_HEADER = dedent2`
256
+ // This file was auto-generated by prisma-effect-schema
257
+ // Do not edit manually - changes will be overwritten
258
+ // https://github.com/frontcore/prisma-effect-schema
259
+
260
+ import { Schema } from "effect"
261
+ `;
262
+ var JSON_VALUE_SCHEMA = dedent2`
263
+ // Recursive JSON value schema matching Prisma's JsonValue type
264
+ type JsonValue = string | number | boolean | null | JsonArray | JsonObject
265
+ type JsonArray = ReadonlyArray<JsonValue>
266
+ type JsonObject = { readonly [key: string]: JsonValue }
267
+
268
+ const JsonValueSchema: Schema.Schema<JsonValue> = Schema.suspend(
269
+ (): Schema.Schema<JsonValue> =>
270
+ Schema.Union(
271
+ Schema.Null,
272
+ Schema.Boolean,
273
+ Schema.Number,
274
+ Schema.String,
275
+ Schema.Array(JsonValueSchema),
276
+ Schema.Record({ key: Schema.String, value: JsonValueSchema })
277
+ )
278
+ )
279
+ `;
280
+ var sectionHeader = (title) => dedent2`
281
+ // ============================================================================
282
+ // ${title}
283
+ // ============================================================================
284
+
285
+ `;
286
+ var ByName = () => Order.mapInput(Order.string, (item) => item.name);
287
+ var sortByName = (items) => Arr3.sort(items, ByName());
288
+ var generateEnumSchema = (enumDef) => {
289
+ const values = pipe3(
290
+ sortByName(enumDef.values),
291
+ Arr3.map((v) => `"${v.name}"`),
292
+ Arr3.join(", ")
293
+ );
294
+ return dedent2`
295
+ export const ${enumDef.name} = Schema.Literal(${values})
296
+ export type ${enumDef.name} = typeof ${enumDef.name}.Type
297
+ `;
298
+ };
299
+ var generateEnumSchemas = (enums) => pipe3(sortByName(enums), Arr3.map(generateEnumSchema), Arr3.join("\n"));
300
+ var generateBrandedIdSchema = ([, brandedIdName]) => dedent2`
301
+ export const ${brandedIdName} = Schema.String.pipe(Schema.brand("${brandedIdName}"))
302
+ export type ${brandedIdName} = typeof ${brandedIdName}.Type
303
+ `;
304
+ var generateBrandedIdSchemas = (brandedIds) => pipe3(
305
+ brandedIds,
306
+ HashMap2.toEntries,
307
+ Arr3.sort(Order.mapInput(Order.string, ([key]) => key)),
308
+ Arr3.map(generateBrandedIdSchema),
309
+ Arr3.join("\n\n")
310
+ );
311
+ var generateFieldsCode = (fields, resolver, sortFields) => pipe3(
312
+ sortFields ? sortByName(fields) : fields,
313
+ Arr3.map((field) => ` ${field.name}: ${resolver.fieldToSchema(field)}`),
314
+ Arr3.join(",\n")
315
+ );
316
+ var generateModelSchema = (model, resolver, config) => {
317
+ const scalarFields = model.fields.filter((f) => f.kind !== "object");
318
+ const hasRelations = model.fields.some((f) => f.kind === "object");
319
+ const scalarFieldsCode = generateFieldsCode(
320
+ scalarFields,
321
+ resolver,
322
+ config.sortFields
323
+ );
324
+ const baseSchema = dedent2`
325
+ export const ${model.name} = Schema.Struct({
326
+ ${scalarFieldsCode}
327
+ })
328
+ export type ${model.name} = typeof ${model.name}.Type
329
+ `;
330
+ if (!config.includeRelations || !hasRelations) {
331
+ return baseSchema;
332
+ }
333
+ const allFieldsCode = generateFieldsCode(
334
+ model.fields,
335
+ resolver,
336
+ config.sortFields
337
+ );
338
+ return dedent2`
339
+ ${baseSchema}
340
+
341
+ export const ${model.name}WithRelations = Schema.Struct({
342
+ ${allFieldsCode}
343
+ })
344
+ export type ${model.name}WithRelations = typeof ${model.name}WithRelations.Type
345
+ `;
346
+ };
347
+ var generateModelSchemas = (models, makeResolver, config) => pipe3(
348
+ sortByName(models),
349
+ Arr3.map((model) => generateModelSchema(model, makeResolver(model.name), config)),
350
+ Arr3.join("\n")
351
+ );
352
+
353
+ // src/generate.ts
354
+ import { HashMap as HashMap3 } from "effect";
355
+ var generate = (input) => {
356
+ const { dmmf, config } = input;
357
+ const { models, enums } = dmmf.datamodel;
358
+ const brandedIds = config.useBrandedIds ? collectBrandedIds(models) : HashMap3.empty();
359
+ const foreignKeys = buildForeignKeyMap(models);
360
+ const brandedIdCount = HashMap3.size(brandedIds);
361
+ const hasJsonFields = models.some(
362
+ (model) => model.fields.some((field) => field.type === "Json")
363
+ );
364
+ const makeResolver = (modelName) => SchemaResolver.make({ modelName, brandedIds, foreignKeys, config });
365
+ const sections = [
366
+ // Header
367
+ config.customHeader ?? DEFAULT_HEADER,
368
+ // JSON schema (only if needed)
369
+ ...hasJsonFields ? ["\n" + JSON_VALUE_SCHEMA] : [],
370
+ // Enums
371
+ ...enums.length > 0 ? [sectionHeader("Enums"), generateEnumSchemas(enums)] : [],
372
+ // Branded IDs
373
+ ...brandedIdCount > 0 ? [sectionHeader("Branded IDs"), generateBrandedIdSchemas(brandedIds), ""] : [],
374
+ // Models
375
+ sectionHeader("Models (scalar fields only)"),
376
+ generateModelSchemas(models, makeResolver, config)
377
+ ];
378
+ return {
379
+ content: sections.join(""),
380
+ stats: {
381
+ enumCount: enums.length,
382
+ modelCount: models.length,
383
+ brandedIdCount
384
+ }
385
+ };
386
+ };
387
+
388
+ export {
389
+ GeneratorConfigSchema,
390
+ parseConfig,
391
+ UnsupportedTypeError,
392
+ NoOutputConfiguredError,
393
+ emitBaseType,
394
+ applyWrapper,
395
+ emit,
396
+ Primitive,
397
+ BrandedId,
398
+ Enum,
399
+ Relation,
400
+ ResolvedType,
401
+ collectBrandedIds,
402
+ buildForeignKeyMap,
403
+ SchemaResolver,
404
+ DEFAULT_HEADER,
405
+ JSON_VALUE_SCHEMA,
406
+ sectionHeader,
407
+ generateEnumSchema,
408
+ generateEnumSchemas,
409
+ generateBrandedIdSchema,
410
+ generateBrandedIdSchemas,
411
+ generateFieldsCode,
412
+ generateModelSchema,
413
+ generateModelSchemas,
414
+ generate
415
+ };
416
+ //# sourceMappingURL=chunk-25MM3WYP.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/config.ts","../src/errors.ts","../src/emit.ts","../src/resolver.ts","../src/templates.ts","../src/generate.ts"],"sourcesContent":["import type { GeneratorOptions } from \"@prisma/generator-helper\";\nimport { Array as Arr, Option, pipe, Schema } from \"effect\";\n\n/**\n * Prisma config values can be string | string[] - normalize to first string\n */\nconst firstString = (value: string | readonly string[]): Option.Option<string> =>\n pipe(value, Arr.ensure, Arr.head);\n\n/**\n * Schema for parsing \"true\"/\"false\" strings to booleans (handles string | string[])\n */\nconst BooleanFromString = Schema.transform(\n Schema.Union(Schema.String, Schema.Array(Schema.String)),\n Schema.Boolean,\n {\n decode: (value) => pipe(value, firstString, Option.map((s) => s === \"true\"), Option.getOrElse(() => false)),\n encode: (b) => (b ? \"true\" : \"false\"),\n }\n);\n\n/**\n * Schema for DateTime handling mode (handles string | string[])\n */\nconst DateTimeHandling = Schema.transform(\n Schema.Union(Schema.String, Schema.Array(Schema.String)),\n Schema.Literal(\"Date\", \"DateTimeString\"),\n {\n decode: (value) =>\n pipe(\n value,\n firstString,\n Option.filter((s) => s === \"DateTimeString\"),\n Option.map(() => \"DateTimeString\" as const),\n Option.getOrElse(() => \"Date\" as const)\n ),\n encode: (s) => s,\n }\n);\n\n/**\n * Generator configuration schema with defaults.\n * Parses Prisma generator config and applies defaults in one step.\n */\nexport const GeneratorConfigSchema = Schema.Struct({\n /**\n * Whether to include relation fields in the generated schemas.\n * Relations use Schema.suspend() for lazy evaluation to handle circular deps.\n * @default false\n */\n includeRelations: Schema.optionalWith(BooleanFromString, {\n default: () => false,\n }),\n\n /**\n * Whether to generate branded ID types for models with string IDs.\n * When true, generates `UserId`, `PostId`, etc. and uses them in model schemas.\n * @default true\n */\n useBrandedIds: Schema.optionalWith(BooleanFromString, {\n default: () => true,\n }),\n\n /**\n * How to handle DateTime fields.\n * - 'Date': Use Schema.Date (expects Date objects, for Prisma results)\n * - 'DateTimeString': Use Schema.Date with dateTime annotation (for API validation)\n * @default 'Date'\n */\n dateTimeHandling: Schema.optionalWith(DateTimeHandling, {\n default: () => \"Date\" as const,\n }),\n\n /**\n * Whether to sort fields alphabetically for deterministic output.\n * @default true\n */\n sortFields: Schema.optionalWith(BooleanFromString, {\n default: () => true,\n }),\n\n /**\n * Custom header to prepend to the generated file.\n * If not provided, uses a default header without timestamps.\n */\n customHeader: Schema.optionalWith(\n Schema.transform(\n Schema.Union(Schema.String, Schema.Array(Schema.String)),\n Schema.NullOr(Schema.String),\n {\n decode: (value) => pipe(value, firstString, Option.getOrElse(() => null as string | null)),\n encode: (s) => s ?? \"\",\n }\n ),\n { default: () => null }\n ),\n});\n\n/**\n * Resolved configuration type (derived from schema)\n */\nexport type GeneratorConfig = typeof GeneratorConfigSchema.Type;\n\n\n\n/**\n * Parse generator config from Prisma schema using Effect Schema\n */\nexport const parseConfig = (options: GeneratorOptions) =>\n Schema.decodeUnknown(GeneratorConfigSchema)(options.generator.config);\n","import dedent from \"dedent\";\nimport { Schema } from \"effect\";\n\nexport const AppTag = \"[prisma-effect-schema]\";\n\n/**\n * Error thrown when an unsupported Prisma type is encountered\n */\nexport class UnsupportedTypeError extends Schema.TaggedError<UnsupportedTypeError>()(\n \"UnsupportedTypeError\",\n {\n typeName: Schema.String,\n fieldName: Schema.String,\n modelName: Schema.String,\n },\n) {\n override get message(): string {\n return dedent`\n ${AppTag} Unsupported Prisma type \"${this.typeName}\" for field \"${this.fieldName}\" in model \"${this.modelName}\". \n Please open an issue at https://github.com/frontcore/prisma-effect-schema/issues\n `;\n }\n}\n\nexport class NoOutputConfiguredError extends Schema.TaggedError<NoOutputConfiguredError>()(\n \"NoOutputConfiguredError\",\n {\n cause: Schema.Unknown,\n details: Schema.String,\n },\n) {\n public static message = `${AppTag} No output path specified in generator config`;\n}\n\nexport const ConfigError = NoOutputConfiguredError;\n","/**\n * Code Emission Module\n *\n * Pure functions for transforming resolved types into Effect Schema strings.\n * Separated from resolution logic for testability and reusability.\n */\nimport { Match } from \"effect\";\nimport type { BaseType, ResolvedType, Wrapper } from \"./resolver.js\";\n\n/**\n * Emit a base type to its Effect Schema string representation\n */\nexport const emitBaseType = (base: BaseType): string =>\n Match.value(base).pipe(\n Match.tag(\"Primitive\", ({ schema }) =>\n schema === \"Json\" ? \"JsonValueSchema\" : `Schema.${schema}`\n ),\n Match.tag(\"BrandedId\", ({ name }) => name),\n Match.tag(\"Enum\", ({ name }) => name),\n Match.tag(\"Relation\", ({ modelName }) => `Schema.suspend(() => ${modelName})`),\n Match.exhaustive\n );\n\n/**\n * Apply a single wrapper to a schema string\n */\nexport const applyWrapper = (inner: string, wrapper: Wrapper): string => {\n switch (wrapper) {\n case \"Array\":\n return `Schema.Array(${inner})`;\n case \"NullOr\":\n return `Schema.NullOr(${inner})`;\n }\n};\n\n/**\n * Emit a fully resolved type (base + wrappers) to Effect Schema string.\n * Wrappers are applied left-to-right (innermost first).\n */\nexport const emit = (type: ResolvedType): string =>\n type.wrappers.reduce(applyWrapper, emitBaseType(type.base));\n","/**\n * Type Resolution Module\n *\n * Separates the \"thinking\" (what type should this field be?) from the \"writing\"\n * (how do we emit it as a string?). Returns structured data that can be tested,\n * logged, and transformed before emission.\n */\nimport type { DMMF } from \"@prisma/generator-helper\";\nimport {\n Array as Arr,\n Data,\n HashMap,\n Option,\n pipe,\n Record,\n} from \"effect\";\nimport { capitalize } from \"effect/String\";\nimport type { GeneratorConfig } from \"./config.js\";\nimport { emit } from \"./emit.js\";\nimport { UnsupportedTypeError } from \"./errors.js\";\n\n// ============================================================================\n// Resolved Types (Data.TaggedClass for structural equality + pattern matching)\n// ============================================================================\n\n/**\n * A primitive scalar type from Prisma mapped to Effect Schema\n */\nexport class Primitive extends Data.TaggedClass(\"Primitive\")<{\n readonly schema:\n | \"Int\"\n | \"String\"\n | \"Boolean\"\n | \"Number\"\n | \"Date\" // Schema.Date - for Prisma results (Date objects)\n | \"DateTimeUtc\" // Schema.DateTimeUtc - for API validation (ISO strings)\n | \"BigInt\"\n | \"Uint8Array\"\n | \"Json\"\n | \"Decimal\";\n}> {}\n\n/**\n * A branded ID type for type-safe IDs\n */\nexport class BrandedId extends Data.TaggedClass(\"BrandedId\")<{\n readonly name: string;\n}> {}\n\n/**\n * An enum type reference\n */\nexport class Enum extends Data.TaggedClass(\"Enum\")<{\n readonly name: string;\n}> {}\n\n/**\n * A relation to another model (uses Schema.suspend for circular refs)\n */\nexport class Relation extends Data.TaggedClass(\"Relation\")<{\n readonly modelName: string;\n}> {}\n\n/**\n * Union of all possible base types a field can resolve to\n */\nexport type BaseType = Primitive | BrandedId | Enum | Relation;\n\n/**\n * Wrappers that can be applied to a base type\n */\nexport type Wrapper = \"Array\" | \"NullOr\";\n\n/**\n * A fully resolved field type: base type + wrappers to apply\n */\nexport class ResolvedType extends Data.Class<{\n readonly base: BaseType;\n readonly wrappers: readonly Wrapper[];\n}> {}\n\n// ============================================================================\n// SchemaResolver Interface\n// ============================================================================\n\nexport interface SchemaResolver {\n /**\n * Resolve a field to its structured type representation.\n * Use this for testing or when you need to inspect the decision.\n */\n readonly resolve: (field: DMMF.Field) => ResolvedType;\n\n /**\n * Convenience method: resolve + emit in one call.\n * Use this for the common case where you just need the string.\n */\n readonly fieldToSchema: (field: DMMF.Field) => string;\n\n /**\n * The computed branded IDs map (modelName -> brandedIdName).\n * Exposed for generating branded ID schema declarations.\n */\n readonly brandedIds: HashMap.HashMap<string, string>;\n}\n\n// ============================================================================\n// Internal: Scalar Type Mapping\n// ============================================================================\n\ntype PrimitiveSchema = Primitive[\"schema\"];\n\n/**\n * Maps Prisma scalar types to Primitive schema names.\n * Note: String is handled separately (may become BrandedId).\n * Note: DateTime is handled separately (respects dateTimeHandling config).\n */\nconst ScalarTypeMap: Record.ReadonlyRecord<string, PrimitiveSchema> = {\n Int: \"Int\",\n Float: \"Number\",\n Boolean: \"Boolean\",\n Json: \"Json\",\n Bytes: \"Uint8Array\",\n BigInt: \"BigInt\",\n Decimal: \"Decimal\",\n};\n\n// ============================================================================\n// Branded ID Collection (uses actual PK field name for suffix)\n// ============================================================================\n\n/**\n * Collects branded IDs for models with string primary keys.\n * Uses the actual PK field name for the suffix:\n * - User.id (String @id) -> \"UserId\"\n * - Course.slug (String @id) -> \"CourseSlug\"\n */\nexport const collectBrandedIds = (\n models: readonly DMMF.Model[]\n): HashMap.HashMap<string, string> =>\n pipe(\n models,\n Arr.filterMap((model) => {\n const pkField = model.fields.find((f) => f.isId && f.type === \"String\");\n if (!pkField) return Option.none();\n \n // Use the PK field name, capitalized: \"id\" -> \"Id\", \"slug\" -> \"Slug\"\n const suffix = capitalize(pkField.name);\n return Option.some([model.name, `${model.name}${suffix}`] as const);\n }),\n HashMap.fromIterable\n );\n\n// ============================================================================\n// Foreign Key Map (relation-based, not heuristic)\n// ============================================================================\n\n/**\n * Builds a map from FK field names to their target model names.\n * Uses DMMF relation metadata (relationFromFields) for accuracy.\n * \n * Example output:\n * {\n * \"userId\": \"User\",\n * \"authorId\": \"User\",\n * \"courseSlug\": \"Course\",\n * \"avatarId\": \"File\"\n * }\n */\nexport const buildForeignKeyMap = (\n models: readonly DMMF.Model[]\n): HashMap.HashMap<string, string> =>\n pipe(\n models,\n Arr.flatMap((model) =>\n pipe(\n model.fields,\n Arr.filter((field) => field.kind === \"object\"),\n Arr.flatMap((relationField) => {\n // relationFromFields contains the FK field names for this relation\n const fkFields = relationField.relationFromFields ?? [];\n // The relation's type is the target model name\n const targetModel = relationField.type;\n \n return fkFields.map((fkField) => [fkField, targetModel] as const);\n })\n )\n ),\n HashMap.fromIterable\n );\n\n// ============================================================================\n// SchemaResolver Factory\n// ============================================================================\n\nexport interface SchemaResolverConfig {\n readonly modelName: string;\n readonly brandedIds: HashMap.HashMap<string, string>;\n readonly foreignKeys: HashMap.HashMap<string, string>;\n readonly config: GeneratorConfig;\n}\n\n/**\n * Create a SchemaResolver for a specific model.\n * Dependencies are captured at construction time.\n */\nexport const SchemaResolver = {\n make: (resolverConfig: SchemaResolverConfig): SchemaResolver => {\n const { modelName, brandedIds, foreignKeys, config } = resolverConfig;\n\n // ========================================================================\n // Branded ID Resolution (relation-based)\n // ========================================================================\n\n const resolveBrandedId = (field: DMMF.Field): Option.Option<string> => {\n if (!config.useBrandedIds) return Option.none();\n\n // Primary key uses this model's branded ID\n if (field.isId) {\n return HashMap.get(brandedIds, modelName);\n }\n\n // Foreign key - look up in FK map, then get target model's branded ID\n return pipe(\n HashMap.get(foreignKeys, field.name),\n Option.flatMap((targetModel) => HashMap.get(brandedIds, targetModel))\n );\n };\n\n // ========================================================================\n // Base Type Resolution\n // ========================================================================\n\n const resolveScalarType = (field: DMMF.Field): BaseType => {\n // Handle DateTime with config\n if (field.type === \"DateTime\") {\n return new Primitive({\n schema: config.dateTimeHandling === \"DateTimeString\" ? \"DateTimeUtc\" : \"Date\",\n });\n }\n\n // Check non-String scalar types\n const mapping = Record.get(ScalarTypeMap, field.type);\n if (Option.isSome(mapping)) {\n return new Primitive({ schema: mapping.value });\n }\n\n // String type: try branded ID, fallback to Primitive String\n if (field.type === \"String\") {\n return pipe(\n resolveBrandedId(field),\n Option.match({\n onNone: () => new Primitive({ schema: \"String\" }),\n onSome: (name) => new BrandedId({ name }),\n })\n );\n }\n\n throw new UnsupportedTypeError({\n typeName: field.type,\n fieldName: field.name,\n modelName,\n });\n };\n\n const resolveBaseType = (field: DMMF.Field): BaseType => {\n switch (field.kind) {\n case \"enum\":\n return new Enum({ name: field.type });\n case \"object\":\n return new Relation({ modelName: field.type });\n default:\n return resolveScalarType(field);\n }\n };\n\n // ========================================================================\n // Full Resolution (base + wrappers)\n // ========================================================================\n\n const resolve = (field: DMMF.Field): ResolvedType => {\n const wrappers: Wrapper[] = [];\n\n if (field.isList) {\n wrappers.push(\"Array\");\n }\n\n if (!field.isRequired) {\n wrappers.push(\"NullOr\");\n }\n\n return new ResolvedType({\n base: resolveBaseType(field),\n wrappers,\n });\n };\n\n const fieldToSchema = (field: DMMF.Field): string => emit(resolve(field));\n\n return {\n resolve,\n fieldToSchema,\n brandedIds,\n };\n },\n};\n","/**\n * Code Templates Module\n *\n * String templates for generating Effect Schema source code.\n * Separated from orchestration logic for clarity and testability.\n */\nimport type { DMMF } from \"@prisma/generator-helper\";\nimport dedent from \"dedent\";\nimport { Array as Arr, HashMap, Order, pipe } from \"effect\";\nimport type { GeneratorConfig } from \"./config.js\";\nimport type { SchemaResolver } from \"./resolver.js\";\n\n// ============================================================================\n// Constants\n// ============================================================================\n\nexport const DEFAULT_HEADER = dedent`\n // This file was auto-generated by prisma-effect-schema\n // Do not edit manually - changes will be overwritten\n // https://github.com/frontcore/prisma-effect-schema\n\n import { Schema } from \"effect\"\n`;\n\nexport const JSON_VALUE_SCHEMA = dedent`\n // Recursive JSON value schema matching Prisma's JsonValue type\n type JsonValue = string | number | boolean | null | JsonArray | JsonObject\n type JsonArray = ReadonlyArray<JsonValue>\n type JsonObject = { readonly [key: string]: JsonValue }\n\n const JsonValueSchema: Schema.Schema<JsonValue> = Schema.suspend(\n (): Schema.Schema<JsonValue> =>\n Schema.Union(\n Schema.Null,\n Schema.Boolean,\n Schema.Number,\n Schema.String,\n Schema.Array(JsonValueSchema),\n Schema.Record({ key: Schema.String, value: JsonValueSchema })\n )\n )\n`;\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\nexport const sectionHeader = (title: string): string => dedent`\n // ============================================================================\n // ${title}\n // ============================================================================\n\n`;\n\nconst ByName = <T extends { name: string }>(): Order.Order<T> =>\n Order.mapInput(Order.string, (item: T) => item.name);\n\nconst sortByName = <T extends { name: string }>(items: readonly T[]): T[] =>\n Arr.sort(items, ByName());\n\n// ============================================================================\n// Enum Templates\n// ============================================================================\n\nexport const generateEnumSchema = (enumDef: DMMF.DatamodelEnum): string => {\n const values = pipe(\n sortByName(enumDef.values),\n Arr.map((v) => `\"${v.name}\"`),\n Arr.join(\", \")\n );\n\n return dedent`\n export const ${enumDef.name} = Schema.Literal(${values})\n export type ${enumDef.name} = typeof ${enumDef.name}.Type\n `;\n};\n\nexport const generateEnumSchemas = (\n enums: readonly DMMF.DatamodelEnum[]\n): string =>\n pipe(sortByName(enums), Arr.map(generateEnumSchema), Arr.join(\"\\n\"));\n\n// ============================================================================\n// Branded ID Templates\n// ============================================================================\n\nexport const generateBrandedIdSchema = ([, brandedIdName]: readonly [\n string,\n string\n]): string =>\n dedent`\n export const ${brandedIdName} = Schema.String.pipe(Schema.brand(\"${brandedIdName}\"))\n export type ${brandedIdName} = typeof ${brandedIdName}.Type\n `;\n\nexport const generateBrandedIdSchemas = (\n brandedIds: HashMap.HashMap<string, string>\n): string =>\n pipe(\n brandedIds,\n HashMap.toEntries,\n Arr.sort(Order.mapInput(Order.string, ([key]: [string, string]) => key)),\n Arr.map(generateBrandedIdSchema),\n Arr.join(\"\\n\\n\")\n );\n\n// ============================================================================\n// Field Templates\n// ============================================================================\n\nexport const generateFieldsCode = (\n fields: readonly DMMF.Field[],\n resolver: SchemaResolver,\n sortFields: boolean\n): string =>\n pipe(\n sortFields ? sortByName(fields) : fields,\n Arr.map((field) => ` ${field.name}: ${resolver.fieldToSchema(field)}`),\n Arr.join(\",\\n\")\n );\n\n// ============================================================================\n// Model Templates\n// ============================================================================\n\nexport const generateModelSchema = (\n model: DMMF.Model,\n resolver: SchemaResolver,\n config: GeneratorConfig\n): string => {\n const scalarFields = model.fields.filter((f) => f.kind !== \"object\");\n const hasRelations = model.fields.some((f) => f.kind === \"object\");\n\n const scalarFieldsCode = generateFieldsCode(\n scalarFields,\n resolver,\n config.sortFields\n );\n\n const baseSchema = dedent`\n export const ${model.name} = Schema.Struct({\n ${scalarFieldsCode}\n })\n export type ${model.name} = typeof ${model.name}.Type\n `;\n\n // Optionally generate schema with relations\n if (!config.includeRelations || !hasRelations) {\n return baseSchema;\n }\n\n const allFieldsCode = generateFieldsCode(\n model.fields,\n resolver,\n config.sortFields\n );\n\n return dedent`\n ${baseSchema}\n\n export const ${model.name}WithRelations = Schema.Struct({\n ${allFieldsCode}\n })\n export type ${model.name}WithRelations = typeof ${model.name}WithRelations.Type\n `;\n};\n\nexport const generateModelSchemas = (\n models: readonly DMMF.Model[],\n makeResolver: (modelName: string) => SchemaResolver,\n config: GeneratorConfig\n): string =>\n pipe(\n sortByName(models),\n Arr.map((model) => generateModelSchema(model, makeResolver(model.name), config)),\n Arr.join(\"\\n\")\n );\n","/**\n * Effect Schema Code Generation\n *\n * Orchestrates generation of Effect Schema source code from Prisma DMMF.\n */\nimport type { DMMF } from \"@prisma/generator-helper\";\nimport { HashMap } from \"effect\";\nimport type { GeneratorConfig } from \"./config.js\";\nimport { buildForeignKeyMap, collectBrandedIds, SchemaResolver } from \"./resolver.js\";\nimport {\n DEFAULT_HEADER,\n generateBrandedIdSchemas,\n generateEnumSchemas,\n generateModelSchemas,\n JSON_VALUE_SCHEMA,\n sectionHeader,\n} from \"./templates.js\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface GenerateInput {\n dmmf: DMMF.Document;\n config: GeneratorConfig;\n}\n\nexport interface GenerateOutput {\n content: string;\n stats: {\n enumCount: number;\n modelCount: number;\n brandedIdCount: number;\n };\n}\n\n// ============================================================================\n// Main Entry Point\n// ============================================================================\n\n/**\n * Generates Effect Schema source code from Prisma DMMF.\n */\nexport const generate = (input: GenerateInput): GenerateOutput => {\n const { dmmf, config } = input;\n const { models, enums } = dmmf.datamodel;\n\n // Collect branded IDs from models with string primary keys\n const brandedIds = config.useBrandedIds\n ? collectBrandedIds(models)\n : HashMap.empty<string, string>();\n\n // Build FK map from relation metadata\n const foreignKeys = buildForeignKeyMap(models);\n\n const brandedIdCount = HashMap.size(brandedIds);\n\n // Check if any model has Json fields (to conditionally include JsonValueSchema)\n const hasJsonFields = models.some((model) =>\n model.fields.some((field) => field.type === \"Json\")\n );\n\n // Factory for creating resolvers per model\n const makeResolver = (modelName: string) =>\n SchemaResolver.make({ modelName, brandedIds, foreignKeys, config });\n\n // Assemble sections\n const sections = [\n // Header\n config.customHeader ?? DEFAULT_HEADER,\n\n // JSON schema (only if needed)\n ...(hasJsonFields ? [\"\\n\" + JSON_VALUE_SCHEMA] : []),\n\n // Enums\n ...(enums.length > 0\n ? [sectionHeader(\"Enums\"), generateEnumSchemas(enums)]\n : []),\n\n // Branded IDs\n ...(brandedIdCount > 0\n ? [sectionHeader(\"Branded IDs\"), generateBrandedIdSchemas(brandedIds), \"\"]\n : []),\n\n // Models\n sectionHeader(\"Models (scalar fields only)\"),\n generateModelSchemas(models, makeResolver, config),\n ];\n\n return {\n content: sections.join(\"\"),\n stats: {\n enumCount: enums.length,\n modelCount: models.length,\n brandedIdCount,\n },\n };\n};\n"],"mappings":";AACA,SAAS,SAAS,KAAK,QAAQ,MAAM,cAAc;AAKnD,IAAM,cAAc,CAAC,UACnB,KAAK,OAAO,IAAI,QAAQ,IAAI,IAAI;AAKlC,IAAM,oBAAoB,OAAO;AAAA,EAC/B,OAAO,MAAM,OAAO,QAAQ,OAAO,MAAM,OAAO,MAAM,CAAC;AAAA,EACvD,OAAO;AAAA,EACP;AAAA,IACE,QAAQ,CAAC,UAAU,KAAK,OAAO,aAAa,OAAO,IAAI,CAAC,MAAM,MAAM,MAAM,GAAG,OAAO,UAAU,MAAM,KAAK,CAAC;AAAA,IAC1G,QAAQ,CAAC,MAAO,IAAI,SAAS;AAAA,EAC/B;AACF;AAKA,IAAM,mBAAmB,OAAO;AAAA,EAC9B,OAAO,MAAM,OAAO,QAAQ,OAAO,MAAM,OAAO,MAAM,CAAC;AAAA,EACvD,OAAO,QAAQ,QAAQ,gBAAgB;AAAA,EACvC;AAAA,IACE,QAAQ,CAAC,UACP;AAAA,MACE;AAAA,MACA;AAAA,MACA,OAAO,OAAO,CAAC,MAAM,MAAM,gBAAgB;AAAA,MAC3C,OAAO,IAAI,MAAM,gBAAyB;AAAA,MAC1C,OAAO,UAAU,MAAM,MAAe;AAAA,IACxC;AAAA,IACF,QAAQ,CAAC,MAAM;AAAA,EACjB;AACF;AAMO,IAAM,wBAAwB,OAAO,OAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMjD,kBAAkB,OAAO,aAAa,mBAAmB;AAAA,IACvD,SAAS,MAAM;AAAA,EACjB,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOD,eAAe,OAAO,aAAa,mBAAmB;AAAA,IACpD,SAAS,MAAM;AAAA,EACjB,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQD,kBAAkB,OAAO,aAAa,kBAAkB;AAAA,IACtD,SAAS,MAAM;AAAA,EACjB,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA,EAMD,YAAY,OAAO,aAAa,mBAAmB;AAAA,IACjD,SAAS,MAAM;AAAA,EACjB,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA,EAMD,cAAc,OAAO;AAAA,IACnB,OAAO;AAAA,MACL,OAAO,MAAM,OAAO,QAAQ,OAAO,MAAM,OAAO,MAAM,CAAC;AAAA,MACvD,OAAO,OAAO,OAAO,MAAM;AAAA,MAC3B;AAAA,QACE,QAAQ,CAAC,UAAU,KAAK,OAAO,aAAa,OAAO,UAAU,MAAM,IAAqB,CAAC;AAAA,QACzF,QAAQ,CAAC,MAAM,KAAK;AAAA,MACtB;AAAA,IACF;AAAA,IACA,EAAE,SAAS,MAAM,KAAK;AAAA,EACxB;AACF,CAAC;AAYM,IAAM,cAAc,CAAC,YAC1B,OAAO,cAAc,qBAAqB,EAAE,QAAQ,UAAU,MAAM;;;AC7GtE,OAAO,YAAY;AACnB,SAAS,UAAAA,eAAc;AAEhB,IAAM,SAAS;AAKf,IAAM,uBAAN,cAAmCA,QAAO,YAAkC;AAAA,EACjF;AAAA,EACA;AAAA,IACE,UAAUA,QAAO;AAAA,IACjB,WAAWA,QAAO;AAAA,IAClB,WAAWA,QAAO;AAAA,EACpB;AACF,EAAE;AAAA,EACA,IAAa,UAAkB;AAC7B,WAAO;AAAA,QACH,MAAM,6BAA6B,KAAK,QAAQ,gBAAgB,KAAK,SAAS,eAAe,KAAK,SAAS;AAAA;AAAA;AAAA,EAGjH;AACF;AAEO,IAAM,0BAAN,cAAsCA,QAAO,YAAqC;AAAA,EACvF;AAAA,EACA;AAAA,IACE,OAAOA,QAAO;AAAA,IACd,SAASA,QAAO;AAAA,EAClB;AACF,EAAE;AAAA,EACA,OAAc,UAAU,GAAG,MAAM;AACnC;;;AC1BA,SAAS,aAAa;AAMf,IAAM,eAAe,CAAC,SAC3B,MAAM,MAAM,IAAI,EAAE;AAAA,EAChB,MAAM;AAAA,IAAI;AAAA,IAAa,CAAC,EAAE,OAAO,MAC/B,WAAW,SAAS,oBAAoB,UAAU,MAAM;AAAA,EAC1D;AAAA,EACA,MAAM,IAAI,aAAa,CAAC,EAAE,KAAK,MAAM,IAAI;AAAA,EACzC,MAAM,IAAI,QAAQ,CAAC,EAAE,KAAK,MAAM,IAAI;AAAA,EACpC,MAAM,IAAI,YAAY,CAAC,EAAE,UAAU,MAAM,wBAAwB,SAAS,GAAG;AAAA,EAC7E,MAAM;AACR;AAKK,IAAM,eAAe,CAAC,OAAe,YAA6B;AACvE,UAAQ,SAAS;AAAA,IACf,KAAK;AACH,aAAO,gBAAgB,KAAK;AAAA,IAC9B,KAAK;AACH,aAAO,iBAAiB,KAAK;AAAA,EACjC;AACF;AAMO,IAAM,OAAO,CAAC,SACnB,KAAK,SAAS,OAAO,cAAc,aAAa,KAAK,IAAI,CAAC;;;AChC5D;AAAA,EACE,SAASC;AAAA,EACT;AAAA,EACA;AAAA,EACA,UAAAC;AAAA,EACA,QAAAC;AAAA,EACA;AAAA,OACK;AACP,SAAS,kBAAkB;AAYpB,IAAM,YAAN,cAAwB,KAAK,YAAY,WAAW,EAYxD;AAAC;AAKG,IAAM,YAAN,cAAwB,KAAK,YAAY,WAAW,EAExD;AAAC;AAKG,IAAM,OAAN,cAAmB,KAAK,YAAY,MAAM,EAE9C;AAAC;AAKG,IAAM,WAAN,cAAuB,KAAK,YAAY,UAAU,EAEtD;AAAC;AAeG,IAAM,eAAN,cAA2B,KAAK,MAGpC;AAAC;AAqCJ,IAAM,gBAAgE;AAAA,EACpE,KAAK;AAAA,EACL,OAAO;AAAA,EACP,SAAS;AAAA,EACT,MAAM;AAAA,EACN,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,SAAS;AACX;AAYO,IAAM,oBAAoB,CAC/B,WAEAC;AAAA,EACE;AAAA,EACAC,KAAI,UAAU,CAAC,UAAU;AACvB,UAAM,UAAU,MAAM,OAAO,KAAK,CAAC,MAAM,EAAE,QAAQ,EAAE,SAAS,QAAQ;AACtE,QAAI,CAAC,QAAS,QAAOC,QAAO,KAAK;AAGjC,UAAM,SAAS,WAAW,QAAQ,IAAI;AACtC,WAAOA,QAAO,KAAK,CAAC,MAAM,MAAM,GAAG,MAAM,IAAI,GAAG,MAAM,EAAE,CAAU;AAAA,EACpE,CAAC;AAAA,EACD,QAAQ;AACV;AAkBK,IAAM,qBAAqB,CAChC,WAEAF;AAAA,EACE;AAAA,EACAC,KAAI;AAAA,IAAQ,CAAC,UACXD;AAAA,MACE,MAAM;AAAA,MACNC,KAAI,OAAO,CAAC,UAAU,MAAM,SAAS,QAAQ;AAAA,MAC7CA,KAAI,QAAQ,CAAC,kBAAkB;AAE7B,cAAM,WAAW,cAAc,sBAAsB,CAAC;AAEtD,cAAM,cAAc,cAAc;AAElC,eAAO,SAAS,IAAI,CAAC,YAAY,CAAC,SAAS,WAAW,CAAU;AAAA,MAClE,CAAC;AAAA,IACH;AAAA,EACF;AAAA,EACA,QAAQ;AACV;AAiBK,IAAM,iBAAiB;AAAA,EAC5B,MAAM,CAAC,mBAAyD;AAC9D,UAAM,EAAE,WAAW,YAAY,aAAa,OAAO,IAAI;AAMvD,UAAM,mBAAmB,CAAC,UAA6C;AACrE,UAAI,CAAC,OAAO,cAAe,QAAOC,QAAO,KAAK;AAG9C,UAAI,MAAM,MAAM;AACd,eAAO,QAAQ,IAAI,YAAY,SAAS;AAAA,MAC1C;AAGA,aAAOF;AAAA,QACL,QAAQ,IAAI,aAAa,MAAM,IAAI;AAAA,QACnCE,QAAO,QAAQ,CAAC,gBAAgB,QAAQ,IAAI,YAAY,WAAW,CAAC;AAAA,MACtE;AAAA,IACF;AAMA,UAAM,oBAAoB,CAAC,UAAgC;AAEzD,UAAI,MAAM,SAAS,YAAY;AAC7B,eAAO,IAAI,UAAU;AAAA,UACnB,QAAQ,OAAO,qBAAqB,mBAAmB,gBAAgB;AAAA,QACzE,CAAC;AAAA,MACH;AAGA,YAAM,UAAU,OAAO,IAAI,eAAe,MAAM,IAAI;AACpD,UAAIA,QAAO,OAAO,OAAO,GAAG;AAC1B,eAAO,IAAI,UAAU,EAAE,QAAQ,QAAQ,MAAM,CAAC;AAAA,MAChD;AAGA,UAAI,MAAM,SAAS,UAAU;AAC3B,eAAOF;AAAA,UACL,iBAAiB,KAAK;AAAA,UACtBE,QAAO,MAAM;AAAA,YACX,QAAQ,MAAM,IAAI,UAAU,EAAE,QAAQ,SAAS,CAAC;AAAA,YAChD,QAAQ,CAAC,SAAS,IAAI,UAAU,EAAE,KAAK,CAAC;AAAA,UAC1C,CAAC;AAAA,QACH;AAAA,MACF;AAEA,YAAM,IAAI,qBAAqB;AAAA,QAC7B,UAAU,MAAM;AAAA,QAChB,WAAW,MAAM;AAAA,QACjB;AAAA,MACF,CAAC;AAAA,IACH;AAEA,UAAM,kBAAkB,CAAC,UAAgC;AACvD,cAAQ,MAAM,MAAM;AAAA,QAClB,KAAK;AACH,iBAAO,IAAI,KAAK,EAAE,MAAM,MAAM,KAAK,CAAC;AAAA,QACtC,KAAK;AACH,iBAAO,IAAI,SAAS,EAAE,WAAW,MAAM,KAAK,CAAC;AAAA,QAC/C;AACE,iBAAO,kBAAkB,KAAK;AAAA,MAClC;AAAA,IACF;AAMA,UAAM,UAAU,CAAC,UAAoC;AACnD,YAAM,WAAsB,CAAC;AAE7B,UAAI,MAAM,QAAQ;AAChB,iBAAS,KAAK,OAAO;AAAA,MACvB;AAEA,UAAI,CAAC,MAAM,YAAY;AACrB,iBAAS,KAAK,QAAQ;AAAA,MACxB;AAEA,aAAO,IAAI,aAAa;AAAA,QACtB,MAAM,gBAAgB,KAAK;AAAA,QAC3B;AAAA,MACF,CAAC;AAAA,IACH;AAEA,UAAM,gBAAgB,CAAC,UAA8B,KAAK,QAAQ,KAAK,CAAC;AAExE,WAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AACF;;;ACzSA,OAAOC,aAAY;AACnB,SAAS,SAASC,MAAK,WAAAC,UAAS,OAAO,QAAAC,aAAY;AAQ5C,IAAM,iBAAiBH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAQvB,IAAM,oBAAoBA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAuB1B,IAAM,gBAAgB,CAAC,UAA0BA;AAAA;AAAA,OAEjD,KAAK;AAAA;AAAA;AAAA;AAKZ,IAAM,SAAS,MACb,MAAM,SAAS,MAAM,QAAQ,CAAC,SAAY,KAAK,IAAI;AAErD,IAAM,aAAa,CAA6B,UAC9CC,KAAI,KAAK,OAAO,OAAO,CAAC;AAMnB,IAAM,qBAAqB,CAAC,YAAwC;AACzE,QAAM,SAASE;AAAA,IACb,WAAW,QAAQ,MAAM;AAAA,IACzBF,KAAI,IAAI,CAAC,MAAM,IAAI,EAAE,IAAI,GAAG;AAAA,IAC5BA,KAAI,KAAK,IAAI;AAAA,EACf;AAEA,SAAOD;AAAA,mBACU,QAAQ,IAAI,qBAAqB,MAAM;AAAA,kBACxC,QAAQ,IAAI,aAAa,QAAQ,IAAI;AAAA;AAEvD;AAEO,IAAM,sBAAsB,CACjC,UAEAG,MAAK,WAAW,KAAK,GAAGF,KAAI,IAAI,kBAAkB,GAAGA,KAAI,KAAK,IAAI,CAAC;AAM9D,IAAM,0BAA0B,CAAC,CAAC,EAAE,aAAa,MAItDD;AAAA,mBACiB,aAAa,uCAAuC,aAAa;AAAA,kBAClE,aAAa,aAAa,aAAa;AAAA;AAGlD,IAAM,2BAA2B,CACtC,eAEAG;AAAA,EACE;AAAA,EACAD,SAAQ;AAAA,EACRD,KAAI,KAAK,MAAM,SAAS,MAAM,QAAQ,CAAC,CAAC,GAAG,MAAwB,GAAG,CAAC;AAAA,EACvEA,KAAI,IAAI,uBAAuB;AAAA,EAC/BA,KAAI,KAAK,MAAM;AACjB;AAMK,IAAM,qBAAqB,CAChC,QACA,UACA,eAEAE;AAAA,EACE,aAAa,WAAW,MAAM,IAAI;AAAA,EAClCF,KAAI,IAAI,CAAC,UAAU,KAAK,MAAM,IAAI,KAAK,SAAS,cAAc,KAAK,CAAC,EAAE;AAAA,EACtEA,KAAI,KAAK,KAAK;AAChB;AAMK,IAAM,sBAAsB,CACjC,OACA,UACA,WACW;AACX,QAAM,eAAe,MAAM,OAAO,OAAO,CAAC,MAAM,EAAE,SAAS,QAAQ;AACnE,QAAM,eAAe,MAAM,OAAO,KAAK,CAAC,MAAM,EAAE,SAAS,QAAQ;AAEjE,QAAM,mBAAmB;AAAA,IACvB;AAAA,IACA;AAAA,IACA,OAAO;AAAA,EACT;AAEA,QAAM,aAAaD;AAAA,mBACF,MAAM,IAAI;AAAA,QACrB,gBAAgB;AAAA;AAAA,kBAEN,MAAM,IAAI,aAAa,MAAM,IAAI;AAAA;AAIjD,MAAI,CAAC,OAAO,oBAAoB,CAAC,cAAc;AAC7C,WAAO;AAAA,EACT;AAEA,QAAM,gBAAgB;AAAA,IACpB,MAAM;AAAA,IACN;AAAA,IACA,OAAO;AAAA,EACT;AAEA,SAAOA;AAAA,MACH,UAAU;AAAA;AAAA,mBAEG,MAAM,IAAI;AAAA,QACrB,aAAa;AAAA;AAAA,kBAEH,MAAM,IAAI,0BAA0B,MAAM,IAAI;AAAA;AAEhE;AAEO,IAAM,uBAAuB,CAClC,QACA,cACA,WAEAG;AAAA,EACE,WAAW,MAAM;AAAA,EACjBF,KAAI,IAAI,CAAC,UAAU,oBAAoB,OAAO,aAAa,MAAM,IAAI,GAAG,MAAM,CAAC;AAAA,EAC/EA,KAAI,KAAK,IAAI;AACf;;;AC1KF,SAAS,WAAAG,gBAAe;AAqCjB,IAAM,WAAW,CAAC,UAAyC;AAChE,QAAM,EAAE,MAAM,OAAO,IAAI;AACzB,QAAM,EAAE,QAAQ,MAAM,IAAI,KAAK;AAG/B,QAAM,aAAa,OAAO,gBACtB,kBAAkB,MAAM,IACxBC,SAAQ,MAAsB;AAGlC,QAAM,cAAc,mBAAmB,MAAM;AAE7C,QAAM,iBAAiBA,SAAQ,KAAK,UAAU;AAG9C,QAAM,gBAAgB,OAAO;AAAA,IAAK,CAAC,UACjC,MAAM,OAAO,KAAK,CAAC,UAAU,MAAM,SAAS,MAAM;AAAA,EACpD;AAGA,QAAM,eAAe,CAAC,cACpB,eAAe,KAAK,EAAE,WAAW,YAAY,aAAa,OAAO,CAAC;AAGpE,QAAM,WAAW;AAAA;AAAA,IAEf,OAAO,gBAAgB;AAAA;AAAA,IAGvB,GAAI,gBAAgB,CAAC,OAAO,iBAAiB,IAAI,CAAC;AAAA;AAAA,IAGlD,GAAI,MAAM,SAAS,IACf,CAAC,cAAc,OAAO,GAAG,oBAAoB,KAAK,CAAC,IACnD,CAAC;AAAA;AAAA,IAGL,GAAI,iBAAiB,IACjB,CAAC,cAAc,aAAa,GAAG,yBAAyB,UAAU,GAAG,EAAE,IACvE,CAAC;AAAA;AAAA,IAGL,cAAc,6BAA6B;AAAA,IAC3C,qBAAqB,QAAQ,cAAc,MAAM;AAAA,EACnD;AAEA,SAAO;AAAA,IACL,SAAS,SAAS,KAAK,EAAE;AAAA,IACzB,OAAO;AAAA,MACL,WAAW,MAAM;AAAA,MACjB,YAAY,OAAO;AAAA,MACnB;AAAA,IACF;AAAA,EACF;AACF;","names":["Schema","Arr","Option","pipe","pipe","Arr","Option","dedent","Arr","HashMap","pipe","HashMap","HashMap"]}
@@ -0,0 +1,55 @@
1
+ import {
2
+ NoOutputConfiguredError,
3
+ generate,
4
+ parseConfig
5
+ } from "./chunk-25MM3WYP.js";
6
+
7
+ // src/generator.ts
8
+ import { FileSystem } from "@effect/platform";
9
+ import { NodeFileSystem, NodePath } from "@effect/platform-node";
10
+ import { Path } from "@effect/platform/Path";
11
+ import { generatorHandler } from "@prisma/generator-helper";
12
+ import { Effect, Layer } from "effect";
13
+ var program = Effect.fnUntraced(function* (options) {
14
+ const fs = yield* FileSystem.FileSystem;
15
+ const path = yield* Path;
16
+ const outputPath = yield* Effect.fromNullable(
17
+ options.generator.output?.value
18
+ ).pipe(
19
+ Effect.mapError(
20
+ ({ cause, message, ...rest }) => new NoOutputConfiguredError({
21
+ cause,
22
+ details: message,
23
+ ...rest
24
+ })
25
+ )
26
+ );
27
+ const config = yield* parseConfig(options);
28
+ const { content, stats } = generate({
29
+ dmmf: options.dmmf,
30
+ config
31
+ });
32
+ yield* fs.makeDirectory(path.dirname(outputPath), { recursive: true });
33
+ yield* fs.writeFileString(outputPath, content);
34
+ yield* Effect.logInfo(`\u2705 prisma-effect-schema: Generated Effect Schemas`);
35
+ yield* Effect.logInfo(` Output: ${outputPath}`);
36
+ yield* Effect.logInfo(
37
+ ` Stats: ${stats.enumCount} enums, ${stats.modelCount} models, ${stats.brandedIdCount} branded IDs`
38
+ );
39
+ });
40
+ var PlatformLive = Layer.mergeAll(NodeFileSystem.layer, NodePath.layer);
41
+ generatorHandler({
42
+ onManifest() {
43
+ return {
44
+ defaultOutput: "./generated/effect-schemas.ts",
45
+ prettyName: "Effect Schema Generator"
46
+ // No requiresGenerators - we only need the DMMF which is always available
47
+ // ?? explain
48
+ };
49
+ },
50
+ onGenerate: (options) => program(options).pipe(
51
+ Effect.provide(PlatformLive),
52
+ Effect.runPromise
53
+ )
54
+ });
55
+ //# sourceMappingURL=chunk-AV5EXPPU.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/generator.ts"],"sourcesContent":["/**\n * Prisma Generator Handler\n *\n * This module registers the generator with Prisma and handles the generation lifecycle.\n */\nimport { FileSystem } from \"@effect/platform\";\nimport { NodeFileSystem, NodePath } from \"@effect/platform-node\";\nimport { Path } from \"@effect/platform/Path\";\nimport type { GeneratorOptions } from \"@prisma/generator-helper\";\nimport { generatorHandler } from \"@prisma/generator-helper\";\nimport { Effect, Layer, Option } from \"effect\";\nimport { parseConfig } from \"./config.js\";\nimport { NoOutputConfiguredError } from \"./errors.js\";\nimport { generate } from \"./generate.js\";\n\n\n\nconst program = Effect.fnUntraced(function* (options: GeneratorOptions) {\n const fs = yield* FileSystem.FileSystem;\n const path = yield* Path;\n\n const outputPath = yield* Effect.fromNullable(\n options.generator.output?.value\n ).pipe(\n Effect.mapError(\n ({ cause, message, ...rest }) =>\n new NoOutputConfiguredError({\n cause,\n details: message,\n ...rest,\n })\n )\n );\n\n const config = yield* parseConfig(options);\n\n const { content, stats } = generate({\n dmmf: options.dmmf,\n config,\n });\n\n // Ensure output directory exists\n yield* fs.makeDirectory(path.dirname(outputPath), { recursive: true });\n\n // Write the generated file\n yield* fs.writeFileString(outputPath, content);\n\n yield* Effect.logInfo(`✅ prisma-effect-schema: Generated Effect Schemas`);\n yield* Effect.logInfo(` Output: ${outputPath}`);\n yield* Effect.logInfo(\n ` Stats: ${stats.enumCount} enums, ${stats.modelCount} models, ${stats.brandedIdCount} branded IDs`\n );\n})\nconst PlatformLive = Layer.mergeAll(NodeFileSystem.layer, NodePath.layer);\n\n\n\n// Register as Prisma generator\ngeneratorHandler({\n onManifest() {\n return {\n defaultOutput: \"./generated/effect-schemas.ts\",\n prettyName: \"Effect Schema Generator\",\n // No requiresGenerators - we only need the DMMF which is always available\n // ?? explain\n };\n },\n onGenerate: (options: GeneratorOptions) => program(options).pipe(\n Effect.provide(PlatformLive),\n Effect.runPromise\n ),\n});\n"],"mappings":";;;;;;;AAKA,SAAS,kBAAkB;AAC3B,SAAS,gBAAgB,gBAAgB;AACzC,SAAS,YAAY;AAErB,SAAS,wBAAwB;AACjC,SAAS,QAAQ,aAAqB;AAOtC,IAAM,UAAU,OAAO,WAAW,WAAW,SAA2B;AACtE,QAAM,KAAK,OAAO,WAAW;AAC7B,QAAM,OAAO,OAAO;AAEpB,QAAM,aAAa,OAAO,OAAO;AAAA,IAC/B,QAAQ,UAAU,QAAQ;AAAA,EAC5B,EAAE;AAAA,IACA,OAAO;AAAA,MACL,CAAC,EAAE,OAAO,SAAS,GAAG,KAAK,MACzB,IAAI,wBAAwB;AAAA,QAC1B;AAAA,QACA,SAAS;AAAA,QACT,GAAG;AAAA,MACL,CAAC;AAAA,IACL;AAAA,EACF;AAEA,QAAM,SAAS,OAAO,YAAY,OAAO;AAEzC,QAAM,EAAE,SAAS,MAAM,IAAI,SAAS;AAAA,IAClC,MAAM,QAAQ;AAAA,IACd;AAAA,EACF,CAAC;AAGD,SAAO,GAAG,cAAc,KAAK,QAAQ,UAAU,GAAG,EAAE,WAAW,KAAK,CAAC;AAGrE,SAAO,GAAG,gBAAgB,YAAY,OAAO;AAE7C,SAAO,OAAO,QAAQ,uDAAkD;AACxE,SAAO,OAAO,QAAQ,cAAc,UAAU,EAAE;AAChD,SAAO,OAAO;AAAA,IACZ,aAAa,MAAM,SAAS,WAAW,MAAM,UAAU,YAAY,MAAM,cAAc;AAAA,EACzF;AACF,CAAC;AACD,IAAM,eAAe,MAAM,SAAS,eAAe,OAAO,SAAS,KAAK;AAKxE,iBAAiB;AAAA,EACf,aAAa;AACX,WAAO;AAAA,MACL,eAAe;AAAA,MACf,YAAY;AAAA;AAAA;AAAA,IAGd;AAAA,EACF;AAAA,EACA,YAAY,CAAC,YAA8B,QAAQ,OAAO,EAAE;AAAA,IAC1D,OAAO,QAAQ,YAAY;AAAA,IAC3B,OAAO;AAAA,EACT;AACF,CAAC;","names":[]}
@@ -0,0 +1,2 @@
1
+
2
+ export { }
@@ -0,0 +1,3 @@
1
+ import "./chunk-AV5EXPPU.js";
2
+ import "./chunk-25MM3WYP.js";
3
+ //# sourceMappingURL=generator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,231 @@
1
+ import { Schema, Data, HashMap } from 'effect';
2
+ import { DMMF } from '@prisma/generator-helper';
3
+ import * as effect_Types from 'effect/Types';
4
+
5
+ /**
6
+ * Generator configuration schema with defaults.
7
+ * Parses Prisma generator config and applies defaults in one step.
8
+ */
9
+ declare const GeneratorConfigSchema: Schema.Struct<{
10
+ /**
11
+ * Whether to include relation fields in the generated schemas.
12
+ * Relations use Schema.suspend() for lazy evaluation to handle circular deps.
13
+ * @default false
14
+ */
15
+ includeRelations: Schema.optionalWith<Schema.transform<Schema.Union<[typeof Schema.String, Schema.Array$<typeof Schema.String>]>, typeof Schema.Boolean>, {
16
+ default: () => false;
17
+ }>;
18
+ /**
19
+ * Whether to generate branded ID types for models with string IDs.
20
+ * When true, generates `UserId`, `PostId`, etc. and uses them in model schemas.
21
+ * @default true
22
+ */
23
+ useBrandedIds: Schema.optionalWith<Schema.transform<Schema.Union<[typeof Schema.String, Schema.Array$<typeof Schema.String>]>, typeof Schema.Boolean>, {
24
+ default: () => true;
25
+ }>;
26
+ /**
27
+ * How to handle DateTime fields.
28
+ * - 'Date': Use Schema.Date (expects Date objects, for Prisma results)
29
+ * - 'DateTimeString': Use Schema.Date with dateTime annotation (for API validation)
30
+ * @default 'Date'
31
+ */
32
+ dateTimeHandling: Schema.optionalWith<Schema.transform<Schema.Union<[typeof Schema.String, Schema.Array$<typeof Schema.String>]>, Schema.Literal<["Date", "DateTimeString"]>>, {
33
+ default: () => "Date";
34
+ }>;
35
+ /**
36
+ * Whether to sort fields alphabetically for deterministic output.
37
+ * @default true
38
+ */
39
+ sortFields: Schema.optionalWith<Schema.transform<Schema.Union<[typeof Schema.String, Schema.Array$<typeof Schema.String>]>, typeof Schema.Boolean>, {
40
+ default: () => true;
41
+ }>;
42
+ /**
43
+ * Custom header to prepend to the generated file.
44
+ * If not provided, uses a default header without timestamps.
45
+ */
46
+ customHeader: Schema.optionalWith<Schema.transform<Schema.Union<[typeof Schema.String, Schema.Array$<typeof Schema.String>]>, Schema.NullOr<typeof Schema.String>>, {
47
+ default: () => null;
48
+ }>;
49
+ }>;
50
+ /**
51
+ * Resolved configuration type (derived from schema)
52
+ */
53
+ type GeneratorConfig = typeof GeneratorConfigSchema.Type;
54
+
55
+ declare const UnsupportedTypeError_base: Schema.TaggedErrorClass<UnsupportedTypeError, "UnsupportedTypeError", {
56
+ readonly _tag: Schema.tag<"UnsupportedTypeError">;
57
+ } & {
58
+ typeName: typeof Schema.String;
59
+ fieldName: typeof Schema.String;
60
+ modelName: typeof Schema.String;
61
+ }>;
62
+ /**
63
+ * Error thrown when an unsupported Prisma type is encountered
64
+ */
65
+ declare class UnsupportedTypeError extends UnsupportedTypeError_base {
66
+ get message(): string;
67
+ }
68
+
69
+ /**
70
+ * Effect Schema Code Generation
71
+ *
72
+ * Orchestrates generation of Effect Schema source code from Prisma DMMF.
73
+ */
74
+
75
+ interface GenerateInput {
76
+ dmmf: DMMF.Document;
77
+ config: GeneratorConfig;
78
+ }
79
+ interface GenerateOutput {
80
+ content: string;
81
+ stats: {
82
+ enumCount: number;
83
+ modelCount: number;
84
+ brandedIdCount: number;
85
+ };
86
+ }
87
+ /**
88
+ * Generates Effect Schema source code from Prisma DMMF.
89
+ */
90
+ declare const generate: (input: GenerateInput) => GenerateOutput;
91
+
92
+ declare const Primitive_base: new <A extends Record<string, any> = {}>(args: effect_Types.VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => Readonly<A> & {
93
+ readonly _tag: "Primitive";
94
+ };
95
+ /**
96
+ * A primitive scalar type from Prisma mapped to Effect Schema
97
+ */
98
+ declare class Primitive extends Primitive_base<{
99
+ readonly schema: "Int" | "String" | "Boolean" | "Number" | "Date" | "DateTimeUtc" | "BigInt" | "Uint8Array" | "Json" | "Decimal";
100
+ }> {
101
+ }
102
+ declare const BrandedId_base: new <A extends Record<string, any> = {}>(args: effect_Types.VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => Readonly<A> & {
103
+ readonly _tag: "BrandedId";
104
+ };
105
+ /**
106
+ * A branded ID type for type-safe IDs
107
+ */
108
+ declare class BrandedId extends BrandedId_base<{
109
+ readonly name: string;
110
+ }> {
111
+ }
112
+ declare const Enum_base: new <A extends Record<string, any> = {}>(args: effect_Types.VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => Readonly<A> & {
113
+ readonly _tag: "Enum";
114
+ };
115
+ /**
116
+ * An enum type reference
117
+ */
118
+ declare class Enum extends Enum_base<{
119
+ readonly name: string;
120
+ }> {
121
+ }
122
+ declare const Relation_base: new <A extends Record<string, any> = {}>(args: effect_Types.VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => Readonly<A> & {
123
+ readonly _tag: "Relation";
124
+ };
125
+ /**
126
+ * A relation to another model (uses Schema.suspend for circular refs)
127
+ */
128
+ declare class Relation extends Relation_base<{
129
+ readonly modelName: string;
130
+ }> {
131
+ }
132
+ /**
133
+ * Union of all possible base types a field can resolve to
134
+ */
135
+ type BaseType = Primitive | BrandedId | Enum | Relation;
136
+ /**
137
+ * Wrappers that can be applied to a base type
138
+ */
139
+ type Wrapper = "Array" | "NullOr";
140
+ /**
141
+ * A fully resolved field type: base type + wrappers to apply
142
+ */
143
+ declare class ResolvedType extends Data.Class<{
144
+ readonly base: BaseType;
145
+ readonly wrappers: readonly Wrapper[];
146
+ }> {
147
+ }
148
+ /**
149
+ * Collects branded IDs for models with string primary keys.
150
+ * Uses the actual PK field name for the suffix:
151
+ * - User.id (String @id) -> "UserId"
152
+ * - Course.slug (String @id) -> "CourseSlug"
153
+ */
154
+ declare const collectBrandedIds: (models: readonly DMMF.Model[]) => HashMap.HashMap<string, string>;
155
+ /**
156
+ * Builds a map from FK field names to their target model names.
157
+ * Uses DMMF relation metadata (relationFromFields) for accuracy.
158
+ *
159
+ * Example output:
160
+ * {
161
+ * "userId": "User",
162
+ * "authorId": "User",
163
+ * "courseSlug": "Course",
164
+ * "avatarId": "File"
165
+ * }
166
+ */
167
+ declare const buildForeignKeyMap: (models: readonly DMMF.Model[]) => HashMap.HashMap<string, string>;
168
+ interface SchemaResolverConfig {
169
+ readonly modelName: string;
170
+ readonly brandedIds: HashMap.HashMap<string, string>;
171
+ readonly foreignKeys: HashMap.HashMap<string, string>;
172
+ readonly config: GeneratorConfig;
173
+ }
174
+ interface SchemaResolver {
175
+ /**
176
+ * Resolve a field to its structured type representation.
177
+ * Use this for testing or when you need to inspect the decision.
178
+ */
179
+ readonly resolve: (field: DMMF.Field) => ResolvedType;
180
+ /**
181
+ * Convenience method: resolve + emit in one call.
182
+ * Use this for the common case where you just need the string.
183
+ */
184
+ readonly fieldToSchema: (field: DMMF.Field) => string;
185
+ /**
186
+ * The computed branded IDs map (modelName -> brandedIdName).
187
+ * Exposed for generating branded ID schema declarations.
188
+ */
189
+ readonly brandedIds: HashMap.HashMap<string, string>;
190
+ }
191
+ /**
192
+ * Create a SchemaResolver for a specific model.
193
+ * Dependencies are captured at construction time.
194
+ */
195
+ declare const SchemaResolver: {
196
+ make: (resolverConfig: SchemaResolverConfig) => SchemaResolver;
197
+ };
198
+
199
+ /**
200
+ * Emit a base type to its Effect Schema string representation
201
+ */
202
+ declare const emitBaseType: (base: BaseType) => string;
203
+ /**
204
+ * Apply a single wrapper to a schema string
205
+ */
206
+ declare const applyWrapper: (inner: string, wrapper: Wrapper) => string;
207
+ /**
208
+ * Emit a fully resolved type (base + wrappers) to Effect Schema string.
209
+ * Wrappers are applied left-to-right (innermost first).
210
+ */
211
+ declare const emit: (type: ResolvedType) => string;
212
+
213
+ /**
214
+ * Code Templates Module
215
+ *
216
+ * String templates for generating Effect Schema source code.
217
+ * Separated from orchestration logic for clarity and testability.
218
+ */
219
+
220
+ declare const DEFAULT_HEADER: string;
221
+ declare const JSON_VALUE_SCHEMA: string;
222
+ declare const sectionHeader: (title: string) => string;
223
+ declare const generateEnumSchema: (enumDef: DMMF.DatamodelEnum) => string;
224
+ declare const generateEnumSchemas: (enums: readonly DMMF.DatamodelEnum[]) => string;
225
+ declare const generateBrandedIdSchema: ([, brandedIdName]: readonly [string, string]) => string;
226
+ declare const generateBrandedIdSchemas: (brandedIds: HashMap.HashMap<string, string>) => string;
227
+ declare const generateFieldsCode: (fields: readonly DMMF.Field[], resolver: SchemaResolver, sortFields: boolean) => string;
228
+ declare const generateModelSchema: (model: DMMF.Model, resolver: SchemaResolver, config: GeneratorConfig) => string;
229
+ declare const generateModelSchemas: (models: readonly DMMF.Model[], makeResolver: (modelName: string) => SchemaResolver, config: GeneratorConfig) => string;
230
+
231
+ export { type BaseType, BrandedId, DEFAULT_HEADER, Enum, type GenerateInput, type GenerateOutput, type GeneratorConfig, GeneratorConfigSchema, JSON_VALUE_SCHEMA, Primitive, Relation, ResolvedType, SchemaResolver, type SchemaResolverConfig, UnsupportedTypeError, type Wrapper, applyWrapper, buildForeignKeyMap, collectBrandedIds, emit, emitBaseType, generate, generateBrandedIdSchema, generateBrandedIdSchemas, generateEnumSchema, generateEnumSchemas, generateFieldsCode, generateModelSchema, generateModelSchemas, sectionHeader };
package/dist/index.js ADDED
@@ -0,0 +1,53 @@
1
+ import {
2
+ BrandedId,
3
+ DEFAULT_HEADER,
4
+ Enum,
5
+ GeneratorConfigSchema,
6
+ JSON_VALUE_SCHEMA,
7
+ Primitive,
8
+ Relation,
9
+ ResolvedType,
10
+ SchemaResolver,
11
+ UnsupportedTypeError,
12
+ applyWrapper,
13
+ buildForeignKeyMap,
14
+ collectBrandedIds,
15
+ emit,
16
+ emitBaseType,
17
+ generate,
18
+ generateBrandedIdSchema,
19
+ generateBrandedIdSchemas,
20
+ generateEnumSchema,
21
+ generateEnumSchemas,
22
+ generateFieldsCode,
23
+ generateModelSchema,
24
+ generateModelSchemas,
25
+ sectionHeader
26
+ } from "./chunk-25MM3WYP.js";
27
+ export {
28
+ BrandedId,
29
+ DEFAULT_HEADER,
30
+ Enum,
31
+ GeneratorConfigSchema,
32
+ JSON_VALUE_SCHEMA,
33
+ Primitive,
34
+ Relation,
35
+ ResolvedType,
36
+ SchemaResolver,
37
+ UnsupportedTypeError,
38
+ applyWrapper,
39
+ buildForeignKeyMap,
40
+ collectBrandedIds,
41
+ emit,
42
+ emitBaseType,
43
+ generate,
44
+ generateBrandedIdSchema,
45
+ generateBrandedIdSchemas,
46
+ generateEnumSchema,
47
+ generateEnumSchemas,
48
+ generateFieldsCode,
49
+ generateModelSchema,
50
+ generateModelSchemas,
51
+ sectionHeader
52
+ };
53
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
package/package.json ADDED
@@ -0,0 +1,67 @@
1
+ {
2
+ "name": "prisma-effect-schema",
3
+ "version": "0.1.1",
4
+ "description": "Prisma generator that creates Effect Schemas from your Prisma models",
5
+ "keywords": [
6
+ "prisma",
7
+ "effect",
8
+ "schema",
9
+ "generator",
10
+ "typescript",
11
+ "validation"
12
+ ],
13
+ "license": "MIT",
14
+ "author": "Frontcore",
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "https://github.com/frontcore/prisma-effect-schema.git"
18
+ },
19
+ "type": "module",
20
+ "exports": {
21
+ ".": {
22
+ "types": "./dist/index.d.ts",
23
+ "import": "./dist/index.js"
24
+ },
25
+ "./generator": {
26
+ "types": "./dist/generator.d.ts",
27
+ "import": "./dist/generator.js"
28
+ }
29
+ },
30
+ "bin": {
31
+ "prisma-effect-schema": "./dist/bin.js"
32
+ },
33
+ "files": [
34
+ "dist",
35
+ "README.md",
36
+ "LICENSE"
37
+ ],
38
+ "scripts": {
39
+ "build": "tsup",
40
+ "dev": "tsup --watch",
41
+ "typecheck": "tsc --noEmit",
42
+ "test": "vitest run",
43
+ "test:watch": "vitest",
44
+ "prepublishOnly": "pnpm build"
45
+ },
46
+ "dependencies": {
47
+ "@effect/platform": "^0.95.0",
48
+ "@effect/platform-node": "^0.105.0",
49
+ "@prisma/generator-helper": "^6.5.0",
50
+ "dedent": "^1.7.2"
51
+ },
52
+ "devDependencies": {
53
+ "@types/dedent": "^0.7.2",
54
+ "@types/node": "^22.13.10",
55
+ "effect": "^3.14.8",
56
+ "prisma": "^6.5.0",
57
+ "tsup": "^8.4.0",
58
+ "typescript": "^5.8.2",
59
+ "vitest": "^3.0.8"
60
+ },
61
+ "peerDependencies": {
62
+ "effect": ">=3.0.0"
63
+ },
64
+ "engines": {
65
+ "node": ">=18"
66
+ }
67
+ }