prisma-effect-schema 0.1.4 → 0.1.5

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.
Files changed (2) hide show
  1. package/README.md +266 -68
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -1,27 +1,47 @@
1
1
  # prisma-effect-schema
2
2
 
3
- A Prisma generator that creates [Effect Schema](https://effect.website/docs/schema/introduction) definitions from your Prisma models.
3
+ [![npm version](https://img.shields.io/npm/v/prisma-effect-schema.svg)](https://www.npmjs.com/package/prisma-effect-schema)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
5
+
6
+ A Prisma generator that creates type-safe [Effect Schema](https://effect.website/docs/schema/introduction) definitions from your Prisma models.
7
+
8
+ > **Disclaimer:** This is a community project and is **not maintained by the Effect team**. It may contain bugs or have incomplete coverage of edge cases. Use at your own discretion and please report any issues you encounter.
9
+
10
+ ## Why prisma-effect-schema?
11
+
12
+ When using Prisma with Effect, you need runtime validation schemas that match your database models. Writing these by hand is tedious and error-prone. This generator:
13
+
14
+ - **Keeps schemas in sync** with your Prisma models automatically
15
+ - **Provides type-safe IDs** through branded types (no more mixing up `UserId` and `PostId`)
16
+ - **Handles complex types** like JSON, enums, and relations out of the box
17
+ - **Produces deterministic output** to minimize git diffs
4
18
 
5
19
  ## Features
6
20
 
7
21
  - Generates Effect Schemas for all Prisma models and enums
8
22
  - Creates branded ID types for type-safe entity references
9
23
  - Handles all Prisma scalar types (String, Int, Float, Boolean, DateTime, Json, Bytes, BigInt, Decimal)
24
+ - Intelligent foreign key resolution using Prisma relation metadata
25
+ - Optional relation schemas with `Schema.suspend()` for circular references
10
26
  - Deterministic output (sorted fields/models) to minimize git diffs
11
27
  - No timestamps in generated files to avoid unnecessary churn
12
- - Configurable via Prisma schema
28
+ - Fully configurable via Prisma schema
13
29
 
14
30
  ## Installation
15
31
 
16
32
  ```bash
17
- npm install prisma-effect-schema
33
+ npm install prisma-effect-schema effect
34
+ # or
35
+ pnpm add prisma-effect-schema effect
18
36
  # or
19
- pnpm add prisma-effect-schema
37
+ yarn add prisma-effect-schema effect
20
38
  ```
21
39
 
22
- ## Usage
40
+ > **Note:** `effect` is a peer dependency and must be installed separately.
23
41
 
24
- Add the generator to your `schema.prisma`:
42
+ ## Quick Start
43
+
44
+ ### 1. Add the generator to your `schema.prisma`
25
45
 
26
46
  ```prisma
27
47
  generator client {
@@ -30,7 +50,7 @@ generator client {
30
50
 
31
51
  generator effectSchema {
32
52
  provider = "prisma-effect-schema"
33
- output = "./generated/effect-schemas.ts"
53
+ output = "./generated/schemas.ts"
34
54
  }
35
55
 
36
56
  datasource db {
@@ -56,121 +76,299 @@ model Post {
56
76
  }
57
77
  ```
58
78
 
59
- Then run:
79
+ ### 2. Run the generator
60
80
 
61
81
  ```bash
62
82
  npx prisma generate
63
83
  ```
64
84
 
65
- This will generate `effect-schemas.ts` with:
85
+ ### 3. Use the generated schemas
66
86
 
67
87
  ```typescript
68
88
  import { Schema } from "effect";
89
+ import { User, UserId, Post, PostId } from "./generated/schemas";
90
+
91
+ // Validate data
92
+ const parseUser = Schema.decodeUnknownSync(User);
93
+ const user = parseUser({
94
+ id: "clx123...",
95
+ email: "alice@example.com",
96
+ name: "Alice",
97
+ createdAt: new Date(),
98
+ });
99
+
100
+ // Type-safe IDs prevent mixing up different entity IDs
101
+ const getUserById = (id: UserId) => { /* ... */ };
102
+ const postId: PostId = "post_123" as PostId;
103
+ // getUserById(postId); // Type error! Can't use PostId where UserId is expected
104
+ ```
105
+
106
+ ## Generated Output
107
+
108
+ The generator produces clean, readable TypeScript:
69
109
 
110
+ ```typescript
111
+ import { Schema } from "effect"
112
+
113
+ // ============================================================================
70
114
  // Branded IDs
71
- export const UserId = Schema.String.pipe(Schema.brand("UserId"));
72
- export type UserId = typeof UserId.Type;
115
+ // ============================================================================
116
+ export const UserId = Schema.String.pipe(Schema.brand("UserId"))
117
+ export type UserId = typeof UserId.Type
73
118
 
74
- export const PostId = Schema.String.pipe(Schema.brand("PostId"));
75
- export type PostId = typeof PostId.Type;
119
+ export const PostId = Schema.String.pipe(Schema.brand("PostId"))
120
+ export type PostId = typeof PostId.Type
76
121
 
77
- // Models
122
+ // ============================================================================
123
+ // Models (scalar fields only)
124
+ // ============================================================================
78
125
  export const User = Schema.Struct({
79
- id: UserId,
126
+ createdAt: Schema.Date,
80
127
  email: Schema.String,
128
+ id: UserId,
81
129
  name: Schema.NullOr(Schema.String),
82
- createdAt: Schema.Date,
83
- });
84
- export type User = typeof User.Type;
130
+ })
131
+ export type User = typeof User.Type
85
132
 
86
133
  export const Post = Schema.Struct({
87
- id: PostId,
88
- title: Schema.String,
134
+ authorId: UserId, // Automatically references User's branded ID
89
135
  content: Schema.NullOr(Schema.String),
136
+ id: PostId,
90
137
  published: Schema.Boolean,
91
- authorId: UserId, // References User's branded ID
92
- });
93
- export type Post = typeof Post.Type;
138
+ title: Schema.String,
139
+ })
140
+ export type Post = typeof Post.Type
94
141
  ```
95
142
 
96
143
  ## Configuration Options
97
144
 
145
+ All options are configured in your `schema.prisma`:
146
+
98
147
  ```prisma
99
148
  generator effectSchema {
100
- provider = "prisma-effect-schema"
101
- output = "./generated/effect-schemas.ts"
149
+ provider = "prisma-effect-schema"
150
+ output = "./generated/schemas.ts"
151
+
152
+ // Generate branded ID types (UserId, PostId, etc.)
153
+ // Default: true
154
+ useBrandedIds = "true"
102
155
 
103
156
  // Include relation fields (uses Schema.suspend for circular refs)
157
+ // When true, generates both `Model` and `ModelWithRelations` schemas
104
158
  // Default: false
105
159
  includeRelations = "true"
106
160
 
107
- // Generate branded ID types (UserId, PostId, etc.)
108
- // Default: true
109
- useBrandedIds = "true"
161
+ // How to handle DateTime fields
162
+ // - "Date": Schema.Date (for Prisma results - Date objects)
163
+ // - "DateTimeString": Schema.DateTimeUtc (for API validation - ISO strings)
164
+ // Default: "Date"
165
+ dateTimeHandling = "Date"
110
166
 
111
167
  // Sort fields alphabetically for deterministic output
112
168
  // Default: true
113
- sortFields = "true"
169
+ sortFields = "true"
170
+
171
+ // Custom header for the generated file (replaces default header)
172
+ // customHeader = "// My custom header\nimport { Schema } from 'effect'"
114
173
  }
115
174
  ```
116
175
 
176
+ ### Option Details
177
+
178
+ #### `useBrandedIds`
179
+
180
+ When enabled (default), generates branded ID types for models with String primary keys:
181
+
182
+ ```typescript
183
+ // With useBrandedIds = true
184
+ export const UserId = Schema.String.pipe(Schema.brand("UserId"))
185
+ export const User = Schema.Struct({
186
+ id: UserId, // Branded type
187
+ // ...
188
+ })
189
+
190
+ // With useBrandedIds = false
191
+ export const User = Schema.Struct({
192
+ id: Schema.String, // Plain string
193
+ // ...
194
+ })
195
+ ```
196
+
197
+ The branded ID name uses the primary key field name:
198
+ - `User.id` (String @id) -> `UserId`
199
+ - `Course.slug` (String @id) -> `CourseSlug`
200
+
201
+ Foreign keys automatically reference their target model's branded ID:
202
+
203
+ ```typescript
204
+ export const Post = Schema.Struct({
205
+ authorId: UserId, // Automatically uses UserId, not plain String
206
+ // ...
207
+ })
208
+ ```
209
+
210
+ #### `includeRelations`
211
+
212
+ When enabled, generates additional `*WithRelations` schemas:
213
+
214
+ ```typescript
215
+ // Base schema (always generated)
216
+ export const Post = Schema.Struct({
217
+ id: PostId,
218
+ title: Schema.String,
219
+ authorId: UserId,
220
+ })
221
+
222
+ // With relations (only when includeRelations = true)
223
+ export const PostWithRelations = Schema.Struct({
224
+ id: PostId,
225
+ title: Schema.String,
226
+ authorId: UserId,
227
+ author: Schema.suspend(() => User), // Handles circular refs
228
+ })
229
+ ```
230
+
231
+ #### `dateTimeHandling`
232
+
233
+ Choose how DateTime fields are handled:
234
+
235
+ ```typescript
236
+ // dateTimeHandling = "Date" (default)
237
+ // For validating Prisma query results (Date objects)
238
+ createdAt: Schema.Date
239
+
240
+ // dateTimeHandling = "DateTimeString"
241
+ // For validating API input (ISO 8601 strings)
242
+ createdAt: Schema.DateTimeUtc
243
+ ```
244
+
117
245
  ## Type Mapping
118
246
 
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(...)` |
247
+ | Prisma Type | Effect Schema | Notes |
248
+ | -------------- | ----------------------------- | ------------------------------- |
249
+ | `String` | `Schema.String` | Or branded ID if PK/FK |
250
+ | `Int` | `Schema.Int` | |
251
+ | `Float` | `Schema.Number` | |
252
+ | `Boolean` | `Schema.Boolean` | |
253
+ | `DateTime` | `Schema.Date` | Or `Schema.DateTimeUtc` |
254
+ | `Json` | `JsonValueSchema` | Recursive schema for JSON |
255
+ | `Bytes` | `Schema.Uint8Array` | |
256
+ | `BigInt` | `Schema.BigInt` | |
257
+ | `Decimal` | `Schema.Decimal` | |
258
+ | `Enum` | `Schema.Literal(...)` | All values as union |
259
+ | Optional (`?`) | `Schema.NullOr(...)` | Wraps the inner type |
260
+ | List (`[]`) | `Schema.Array(...)` | Wraps the inner type |
261
+
262
+ ## Advanced Usage
133
263
 
134
- ## Date Handling
264
+ ### Enums
135
265
 
136
- By default, the generator uses `Schema.Date` which expects JavaScript `Date` objects. This matches what Prisma returns from database queries.
266
+ Prisma enums are converted to `Schema.Literal` unions:
137
267
 
138
- If you're validating API input where dates come as ISO strings, you can compose the schema:
268
+ ```prisma
269
+ enum PostStatus {
270
+ DRAFT
271
+ PUBLISHED
272
+ ARCHIVED
273
+ }
274
+ ```
275
+
276
+ Generates:
277
+
278
+ ```typescript
279
+ export const PostStatus = Schema.Literal("ARCHIVED", "DRAFT", "PUBLISHED")
280
+ export type PostStatus = typeof PostStatus.Type
281
+ ```
282
+
283
+ ### JSON Fields
284
+
285
+ JSON fields use a recursive schema that matches Prisma's `JsonValue` type:
286
+
287
+ ```typescript
288
+ type JsonValue = string | number | boolean | null | JsonArray | JsonObject
289
+ type JsonArray = ReadonlyArray<JsonValue>
290
+ type JsonObject = { readonly [key: string]: JsonValue }
291
+
292
+ const JsonValueSchema: Schema.Schema<JsonValue> = Schema.suspend(
293
+ (): Schema.Schema<JsonValue> =>
294
+ Schema.Union(
295
+ Schema.Null,
296
+ Schema.Boolean,
297
+ Schema.Number,
298
+ Schema.String,
299
+ Schema.Array(JsonValueSchema),
300
+ Schema.Record({ key: Schema.String, value: JsonValueSchema })
301
+ )
302
+ )
303
+ ```
304
+
305
+ ### Transforming for API Input
306
+
307
+ The generated schemas use `Schema.Date` by default, matching Prisma's output. For API input validation where dates come as ISO strings, you can either:
308
+
309
+ 1. **Use `dateTimeHandling = "DateTimeString"`** to generate schemas that expect ISO strings
310
+
311
+ 2. **Transform at the boundary**:
139
312
 
140
313
  ```typescript
141
314
  import { Schema } from "effect";
142
- import { User } from "./generated/effect-schemas";
315
+ import { User } from "./generated/schemas";
143
316
 
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
- );
317
+ // For API input validation (dates as ISO strings)
318
+ const UserInput = Schema.Struct({
319
+ ...User.fields,
320
+ createdAt: Schema.DateFromString, // Accepts ISO string, returns Date
321
+ });
157
322
  ```
158
323
 
159
- ## Programmatic API
324
+ ### Programmatic API
160
325
 
161
- You can also use the generator programmatically:
326
+ Use the generator programmatically for custom workflows:
162
327
 
163
328
  ```typescript
164
- import { generate, resolveConfig } from "prisma-effect-schema";
165
- import { getDMMF } from "@prisma/sdk";
329
+ import { generate } from "prisma-effect-schema";
330
+ import { getDMMF } from "@prisma/internals";
166
331
 
167
332
  const dmmf = await getDMMF({ datamodelPath: "./prisma/schema.prisma" });
168
- const config = resolveConfig({ useBrandedIds: true });
169
333
 
170
- const { content, stats } = generate({ dmmf, config });
171
- console.log(`Generated ${stats.modelCount} models`);
334
+ const { content, stats } = generate({
335
+ dmmf,
336
+ config: {
337
+ useBrandedIds: true,
338
+ includeRelations: false,
339
+ dateTimeHandling: "Date",
340
+ sortFields: true,
341
+ customHeader: null,
342
+ },
343
+ });
344
+
345
+ console.log(`Generated ${stats.modelCount} models, ${stats.enumCount} enums`);
346
+ ```
347
+
348
+ ## Requirements
349
+
350
+ - Node.js >= 18
351
+ - Prisma >= 6.0
352
+ - Effect >= 3.0
353
+
354
+ ## Contributing
355
+
356
+ Contributions are welcome! Please feel free to submit a Pull Request.
357
+
358
+ ```bash
359
+ # Clone the repository
360
+ git clone https://github.com/frontcore/prisma-effect-schema.git
361
+
362
+ # Install dependencies
363
+ pnpm install
364
+
365
+ # Run tests
366
+ pnpm test
367
+
368
+ # Build
369
+ pnpm build
172
370
  ```
173
371
 
174
372
  ## License
175
373
 
176
- MIT
374
+ [MIT](LICENSE) - Frontcore
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prisma-effect-schema",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "description": "Prisma generator that creates Effect Schemas from your Prisma models",
5
5
  "keywords": [
6
6
  "prisma",