prisma-arktype 2.0.0
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 +21 -0
- package/README.md +345 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +829 -0
- package/package.json +71 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Ibrahim Saberi
|
|
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,345 @@
|
|
|
1
|
+
# prisma-arktype
|
|
2
|
+
|
|
3
|
+
Generate [ArkType](https://arktype.io) validation schemas from your [Prisma](https://www.prisma.io) schema.
|
|
4
|
+
|
|
5
|
+
This package is heavily inspired by and based on the structure of [prismabox](https://github.com/m1212e/prismabox), which generates TypeBox schemas from Prisma schemas.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- 🎯 **Type-safe validation** - Generate ArkType schemas that match your Prisma models
|
|
10
|
+
- 🔄 **Automatic generation** - Schemas are generated automatically when you run `prisma generate`
|
|
11
|
+
- 📦 **Comprehensive coverage** - Generates schemas for models, relations, where clauses, select, include, orderBy, and more
|
|
12
|
+
- 🎨 **Customizable** - Control schema generation with annotations
|
|
13
|
+
- 🚀 **Zero config** - Works out of the box with sensible defaults
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install prisma-arktype arktype
|
|
19
|
+
# or
|
|
20
|
+
pnpm add prisma-arktype arktype
|
|
21
|
+
# or
|
|
22
|
+
yarn add prisma-arktype arktype
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Usage
|
|
26
|
+
|
|
27
|
+
### Basic Setup
|
|
28
|
+
|
|
29
|
+
Add the generator to your `schema.prisma` file:
|
|
30
|
+
|
|
31
|
+
```prisma
|
|
32
|
+
generator prisma-arktype {
|
|
33
|
+
provider = "prisma-arktype"
|
|
34
|
+
output = "./generated/validators"
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
model User {
|
|
38
|
+
id String @id @default(cuid())
|
|
39
|
+
email String @unique
|
|
40
|
+
name String?
|
|
41
|
+
posts Post[]
|
|
42
|
+
createdAt DateTime @default(now())
|
|
43
|
+
updatedAt DateTime @updatedAt
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
model Post {
|
|
47
|
+
id String @id @default(cuid())
|
|
48
|
+
title String
|
|
49
|
+
content String?
|
|
50
|
+
published Boolean @default(false)
|
|
51
|
+
author User @relation(fields: [authorId], references: [id])
|
|
52
|
+
authorId String
|
|
53
|
+
createdAt DateTime @default(now())
|
|
54
|
+
updatedAt DateTime @updatedAt
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Then run:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
npx prisma generate
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Configuration Options
|
|
65
|
+
|
|
66
|
+
Configure the generator in your `schema.prisma`:
|
|
67
|
+
|
|
68
|
+
```prisma
|
|
69
|
+
generator prisma-arktype {
|
|
70
|
+
provider = "prisma-arktype"
|
|
71
|
+
output = "./generated/validators"
|
|
72
|
+
arktypeImportDependencyName = "arktype"
|
|
73
|
+
ignoredKeysOnInputModels = ["id", "createdAt", "updatedAt"]
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
#### Configuration Options
|
|
78
|
+
|
|
79
|
+
| Option | Type | Default | Description |
|
|
80
|
+
|--------|------|---------|-------------|
|
|
81
|
+
| `output` | `string` | `"./prisma/generated/validators"` | Output directory for generated schemas |
|
|
82
|
+
| `arktypeImportDependencyName` | `string` | `"arktype"` | The package name to import from |
|
|
83
|
+
| `ignoredKeysOnInputModels` | `string[]` | `["id", "createdAt", "updatedAt"]` | Fields to exclude from input models |
|
|
84
|
+
|
|
85
|
+
### Generated Schemas
|
|
86
|
+
|
|
87
|
+
For each model, the generator creates multiple schema types:
|
|
88
|
+
|
|
89
|
+
- **`ModelPlain`** - Fields without relationships
|
|
90
|
+
- **`ModelRelations`** - Relationship definitions only
|
|
91
|
+
- **`Model`** - Complete composite schema (Plain & Relations)
|
|
92
|
+
- **`ModelWhere`** - Where clause schema
|
|
93
|
+
- **`ModelWhereUnique`** - Unique where clause schema
|
|
94
|
+
- **`ModelCreate`** - Create input schema
|
|
95
|
+
- **`ModelUpdate`** - Update input schema
|
|
96
|
+
- **`ModelSelect`** - Select schema
|
|
97
|
+
- **`ModelInclude`** - Include schema
|
|
98
|
+
- **`ModelOrderBy`** - OrderBy schema
|
|
99
|
+
|
|
100
|
+
### Using Generated Schemas
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
import { type } from "arktype";
|
|
104
|
+
import { User, UserCreate, UserWhere } from "./generated/validators";
|
|
105
|
+
|
|
106
|
+
// Validate a user object
|
|
107
|
+
const userResult = User(someUserData);
|
|
108
|
+
if (userResult instanceof type.errors) {
|
|
109
|
+
console.error(userResult.summary);
|
|
110
|
+
} else {
|
|
111
|
+
// userResult is validated user data
|
|
112
|
+
console.log(userResult);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Validate create input
|
|
116
|
+
const createData = {
|
|
117
|
+
email: "user@example.com",
|
|
118
|
+
name: "John Doe"
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const createResult = UserCreate(createData);
|
|
122
|
+
// ...
|
|
123
|
+
|
|
124
|
+
// Validate where clauses
|
|
125
|
+
const whereClause = {
|
|
126
|
+
email: "user@example.com"
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
const whereResult = UserWhere(whereClause);
|
|
130
|
+
// ...
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## Annotations
|
|
134
|
+
|
|
135
|
+
Control schema generation using annotations in your Prisma schema:
|
|
136
|
+
|
|
137
|
+
### Hide Fields/Models
|
|
138
|
+
|
|
139
|
+
```prisma
|
|
140
|
+
/// @prisma-arktype.hide
|
|
141
|
+
model InternalModel {
|
|
142
|
+
id String @id
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
model User {
|
|
146
|
+
id String @id
|
|
147
|
+
/// @prisma-arktype.hide
|
|
148
|
+
internalField String
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### Hide from Input Models
|
|
153
|
+
|
|
154
|
+
```prisma
|
|
155
|
+
model User {
|
|
156
|
+
id String @id
|
|
157
|
+
/// @prisma-arktype.input.hide
|
|
158
|
+
computedField String
|
|
159
|
+
/// @prisma-arktype.create.input.hide
|
|
160
|
+
onlyInUpdates String
|
|
161
|
+
/// @prisma-arktype.update.input.hide
|
|
162
|
+
onlyInCreates String
|
|
163
|
+
}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### Type Override
|
|
167
|
+
|
|
168
|
+
```prisma
|
|
169
|
+
model User {
|
|
170
|
+
id String @id
|
|
171
|
+
/// @prisma-arktype.typeOverwrite="string.email"
|
|
172
|
+
email String
|
|
173
|
+
/// @prisma-arktype.typeOverwrite="string.numeric"
|
|
174
|
+
phone String
|
|
175
|
+
}
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### Custom Options
|
|
179
|
+
|
|
180
|
+
```prisma
|
|
181
|
+
model User {
|
|
182
|
+
id String @id
|
|
183
|
+
/// @prisma-arktype.options{minLength: 3, maxLength: 50}
|
|
184
|
+
username String
|
|
185
|
+
}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
## Type Mapping
|
|
189
|
+
|
|
190
|
+
Prisma types are mapped to ArkType as follows:
|
|
191
|
+
|
|
192
|
+
| Prisma Type | ArkType Type |
|
|
193
|
+
|-------------|--------------|
|
|
194
|
+
| `String` | `"string"` |
|
|
195
|
+
| `Int` | `"integer"` |
|
|
196
|
+
| `BigInt` | `"integer"` |
|
|
197
|
+
| `Float` | `"number"` |
|
|
198
|
+
| `Decimal` | `"number"` |
|
|
199
|
+
| `Boolean` | `"boolean"` |
|
|
200
|
+
| `DateTime` | `"Date"` |
|
|
201
|
+
| `Json` | `"unknown"` |
|
|
202
|
+
| `Bytes` | `"instanceof Buffer"` |
|
|
203
|
+
| Enums | Union of literal values |
|
|
204
|
+
|
|
205
|
+
## Differences from prismabox
|
|
206
|
+
|
|
207
|
+
While this package is inspired by prismabox, there are some key differences:
|
|
208
|
+
|
|
209
|
+
1. **ArkType vs TypeBox**: Uses ArkType's syntax and type system instead of TypeBox
|
|
210
|
+
2. **Simpler type definitions**: ArkType's string-based syntax makes schemas more readable
|
|
211
|
+
3. **No nullable wrapper**: ArkType handles nullable types directly with union syntax
|
|
212
|
+
4. **Different validation API**: Uses ArkType's validation approach
|
|
213
|
+
|
|
214
|
+
## Development
|
|
215
|
+
|
|
216
|
+
### Setup
|
|
217
|
+
|
|
218
|
+
```bash
|
|
219
|
+
# Clone the repository
|
|
220
|
+
git clone https://github.com/yourusername/prisma-arktype.git
|
|
221
|
+
cd prisma-arktype
|
|
222
|
+
|
|
223
|
+
# Install dependencies
|
|
224
|
+
pnpm install
|
|
225
|
+
|
|
226
|
+
# Build the project
|
|
227
|
+
pnpm build
|
|
228
|
+
|
|
229
|
+
# Run tests
|
|
230
|
+
pnpm test
|
|
231
|
+
|
|
232
|
+
# Run tests in watch mode
|
|
233
|
+
pnpm test:watch
|
|
234
|
+
|
|
235
|
+
# Run linter
|
|
236
|
+
pnpm lint
|
|
237
|
+
|
|
238
|
+
# Fix linting issues
|
|
239
|
+
pnpm lint:fix
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
### Publishing
|
|
243
|
+
|
|
244
|
+
This project uses [Changesets](https://github.com/changesets/changesets) for version management and publishing.
|
|
245
|
+
|
|
246
|
+
#### Creating a changeset
|
|
247
|
+
|
|
248
|
+
When you make changes that should be included in the next release:
|
|
249
|
+
|
|
250
|
+
```bash
|
|
251
|
+
pnpm changeset
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
This will prompt you to:
|
|
255
|
+
1. Select the type of change (major, minor, patch)
|
|
256
|
+
2. Provide a description of the changes
|
|
257
|
+
|
|
258
|
+
Commit the generated changeset file along with your changes.
|
|
259
|
+
|
|
260
|
+
#### Publishing workflow
|
|
261
|
+
|
|
262
|
+
1. **Create a changeset** for your changes
|
|
263
|
+
2. **Open a PR** with your changes and the changeset
|
|
264
|
+
3. **Merge the PR** - The GitHub Action will automatically create a "Version Packages" PR
|
|
265
|
+
4. **Review and merge** the Version Packages PR - This will:
|
|
266
|
+
- Update the version in package.json
|
|
267
|
+
- Update the CHANGELOG.md
|
|
268
|
+
- Publish the package to npm
|
|
269
|
+
- Create a GitHub release
|
|
270
|
+
|
|
271
|
+
#### Manual publishing (maintainers only)
|
|
272
|
+
|
|
273
|
+
```bash
|
|
274
|
+
# Build and publish
|
|
275
|
+
pnpm release
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
**Prerequisites:**
|
|
279
|
+
- Set up `NPM_TOKEN` secret in GitHub repository settings
|
|
280
|
+
- Ensure you have publish access to the npm package
|
|
281
|
+
|
|
282
|
+
## Contributing
|
|
283
|
+
|
|
284
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
285
|
+
|
|
286
|
+
1. Fork the repository
|
|
287
|
+
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
|
|
288
|
+
3. Make your changes
|
|
289
|
+
4. Create a changeset (`pnpm changeset`)
|
|
290
|
+
5. Commit your changes following the commit message format (see below)
|
|
291
|
+
6. Push to the branch (`git push origin feature/amazing-feature`)
|
|
292
|
+
7. Open a Pull Request
|
|
293
|
+
|
|
294
|
+
### Commit Message Format
|
|
295
|
+
|
|
296
|
+
This project uses [Conventional Commits](https://www.conventionalcommits.org/). Commit messages are automatically linted using commitlint and lefthook.
|
|
297
|
+
|
|
298
|
+
Format: `<type>(<scope>): <subject>`
|
|
299
|
+
|
|
300
|
+
**Types:**
|
|
301
|
+
- `feat`: New feature
|
|
302
|
+
- `fix`: Bug fix
|
|
303
|
+
- `docs`: Documentation changes
|
|
304
|
+
- `style`: Code style changes (formatting, etc.)
|
|
305
|
+
- `refactor`: Code refactoring
|
|
306
|
+
- `perf`: Performance improvements
|
|
307
|
+
- `test`: Adding or updating tests
|
|
308
|
+
- `build`: Build system changes
|
|
309
|
+
- `ci`: CI/CD changes
|
|
310
|
+
- `chore`: Other changes
|
|
311
|
+
|
|
312
|
+
**Examples:**
|
|
313
|
+
```bash
|
|
314
|
+
git commit -m "feat: add support for custom type validators"
|
|
315
|
+
git commit -m "fix: resolve issue with nullable DateTime fields"
|
|
316
|
+
git commit -m "docs: update installation instructions"
|
|
317
|
+
git commit -m "refactor: simplify where clause generation"
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
### Git Hooks
|
|
321
|
+
|
|
322
|
+
This project uses [lefthook](https://github.com/evilmartians/lefthook) to manage git hooks:
|
|
323
|
+
|
|
324
|
+
- **commit-msg**: Validates commit message format
|
|
325
|
+
- **pre-commit**: Runs linter and checks for debug statements
|
|
326
|
+
- **pre-push**: Runs tests before pushing
|
|
327
|
+
|
|
328
|
+
To skip hooks (use sparingly):
|
|
329
|
+
```bash
|
|
330
|
+
git commit --no-verify -m "your message"
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
## License
|
|
334
|
+
|
|
335
|
+
MIT
|
|
336
|
+
|
|
337
|
+
## Credits
|
|
338
|
+
|
|
339
|
+
This package is heavily based on [prismabox](https://github.com/m1212e/prismabox) by m1212e. Many thanks for the excellent foundation and architecture!
|
|
340
|
+
|
|
341
|
+
## Related Projects
|
|
342
|
+
|
|
343
|
+
- [ArkType](https://arktype.io) - TypeScript's 1:1 validator
|
|
344
|
+
- [Prisma](https://www.prisma.io) - Next-generation ORM for Node.js & TypeScript
|
|
345
|
+
- [prismabox](https://github.com/m1212e/prismabox) - Generate TypeBox schemas from Prisma (inspiration for this project)
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,829 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { writeFile, access, rm, mkdir } from 'node:fs/promises';
|
|
3
|
+
import generatorHelperPkg from '@prisma/generator-helper';
|
|
4
|
+
import { type, ArkErrors } from 'arktype';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
|
|
7
|
+
const configSchema = type({
|
|
8
|
+
output: "string = './prisma/generated/validators'",
|
|
9
|
+
arktypeImportDependencyName: "string = 'arktype'",
|
|
10
|
+
ignoredKeysOnInputModels: "string[]"
|
|
11
|
+
});
|
|
12
|
+
let config = {
|
|
13
|
+
output: "./prisma/generated/validators",
|
|
14
|
+
arktypeImportDependencyName: "arktype",
|
|
15
|
+
ignoredKeysOnInputModels: ["id", "createdAt", "updatedAt"]
|
|
16
|
+
};
|
|
17
|
+
const configPartialSchema = configSchema.partial();
|
|
18
|
+
function setConfig(newConfig) {
|
|
19
|
+
const out = configPartialSchema(newConfig);
|
|
20
|
+
if (out instanceof ArkErrors) {
|
|
21
|
+
throw new Error(`Invalid generator config:
|
|
22
|
+
${out.toString()}`);
|
|
23
|
+
}
|
|
24
|
+
config = { ...config, ...out };
|
|
25
|
+
Object.freeze(config);
|
|
26
|
+
}
|
|
27
|
+
function getConfig() {
|
|
28
|
+
return config;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function isHidden(annotation) {
|
|
32
|
+
return annotation.type === "HIDDEN";
|
|
33
|
+
}
|
|
34
|
+
function isHiddenInput(annotation) {
|
|
35
|
+
return annotation.type === "HIDDEN_INPUT";
|
|
36
|
+
}
|
|
37
|
+
function isHiddenInputCreate(annotation) {
|
|
38
|
+
return annotation.type === "HIDDEN_INPUT_CREATE";
|
|
39
|
+
}
|
|
40
|
+
function isHiddenInputUpdate(annotation) {
|
|
41
|
+
return annotation.type === "HIDDEN_INPUT_UPDATE";
|
|
42
|
+
}
|
|
43
|
+
function isOptions(annotation) {
|
|
44
|
+
return annotation.type === "OPTIONS";
|
|
45
|
+
}
|
|
46
|
+
function isTypeOverwrite(annotation) {
|
|
47
|
+
return annotation.type === "TYPE_OVERWRITE";
|
|
48
|
+
}
|
|
49
|
+
const annotationKeys = [
|
|
50
|
+
{
|
|
51
|
+
keys: ["@prisma-arktype.hide", "@prisma-arktype.hidden"],
|
|
52
|
+
type: "HIDDEN"
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
keys: ["@prisma-arktype.input.hide", "@prisma-arktype.input.hidden"],
|
|
56
|
+
type: "HIDDEN_INPUT"
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
keys: [
|
|
60
|
+
"@prisma-arktype.create.input.hide",
|
|
61
|
+
"@prisma-arktype.create.input.hidden"
|
|
62
|
+
],
|
|
63
|
+
type: "HIDDEN_INPUT_CREATE"
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
keys: [
|
|
67
|
+
"@prisma-arktype.update.input.hide",
|
|
68
|
+
"@prisma-arktype.update.input.hidden"
|
|
69
|
+
],
|
|
70
|
+
type: "HIDDEN_INPUT_UPDATE"
|
|
71
|
+
},
|
|
72
|
+
{ keys: ["@prisma-arktype.options"], type: "OPTIONS" },
|
|
73
|
+
{ keys: ["@prisma-arktype.typeOverwrite"], type: "TYPE_OVERWRITE" }
|
|
74
|
+
];
|
|
75
|
+
const prismaArktypeOptionsRegex = /@prisma-arktype\.options\{(.+)\}/;
|
|
76
|
+
const prismaArktypeTypeOverwriteRegex = /@prisma-arktype\.typeOverwrite=(.+)/;
|
|
77
|
+
function extractAnnotations(documentation) {
|
|
78
|
+
const annotations = [];
|
|
79
|
+
const descriptionLines = [];
|
|
80
|
+
if (documentation) {
|
|
81
|
+
for (const line of documentation.split("\n")) {
|
|
82
|
+
let isAnnotation = false;
|
|
83
|
+
for (const { keys, type } of annotationKeys) {
|
|
84
|
+
for (const key of keys) {
|
|
85
|
+
if (line.includes(key)) {
|
|
86
|
+
isAnnotation = true;
|
|
87
|
+
if (type === "OPTIONS") {
|
|
88
|
+
const match = line.match(prismaArktypeOptionsRegex);
|
|
89
|
+
if (match && match[1]) {
|
|
90
|
+
annotations.push({ type: "OPTIONS", value: match[1] });
|
|
91
|
+
} else {
|
|
92
|
+
throw new Error(`Invalid OPTIONS annotation: ${line}`);
|
|
93
|
+
}
|
|
94
|
+
} else if (type === "TYPE_OVERWRITE") {
|
|
95
|
+
const match = line.match(prismaArktypeTypeOverwriteRegex);
|
|
96
|
+
if (match && match[1]) {
|
|
97
|
+
annotations.push({
|
|
98
|
+
type: "TYPE_OVERWRITE",
|
|
99
|
+
value: match[1].trim()
|
|
100
|
+
});
|
|
101
|
+
} else {
|
|
102
|
+
throw new Error(`Invalid TYPE_OVERWRITE annotation: ${line}`);
|
|
103
|
+
}
|
|
104
|
+
} else {
|
|
105
|
+
annotations.push({ type });
|
|
106
|
+
}
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
if (isAnnotation) break;
|
|
111
|
+
}
|
|
112
|
+
if (!isAnnotation) {
|
|
113
|
+
descriptionLines.push(line);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return {
|
|
118
|
+
annotations,
|
|
119
|
+
description: descriptionLines.join("\n").trim(),
|
|
120
|
+
hidden: annotations.some(isHidden),
|
|
121
|
+
hiddenInput: annotations.some(isHiddenInput),
|
|
122
|
+
hiddenInputCreate: annotations.some(isHiddenInputCreate),
|
|
123
|
+
hiddenInputUpdate: annotations.some(isHiddenInputUpdate)
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
function generateArktypeOptions(annotations) {
|
|
127
|
+
const optionsAnnotations = annotations.filter(isOptions);
|
|
128
|
+
if (optionsAnnotations.length === 0) {
|
|
129
|
+
return "";
|
|
130
|
+
}
|
|
131
|
+
return `.pipe(${optionsAnnotations.map((a) => a.value).join(", ")})`;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const primitiveTypes = [
|
|
135
|
+
"Int",
|
|
136
|
+
"BigInt",
|
|
137
|
+
"Float",
|
|
138
|
+
"Decimal",
|
|
139
|
+
"String",
|
|
140
|
+
"DateTime",
|
|
141
|
+
"Json",
|
|
142
|
+
"Boolean",
|
|
143
|
+
"Bytes"
|
|
144
|
+
];
|
|
145
|
+
function isPrimitivePrismaFieldType(type) {
|
|
146
|
+
return primitiveTypes.includes(type);
|
|
147
|
+
}
|
|
148
|
+
function stringifyPrimitiveType(type, annotations) {
|
|
149
|
+
const options = generateArktypeOptions(annotations);
|
|
150
|
+
switch (type) {
|
|
151
|
+
case "Int":
|
|
152
|
+
case "BigInt":
|
|
153
|
+
return `"number.integer"${options}`;
|
|
154
|
+
case "Float":
|
|
155
|
+
case "Decimal":
|
|
156
|
+
return `"number"${options}`;
|
|
157
|
+
case "String":
|
|
158
|
+
return `"string"${options}`;
|
|
159
|
+
case "DateTime":
|
|
160
|
+
return `"Date"${options}`;
|
|
161
|
+
case "Json":
|
|
162
|
+
return `"unknown"${options}`;
|
|
163
|
+
case "Boolean":
|
|
164
|
+
return `"boolean"${options}`;
|
|
165
|
+
case "Bytes":
|
|
166
|
+
return `"instanceof Buffer"${options}`;
|
|
167
|
+
default:
|
|
168
|
+
throw new Error(`Unsupported primitive type: ${type}`);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function wrapWithArray(input) {
|
|
173
|
+
return `${input}[]`;
|
|
174
|
+
}
|
|
175
|
+
function makeUnion(inputModels) {
|
|
176
|
+
return inputModels.join(" | ");
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const processedEnums = [];
|
|
180
|
+
function processEnums(enums) {
|
|
181
|
+
for (const enumData of enums) {
|
|
182
|
+
const stringified = stringifyEnum(enumData);
|
|
183
|
+
if (stringified) {
|
|
184
|
+
processedEnums.push({
|
|
185
|
+
name: enumData.name,
|
|
186
|
+
stringified
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
Object.freeze(processedEnums);
|
|
191
|
+
}
|
|
192
|
+
function stringifyEnum(enumData) {
|
|
193
|
+
const { annotations, hidden } = extractAnnotations(enumData.documentation);
|
|
194
|
+
if (hidden) {
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
const values = enumData.values.map((v) => `'${v.name}'`);
|
|
198
|
+
const options = generateArktypeOptions(annotations);
|
|
199
|
+
return `type("${makeUnion(values)}")${options}`;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const processedPlain = [];
|
|
203
|
+
function processPlain(models) {
|
|
204
|
+
for (const model of models) {
|
|
205
|
+
const stringified = stringifyPlain(model);
|
|
206
|
+
if (stringified) {
|
|
207
|
+
processedPlain.push({
|
|
208
|
+
name: model.name,
|
|
209
|
+
stringified
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
Object.freeze(processedPlain);
|
|
214
|
+
}
|
|
215
|
+
const enumMatch$1 = /type\("(.+)"\)/;
|
|
216
|
+
function stringifyPlain(model, isInputCreate = false, isInputUpdate = false) {
|
|
217
|
+
const config = getConfig();
|
|
218
|
+
const { annotations: modelAnnotations, hidden } = extractAnnotations(
|
|
219
|
+
model.documentation
|
|
220
|
+
);
|
|
221
|
+
if (hidden) {
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
const fields = [];
|
|
225
|
+
for (const field of model.fields) {
|
|
226
|
+
const {
|
|
227
|
+
annotations: fieldAnnotations,
|
|
228
|
+
hidden: fieldHidden,
|
|
229
|
+
hiddenInput,
|
|
230
|
+
hiddenInputCreate,
|
|
231
|
+
hiddenInputUpdate
|
|
232
|
+
} = extractAnnotations(field.documentation);
|
|
233
|
+
if (fieldHidden) continue;
|
|
234
|
+
if (isInputCreate && (hiddenInput || hiddenInputCreate)) continue;
|
|
235
|
+
if (isInputUpdate && (hiddenInput || hiddenInputUpdate)) continue;
|
|
236
|
+
if (field.kind === "object") continue;
|
|
237
|
+
if (isInputCreate || isInputUpdate) {
|
|
238
|
+
if (config.ignoredKeysOnInputModels.includes(field.name)) continue;
|
|
239
|
+
if (field.name.endsWith("Id") && field.relationName) continue;
|
|
240
|
+
}
|
|
241
|
+
const typeOverwrite = fieldAnnotations.find(isTypeOverwrite);
|
|
242
|
+
let fieldType;
|
|
243
|
+
let fieldName = field.name;
|
|
244
|
+
if (typeOverwrite) {
|
|
245
|
+
fieldType = typeOverwrite.value;
|
|
246
|
+
} else if (isPrimitivePrismaFieldType(field.type)) {
|
|
247
|
+
fieldType = stringifyPrimitiveType(field.type, fieldAnnotations);
|
|
248
|
+
} else if (field.kind === "enum") {
|
|
249
|
+
const enumDef = processedEnums.find((e) => e.name === field.type);
|
|
250
|
+
if (!enumDef) continue;
|
|
251
|
+
const match = enumDef.stringified.match(enumMatch$1);
|
|
252
|
+
fieldType = match ? `"${match[1]}"` : `"'${field.type}'"`;
|
|
253
|
+
} else {
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
if (field.isList) {
|
|
257
|
+
fieldType = `"${wrapWithArray(fieldType.slice(1, -1))}"`;
|
|
258
|
+
}
|
|
259
|
+
if (!field.isRequired) {
|
|
260
|
+
const inner = fieldType.slice(1, -1);
|
|
261
|
+
fieldType = `"${inner} | null"`;
|
|
262
|
+
fieldName += "?";
|
|
263
|
+
}
|
|
264
|
+
if (field.hasDefaultValue || isInputUpdate) {
|
|
265
|
+
if (!fieldName.endsWith("?")) {
|
|
266
|
+
fieldName += "?";
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
fields.push(`"${fieldName}": ${fieldType}`);
|
|
270
|
+
}
|
|
271
|
+
const options = generateArktypeOptions(modelAnnotations);
|
|
272
|
+
return `{
|
|
273
|
+
${fields.join(",\n ")}
|
|
274
|
+
}${options}`;
|
|
275
|
+
}
|
|
276
|
+
function stringifyPlainInputCreate(model) {
|
|
277
|
+
return stringifyPlain(model, true, false);
|
|
278
|
+
}
|
|
279
|
+
function stringifyPlainInputUpdate(model) {
|
|
280
|
+
return stringifyPlain(model, false, true);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const processedCreate = [];
|
|
284
|
+
function processCreate(models) {
|
|
285
|
+
for (const model of models) {
|
|
286
|
+
const { hidden } = extractAnnotations(model.documentation);
|
|
287
|
+
if (hidden) continue;
|
|
288
|
+
const stringified = stringifyPlainInputCreate(model);
|
|
289
|
+
if (stringified) {
|
|
290
|
+
processedCreate.push({
|
|
291
|
+
name: model.name,
|
|
292
|
+
stringified
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
Object.freeze(processedCreate);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const processedInclude = [];
|
|
300
|
+
function processInclude(models) {
|
|
301
|
+
for (const model of models) {
|
|
302
|
+
const stringified = stringifyInclude(model);
|
|
303
|
+
if (stringified) {
|
|
304
|
+
processedInclude.push({
|
|
305
|
+
name: model.name,
|
|
306
|
+
stringified
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
Object.freeze(processedInclude);
|
|
311
|
+
}
|
|
312
|
+
function stringifyInclude(model) {
|
|
313
|
+
const { annotations: modelAnnotations, hidden } = extractAnnotations(
|
|
314
|
+
model.documentation
|
|
315
|
+
);
|
|
316
|
+
if (hidden) {
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
const fields = [];
|
|
320
|
+
for (const field of model.fields) {
|
|
321
|
+
const { hidden: fieldHidden } = extractAnnotations(field.documentation);
|
|
322
|
+
if (fieldHidden) continue;
|
|
323
|
+
if (field.kind !== "object") continue;
|
|
324
|
+
fields.push(`"${field.name}?": "boolean"`);
|
|
325
|
+
}
|
|
326
|
+
fields.push(`"_count?": "boolean"`);
|
|
327
|
+
if (fields.length <= 1) {
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
const options = generateArktypeOptions(modelAnnotations);
|
|
331
|
+
return `{
|
|
332
|
+
${fields.join(",\n ")}
|
|
333
|
+
}${options}`;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
const processedOrderBy = [];
|
|
337
|
+
function processOrderBy(models) {
|
|
338
|
+
for (const model of models) {
|
|
339
|
+
const stringified = stringifyOrderBy(model);
|
|
340
|
+
if (stringified) {
|
|
341
|
+
processedOrderBy.push({
|
|
342
|
+
name: model.name,
|
|
343
|
+
stringified
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
Object.freeze(processedOrderBy);
|
|
348
|
+
}
|
|
349
|
+
function stringifyOrderBy(model) {
|
|
350
|
+
const { annotations: modelAnnotations, hidden } = extractAnnotations(
|
|
351
|
+
model.documentation
|
|
352
|
+
);
|
|
353
|
+
if (hidden) {
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
const fields = [];
|
|
357
|
+
const sortOrder = `"'asc' | 'desc'"`;
|
|
358
|
+
for (const field of model.fields) {
|
|
359
|
+
const { hidden: fieldHidden } = extractAnnotations(field.documentation);
|
|
360
|
+
if (fieldHidden) continue;
|
|
361
|
+
if (field.kind === "object") continue;
|
|
362
|
+
fields.push(`"${field.name}?": ${sortOrder}`);
|
|
363
|
+
}
|
|
364
|
+
const options = generateArktypeOptions(modelAnnotations);
|
|
365
|
+
return `{
|
|
366
|
+
${fields.join(",\n ")}
|
|
367
|
+
}${options}`;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
const processedRelations = [];
|
|
371
|
+
const processedRelationsCreate = [];
|
|
372
|
+
const processedRelationsUpdate = [];
|
|
373
|
+
function processRelations(models) {
|
|
374
|
+
for (const model of models) {
|
|
375
|
+
const stringified = stringifyRelations(model);
|
|
376
|
+
if (stringified) {
|
|
377
|
+
processedRelations.push({
|
|
378
|
+
name: model.name,
|
|
379
|
+
stringified
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
Object.freeze(processedRelations);
|
|
384
|
+
}
|
|
385
|
+
function stringifyRelations(model) {
|
|
386
|
+
const { annotations: modelAnnotations, hidden } = extractAnnotations(
|
|
387
|
+
model.documentation
|
|
388
|
+
);
|
|
389
|
+
if (hidden) {
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
const fields = [];
|
|
393
|
+
for (const field of model.fields) {
|
|
394
|
+
const { hidden: fieldHidden } = extractAnnotations(field.documentation);
|
|
395
|
+
if (fieldHidden) continue;
|
|
396
|
+
if (field.kind !== "object") continue;
|
|
397
|
+
let fieldType = `"unknown"`;
|
|
398
|
+
if (field.isList) {
|
|
399
|
+
fieldType = `"unknown[]"`;
|
|
400
|
+
}
|
|
401
|
+
if (!field.isRequired) {
|
|
402
|
+
fieldType = `"unknown | null"`;
|
|
403
|
+
}
|
|
404
|
+
fields.push(`"${field.name}": ${fieldType}`);
|
|
405
|
+
}
|
|
406
|
+
if (fields.length === 0) {
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
const options = generateArktypeOptions(modelAnnotations);
|
|
410
|
+
return `{
|
|
411
|
+
${fields.join(",\n ")}
|
|
412
|
+
}${options}`;
|
|
413
|
+
}
|
|
414
|
+
function processRelationsCreate(models) {
|
|
415
|
+
for (const model of models) {
|
|
416
|
+
const stringified = stringifyRelationsInputCreate(model, models);
|
|
417
|
+
if (stringified) {
|
|
418
|
+
processedRelationsCreate.push({
|
|
419
|
+
name: model.name,
|
|
420
|
+
stringified
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
Object.freeze(processedRelationsCreate);
|
|
425
|
+
}
|
|
426
|
+
function stringifyRelationsInputCreate(model, allModels) {
|
|
427
|
+
const { annotations: modelAnnotations, hidden } = extractAnnotations(
|
|
428
|
+
model.documentation
|
|
429
|
+
);
|
|
430
|
+
if (hidden) {
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
const fields = [];
|
|
434
|
+
for (const field of model.fields) {
|
|
435
|
+
const {
|
|
436
|
+
hidden: fieldHidden,
|
|
437
|
+
hiddenInput,
|
|
438
|
+
hiddenInputCreate
|
|
439
|
+
} = extractAnnotations(field.documentation);
|
|
440
|
+
if (fieldHidden || hiddenInput || hiddenInputCreate) continue;
|
|
441
|
+
if (field.kind !== "object") continue;
|
|
442
|
+
const relatedModel = allModels.find((m) => m.name === field.type);
|
|
443
|
+
if (!relatedModel) continue;
|
|
444
|
+
const idField = relatedModel.fields.find((f) => f.isId);
|
|
445
|
+
let idType = '"string"';
|
|
446
|
+
if (idField) {
|
|
447
|
+
if (idField.type === "Int" || idField.type === "BigInt") {
|
|
448
|
+
idType = '"integer"';
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
const connectType = `{ "connect": { "id": ${idType} } }`;
|
|
452
|
+
fields.push(`"${field.name}?": ${connectType}`);
|
|
453
|
+
}
|
|
454
|
+
if (fields.length === 0) {
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
const options = generateArktypeOptions(modelAnnotations);
|
|
458
|
+
return `{
|
|
459
|
+
${fields.join(",\n ")}
|
|
460
|
+
}${options}`;
|
|
461
|
+
}
|
|
462
|
+
function processRelationsUpdate(models) {
|
|
463
|
+
for (const model of models) {
|
|
464
|
+
const stringified = stringifyRelationsInputUpdate(model, models);
|
|
465
|
+
if (stringified) {
|
|
466
|
+
processedRelationsUpdate.push({
|
|
467
|
+
name: model.name,
|
|
468
|
+
stringified
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
Object.freeze(processedRelationsUpdate);
|
|
473
|
+
}
|
|
474
|
+
function stringifyRelationsInputUpdate(model, allModels) {
|
|
475
|
+
const { annotations: modelAnnotations, hidden } = extractAnnotations(
|
|
476
|
+
model.documentation
|
|
477
|
+
);
|
|
478
|
+
if (hidden) {
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
const fields = [];
|
|
482
|
+
for (const field of model.fields) {
|
|
483
|
+
const {
|
|
484
|
+
hidden: fieldHidden,
|
|
485
|
+
hiddenInput,
|
|
486
|
+
hiddenInputUpdate
|
|
487
|
+
} = extractAnnotations(field.documentation);
|
|
488
|
+
if (fieldHidden || hiddenInput || hiddenInputUpdate) continue;
|
|
489
|
+
if (field.kind !== "object") continue;
|
|
490
|
+
const relatedModel = allModels.find((m) => m.name === field.type);
|
|
491
|
+
if (!relatedModel) continue;
|
|
492
|
+
const idField = relatedModel.fields.find((f) => f.isId);
|
|
493
|
+
let idType = '"string"';
|
|
494
|
+
if (idField) {
|
|
495
|
+
if (idField.type === "Int" || idField.type === "BigInt") {
|
|
496
|
+
idType = '"integer"';
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
let fieldType;
|
|
500
|
+
if (field.isList) {
|
|
501
|
+
fieldType = `{ "connect"?: { "id": ${idType} }[], "disconnect"?: { "id": ${idType} }[] }`;
|
|
502
|
+
} else {
|
|
503
|
+
if (field.isRequired) {
|
|
504
|
+
fieldType = `{ "connect": { "id": ${idType} } }`;
|
|
505
|
+
} else {
|
|
506
|
+
fieldType = `{ "connect"?: { "id": ${idType} }, "disconnect"?: "boolean" }`;
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
fields.push(`"${field.name}?": ${fieldType}`);
|
|
510
|
+
}
|
|
511
|
+
if (fields.length === 0) {
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
514
|
+
const options = generateArktypeOptions(modelAnnotations);
|
|
515
|
+
return `{
|
|
516
|
+
${fields.join(",\n ")}
|
|
517
|
+
}${options}`;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
const processedSelect = [];
|
|
521
|
+
function processSelect(models) {
|
|
522
|
+
for (const model of models) {
|
|
523
|
+
const stringified = stringifySelect(model);
|
|
524
|
+
if (stringified) {
|
|
525
|
+
processedSelect.push({
|
|
526
|
+
name: model.name,
|
|
527
|
+
stringified
|
|
528
|
+
});
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
Object.freeze(processedSelect);
|
|
532
|
+
}
|
|
533
|
+
function stringifySelect(model) {
|
|
534
|
+
const { annotations: modelAnnotations, hidden } = extractAnnotations(
|
|
535
|
+
model.documentation
|
|
536
|
+
);
|
|
537
|
+
if (hidden) {
|
|
538
|
+
return;
|
|
539
|
+
}
|
|
540
|
+
const fields = [];
|
|
541
|
+
for (const field of model.fields) {
|
|
542
|
+
const { hidden: fieldHidden } = extractAnnotations(field.documentation);
|
|
543
|
+
if (fieldHidden) continue;
|
|
544
|
+
fields.push(`"${field.name}?": "boolean"`);
|
|
545
|
+
}
|
|
546
|
+
fields.push(`"_count?": "boolean"`);
|
|
547
|
+
const options = generateArktypeOptions(modelAnnotations);
|
|
548
|
+
return `{
|
|
549
|
+
${fields.join(",\n ")}
|
|
550
|
+
}${options}`;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
const processedUpdate = [];
|
|
554
|
+
function processUpdate(models) {
|
|
555
|
+
for (const model of models) {
|
|
556
|
+
const { hidden } = extractAnnotations(model.documentation);
|
|
557
|
+
if (hidden) continue;
|
|
558
|
+
const stringified = stringifyPlainInputUpdate(model);
|
|
559
|
+
if (stringified) {
|
|
560
|
+
processedUpdate.push({
|
|
561
|
+
name: model.name,
|
|
562
|
+
stringified
|
|
563
|
+
});
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
Object.freeze(processedUpdate);
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
const processedWhere = [];
|
|
570
|
+
const processedWhereUnique = [];
|
|
571
|
+
function processWhere(models) {
|
|
572
|
+
for (const model of models) {
|
|
573
|
+
const stringified = stringifyWhere(model);
|
|
574
|
+
if (stringified) {
|
|
575
|
+
processedWhere.push({
|
|
576
|
+
name: model.name,
|
|
577
|
+
stringified
|
|
578
|
+
});
|
|
579
|
+
}
|
|
580
|
+
const stringifiedUnique = stringifyWhereUnique(model);
|
|
581
|
+
if (stringifiedUnique) {
|
|
582
|
+
processedWhereUnique.push({
|
|
583
|
+
name: model.name,
|
|
584
|
+
stringified: stringifiedUnique
|
|
585
|
+
});
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
Object.freeze(processedWhere);
|
|
589
|
+
Object.freeze(processedWhereUnique);
|
|
590
|
+
}
|
|
591
|
+
const enumMatch = /type\("(.+)"\)/;
|
|
592
|
+
function stringifyWhere(model) {
|
|
593
|
+
const { annotations: modelAnnotations, hidden } = extractAnnotations(
|
|
594
|
+
model.documentation
|
|
595
|
+
);
|
|
596
|
+
if (hidden) {
|
|
597
|
+
return;
|
|
598
|
+
}
|
|
599
|
+
const fields = [];
|
|
600
|
+
for (const field of model.fields) {
|
|
601
|
+
const { annotations: fieldAnnotations, hidden: fieldHidden } = extractAnnotations(field.documentation);
|
|
602
|
+
if (fieldHidden) continue;
|
|
603
|
+
if (field.kind === "object") continue;
|
|
604
|
+
const typeOverwrite = fieldAnnotations.find(isTypeOverwrite);
|
|
605
|
+
let fieldType;
|
|
606
|
+
if (typeOverwrite) {
|
|
607
|
+
fieldType = `"${typeOverwrite.value}"`;
|
|
608
|
+
} else if (isPrimitivePrismaFieldType(field.type)) {
|
|
609
|
+
fieldType = stringifyPrimitiveType(field.type, fieldAnnotations);
|
|
610
|
+
} else if (field.kind === "enum") {
|
|
611
|
+
const enumDef = processedEnums.find((e) => e.name === field.type);
|
|
612
|
+
if (!enumDef) continue;
|
|
613
|
+
const match = enumDef.stringified.match(enumMatch);
|
|
614
|
+
fieldType = match ? `"${match[1]}"` : `"'${field.type}'"`;
|
|
615
|
+
} else {
|
|
616
|
+
continue;
|
|
617
|
+
}
|
|
618
|
+
if (field.isList) {
|
|
619
|
+
const inner = fieldType.slice(1, -1);
|
|
620
|
+
fieldType = `"${wrapWithArray(inner)}"`;
|
|
621
|
+
}
|
|
622
|
+
fields.push(`"${field.name}?": ${fieldType}`);
|
|
623
|
+
}
|
|
624
|
+
const options = generateArktypeOptions(modelAnnotations);
|
|
625
|
+
return `{
|
|
626
|
+
${fields.join(",\n ")}
|
|
627
|
+
}${options}`;
|
|
628
|
+
}
|
|
629
|
+
function stringifyWhereUnique(model) {
|
|
630
|
+
const { annotations: modelAnnotations, hidden } = extractAnnotations(
|
|
631
|
+
model.documentation
|
|
632
|
+
);
|
|
633
|
+
if (hidden) {
|
|
634
|
+
return;
|
|
635
|
+
}
|
|
636
|
+
const fields = [];
|
|
637
|
+
for (const field of model.fields) {
|
|
638
|
+
const { annotations: fieldAnnotations, hidden: fieldHidden } = extractAnnotations(field.documentation);
|
|
639
|
+
if (fieldHidden) continue;
|
|
640
|
+
if (field.kind === "object") continue;
|
|
641
|
+
if (!(field.isId || field.isUnique)) continue;
|
|
642
|
+
const typeOverwrite = fieldAnnotations.find(isTypeOverwrite);
|
|
643
|
+
let fieldType;
|
|
644
|
+
if (typeOverwrite) {
|
|
645
|
+
fieldType = `"${typeOverwrite.value}"`;
|
|
646
|
+
} else if (isPrimitivePrismaFieldType(field.type)) {
|
|
647
|
+
fieldType = stringifyPrimitiveType(field.type, fieldAnnotations);
|
|
648
|
+
} else if (field.kind === "enum") {
|
|
649
|
+
const enumDef = processedEnums.find((e) => e.name === field.type);
|
|
650
|
+
if (!enumDef) continue;
|
|
651
|
+
const match = enumDef.stringified.match(enumMatch);
|
|
652
|
+
fieldType = match ? `"${match[1]}"` : `"'${field.type}'"`;
|
|
653
|
+
} else {
|
|
654
|
+
continue;
|
|
655
|
+
}
|
|
656
|
+
fields.push(`"${field.name}?": ${fieldType}`);
|
|
657
|
+
}
|
|
658
|
+
if (fields.length === 0) {
|
|
659
|
+
return;
|
|
660
|
+
}
|
|
661
|
+
const options = generateArktypeOptions(modelAnnotations);
|
|
662
|
+
return `{
|
|
663
|
+
${fields.join(",\n ")}
|
|
664
|
+
}${options}`;
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
async function format(input) {
|
|
668
|
+
return input;
|
|
669
|
+
}
|
|
670
|
+
function mapAllModelsForWrite(processedEnums, processedPlain, processedRelations, processedWhere, processedWhereUnique, processedCreate, processedUpdate, processedRelationsCreate, processedRelationsUpdate, processedSelect, processedInclude, processedOrderBy) {
|
|
671
|
+
const config = getConfig();
|
|
672
|
+
const modelMap = /* @__PURE__ */ new Map();
|
|
673
|
+
const arktypeImport = `import { type } from "${config.arktypeImportDependencyName}";
|
|
674
|
+
|
|
675
|
+
`;
|
|
676
|
+
for (const model of processedEnums) {
|
|
677
|
+
const content = `${arktypeImport}export const ${model.name} = ${model.stringified};
|
|
678
|
+
`;
|
|
679
|
+
modelMap.set(model.name, content);
|
|
680
|
+
}
|
|
681
|
+
for (const model of processedPlain) {
|
|
682
|
+
const content = `${arktypeImport}export const ${model.name}Plain = type(${model.stringified});
|
|
683
|
+
`;
|
|
684
|
+
modelMap.set(`${model.name}Plain`, content);
|
|
685
|
+
}
|
|
686
|
+
for (const model of processedRelations) {
|
|
687
|
+
const content = `${arktypeImport}export const ${model.name}Relations = type(${model.stringified});
|
|
688
|
+
`;
|
|
689
|
+
modelMap.set(`${model.name}Relations`, content);
|
|
690
|
+
}
|
|
691
|
+
for (const plain of processedPlain) {
|
|
692
|
+
const hasRelations = processedRelations.some((r) => r.name === plain.name);
|
|
693
|
+
if (hasRelations) {
|
|
694
|
+
const content = `${arktypeImport}import { ${plain.name}Plain } from "./${plain.name}Plain";
|
|
695
|
+
import { ${plain.name}Relations } from "./${plain.name}Relations";
|
|
696
|
+
|
|
697
|
+
export const ${plain.name} = type(() => ${plain.name}Plain.and(${plain.name}Relations));
|
|
698
|
+
`;
|
|
699
|
+
modelMap.set(plain.name, content);
|
|
700
|
+
} else {
|
|
701
|
+
const content = `${arktypeImport}import { ${plain.name}Plain } from "./${plain.name}Plain";
|
|
702
|
+
|
|
703
|
+
export const ${plain.name} = ${plain.name}Plain;
|
|
704
|
+
`;
|
|
705
|
+
modelMap.set(plain.name, content);
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
for (const model of processedWhere) {
|
|
709
|
+
const content = `${arktypeImport}export const ${model.name}Where = type(${model.stringified});
|
|
710
|
+
`;
|
|
711
|
+
modelMap.set(`${model.name}Where`, content);
|
|
712
|
+
}
|
|
713
|
+
for (const model of processedWhereUnique) {
|
|
714
|
+
const content = `${arktypeImport}export const ${model.name}WhereUnique = type(${model.stringified});
|
|
715
|
+
`;
|
|
716
|
+
modelMap.set(`${model.name}WhereUnique`, content);
|
|
717
|
+
}
|
|
718
|
+
for (const model of processedCreate) {
|
|
719
|
+
const content = `${arktypeImport}export const ${model.name}Create = type(${model.stringified});
|
|
720
|
+
`;
|
|
721
|
+
modelMap.set(`${model.name}Create`, content);
|
|
722
|
+
}
|
|
723
|
+
for (const model of processedUpdate) {
|
|
724
|
+
const content = `${arktypeImport}export const ${model.name}Update = type(${model.stringified});
|
|
725
|
+
`;
|
|
726
|
+
modelMap.set(`${model.name}Update`, content);
|
|
727
|
+
}
|
|
728
|
+
for (const model of processedRelationsCreate) {
|
|
729
|
+
const content = `${arktypeImport}export const ${model.name}RelationsCreate = type(${model.stringified});
|
|
730
|
+
`;
|
|
731
|
+
modelMap.set(`${model.name}RelationsCreate`, content);
|
|
732
|
+
}
|
|
733
|
+
for (const model of processedRelationsUpdate) {
|
|
734
|
+
const content = `${arktypeImport}export const ${model.name}RelationsUpdate = type(${model.stringified});
|
|
735
|
+
`;
|
|
736
|
+
modelMap.set(`${model.name}RelationsUpdate`, content);
|
|
737
|
+
}
|
|
738
|
+
for (const model of processedSelect) {
|
|
739
|
+
const content = `${arktypeImport}export const ${model.name}Select = type(${model.stringified});
|
|
740
|
+
`;
|
|
741
|
+
modelMap.set(`${model.name}Select`, content);
|
|
742
|
+
}
|
|
743
|
+
for (const model of processedInclude) {
|
|
744
|
+
const content = `${arktypeImport}export const ${model.name}Include = type(${model.stringified});
|
|
745
|
+
`;
|
|
746
|
+
modelMap.set(`${model.name}Include`, content);
|
|
747
|
+
}
|
|
748
|
+
for (const model of processedOrderBy) {
|
|
749
|
+
const content = `${arktypeImport}export const ${model.name}OrderBy = type(${model.stringified});
|
|
750
|
+
`;
|
|
751
|
+
modelMap.set(`${model.name}OrderBy`, content);
|
|
752
|
+
}
|
|
753
|
+
return modelMap;
|
|
754
|
+
}
|
|
755
|
+
async function write(processedEnums, processedPlain, processedRelations, processedWhere, processedWhereUnique, processedCreate, processedUpdate, processedRelationsCreate, processedRelationsUpdate, processedSelect, processedInclude, processedOrderBy) {
|
|
756
|
+
const config = getConfig();
|
|
757
|
+
const modelMap = mapAllModelsForWrite(
|
|
758
|
+
processedEnums,
|
|
759
|
+
processedPlain,
|
|
760
|
+
processedRelations,
|
|
761
|
+
processedWhere,
|
|
762
|
+
processedWhereUnique,
|
|
763
|
+
processedCreate,
|
|
764
|
+
processedUpdate,
|
|
765
|
+
processedRelationsCreate,
|
|
766
|
+
processedRelationsUpdate,
|
|
767
|
+
processedSelect,
|
|
768
|
+
processedInclude,
|
|
769
|
+
processedOrderBy
|
|
770
|
+
);
|
|
771
|
+
const writePromises = [];
|
|
772
|
+
for (const [name, content] of modelMap.entries()) {
|
|
773
|
+
const filePath = join(config.output, `${name}.ts`);
|
|
774
|
+
writePromises.push(writeFile(filePath, await format(content)));
|
|
775
|
+
}
|
|
776
|
+
const barrelExports = Array.from(modelMap.keys()).map((name) => `export * from "./${name}";`).join("\n");
|
|
777
|
+
writePromises.push(
|
|
778
|
+
writeFile(join(config.output, "index.ts"), await format(barrelExports))
|
|
779
|
+
);
|
|
780
|
+
await Promise.all(writePromises);
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
const { generatorHandler } = generatorHelperPkg;
|
|
784
|
+
generatorHandler({
|
|
785
|
+
onManifest() {
|
|
786
|
+
return {
|
|
787
|
+
defaultOutput: "./prisma/generated/validators",
|
|
788
|
+
prettyName: "prisma-arktype"
|
|
789
|
+
};
|
|
790
|
+
},
|
|
791
|
+
async onGenerate(options) {
|
|
792
|
+
setConfig({
|
|
793
|
+
...options.generator.config,
|
|
794
|
+
output: options.generator.output?.value
|
|
795
|
+
});
|
|
796
|
+
try {
|
|
797
|
+
await access(getConfig().output);
|
|
798
|
+
await rm(getConfig().output, { recursive: true });
|
|
799
|
+
} catch {
|
|
800
|
+
}
|
|
801
|
+
await mkdir(getConfig().output, { recursive: true });
|
|
802
|
+
processEnums(options.dmmf.datamodel.enums);
|
|
803
|
+
processPlain(options.dmmf.datamodel.models);
|
|
804
|
+
processWhere(options.dmmf.datamodel.models);
|
|
805
|
+
processCreate(options.dmmf.datamodel.models);
|
|
806
|
+
processUpdate(options.dmmf.datamodel.models);
|
|
807
|
+
processSelect(options.dmmf.datamodel.models);
|
|
808
|
+
processInclude(options.dmmf.datamodel.models);
|
|
809
|
+
processOrderBy(options.dmmf.datamodel.models);
|
|
810
|
+
processRelations(options.dmmf.datamodel.models);
|
|
811
|
+
processRelationsCreate(options.dmmf.datamodel.models);
|
|
812
|
+
processRelationsUpdate(options.dmmf.datamodel.models);
|
|
813
|
+
await write(
|
|
814
|
+
processedEnums,
|
|
815
|
+
processedPlain,
|
|
816
|
+
processedRelations,
|
|
817
|
+
processedWhere,
|
|
818
|
+
processedWhereUnique,
|
|
819
|
+
processedCreate,
|
|
820
|
+
processedUpdate,
|
|
821
|
+
processedRelationsCreate,
|
|
822
|
+
processedRelationsUpdate,
|
|
823
|
+
processedSelect,
|
|
824
|
+
processedInclude,
|
|
825
|
+
processedOrderBy
|
|
826
|
+
);
|
|
827
|
+
console.info("\u2705 prisma-arktype: Generated ArkType schemas successfully!");
|
|
828
|
+
}
|
|
829
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "prisma-arktype",
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "Generate ArkType schemas from your Prisma schema",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/index.js",
|
|
11
|
+
"types": "./dist/index.d.ts"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"bin": {
|
|
15
|
+
"prisma-arktype": "./dist/index.js"
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist"
|
|
19
|
+
],
|
|
20
|
+
"keywords": [
|
|
21
|
+
"prisma",
|
|
22
|
+
"arktype",
|
|
23
|
+
"validation",
|
|
24
|
+
"schema",
|
|
25
|
+
"generator",
|
|
26
|
+
"typesafe"
|
|
27
|
+
],
|
|
28
|
+
"author": "",
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "https://github.com/GeorgeIpsum/prisma-arktype.git"
|
|
33
|
+
},
|
|
34
|
+
"homepage": "https://github.com/GeorgeIpsum/prisma-arktype#readme",
|
|
35
|
+
"bugs": {
|
|
36
|
+
"url": "https://github.com/GeorgeIpsum/prisma-arktype/issues"
|
|
37
|
+
},
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"@prisma/generator-helper": "^6.18.0",
|
|
40
|
+
"arktype": "^2.1.25"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@biomejs/biome": "^2.3.3",
|
|
44
|
+
"@changesets/cli": "^2.29.7",
|
|
45
|
+
"@commitlint/cli": "^20.1.0",
|
|
46
|
+
"@commitlint/config-conventional": "^20.0.0",
|
|
47
|
+
"@prisma/client": "^6.18.0",
|
|
48
|
+
"@tilli-pro/biome": "^0.8.0",
|
|
49
|
+
"@types/node": "^24.10.0",
|
|
50
|
+
"lefthook": "^2.0.2",
|
|
51
|
+
"pkgroll": "^2.20.1",
|
|
52
|
+
"prisma": "^6.18.0",
|
|
53
|
+
"tsx": "^4.20.6",
|
|
54
|
+
"typescript": "^5.9.3",
|
|
55
|
+
"vitest": "^2.1.9"
|
|
56
|
+
},
|
|
57
|
+
"scripts": {
|
|
58
|
+
"build": "pkgroll",
|
|
59
|
+
"dev": "pkgroll --watch",
|
|
60
|
+
"test": "vitest run",
|
|
61
|
+
"test:watch": "vitest",
|
|
62
|
+
"test:e2e": "pnpm build && prisma generate && vitest run",
|
|
63
|
+
"pretest": "pnpm build",
|
|
64
|
+
"postinstall": "lefthook install",
|
|
65
|
+
"lint": "biome check",
|
|
66
|
+
"lint:fix": "biome check --write",
|
|
67
|
+
"changeset": "changeset",
|
|
68
|
+
"version": "changeset version",
|
|
69
|
+
"release": "pnpm build && changeset publish"
|
|
70
|
+
}
|
|
71
|
+
}
|