prisma-arktype 2.0.0 → 2.2.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/README.md +103 -25
- package/dist/index.js +126 -110
- package/package.json +7 -7
package/README.md
CHANGED
|
@@ -132,75 +132,102 @@ const whereResult = UserWhere(whereClause);
|
|
|
132
132
|
|
|
133
133
|
## Annotations
|
|
134
134
|
|
|
135
|
-
Control schema generation using annotations in your Prisma schema
|
|
135
|
+
Control schema generation using annotations in your Prisma schema. All annotations are added as documentation comments (`///`).
|
|
136
|
+
|
|
137
|
+
### Available Annotations
|
|
138
|
+
|
|
139
|
+
| Annotation | Scope | Description |
|
|
140
|
+
|------------|-------|-------------|
|
|
141
|
+
| `@prisma-arktype.hide` | Model or Field | Completely hide from all generated schemas |
|
|
142
|
+
| `@prisma-arktype.input.hide` | Field | Hide from Create and Update input schemas |
|
|
143
|
+
| `@prisma-arktype.create.input.hide` | Field | Hide from Create input schema only |
|
|
144
|
+
| `@prisma-arktype.update.input.hide` | Field | Hide from Update input schema only |
|
|
145
|
+
| `@prisma-arktype.typeOverwrite="<type>"` | Field | Override the generated ArkType type |
|
|
136
146
|
|
|
137
147
|
### Hide Fields/Models
|
|
138
148
|
|
|
149
|
+
Completely exclude models or fields from all generated schemas:
|
|
150
|
+
|
|
139
151
|
```prisma
|
|
140
152
|
/// @prisma-arktype.hide
|
|
141
153
|
model InternalModel {
|
|
142
154
|
id String @id
|
|
155
|
+
secret String
|
|
143
156
|
}
|
|
144
157
|
|
|
145
158
|
model User {
|
|
146
159
|
id String @id
|
|
160
|
+
email String
|
|
147
161
|
/// @prisma-arktype.hide
|
|
148
|
-
|
|
162
|
+
passwordHash String
|
|
149
163
|
}
|
|
150
164
|
```
|
|
151
165
|
|
|
152
166
|
### Hide from Input Models
|
|
153
167
|
|
|
168
|
+
Control which fields appear in Create and Update schemas:
|
|
169
|
+
|
|
154
170
|
```prisma
|
|
155
171
|
model User {
|
|
156
172
|
id String @id
|
|
173
|
+
email String
|
|
174
|
+
|
|
157
175
|
/// @prisma-arktype.input.hide
|
|
176
|
+
/// Hidden from both Create and Update
|
|
158
177
|
computedField String
|
|
178
|
+
|
|
159
179
|
/// @prisma-arktype.create.input.hide
|
|
160
|
-
|
|
180
|
+
/// Only appears in Update schema
|
|
181
|
+
lastModified DateTime
|
|
182
|
+
|
|
161
183
|
/// @prisma-arktype.update.input.hide
|
|
162
|
-
|
|
184
|
+
/// Only appears in Create schema
|
|
185
|
+
initialStatus String
|
|
163
186
|
}
|
|
164
187
|
```
|
|
165
188
|
|
|
166
189
|
### Type Override
|
|
167
190
|
|
|
191
|
+
Override the default type mapping with custom ArkType type strings:
|
|
192
|
+
|
|
168
193
|
```prisma
|
|
169
194
|
model User {
|
|
170
195
|
id String @id
|
|
171
196
|
/// @prisma-arktype.typeOverwrite="string.email"
|
|
172
197
|
email String
|
|
198
|
+
/// @prisma-arktype.typeOverwrite="string.url"
|
|
199
|
+
website String
|
|
173
200
|
/// @prisma-arktype.typeOverwrite="string.numeric"
|
|
174
201
|
phone String
|
|
175
202
|
}
|
|
176
203
|
```
|
|
177
204
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
```prisma
|
|
181
|
-
model User {
|
|
182
|
-
id String @id
|
|
183
|
-
/// @prisma-arktype.options{minLength: 3, maxLength: 50}
|
|
184
|
-
username String
|
|
185
|
-
}
|
|
186
|
-
```
|
|
205
|
+
This allows you to use any ArkType type definition, including built-in refinements like `string.email`, `string.url`, `number.integer`, etc.
|
|
187
206
|
|
|
188
207
|
## Type Mapping
|
|
189
208
|
|
|
190
209
|
Prisma types are mapped to ArkType as follows:
|
|
191
210
|
|
|
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 |
|
|
211
|
+
| Prisma Type | ArkType Type | Example Output |
|
|
212
|
+
|-------------|--------------|----------------|
|
|
213
|
+
| `String` | `"string"` | `"string"` |
|
|
214
|
+
| `Int` | `"number.integer"` | `"number.integer"` |
|
|
215
|
+
| `BigInt` | `"number.integer"` | `"number.integer"` |
|
|
216
|
+
| `Float` | `"number"` | `"number"` |
|
|
217
|
+
| `Decimal` | `"number"` | `"number"` |
|
|
218
|
+
| `Boolean` | `"boolean"` | `"boolean"` |
|
|
219
|
+
| `DateTime` | `"Date"` | `"Date"` |
|
|
220
|
+
| `Json` | `"unknown"` | `"unknown"` |
|
|
221
|
+
| `Bytes` | `"instanceof Buffer"` | `"instanceof Buffer"` |
|
|
222
|
+
| Enums | Union of literal values | `type("'USD' \| 'EUR' \| 'GBP'")` |
|
|
223
|
+
| Relations | `"unknown"` | `type("unknown").array()` for lists |
|
|
224
|
+
|
|
225
|
+
### Special Handling
|
|
226
|
+
|
|
227
|
+
- **Optional fields**: Use `?` on the key name (`"name?": "string"`)
|
|
228
|
+
- **Nullable fields**: Add `| null` to the type (`"string | null"`)
|
|
229
|
+
- **Arrays**: Use `.array()` syntax for lists (`type("string").array()`)
|
|
230
|
+
- **Enums**: Generated as string literal unions wrapped in `type()`
|
|
204
231
|
|
|
205
232
|
## Differences from prismabox
|
|
206
233
|
|
|
@@ -239,6 +266,57 @@ pnpm lint
|
|
|
239
266
|
pnpm lint:fix
|
|
240
267
|
```
|
|
241
268
|
|
|
269
|
+
### Testing
|
|
270
|
+
|
|
271
|
+
This library has a **completely schema-independent test suite** using self-contained test models in `prisma/schema/test-models.prisma`.
|
|
272
|
+
|
|
273
|
+
#### Running Tests
|
|
274
|
+
|
|
275
|
+
```bash
|
|
276
|
+
# Run all tests
|
|
277
|
+
pnpm test:e2e
|
|
278
|
+
|
|
279
|
+
# Run tests in watch mode
|
|
280
|
+
pnpm test:watch
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
#### Test Architecture
|
|
284
|
+
|
|
285
|
+
The test suite is designed to be **100% independent** of production schemas:
|
|
286
|
+
|
|
287
|
+
- **Self-Contained Schema** - `prisma/schema/test-models.prisma` contains all models needed for testing
|
|
288
|
+
- **No Production Dependencies** - Tests work even if production schemas don't exist
|
|
289
|
+
- **Comprehensive Coverage** - Test models cover all Prisma types, relations, and generator features
|
|
290
|
+
- **Portable** - Can be used across different projects or extracted as a standalone test suite
|
|
291
|
+
|
|
292
|
+
#### Test Model Categories
|
|
293
|
+
|
|
294
|
+
The test schema includes specialized models for testing:
|
|
295
|
+
|
|
296
|
+
1. **Basic CRUD** - `TestUser`, `TestPost`, `TestProfile`
|
|
297
|
+
2. **All Prisma Types** - `TestAllTypes` (String, Int, BigInt, Float, Decimal, Boolean, DateTime, Json, Bytes)
|
|
298
|
+
3. **Relations** - One-to-one, one-to-many, many-to-many, composite keys
|
|
299
|
+
4. **Annotations** - `@prisma-arktype.hide`, `@prisma-arktype.input.hide`, `@prisma-arktype.typeOverwrite`
|
|
300
|
+
5. **Query Operations** - Select, Include, OrderBy schemas
|
|
301
|
+
6. **Enums** - `TestCurrency`, `TestStatus`
|
|
302
|
+
|
|
303
|
+
#### Adding New Tests
|
|
304
|
+
|
|
305
|
+
1. **Add test models** to `prisma/schema/test-models.prisma` if needed
|
|
306
|
+
2. **Update mapping** in `__tests__/config/model-mapping.ts` to reference your models
|
|
307
|
+
3. **Write tests** using helper functions from `__tests__/utils/test-helpers.ts`
|
|
308
|
+
4. **Run tests** - `pnpm test`
|
|
309
|
+
|
|
310
|
+
See existing test files for examples.
|
|
311
|
+
|
|
312
|
+
#### Why Schema-Independent?
|
|
313
|
+
|
|
314
|
+
- ✅ Tests never break due to production schema changes
|
|
315
|
+
- ✅ Contributors can run tests without setting up production databases
|
|
316
|
+
- ✅ Tests can be run in isolation (CI/CD, local development)
|
|
317
|
+
- ✅ Clear, documented examples of generator usage
|
|
318
|
+
- ✅ Easy to test new features by adding new test models
|
|
319
|
+
|
|
242
320
|
### Publishing
|
|
243
321
|
|
|
244
322
|
This project uses [Changesets](https://github.com/changesets/changesets) for version management and publishing.
|
package/dist/index.js
CHANGED
|
@@ -40,9 +40,6 @@ function isHiddenInputCreate(annotation) {
|
|
|
40
40
|
function isHiddenInputUpdate(annotation) {
|
|
41
41
|
return annotation.type === "HIDDEN_INPUT_UPDATE";
|
|
42
42
|
}
|
|
43
|
-
function isOptions(annotation) {
|
|
44
|
-
return annotation.type === "OPTIONS";
|
|
45
|
-
}
|
|
46
43
|
function isTypeOverwrite(annotation) {
|
|
47
44
|
return annotation.type === "TYPE_OVERWRITE";
|
|
48
45
|
}
|
|
@@ -69,10 +66,8 @@ const annotationKeys = [
|
|
|
69
66
|
],
|
|
70
67
|
type: "HIDDEN_INPUT_UPDATE"
|
|
71
68
|
},
|
|
72
|
-
{ keys: ["@prisma-arktype.options"], type: "OPTIONS" },
|
|
73
69
|
{ keys: ["@prisma-arktype.typeOverwrite"], type: "TYPE_OVERWRITE" }
|
|
74
70
|
];
|
|
75
|
-
const prismaArktypeOptionsRegex = /@prisma-arktype\.options\{(.+)\}/;
|
|
76
71
|
const prismaArktypeTypeOverwriteRegex = /@prisma-arktype\.typeOverwrite=(.+)/;
|
|
77
72
|
function extractAnnotations(documentation) {
|
|
78
73
|
const annotations = [];
|
|
@@ -84,14 +79,7 @@ function extractAnnotations(documentation) {
|
|
|
84
79
|
for (const key of keys) {
|
|
85
80
|
if (line.includes(key)) {
|
|
86
81
|
isAnnotation = true;
|
|
87
|
-
if (type === "
|
|
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") {
|
|
82
|
+
if (type === "TYPE_OVERWRITE") {
|
|
95
83
|
const match = line.match(prismaArktypeTypeOverwriteRegex);
|
|
96
84
|
if (match && match[1]) {
|
|
97
85
|
annotations.push({
|
|
@@ -123,13 +111,6 @@ function extractAnnotations(documentation) {
|
|
|
123
111
|
hiddenInputUpdate: annotations.some(isHiddenInputUpdate)
|
|
124
112
|
};
|
|
125
113
|
}
|
|
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
114
|
|
|
134
115
|
const primitiveTypes = [
|
|
135
116
|
"Int",
|
|
@@ -146,30 +127,29 @@ function isPrimitivePrismaFieldType(type) {
|
|
|
146
127
|
return primitiveTypes.includes(type);
|
|
147
128
|
}
|
|
148
129
|
function stringifyPrimitiveType(type, annotations) {
|
|
149
|
-
const options = generateArktypeOptions(annotations);
|
|
150
130
|
switch (type) {
|
|
151
131
|
case "Int":
|
|
152
132
|
case "BigInt":
|
|
153
|
-
return `"number.integer"
|
|
133
|
+
return `"number.integer"`;
|
|
154
134
|
case "Float":
|
|
155
135
|
case "Decimal":
|
|
156
|
-
return `"number"
|
|
136
|
+
return `"number"`;
|
|
157
137
|
case "String":
|
|
158
|
-
return `"string"
|
|
138
|
+
return `"string"`;
|
|
159
139
|
case "DateTime":
|
|
160
|
-
return `"Date"
|
|
140
|
+
return `"Date"`;
|
|
161
141
|
case "Json":
|
|
162
|
-
return `"unknown"
|
|
142
|
+
return `"unknown"`;
|
|
163
143
|
case "Boolean":
|
|
164
|
-
return `"boolean"
|
|
144
|
+
return `"boolean"`;
|
|
165
145
|
case "Bytes":
|
|
166
|
-
return `"instanceof Buffer"
|
|
146
|
+
return `"instanceof Buffer"`;
|
|
167
147
|
default:
|
|
168
148
|
throw new Error(`Unsupported primitive type: ${type}`);
|
|
169
149
|
}
|
|
170
150
|
}
|
|
171
151
|
|
|
172
|
-
function
|
|
152
|
+
function wrapPrimitiveWithArray(input) {
|
|
173
153
|
return `${input}[]`;
|
|
174
154
|
}
|
|
175
155
|
function makeUnion(inputModels) {
|
|
@@ -190,38 +170,38 @@ function processEnums(enums) {
|
|
|
190
170
|
Object.freeze(processedEnums);
|
|
191
171
|
}
|
|
192
172
|
function stringifyEnum(enumData) {
|
|
193
|
-
const {
|
|
173
|
+
const { hidden } = extractAnnotations(enumData.documentation);
|
|
194
174
|
if (hidden) {
|
|
195
175
|
return;
|
|
196
176
|
}
|
|
197
177
|
const values = enumData.values.map((v) => `'${v.name}'`);
|
|
198
|
-
|
|
199
|
-
return `type("${makeUnion(values)}")${options}`;
|
|
178
|
+
return `type("${makeUnion(values)}")`;
|
|
200
179
|
}
|
|
201
180
|
|
|
202
181
|
const processedPlain = [];
|
|
203
182
|
function processPlain(models) {
|
|
204
183
|
for (const model of models) {
|
|
205
|
-
const
|
|
206
|
-
if (
|
|
184
|
+
const result = stringifyPlain(model);
|
|
185
|
+
if (result) {
|
|
207
186
|
processedPlain.push({
|
|
208
187
|
name: model.name,
|
|
209
|
-
stringified
|
|
188
|
+
stringified: result.stringified,
|
|
189
|
+
enumDependencies: result.enumDependencies
|
|
210
190
|
});
|
|
211
191
|
}
|
|
212
192
|
}
|
|
213
193
|
Object.freeze(processedPlain);
|
|
214
194
|
}
|
|
215
|
-
const enumMatch$1 = /type\("(.+)"\)/;
|
|
216
195
|
function stringifyPlain(model, isInputCreate = false, isInputUpdate = false) {
|
|
217
196
|
const config = getConfig();
|
|
218
|
-
const {
|
|
197
|
+
const { hidden } = extractAnnotations(
|
|
219
198
|
model.documentation
|
|
220
199
|
);
|
|
221
200
|
if (hidden) {
|
|
222
201
|
return;
|
|
223
202
|
}
|
|
224
203
|
const fields = [];
|
|
204
|
+
const enumDependencies = [];
|
|
225
205
|
for (const field of model.fields) {
|
|
226
206
|
const {
|
|
227
207
|
annotations: fieldAnnotations,
|
|
@@ -244,21 +224,32 @@ function stringifyPlain(model, isInputCreate = false, isInputUpdate = false) {
|
|
|
244
224
|
if (typeOverwrite) {
|
|
245
225
|
fieldType = typeOverwrite.value;
|
|
246
226
|
} else if (isPrimitivePrismaFieldType(field.type)) {
|
|
247
|
-
fieldType = stringifyPrimitiveType(field.type
|
|
227
|
+
fieldType = stringifyPrimitiveType(field.type);
|
|
248
228
|
} else if (field.kind === "enum") {
|
|
249
229
|
const enumDef = processedEnums.find((e) => e.name === field.type);
|
|
250
230
|
if (!enumDef) continue;
|
|
251
|
-
|
|
252
|
-
|
|
231
|
+
if (!enumDependencies.includes(field.type)) {
|
|
232
|
+
enumDependencies.push(field.type);
|
|
233
|
+
}
|
|
234
|
+
fieldType = field.type;
|
|
253
235
|
} else {
|
|
254
236
|
continue;
|
|
255
237
|
}
|
|
238
|
+
const isEnumType = field.kind === "enum" && !typeOverwrite;
|
|
256
239
|
if (field.isList) {
|
|
257
|
-
|
|
240
|
+
if (isEnumType) {
|
|
241
|
+
fieldType = `${fieldType}.array()`;
|
|
242
|
+
} else {
|
|
243
|
+
fieldType = `"${wrapPrimitiveWithArray(fieldType.slice(1, -1))}"`;
|
|
244
|
+
}
|
|
258
245
|
}
|
|
259
246
|
if (!field.isRequired) {
|
|
260
|
-
|
|
261
|
-
|
|
247
|
+
if (isEnumType) {
|
|
248
|
+
fieldType = `${fieldType}.or("null")`;
|
|
249
|
+
} else {
|
|
250
|
+
const inner = fieldType.slice(1, -1);
|
|
251
|
+
fieldType = `"${inner} | null"`;
|
|
252
|
+
}
|
|
262
253
|
fieldName += "?";
|
|
263
254
|
}
|
|
264
255
|
if (field.hasDefaultValue || isInputUpdate) {
|
|
@@ -268,10 +259,12 @@ function stringifyPlain(model, isInputCreate = false, isInputUpdate = false) {
|
|
|
268
259
|
}
|
|
269
260
|
fields.push(`"${fieldName}": ${fieldType}`);
|
|
270
261
|
}
|
|
271
|
-
|
|
272
|
-
|
|
262
|
+
return {
|
|
263
|
+
stringified: `{
|
|
273
264
|
${fields.join(",\n ")}
|
|
274
|
-
}
|
|
265
|
+
}`,
|
|
266
|
+
enumDependencies
|
|
267
|
+
};
|
|
275
268
|
}
|
|
276
269
|
function stringifyPlainInputCreate(model) {
|
|
277
270
|
return stringifyPlain(model, true, false);
|
|
@@ -285,11 +278,12 @@ function processCreate(models) {
|
|
|
285
278
|
for (const model of models) {
|
|
286
279
|
const { hidden } = extractAnnotations(model.documentation);
|
|
287
280
|
if (hidden) continue;
|
|
288
|
-
const
|
|
289
|
-
if (
|
|
281
|
+
const result = stringifyPlainInputCreate(model);
|
|
282
|
+
if (result) {
|
|
290
283
|
processedCreate.push({
|
|
291
284
|
name: model.name,
|
|
292
|
-
stringified
|
|
285
|
+
stringified: result.stringified,
|
|
286
|
+
enumDependencies: result.enumDependencies
|
|
293
287
|
});
|
|
294
288
|
}
|
|
295
289
|
}
|
|
@@ -310,7 +304,7 @@ function processInclude(models) {
|
|
|
310
304
|
Object.freeze(processedInclude);
|
|
311
305
|
}
|
|
312
306
|
function stringifyInclude(model) {
|
|
313
|
-
const {
|
|
307
|
+
const { hidden } = extractAnnotations(
|
|
314
308
|
model.documentation
|
|
315
309
|
);
|
|
316
310
|
if (hidden) {
|
|
@@ -327,10 +321,9 @@ function stringifyInclude(model) {
|
|
|
327
321
|
if (fields.length <= 1) {
|
|
328
322
|
return;
|
|
329
323
|
}
|
|
330
|
-
const options = generateArktypeOptions(modelAnnotations);
|
|
331
324
|
return `{
|
|
332
325
|
${fields.join(",\n ")}
|
|
333
|
-
}
|
|
326
|
+
}`;
|
|
334
327
|
}
|
|
335
328
|
|
|
336
329
|
const processedOrderBy = [];
|
|
@@ -347,7 +340,7 @@ function processOrderBy(models) {
|
|
|
347
340
|
Object.freeze(processedOrderBy);
|
|
348
341
|
}
|
|
349
342
|
function stringifyOrderBy(model) {
|
|
350
|
-
const {
|
|
343
|
+
const { hidden } = extractAnnotations(
|
|
351
344
|
model.documentation
|
|
352
345
|
);
|
|
353
346
|
if (hidden) {
|
|
@@ -361,10 +354,9 @@ function stringifyOrderBy(model) {
|
|
|
361
354
|
if (field.kind === "object") continue;
|
|
362
355
|
fields.push(`"${field.name}?": ${sortOrder}`);
|
|
363
356
|
}
|
|
364
|
-
const options = generateArktypeOptions(modelAnnotations);
|
|
365
357
|
return `{
|
|
366
358
|
${fields.join(",\n ")}
|
|
367
|
-
}
|
|
359
|
+
}`;
|
|
368
360
|
}
|
|
369
361
|
|
|
370
362
|
const processedRelations = [];
|
|
@@ -383,7 +375,7 @@ function processRelations(models) {
|
|
|
383
375
|
Object.freeze(processedRelations);
|
|
384
376
|
}
|
|
385
377
|
function stringifyRelations(model) {
|
|
386
|
-
const {
|
|
378
|
+
const { hidden } = extractAnnotations(
|
|
387
379
|
model.documentation
|
|
388
380
|
);
|
|
389
381
|
if (hidden) {
|
|
@@ -394,22 +386,22 @@ function stringifyRelations(model) {
|
|
|
394
386
|
const { hidden: fieldHidden } = extractAnnotations(field.documentation);
|
|
395
387
|
if (fieldHidden) continue;
|
|
396
388
|
if (field.kind !== "object") continue;
|
|
397
|
-
let fieldType
|
|
389
|
+
let fieldType;
|
|
398
390
|
if (field.isList) {
|
|
399
|
-
fieldType = `"unknown
|
|
400
|
-
}
|
|
401
|
-
if (!field.isRequired) {
|
|
391
|
+
fieldType = `type("unknown").array()`;
|
|
392
|
+
} else if (!field.isRequired) {
|
|
402
393
|
fieldType = `"unknown | null"`;
|
|
394
|
+
} else {
|
|
395
|
+
fieldType = `"unknown"`;
|
|
403
396
|
}
|
|
404
397
|
fields.push(`"${field.name}": ${fieldType}`);
|
|
405
398
|
}
|
|
406
399
|
if (fields.length === 0) {
|
|
407
400
|
return;
|
|
408
401
|
}
|
|
409
|
-
const options = generateArktypeOptions(modelAnnotations);
|
|
410
402
|
return `{
|
|
411
403
|
${fields.join(",\n ")}
|
|
412
|
-
}
|
|
404
|
+
}`;
|
|
413
405
|
}
|
|
414
406
|
function processRelationsCreate(models) {
|
|
415
407
|
for (const model of models) {
|
|
@@ -424,7 +416,7 @@ function processRelationsCreate(models) {
|
|
|
424
416
|
Object.freeze(processedRelationsCreate);
|
|
425
417
|
}
|
|
426
418
|
function stringifyRelationsInputCreate(model, allModels) {
|
|
427
|
-
const {
|
|
419
|
+
const { hidden } = extractAnnotations(
|
|
428
420
|
model.documentation
|
|
429
421
|
);
|
|
430
422
|
if (hidden) {
|
|
@@ -454,10 +446,9 @@ function stringifyRelationsInputCreate(model, allModels) {
|
|
|
454
446
|
if (fields.length === 0) {
|
|
455
447
|
return;
|
|
456
448
|
}
|
|
457
|
-
const options = generateArktypeOptions(modelAnnotations);
|
|
458
449
|
return `{
|
|
459
450
|
${fields.join(",\n ")}
|
|
460
|
-
}
|
|
451
|
+
}`;
|
|
461
452
|
}
|
|
462
453
|
function processRelationsUpdate(models) {
|
|
463
454
|
for (const model of models) {
|
|
@@ -472,7 +463,7 @@ function processRelationsUpdate(models) {
|
|
|
472
463
|
Object.freeze(processedRelationsUpdate);
|
|
473
464
|
}
|
|
474
465
|
function stringifyRelationsInputUpdate(model, allModels) {
|
|
475
|
-
const {
|
|
466
|
+
const { hidden } = extractAnnotations(
|
|
476
467
|
model.documentation
|
|
477
468
|
);
|
|
478
469
|
if (hidden) {
|
|
@@ -498,12 +489,14 @@ function stringifyRelationsInputUpdate(model, allModels) {
|
|
|
498
489
|
}
|
|
499
490
|
let fieldType;
|
|
500
491
|
if (field.isList) {
|
|
501
|
-
|
|
492
|
+
const connectType = `type({ "id": ${idType} }).array()`;
|
|
493
|
+
const disconnectType = `type({ "id": ${idType} }).array()`;
|
|
494
|
+
fieldType = `{ "connect?": ${connectType}, "disconnect?": ${disconnectType} }`;
|
|
502
495
|
} else {
|
|
503
496
|
if (field.isRequired) {
|
|
504
497
|
fieldType = `{ "connect": { "id": ${idType} } }`;
|
|
505
498
|
} else {
|
|
506
|
-
fieldType = `{ "connect"
|
|
499
|
+
fieldType = `{ "connect?": { "id": ${idType} }, "disconnect?": "boolean" }`;
|
|
507
500
|
}
|
|
508
501
|
}
|
|
509
502
|
fields.push(`"${field.name}?": ${fieldType}`);
|
|
@@ -511,10 +504,9 @@ function stringifyRelationsInputUpdate(model, allModels) {
|
|
|
511
504
|
if (fields.length === 0) {
|
|
512
505
|
return;
|
|
513
506
|
}
|
|
514
|
-
const options = generateArktypeOptions(modelAnnotations);
|
|
515
507
|
return `{
|
|
516
508
|
${fields.join(",\n ")}
|
|
517
|
-
}
|
|
509
|
+
}`;
|
|
518
510
|
}
|
|
519
511
|
|
|
520
512
|
const processedSelect = [];
|
|
@@ -531,7 +523,7 @@ function processSelect(models) {
|
|
|
531
523
|
Object.freeze(processedSelect);
|
|
532
524
|
}
|
|
533
525
|
function stringifySelect(model) {
|
|
534
|
-
const {
|
|
526
|
+
const { hidden } = extractAnnotations(
|
|
535
527
|
model.documentation
|
|
536
528
|
);
|
|
537
529
|
if (hidden) {
|
|
@@ -544,10 +536,9 @@ function stringifySelect(model) {
|
|
|
544
536
|
fields.push(`"${field.name}?": "boolean"`);
|
|
545
537
|
}
|
|
546
538
|
fields.push(`"_count?": "boolean"`);
|
|
547
|
-
const options = generateArktypeOptions(modelAnnotations);
|
|
548
539
|
return `{
|
|
549
540
|
${fields.join(",\n ")}
|
|
550
|
-
}
|
|
541
|
+
}`;
|
|
551
542
|
}
|
|
552
543
|
|
|
553
544
|
const processedUpdate = [];
|
|
@@ -555,11 +546,12 @@ function processUpdate(models) {
|
|
|
555
546
|
for (const model of models) {
|
|
556
547
|
const { hidden } = extractAnnotations(model.documentation);
|
|
557
548
|
if (hidden) continue;
|
|
558
|
-
const
|
|
559
|
-
if (
|
|
549
|
+
const result = stringifyPlainInputUpdate(model);
|
|
550
|
+
if (result) {
|
|
560
551
|
processedUpdate.push({
|
|
561
552
|
name: model.name,
|
|
562
|
-
stringified
|
|
553
|
+
stringified: result.stringified,
|
|
554
|
+
enumDependencies: result.enumDependencies
|
|
563
555
|
});
|
|
564
556
|
}
|
|
565
557
|
}
|
|
@@ -570,33 +562,35 @@ const processedWhere = [];
|
|
|
570
562
|
const processedWhereUnique = [];
|
|
571
563
|
function processWhere(models) {
|
|
572
564
|
for (const model of models) {
|
|
573
|
-
const
|
|
574
|
-
if (
|
|
565
|
+
const result = stringifyWhere(model);
|
|
566
|
+
if (result) {
|
|
575
567
|
processedWhere.push({
|
|
576
568
|
name: model.name,
|
|
577
|
-
stringified
|
|
569
|
+
stringified: result.stringified,
|
|
570
|
+
enumDependencies: result.enumDependencies
|
|
578
571
|
});
|
|
579
572
|
}
|
|
580
|
-
const
|
|
581
|
-
if (
|
|
573
|
+
const uniqueResult = stringifyWhereUnique(model);
|
|
574
|
+
if (uniqueResult) {
|
|
582
575
|
processedWhereUnique.push({
|
|
583
576
|
name: model.name,
|
|
584
|
-
stringified:
|
|
577
|
+
stringified: uniqueResult.stringified,
|
|
578
|
+
enumDependencies: uniqueResult.enumDependencies
|
|
585
579
|
});
|
|
586
580
|
}
|
|
587
581
|
}
|
|
588
582
|
Object.freeze(processedWhere);
|
|
589
583
|
Object.freeze(processedWhereUnique);
|
|
590
584
|
}
|
|
591
|
-
const enumMatch = /type\("(.+)"\)/;
|
|
592
585
|
function stringifyWhere(model) {
|
|
593
|
-
const {
|
|
586
|
+
const { hidden } = extractAnnotations(
|
|
594
587
|
model.documentation
|
|
595
588
|
);
|
|
596
589
|
if (hidden) {
|
|
597
590
|
return;
|
|
598
591
|
}
|
|
599
592
|
const fields = [];
|
|
593
|
+
const enumDependencies = [];
|
|
600
594
|
for (const field of model.fields) {
|
|
601
595
|
const { annotations: fieldAnnotations, hidden: fieldHidden } = extractAnnotations(field.documentation);
|
|
602
596
|
if (fieldHidden) continue;
|
|
@@ -604,36 +598,46 @@ function stringifyWhere(model) {
|
|
|
604
598
|
const typeOverwrite = fieldAnnotations.find(isTypeOverwrite);
|
|
605
599
|
let fieldType;
|
|
606
600
|
if (typeOverwrite) {
|
|
607
|
-
fieldType =
|
|
601
|
+
fieldType = typeOverwrite.value;
|
|
608
602
|
} else if (isPrimitivePrismaFieldType(field.type)) {
|
|
609
|
-
fieldType = stringifyPrimitiveType(field.type
|
|
603
|
+
fieldType = stringifyPrimitiveType(field.type);
|
|
610
604
|
} else if (field.kind === "enum") {
|
|
611
605
|
const enumDef = processedEnums.find((e) => e.name === field.type);
|
|
612
606
|
if (!enumDef) continue;
|
|
613
|
-
|
|
614
|
-
|
|
607
|
+
if (!enumDependencies.includes(field.type)) {
|
|
608
|
+
enumDependencies.push(field.type);
|
|
609
|
+
}
|
|
610
|
+
fieldType = field.type;
|
|
615
611
|
} else {
|
|
616
612
|
continue;
|
|
617
613
|
}
|
|
614
|
+
const isEnumType = field.kind === "enum" && !typeOverwrite;
|
|
618
615
|
if (field.isList) {
|
|
619
|
-
|
|
620
|
-
|
|
616
|
+
if (isEnumType) {
|
|
617
|
+
fieldType = `${fieldType}.array()`;
|
|
618
|
+
} else {
|
|
619
|
+
const inner = fieldType.slice(1, -1);
|
|
620
|
+
fieldType = `"${wrapPrimitiveWithArray(inner)}"`;
|
|
621
|
+
}
|
|
621
622
|
}
|
|
622
623
|
fields.push(`"${field.name}?": ${fieldType}`);
|
|
623
624
|
}
|
|
624
|
-
|
|
625
|
-
|
|
625
|
+
return {
|
|
626
|
+
stringified: `{
|
|
626
627
|
${fields.join(",\n ")}
|
|
627
|
-
}
|
|
628
|
+
}`,
|
|
629
|
+
enumDependencies
|
|
630
|
+
};
|
|
628
631
|
}
|
|
629
632
|
function stringifyWhereUnique(model) {
|
|
630
|
-
const {
|
|
633
|
+
const { hidden } = extractAnnotations(
|
|
631
634
|
model.documentation
|
|
632
635
|
);
|
|
633
636
|
if (hidden) {
|
|
634
637
|
return;
|
|
635
638
|
}
|
|
636
639
|
const fields = [];
|
|
640
|
+
const enumDependencies = [];
|
|
637
641
|
for (const field of model.fields) {
|
|
638
642
|
const { annotations: fieldAnnotations, hidden: fieldHidden } = extractAnnotations(field.documentation);
|
|
639
643
|
if (fieldHidden) continue;
|
|
@@ -642,14 +646,16 @@ function stringifyWhereUnique(model) {
|
|
|
642
646
|
const typeOverwrite = fieldAnnotations.find(isTypeOverwrite);
|
|
643
647
|
let fieldType;
|
|
644
648
|
if (typeOverwrite) {
|
|
645
|
-
fieldType =
|
|
649
|
+
fieldType = typeOverwrite.value;
|
|
646
650
|
} else if (isPrimitivePrismaFieldType(field.type)) {
|
|
647
|
-
fieldType = stringifyPrimitiveType(field.type
|
|
651
|
+
fieldType = stringifyPrimitiveType(field.type);
|
|
648
652
|
} else if (field.kind === "enum") {
|
|
649
653
|
const enumDef = processedEnums.find((e) => e.name === field.type);
|
|
650
654
|
if (!enumDef) continue;
|
|
651
|
-
|
|
652
|
-
|
|
655
|
+
if (!enumDependencies.includes(field.type)) {
|
|
656
|
+
enumDependencies.push(field.type);
|
|
657
|
+
}
|
|
658
|
+
fieldType = field.type;
|
|
653
659
|
} else {
|
|
654
660
|
continue;
|
|
655
661
|
}
|
|
@@ -658,15 +664,24 @@ function stringifyWhereUnique(model) {
|
|
|
658
664
|
if (fields.length === 0) {
|
|
659
665
|
return;
|
|
660
666
|
}
|
|
661
|
-
|
|
662
|
-
|
|
667
|
+
return {
|
|
668
|
+
stringified: `{
|
|
663
669
|
${fields.join(",\n ")}
|
|
664
|
-
}
|
|
670
|
+
}`,
|
|
671
|
+
enumDependencies
|
|
672
|
+
};
|
|
665
673
|
}
|
|
666
674
|
|
|
667
675
|
async function format(input) {
|
|
668
676
|
return input;
|
|
669
677
|
}
|
|
678
|
+
function generateEnumImports(enumDependencies) {
|
|
679
|
+
if (!enumDependencies || enumDependencies.length === 0) {
|
|
680
|
+
return "";
|
|
681
|
+
}
|
|
682
|
+
return `${enumDependencies.map((enumName) => `import { ${enumName} } from "./${enumName}";`).join("\n")}
|
|
683
|
+
`;
|
|
684
|
+
}
|
|
670
685
|
function mapAllModelsForWrite(processedEnums, processedPlain, processedRelations, processedWhere, processedWhereUnique, processedCreate, processedUpdate, processedRelationsCreate, processedRelationsUpdate, processedSelect, processedInclude, processedOrderBy) {
|
|
671
686
|
const config = getConfig();
|
|
672
687
|
const modelMap = /* @__PURE__ */ new Map();
|
|
@@ -679,7 +694,8 @@ function mapAllModelsForWrite(processedEnums, processedPlain, processedRelations
|
|
|
679
694
|
modelMap.set(model.name, content);
|
|
680
695
|
}
|
|
681
696
|
for (const model of processedPlain) {
|
|
682
|
-
const
|
|
697
|
+
const enumImports = generateEnumImports(model.enumDependencies);
|
|
698
|
+
const content = `${arktypeImport}${enumImports}export const ${model.name}Plain = type(${model.stringified});
|
|
683
699
|
`;
|
|
684
700
|
modelMap.set(`${model.name}Plain`, content);
|
|
685
701
|
}
|
|
@@ -706,22 +722,26 @@ export const ${plain.name} = ${plain.name}Plain;
|
|
|
706
722
|
}
|
|
707
723
|
}
|
|
708
724
|
for (const model of processedWhere) {
|
|
709
|
-
const
|
|
725
|
+
const enumImports = generateEnumImports(model.enumDependencies);
|
|
726
|
+
const content = `${arktypeImport}${enumImports}export const ${model.name}Where = type(${model.stringified});
|
|
710
727
|
`;
|
|
711
728
|
modelMap.set(`${model.name}Where`, content);
|
|
712
729
|
}
|
|
713
730
|
for (const model of processedWhereUnique) {
|
|
714
|
-
const
|
|
731
|
+
const enumImports = generateEnumImports(model.enumDependencies);
|
|
732
|
+
const content = `${arktypeImport}${enumImports}export const ${model.name}WhereUnique = type(${model.stringified});
|
|
715
733
|
`;
|
|
716
734
|
modelMap.set(`${model.name}WhereUnique`, content);
|
|
717
735
|
}
|
|
718
736
|
for (const model of processedCreate) {
|
|
719
|
-
const
|
|
737
|
+
const enumImports = generateEnumImports(model.enumDependencies);
|
|
738
|
+
const content = `${arktypeImport}${enumImports}export const ${model.name}Create = type(${model.stringified});
|
|
720
739
|
`;
|
|
721
740
|
modelMap.set(`${model.name}Create`, content);
|
|
722
741
|
}
|
|
723
742
|
for (const model of processedUpdate) {
|
|
724
|
-
const
|
|
743
|
+
const enumImports = generateEnumImports(model.enumDependencies);
|
|
744
|
+
const content = `${arktypeImport}${enumImports}export const ${model.name}Update = type(${model.stringified});
|
|
725
745
|
`;
|
|
726
746
|
modelMap.set(`${model.name}Update`, content);
|
|
727
747
|
}
|
|
@@ -773,10 +793,6 @@ async function write(processedEnums, processedPlain, processedRelations, process
|
|
|
773
793
|
const filePath = join(config.output, `${name}.ts`);
|
|
774
794
|
writePromises.push(writeFile(filePath, await format(content)));
|
|
775
795
|
}
|
|
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
796
|
await Promise.all(writePromises);
|
|
781
797
|
}
|
|
782
798
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "prisma-arktype",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.2.0",
|
|
4
4
|
"description": "Generate ArkType schemas from your Prisma schema",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -40,19 +40,19 @@
|
|
|
40
40
|
"arktype": "^2.1.25"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
|
-
"@biomejs/biome": "
|
|
44
|
-
"@changesets/cli": "
|
|
43
|
+
"@biomejs/biome": "2.3.11",
|
|
44
|
+
"@changesets/cli": "2.29.8",
|
|
45
45
|
"@commitlint/cli": "^20.1.0",
|
|
46
46
|
"@commitlint/config-conventional": "^20.0.0",
|
|
47
47
|
"@prisma/client": "^6.18.0",
|
|
48
|
-
"@tilli-pro/biome": "
|
|
48
|
+
"@tilli-pro/biome": "0.9.0",
|
|
49
49
|
"@types/node": "^24.10.0",
|
|
50
50
|
"lefthook": "^2.0.2",
|
|
51
51
|
"pkgroll": "^2.20.1",
|
|
52
|
-
"prisma": "
|
|
53
|
-
"tsx": "^4.
|
|
52
|
+
"prisma": "7.2.0",
|
|
53
|
+
"tsx": "^4.21.0",
|
|
54
54
|
"typescript": "^5.9.3",
|
|
55
|
-
"vitest": "^
|
|
55
|
+
"vitest": "^4.0.16"
|
|
56
56
|
},
|
|
57
57
|
"scripts": {
|
|
58
58
|
"build": "pkgroll",
|