Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add support for type declarations #389

Merged
merged 5 commits into from
Sep 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,15 @@ function myFunction(example: Example) {
}
```

You can also declare the type on the spot using the following syntax:

```prisma
model Example {
/// ![Record<string, string>]
map Json
}
```

### How it works

> ⚠️ **It just changes the declaration files of your generated client, no runtime code is
Expand All @@ -92,6 +101,3 @@ You can either remove it or add an empty `export {}` to make it a module.

- This project **should be** a temporary workaround _(and possible solution)_ to
https://github.com/prisma/prisma/issues/3219.

- Json types inside `type` declarations won't work. (see
https://github.com/prisma/prisma/issues/13726)
8 changes: 5 additions & 3 deletions src/handler/model-payload.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import ts from 'typescript';
import type { ModelWithRegex } from '../helpers/dmmf';
import type { PrismaEntity } from '../helpers/dmmf';
import { PrismaJsonTypesGeneratorConfig } from '../util/config';
import type { DeclarationWriter } from '../util/declaration-writer';
import { PrismaJsonTypesGeneratorError } from '../util/error';
Expand All @@ -9,7 +9,7 @@ import { replaceObject } from './replace-object';
export function handleModelPayload(
typeAlias: ts.TypeAliasDeclaration,
writer: DeclarationWriter,
model: ModelWithRegex,
model: PrismaEntity,
config: PrismaJsonTypesGeneratorConfig
) {
const type = typeAlias.type as ts.TypeLiteralNode;
Expand All @@ -34,7 +34,9 @@ export function handleModelPayload(
// Gets the inner object type we should change.
// scalars format is: $Extensions.GetResult<OBJECT, ExtArgs["result"]["user"]>
// this is the OBJECT part
const object = scalarsField?.type?.typeArguments?.[0] as ts.TypeLiteralNode;
const object = (
model.type === 'model' ? scalarsField?.type?.typeArguments?.[0] : scalarsField?.type
) as ts.TypeLiteralNode;

if (!object) {
throw new PrismaJsonTypesGeneratorError('Payload scalars could not be resolved', {
Expand Down
4 changes: 2 additions & 2 deletions src/handler/module.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import ts from 'typescript';
import type { ModelWithRegex } from '../helpers/dmmf';
import type { PrismaEntity } from '../helpers/dmmf';
import { PrismaJsonTypesGeneratorConfig } from '../util/config';
import { PRISMA_NAMESPACE_NAME } from '../util/constants';
import { DeclarationWriter } from '../util/declaration-writer';
Expand All @@ -10,7 +10,7 @@ import { handleStatement } from './statement';
export function handlePrismaModule(
child: ts.ModuleDeclaration,
writer: DeclarationWriter,
models: ModelWithRegex[],
models: PrismaEntity[],
config: PrismaJsonTypesGeneratorConfig
) {
const name = child
Expand Down
4 changes: 2 additions & 2 deletions src/handler/replace-object.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import ts from 'typescript';
import type { ModelWithRegex } from '../helpers/dmmf';
import type { PrismaEntity } from '../helpers/dmmf';
import { findNewSignature } from '../helpers/find-signature';
import { JSON_REGEX } from '../helpers/regex';
import { PrismaJsonTypesGeneratorConfig } from '../util/config';
Expand All @@ -11,7 +11,7 @@ import { PrismaJsonTypesGeneratorError } from '../util/error';
export function replaceObject(
object: ts.TypeLiteralNode,
writer: DeclarationWriter,
model: ModelWithRegex,
model: PrismaEntity,
config: PrismaJsonTypesGeneratorConfig
) {
for (const field of model.fields) {
Expand Down
4 changes: 2 additions & 2 deletions src/handler/statement.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import ts from 'typescript';
import type { ModelWithRegex } from '../helpers/dmmf';
import type { PrismaEntity } from '../helpers/dmmf';
import { PrismaJsonTypesGeneratorConfig } from '../util/config';
import { DeclarationWriter } from '../util/declaration-writer';
import { handleModelPayload } from './model-payload';
Expand All @@ -12,7 +12,7 @@ import { replaceObject } from './replace-object';
export function handleStatement(
statement: ts.Statement,
writer: DeclarationWriter,
models: ModelWithRegex[],
models: PrismaEntity[],
config: PrismaJsonTypesGeneratorConfig
) {
if (statement.kind !== ts.SyntaxKind.TypeAliasDeclaration) {
Expand Down
36 changes: 23 additions & 13 deletions src/helpers/dmmf.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,34 @@
import type { DMMF } from '@prisma/generator-helper';
import { createRegexForType } from './regex';

/** A Prisma DMMF model with the regexes for each field. */
export interface ModelWithRegex extends DMMF.Model {
/** A Prisma DMMF model/type with the regexes for each field. */
export interface PrismaEntity extends DMMF.Model {
regexps: RegExp[];
type: 'model' | 'type';
}

/**
* Parses the DMMF document and returns a list of models that have at least one field with
* typed json and the regexes for each field type.
*/
export function extractPrismaModels(dmmf: DMMF.Document): ModelWithRegex[] {
return (
dmmf.datamodel.models
// Define the regexes for each model
.map(
(model): ModelWithRegex => ({
...model,
regexps: createRegexForType(model.name)
})
)
);
export function extractPrismaModels(dmmf: DMMF.Document): PrismaEntity[] {
const models = dmmf.datamodel.models
// Define the regexes for each model
.map(
(model): PrismaEntity => ({
...model,
type: 'model',
regexps: createRegexForType(model.name)
})
);
const types = dmmf.datamodel.types
// Define the regexes for each model
.map(
(model): PrismaEntity => ({
...model,
type: 'type',
regexps: createRegexForType(model.name)
})
);
return models.concat(types);
}
13 changes: 13 additions & 0 deletions test/schemas/mongo.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ model Model {

/// [List]
list Json[]

nested Nested
}

model Text {
Expand All @@ -35,3 +37,14 @@ model Text {
/// !['A' | 'B']
literal String
}

type Nested {
/// [Simple]
simple Json

/// [Optional]
optional Json?

/// [List]
list Json[]
}
9 changes: 0 additions & 9 deletions test/types/cockroach.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,12 +71,3 @@ expectNotType<Text>({
typed: 'D' as string,
literal: 'D' as string
});

expectType<Text>({
id: 0,
untyped: '' as string,
typed: {
in: ['C'] as PCockroachJson.WithType[]
},
literal: 'A' as 'A' | 'B'
});
67 changes: 49 additions & 18 deletions test/types/mongo.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,28 +14,48 @@ expectAssignable<Model>({
id: '0',
simple: 1,
optional: 2,
list: [3]
list: [3],
nested: {
simple: 1,
optional: 2,
list: [3]
}
});

expectAssignable<Model>({
id: '0',
simple: 1,
optional: null,
list: [3]
list: [3],
nested: {
simple: 1,
optional: null,
list: [3]
}
});

expectAssignable<Model>({
id: '0',
simple: 1,
optional: null,
list: []
list: [],
nested: {
simple: 1,
optional: null,
list: []
}
});

expectAssignable<Model>({
id: '0',
simple: 1,
optional: 2,
list: [3, 3, 3]
list: [3, 3, 3],
nested: {
simple: 1,
optional: 2,
list: [3, 3, 3]
}
});

expectAssignable<UpdateManyInput<Model['list'][number]>>({
Expand Down Expand Up @@ -68,30 +88,50 @@ expectAssignable<UpdateManyInput<Model['list'][number]>>({

expectNotAssignable<Model>({
id: '0',
simple: '1',
simple: 1,
optional: 2,
list: [3]
list: [3],
nested: {
simple: 1,
optional: 2,
list: [3]
}
});

expectNotAssignable<Model>({
id: '0',
simple: 1,
optional: '2',
list: [3]
list: [3],
nested: {
simple: 1,
optional: 2,
list: [3]
}
});

expectNotAssignable<Model>({
id: '0',
simple: 1,
optional: 'undefined',
list: 3
list: 3,
nested: {
simple: 1,
optional: 2,
list: [3]
}
});

expectNotAssignable<Model>({
id: '0',
simple: 1,
optional: 2,
list: '3,3,3'
list: '3,3,3',
nested: {
simple: 1,
optional: 2,
list: [3]
}
});

expectNotAssignable<UpdateManyInput<Model['list'][number]>>({
Expand Down Expand Up @@ -127,12 +167,3 @@ expectNotType<Text>({
typed: 'D' as string,
literal: 'D' as string
});

expectType<Text>({
id: '0',
untyped: '' as string,
typed: {
in: ['C'] as PMongoJson.WithType[]
},
literal: 'A' as 'A' | 'B'
});
9 changes: 0 additions & 9 deletions test/types/mssql.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,3 @@ expectNotType<Text>({
typed: 'D' as string,
literal: 'D' as string
});

expectType<Text>({
id: 0,
untyped: '' as string,
typed: {
in: ['C'] as PMssqlJson.WithType[]
},
literal: 'A' as 'A' | 'B'
});
9 changes: 0 additions & 9 deletions test/types/mysql.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,12 +71,3 @@ expectNotType<Text>({
typed: 'D' as string,
literal: 'D' as string
});

expectType<Text>({
id: 0,
untyped: '' as string,
typed: {
in: ['C'] as PMysqlJson.WithType[]
},
literal: 'A' as 'A' | 'B'
});
9 changes: 0 additions & 9 deletions test/types/sqlite.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,3 @@ expectNotType<Text>({
typed: 'D' as string,
literal: 'D' as string
});

expectType<Text>({
id: 0,
untyped: '' as string,
typed: {
in: ['C'] as PSqliteJson.WithType[]
},
literal: 'A' as 'A' | 'B'
});
9 changes: 0 additions & 9 deletions test/types/string.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,3 @@ expectNotType<Model>({
typed: 'D' as string,
literal: 'D' as string
});

expectType<Model>({
id: 0,
untyped: '' as string,
typed: {
in: ['C'] as PStringJson.WithType[]
},
literal: 'A' as 'A' | 'B'
});
Loading