🚀 Big News: Socket Acquires Coana to Bring Reachability Analysis to Every Appsec Team.Learn more
Socket
DemoInstallSign in
Socket

prisma-json-types-generator

Package Overview
Dependencies
Maintainers
1
Versions
37
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

prisma-json-types-generator - npm Package Compare versions

Comparing version

to
3.0.0-beta.1

assets/namespace.d.ts

37

package.json
{
"name": "prisma-json-types-generator",
"version": "2.5.0",
"version": "3.0.0-beta.1",
"description": "Changes JsonValues to your custom typescript type",

@@ -17,19 +17,29 @@ "keywords": [

"author": "Arthur Fiorette <npm@arthur.place>",
"main": "dist/generator.js",
"bin": "./dist/bin.js",
"main": "./index.js",
"bin": "./index.js",
"scripts": {
"build": "tsc",
"dev": "tsc -w",
"format": "prettier --write .",
"start": "node dist/bin.js",
"test": "sh ./scripts/test.sh"
},
"dependencies": {
"@prisma/generator-helper": "4.16.0",
"tslib": "2.5.3"
"@prisma/generator-helper": "4.16.2",
"tslib": "2.6.0"
},
"devDependencies": {
"@arthurfiorette/prettier-config": "1.0.9",
"@prisma/client": "4.16.0",
"@types/node": "20.3.1",
"@prisma/client": "5.1.1",
"@types/node": "20.3.3",
"@types/prettier": "2.7.3",
"prettier": "2.8.8",
"prisma": "4.16.0",
"prisma": "^5.1.1",
"source-map-support": "^0.5.21",
"tsd": "^0.28.1",
"typescript": "5.1.3"
},
"peerDependencies": {
"prisma": "^4.16.0"
"prisma": "^5.1",
"typescript": "^5.1"
},

@@ -39,10 +49,3 @@ "packageManager": "pnpm@8.4.0",

"node": ">=14.0"
},
"scripts": {
"build": "tsc",
"dev": "tsc -w",
"format": "prettier --write .",
"start": "node dist/bin.js",
"test": "prisma generate && tsc --noEmit -p tsconfig.test.json"
}
}
}

@@ -35,8 +35,2 @@ <p align="center">

provider = "prisma-json-types-generator"
// namespace = "PrismaJson"
// clientOutput = "<finds it automatically>"
// (./ -> relative to schema, or an importable path to require() it)
// useType = "MyType"
// In case you need to use a type, export it inside the namespace and we will add a index signature to it
// (e.g. export namespace PrismaJson { export type MyType = {a: 1, b: 2} }; will generate namespace.MyType["TYPE HERE"])
}

@@ -56,2 +50,5 @@

complex Json
/// ![number | string]
literal Json
}

@@ -70,2 +67,3 @@ ```

type MyType = boolean;
// or you can use classes, interfaces, object types, etc.

@@ -93,5 +91,55 @@ type ComplexType = {

// example.complex is now a { foo: string; bar: number }
// example.literal is now a string | number
}
```
### Configuration
````ts
export interface PrismaJsonTypesGeneratorConfig {
/**
* The namespace to generate the types in.
*
* @default 'PrismaJson'
*/
namespace: string;
/**
* The name of the client output type. By default it will try to find it automatically
*
* (./ -> relative to schema, or an importable path to require() it)
*
* @default undefined
*/
clientOutput?: string;
/**
* In case you need to use a type, export it inside the namespace and we will add a
* index signature to it
*
* @example
*
* ```ts
* export namespace PrismaJson {
* export type GlobalType = {
* fieldA: string;
* fieldB: MyType;
* };
* }
* ```
*
* @default undefined
*/
useType?: string;
/**
* If we should allow untyped JSON fields to be any, otherwise we change them to
* unknown.
*
* @default false
*/
allowAny?: boolean;
}
````
### How it works

@@ -98,0 +146,0 @@

@@ -5,2 +5,3 @@ import { generatorHandler } from '@prisma/generator-helper';

// Defines the entry point of the generator.
generatorHandler({

@@ -7,0 +8,0 @@ onManifest,

import ts from 'typescript';
import type { Declaration } from '../file/reader';
import type { ModelWithRegex } from '../helpers/dmmf';
import { replaceObject } from '../helpers/replace-object';
import { PrismaJsonTypesGeneratorConfig } from '../util/config';
import type { DeclarationWriter } from '../util/declaration-writer';
import { PrismaJsonTypesGeneratorError } from '../util/error';
import { replaceObject } from './replace-object';
export async function handleModelPayload(
/** Replacer responsible for the main <Model>Payload type. */
export function handleModelPayload(
typeAlias: ts.TypeAliasDeclaration,
replacer: Declaration['replacer'],
writer: DeclarationWriter,
model: ModelWithRegex,
nsName: string,
useType?: string
config: PrismaJsonTypesGeneratorConfig
) {

@@ -16,11 +18,14 @@ const type = typeAlias.type as ts.TypeLiteralNode;

if (type.kind !== ts.SyntaxKind.TypeLiteral) {
throw new Error(
`prisma-json-types-generator: Provided model payload is not a type literal: ${type.getText()}`
throw new PrismaJsonTypesGeneratorError(
'Provided model payload is not a type literal',
{ type: type.getText() }
);
}
const scalarsField = type.members.find((m) => m.name?.getText() === 'scalars');
const scalarsField: any = type.members.find((m) => m.name?.getText() === 'scalars');
// Besides `scalars` field, the `objects` field also exists, but we don't need to handle it
// because it just contains references to other <model>Payloads that we already change separately
// Currently, there are 4 possible fields in the <model>Payload type:
// - `scalars` field, which is what we mainly change
// - `objects` are just references to other fields in which we change separately
// - `name` and `composites` we do not have to change
if (!scalarsField) {

@@ -30,12 +35,15 @@ return;

const object = ((scalarsField as ts.PropertySignature)?.type as ts.TypeReferenceNode)
?.typeArguments?.[0] as ts.TypeLiteralNode;
// 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;
if (!object) {
throw new Error(
`prisma-json-types-generator: Payload scalars could not be resolved: ${type.getText()}`
);
throw new PrismaJsonTypesGeneratorError('Payload scalars could not be resolved', {
type: type.getText()
});
}
replaceObject(model, object, nsName, replacer, typeAlias.name.getText(), useType);
// Replaces this object
return replaceObject(object, writer, model, config);
}
import ts from 'typescript';
import type { Declaration } from '../file/reader';
import type { ModelWithRegex } from '../helpers/dmmf';
import { replaceSignature } from '../helpers/handle-signature';
import { JSON_REGEX } from '../helpers/regex';
import { PrismaJsonTypesGeneratorConfig } from '../util/config';
import { PRISMA_NAMESPACE_NAME } from '../util/constants';
import { DeclarationWriter } from '../util/declaration-writer';
import { PrismaJsonTypesGeneratorError } from '../util/error';
import { handleStatement } from './statement';
export async function handleModule(
module: ts.ModuleDeclaration,
replacer: Declaration['replacer'],
/** Handles the prisma namespace module. */
export function handlePrismaModule(
child: ts.ModuleDeclaration,
writer: DeclarationWriter,
models: ModelWithRegex[],
nsName: string,
useType?: string
config: PrismaJsonTypesGeneratorConfig
) {
const namespace = module
const name = child
.getChildren()
.find((n): n is ts.ModuleBlock => n.kind === ts.SyntaxKind.ModuleBlock);
.find((n): n is ts.Identifier => n.kind === ts.SyntaxKind.Identifier);
if (!namespace) {
throw new Error('Prisma namespace could not be found');
// Not a prisma namespace
if (!name || name.text !== PRISMA_NAMESPACE_NAME) {
return;
}
for (const statement of namespace.statements) {
const typeAlias = statement as ts.TypeAliasDeclaration;
const content = child
.getChildren()
.find((n): n is ts.ModuleBlock => n.kind === ts.SyntaxKind.ModuleBlock);
// Filters any statement that isn't a export type declaration
if (
statement.kind !== ts.SyntaxKind.TypeAliasDeclaration ||
typeAlias.type.kind !== ts.SyntaxKind.TypeLiteral
) {
continue;
}
if (!content || !content.statements.length) {
throw new PrismaJsonTypesGeneratorError(
'Prisma namespace content could not be found'
);
}
const typeAliasName = typeAlias.name.getText();
const typeAliasType = typeAlias.type as ts.TypeLiteralNode;
// May includes the model name but is not the actual model, like
// UserCreateWithoutPostsInput for Post model. that's why we need
// to check if the model name is in the regex
const model = models.find((m) => m.regexps.some((r) => r.test(typeAliasName)));
if (!model) {
continue;
}
const fields = model.fields.filter((f) => f.documentation?.match(JSON_REGEX));
for (const member of typeAliasType.members) {
if (member.kind !== ts.SyntaxKind.PropertySignature) {
continue;
// Loops through all statements in the prisma namespace
for (const statement of content.statements) {
try {
handleStatement(statement, writer, models, config);
} catch (error) {
// This allows some types to be generated even if others may fail
// which is good for incremental development/testing
if (error instanceof PrismaJsonTypesGeneratorError) {
return PrismaJsonTypesGeneratorError.handler(error);
}
const signature = member as ts.PropertySignature;
const fieldName = member.name?.getText();
const field = fields.find((f) => f.name === fieldName);
if (!field || !fieldName) {
continue;
}
if (!signature.type) {
throw new Error(
`prisma-json-types-generator: No type found for field ${fieldName} at model ${typeAliasName}`
);
}
const typename = field.documentation?.match(JSON_REGEX)?.[1];
if (!typename) {
throw new Error(
`prisma-json-types-generator: No typename found for field ${fieldName} at model ${typeAliasName}`
);
}
replaceSignature(
signature.type,
typename,
nsName,
replacer,
fieldName,
model.name,
typeAliasName,
useType
);
// Stops this generator is error thrown is not manually added by our code.
throw error;
}
}
}
import type { DMMF } from '@prisma/generator-helper';
import { JSON_REGEX, regexForPrismaType } from './regex';
import { createRegexForType } from './regex';
export type ModelWithRegex = DMMF.Model & {
/** A Prisma DMMF model with the regexes for each field. */
export interface ModelWithRegex extends DMMF.Model {
regexps: RegExp[];
};
}
export function parseDmmf(dmmf: DMMF.Document): ModelWithRegex[] {
/**
* 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
// All models that have at least one field with typed json
.filter((m) => m.fields.some((f) => f.documentation?.match(JSON_REGEX)))
.map((m) => ({
...m,
// Loads all names and subnames regexes for the model
regexps: regexForPrismaType(m.name)
}))
// Define the regexes for each model
.map(
(model): ModelWithRegex => ({
...model,
regexps: createRegexForType(model.name)
})
)
);
}

@@ -1,2 +0,8 @@

export const JSON_REGEX = /^\s*\[(.*?)\]/m;
/**
* A regex to match the JSON output of a field's comment type.
*
* @example `[TYPE] comment...`
*/
export const JSON_REGEX = /^\s*!?\[(.*?)\]/m;
export const LITERAL_REGEX = /^\s*!?/m;

@@ -7,3 +13,3 @@ /**

*/
export function regexForPrismaType(name: string) {
export function createRegexForType(name: string) {
return [

@@ -37,4 +43,4 @@ new RegExp(`^${name}CountAggregate$`, 'm'),

/** If the provided type is a update one variant */
export function isUpdateOne(type: string) {
return type.match(/UpdateInput$/m) || type.match(/UpdateWithout(?:\\w+?)Input$/m);
export function isUpdateOneType(type: string) {
return type.match(/UpdateInput$/m) || type.match(/UpdateWithout(?:\w+?)Input$/m);
}
import type { GeneratorOptions } from '@prisma/generator-helper';
import ts from 'typescript';
import { readPrismaDeclarations } from './file/reader';
import { handleModule } from './handler/module';
import { handleTypeAlias } from './handler/type-alias';
import { parseDmmf } from './helpers/dmmf';
import { handlePrismaModule } from './handler/module';
import { extractPrismaModels } from './helpers/dmmf';
import { parseConfig } from './util/config';
import { DeclarationWriter } from './util/declaration-writer';
import { findPrismaClientGenerator } from './util/prisma-generator';
import { buildTypesFilePath } from './util/source-path';
/** Runs the generator with the given options. */
export async function onGenerate(options: GeneratorOptions) {
const nsName = options.generator.config.namespace || 'PrismaJson';
const prismaClient = findPrismaClientGenerator(options.otherGenerators);
const prismaClientOptions = options.otherGenerators.find((g) => g.name === 'client');
const config = parseConfig(options.generator.config);
if (!prismaClientOptions) {
throw new Error(
'prisma-json-types-generator: Could not find client generator options, are you using prisma-client-js before prisma-json-types-generator?'
);
}
if (!prismaClientOptions.output?.value) {
throw new Error(
'prisma-json-types-generator: prisma client output not found: ' +
JSON.stringify(prismaClientOptions, null, 2)
);
}
const { content, replacer, sourcePath, update } = await readPrismaDeclarations(
nsName,
prismaClientOptions.output.value,
options.generator.config.clientOutput,
const clientOutput = buildTypesFilePath(
prismaClient.output.value,
config.clientOutput,
options.schemaPath
);
const writer = new DeclarationWriter(clientOutput, config);
// Reads the prisma declaration file content.
await writer.load();
const tsSource = ts.createSourceFile(
sourcePath,
content,
writer.filepath,
writer.content,
ts.ScriptTarget.ESNext,

@@ -41,37 +35,16 @@ true,

const models = parseDmmf(options.dmmf);
const prismaModels = extractPrismaModels(options.dmmf);
const promises: Promise<void>[] = [];
// Handles the prisma namespace.
tsSource.forEachChild((child) => {
switch (child.kind) {
case ts.SyntaxKind.TypeAliasDeclaration:
promises.push(
handleTypeAlias(
child as ts.TypeAliasDeclaration,
replacer,
models,
nsName,
options.generator.config.useType
)
);
break;
case ts.SyntaxKind.ModuleDeclaration:
promises.push(
handleModule(
child as ts.ModuleDeclaration,
replacer,
models,
nsName,
options.generator.config.useType
)
);
break;
try {
if (child.kind === ts.SyntaxKind.ModuleDeclaration) {
handlePrismaModule(child as ts.ModuleDeclaration, writer, prismaModels, config);
}
} catch (error) {
console.log(error);
}
});
await Promise.all(promises);
await update();
await writer.save();
}

@@ -5,5 +5,8 @@ import type { GeneratorManifest } from '@prisma/generator-helper';

/** Generates simple metadata for this generator. */
export function onManifest(): GeneratorManifest {
return {
version,
// TODO: We should change this to the real output of the generator in some way. But we cannot get its real output here
// because we need to await the prisma client to be generated first.
defaultOutput: './',

@@ -10,0 +13,0 @@ prettyName: 'Prisma Json Types Generator',

@@ -92,3 +92,3 @@ /* prettier-ignore */

"noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
"noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */

@@ -95,0 +95,0 @@ "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */