prisma-effect-schema 0.1.3 → 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.
- package/README.md +266 -68
- package/dist/bin.js +2 -2
- package/dist/{chunk-CM4H5F25.js → chunk-4XK2MZLU.js} +2 -2
- package/dist/{chunk-TRP4TWLF.js → chunk-EB2BVI3Q.js} +53 -37
- package/dist/chunk-EB2BVI3Q.js.map +1 -0
- package/dist/generator.js +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/package.json +4 -2
- package/dist/chunk-TRP4TWLF.js.map +0 -1
- /package/dist/{chunk-CM4H5F25.js.map → chunk-4XK2MZLU.js.map} +0 -0
package/README.md
CHANGED
|
@@ -1,27 +1,47 @@
|
|
|
1
1
|
# prisma-effect-schema
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/prisma-effect-schema)
|
|
4
|
+
[](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
|
-
-
|
|
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
|
-
|
|
37
|
+
yarn add prisma-effect-schema effect
|
|
20
38
|
```
|
|
21
39
|
|
|
22
|
-
|
|
40
|
+
> **Note:** `effect` is a peer dependency and must be installed separately.
|
|
23
41
|
|
|
24
|
-
|
|
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/
|
|
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
|
-
|
|
79
|
+
### 2. Run the generator
|
|
60
80
|
|
|
61
81
|
```bash
|
|
62
82
|
npx prisma generate
|
|
63
83
|
```
|
|
64
84
|
|
|
65
|
-
|
|
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
|
-
|
|
72
|
-
export
|
|
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
|
-
//
|
|
122
|
+
// ============================================================================
|
|
123
|
+
// Models (scalar fields only)
|
|
124
|
+
// ============================================================================
|
|
78
125
|
export const User = Schema.Struct({
|
|
79
|
-
|
|
126
|
+
createdAt: Schema.Date,
|
|
80
127
|
email: Schema.String,
|
|
128
|
+
id: UserId,
|
|
81
129
|
name: Schema.NullOr(Schema.String),
|
|
82
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
101
|
-
output
|
|
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
|
-
//
|
|
108
|
-
//
|
|
109
|
-
|
|
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
|
|
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`
|
|
127
|
-
| `Bytes` | `Schema.Uint8Array` |
|
|
128
|
-
| `BigInt` | `Schema.BigInt` |
|
|
129
|
-
| `Decimal` | `Schema.
|
|
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
|
-
|
|
264
|
+
### Enums
|
|
135
265
|
|
|
136
|
-
|
|
266
|
+
Prisma enums are converted to `Schema.Literal` unions:
|
|
137
267
|
|
|
138
|
-
|
|
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/
|
|
315
|
+
import { User } from "./generated/schemas";
|
|
143
316
|
|
|
144
|
-
// For API input validation
|
|
145
|
-
const UserInput =
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
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
|
-
|
|
324
|
+
### Programmatic API
|
|
160
325
|
|
|
161
|
-
|
|
326
|
+
Use the generator programmatically for custom workflows:
|
|
162
327
|
|
|
163
328
|
```typescript
|
|
164
|
-
import { generate
|
|
165
|
-
import { getDMMF } from "@prisma/
|
|
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({
|
|
171
|
-
|
|
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/dist/bin.js
CHANGED
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
NoOutputConfiguredError,
|
|
3
3
|
generate,
|
|
4
4
|
parseConfig
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-EB2BVI3Q.js";
|
|
6
6
|
|
|
7
7
|
// src/generator.ts
|
|
8
8
|
import { FileSystem } from "@effect/platform";
|
|
@@ -52,4 +52,4 @@ generatorHelper.generatorHandler({
|
|
|
52
52
|
Effect.runPromise
|
|
53
53
|
)
|
|
54
54
|
});
|
|
55
|
-
//# sourceMappingURL=chunk-
|
|
55
|
+
//# sourceMappingURL=chunk-4XK2MZLU.js.map
|
|
@@ -75,6 +75,7 @@ var GeneratorConfigSchema = Schema.Struct({
|
|
|
75
75
|
var parseConfig = (options) => Schema.decodeUnknown(GeneratorConfigSchema)(options.generator.config);
|
|
76
76
|
|
|
77
77
|
// src/errors.ts
|
|
78
|
+
import dedent from "dedent";
|
|
78
79
|
import { Schema as Schema2 } from "effect";
|
|
79
80
|
var AppTag = "[prisma-effect-schema]";
|
|
80
81
|
var UnsupportedTypeError = class extends Schema2.TaggedError()(
|
|
@@ -86,7 +87,10 @@ var UnsupportedTypeError = class extends Schema2.TaggedError()(
|
|
|
86
87
|
}
|
|
87
88
|
) {
|
|
88
89
|
get message() {
|
|
89
|
-
return
|
|
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
|
+
`;
|
|
90
94
|
}
|
|
91
95
|
};
|
|
92
96
|
var NoOutputConfiguredError = class extends Schema2.TaggedError()(
|
|
@@ -246,34 +250,37 @@ var SchemaResolver = {
|
|
|
246
250
|
};
|
|
247
251
|
|
|
248
252
|
// src/templates.ts
|
|
253
|
+
import dedent2 from "dedent";
|
|
249
254
|
import { Array as Arr3, HashMap as HashMap2, Order, pipe as pipe3 } from "effect";
|
|
250
|
-
var DEFAULT_HEADER =
|
|
251
|
-
//
|
|
252
|
-
//
|
|
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
|
|
253
259
|
|
|
254
|
-
import { Schema } from "effect"
|
|
260
|
+
import { Schema } from "effect"
|
|
255
261
|
`;
|
|
256
|
-
var JSON_VALUE_SCHEMA =
|
|
257
|
-
|
|
258
|
-
type
|
|
259
|
-
type
|
|
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 }
|
|
260
267
|
|
|
261
|
-
const JsonValueSchema: Schema.Schema<JsonValue> = Schema.suspend(
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
)
|
|
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
|
+
)
|
|
272
279
|
`;
|
|
273
|
-
var sectionHeader = (title) =>
|
|
274
|
-
//
|
|
275
|
-
//
|
|
276
|
-
|
|
280
|
+
var sectionHeader = (title) => dedent2`
|
|
281
|
+
// ============================================================================
|
|
282
|
+
// ${title}
|
|
283
|
+
// ============================================================================
|
|
277
284
|
`;
|
|
278
285
|
var ByName = () => Order.mapInput(Order.string, (item) => item.name);
|
|
279
286
|
var sortByName = (items) => Arr3.sort(items, ByName());
|
|
@@ -283,12 +290,16 @@ var generateEnumSchema = (enumDef) => {
|
|
|
283
290
|
Arr3.map((v) => `"${v.name}"`),
|
|
284
291
|
Arr3.join(", ")
|
|
285
292
|
);
|
|
286
|
-
return `
|
|
287
|
-
export
|
|
293
|
+
return dedent2`
|
|
294
|
+
export const ${enumDef.name} = Schema.Literal(${values})
|
|
295
|
+
export type ${enumDef.name} = typeof ${enumDef.name}.Type
|
|
296
|
+
`;
|
|
288
297
|
};
|
|
289
298
|
var generateEnumSchemas = (enums) => pipe3(sortByName(enums), Arr3.map(generateEnumSchema), Arr3.join("\n"));
|
|
290
|
-
var generateBrandedIdSchema = ([, brandedIdName]) => `
|
|
291
|
-
export
|
|
299
|
+
var generateBrandedIdSchema = ([, brandedIdName]) => dedent2`
|
|
300
|
+
export const ${brandedIdName} = Schema.String.pipe(Schema.brand("${brandedIdName}"))
|
|
301
|
+
export type ${brandedIdName} = typeof ${brandedIdName}.Type
|
|
302
|
+
`;
|
|
292
303
|
var generateBrandedIdSchemas = (brandedIds) => pipe3(
|
|
293
304
|
brandedIds,
|
|
294
305
|
HashMap2.toEntries,
|
|
@@ -309,10 +320,12 @@ var generateModelSchema = (model, resolver, config) => {
|
|
|
309
320
|
resolver,
|
|
310
321
|
config.sortFields
|
|
311
322
|
);
|
|
312
|
-
const baseSchema =
|
|
313
|
-
${
|
|
314
|
-
|
|
315
|
-
|
|
323
|
+
const baseSchema = [
|
|
324
|
+
`export const ${model.name} = Schema.Struct({`,
|
|
325
|
+
scalarFieldsCode,
|
|
326
|
+
`})`,
|
|
327
|
+
`export type ${model.name} = typeof ${model.name}.Type`
|
|
328
|
+
].join("\n");
|
|
316
329
|
if (!config.includeRelations || !hasRelations) {
|
|
317
330
|
return baseSchema;
|
|
318
331
|
}
|
|
@@ -321,12 +334,15 @@ export type ${model.name} = typeof ${model.name}.Type`;
|
|
|
321
334
|
resolver,
|
|
322
335
|
config.sortFields
|
|
323
336
|
);
|
|
337
|
+
const withRelationsSchema = [
|
|
338
|
+
`export const ${model.name}WithRelations = Schema.Struct({`,
|
|
339
|
+
allFieldsCode,
|
|
340
|
+
`})`,
|
|
341
|
+
`export type ${model.name}WithRelations = typeof ${model.name}WithRelations.Type`
|
|
342
|
+
].join("\n");
|
|
324
343
|
return `${baseSchema}
|
|
325
344
|
|
|
326
|
-
|
|
327
|
-
${allFieldsCode}
|
|
328
|
-
})
|
|
329
|
-
export type ${model.name}WithRelations = typeof ${model.name}WithRelations.Type`;
|
|
345
|
+
${withRelationsSchema}`;
|
|
330
346
|
};
|
|
331
347
|
var generateModelSchemas = (models, makeResolver, config) => pipe3(
|
|
332
348
|
sortByName(models),
|
|
@@ -397,4 +413,4 @@ export {
|
|
|
397
413
|
generateModelSchemas,
|
|
398
414
|
generate
|
|
399
415
|
};
|
|
400
|
-
//# sourceMappingURL=chunk-
|
|
416
|
+
//# sourceMappingURL=chunk-EB2BVI3Q.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\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 => 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 = [\n `export const ${model.name} = Schema.Struct({`,\n scalarFieldsCode,\n `})`,\n `export type ${model.name} = typeof ${model.name}.Type`,\n ].join(\"\\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 const withRelationsSchema = [\n `export const ${model.name}WithRelations = Schema.Struct({`,\n allFieldsCode,\n `})`,\n `export type ${model.name}WithRelations = typeof ${model.name}WithRelations.Type`,\n ].join(\"\\n\");\n\n return `${baseSchema}\\n\\n${withRelationsSchema}`;\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\"),\n stats: {\n enumCount: enums.length,\n modelCount: models.length,\n brandedIdCount,\n },\n };\n};\n"],"mappings":"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","names":["Schema","Arr","Option","pipe","pipe","Arr","Option","dedent","Arr","HashMap","pipe","HashMap","HashMap"]}
|
package/dist/generator.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import "./chunk-
|
|
2
|
-
import "./chunk-
|
|
1
|
+
import "./chunk-4XK2MZLU.js";
|
|
2
|
+
import "./chunk-EB2BVI3Q.js";
|
|
3
3
|
//# sourceMappingURL=generator.js.map
|
package/dist/index.d.ts
CHANGED
|
@@ -217,8 +217,8 @@ declare const emit: (type: ResolvedType) => string;
|
|
|
217
217
|
* Separated from orchestration logic for clarity and testability.
|
|
218
218
|
*/
|
|
219
219
|
|
|
220
|
-
declare const DEFAULT_HEADER
|
|
221
|
-
declare const JSON_VALUE_SCHEMA
|
|
220
|
+
declare const DEFAULT_HEADER: string;
|
|
221
|
+
declare const JSON_VALUE_SCHEMA: string;
|
|
222
222
|
declare const sectionHeader: (title: string) => string;
|
|
223
223
|
declare const generateEnumSchema: (enumDef: DMMF.DatamodelEnum) => string;
|
|
224
224
|
declare const generateEnumSchemas: (enums: readonly DMMF.DatamodelEnum[]) => string;
|
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "prisma-effect-schema",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "Prisma generator that creates Effect Schemas from your Prisma models",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"prisma",
|
|
@@ -46,9 +46,11 @@
|
|
|
46
46
|
"dependencies": {
|
|
47
47
|
"@effect/platform": "^0.95.0",
|
|
48
48
|
"@effect/platform-node": "^0.105.0",
|
|
49
|
-
"@prisma/generator-helper": "^6.5.0"
|
|
49
|
+
"@prisma/generator-helper": "^6.5.0",
|
|
50
|
+
"dedent": "^1.7.2"
|
|
50
51
|
},
|
|
51
52
|
"devDependencies": {
|
|
53
|
+
"@types/dedent": "^0.7.2",
|
|
52
54
|
"@types/node": "^22.13.10",
|
|
53
55
|
"effect": "^3.14.8",
|
|
54
56
|
"prisma": "^6.5.0",
|
|
@@ -1 +0,0 @@
|
|
|
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 { 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 `${AppTag} Unsupported Prisma type \"${this.typeName}\" for field \"${this.fieldName}\" in model \"${this.modelName}\". Please open an issue at https://github.com/frontcore/prisma-effect-schema/issues`;\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 { 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 = `// 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\nimport { Schema } from \"effect\"\n`;\n\nexport const JSON_VALUE_SCHEMA = `// Recursive JSON value schema matching Prisma's JsonValue type\ntype JsonValue = string | number | boolean | null | JsonArray | JsonObject\ntype JsonArray = ReadonlyArray<JsonValue>\ntype JsonObject = { readonly [key: string]: JsonValue }\n\nconst 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 =>\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 `export const ${enumDef.name} = Schema.Literal(${values})\nexport type ${enumDef.name} = typeof ${enumDef.name}.Type`;\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 `export const ${brandedIdName} = Schema.String.pipe(Schema.brand(\"${brandedIdName}\"))\nexport type ${brandedIdName} = typeof ${brandedIdName}.Type`;\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 = `export const ${model.name} = Schema.Struct({\n${scalarFieldsCode}\n})\nexport type ${model.name} = typeof ${model.name}.Type`;\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 `${baseSchema}\n\nexport const ${model.name}WithRelations = Schema.Struct({\n${allFieldsCode}\n})\nexport type ${model.name}WithRelations = typeof ${model.name}WithRelations.Type`;\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\"),\n stats: {\n enumCount: enums.length,\n modelCount: models.length,\n brandedIdCount,\n },\n };\n};\n"],"mappings":"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","names":["Schema","Arr","Option","pipe","pipe","Arr","Option","Arr","HashMap","pipe","HashMap","HashMap"]}
|
|
File without changes
|