Security News
Research
Data Theft Repackaged: A Case Study in Malicious Wrapper Packages on npm
The Socket Research Team breaks down a malicious wrapper package that uses obfuscation to harvest credentials and exfiltrate sensitive data.
@mrleebo/prisma-ast
Advanced tools
This library uses an abstract syntax tree to parse schema.prisma files into an object in JavaScript. It is similar to [@prisma/sdk](https://github.com/prisma/prisma/tree/master/src/packages/sdk) except that it preserves comments and model attributes.
@mrleebo/prisma-ast is an npm package designed to parse and manipulate Prisma schema files. It provides an Abstract Syntax Tree (AST) representation of Prisma schema files, allowing developers to programmatically read, modify, and generate Prisma schema definitions.
Parsing Prisma Schema
This feature allows you to parse a Prisma schema string into an AST. The code sample demonstrates how to parse a simple Prisma schema and output the resulting AST.
const { parseSchema } = require('@mrleebo/prisma-ast');
const schema = `
model User {
id Int @id @default(autoincrement())
name String
}
`;
const ast = parseSchema(schema);
console.log(JSON.stringify(ast, null, 2));
Modifying Prisma Schema
This feature allows you to modify an existing Prisma schema by manipulating its AST. The code sample shows how to add a new field to the User model and then print the updated schema.
const { parseSchema, printSchema } = require('@mrleebo/prisma-ast');
let schema = `
model User {
id Int @id @default(autoincrement())
name String
}
`;
let ast = parseSchema(schema);
ast.declarations[0].fields.push({
name: 'email',
type: 'String',
attributes: []
});
schema = printSchema(ast);
console.log(schema);
Generating Prisma Schema
This feature allows you to generate a Prisma schema from an AST. The code sample demonstrates how to create an AST for a simple User model and then generate the corresponding Prisma schema string.
const { printSchema } = require('@mrleebo/prisma-ast');
const ast = {
type: 'schema',
declarations: [
{
type: 'model',
name: 'User',
fields: [
{ name: 'id', type: 'Int', attributes: [{ type: 'attribute', name: 'id' }, { type: 'attribute', name: 'default', args: [{ type: 'function', name: 'autoincrement' }] }] },
{ name: 'name', type: 'String', attributes: [] }
]
}
]
};
const schema = printSchema(ast);
console.log(schema);
The official Prisma ORM package. While it does not provide direct AST manipulation capabilities, it offers a comprehensive set of tools for working with databases using Prisma schema files. It focuses more on database interactions and migrations rather than schema parsing and manipulation.
A package that provides a Domain Specific Language (DSL) for generating Prisma schema files. It allows for programmatic creation of Prisma schema definitions but does not offer the same level of AST manipulation as @mrleebo/prisma-ast.
A package that converts Prisma schema files to DBML (Database Markup Language) format. It focuses on schema conversion rather than direct AST manipulation, making it useful for different use cases compared to @mrleebo/prisma-ast.
This library uses an abstract syntax tree to parse schema.prisma files into an object in JavaScript. It also allows you to update your Prisma schema files using a Builder object pattern that is fully implemented in TypeScript.
It is similar to @prisma/sdk except that it preserves comments and model attributes. It also doesn't attempt to validate the correctness of the schema at all; the focus is instead on the ability to parse the schema into an object, manipulate it using JavaScript, and re-print the schema back to a file without losing information that isn't captured by other parsers.
It is probable that a future version of @prisma/sdk will render this library obsolete.
npm install @mrleebo/prisma-ast
produceSchema(source: string, (builder: PrismaSchemaBuilder) => void, printOptions?: PrintOptions): string
produceSchema is the simplest way to interact with prisma-ast; you input your schema source and a producer function to produce modifications to it, and it will output the schema source with your modifications applied.
import { produceSchema } from '@mrleebo/prisma-ast'
const input = `
model User {
id Int @id @default(autoincrement())
name String @unique
}
`
const output = produceSchema(source, (builder) => {
builder
.model("AppSetting")
.field('key', 'String', [{ name: 'id' }])
.field('value', 'Json')
})
model User {
id Int @id @default(autoincrement())
name String @unique
}
model AppSetting {
key String @id
value Json
}
For more information about what the builder can do, check out the PrismaSchemaBuilder class.
The produceSchema()
utility will construct a builder for you, but you can also create your own instance, which may be useful for more interactive use-cases.
import { createPrismaSchemaBuilder } from '@mrleebo/prisma-ast'
const builder = createPrismaSchemaBuilder()
builder.model('User')
.field('id', 'Int')
.attribute('id')
.attribute('default', [{ name: 'autoincrement' }])
.field('name', 'String')
.attribute('unique')
.break()
.comment("this is a comment")
.blockAttribute('index', ['name'])
const output = builder.print()
model User {
id Int @id @default(autoincrement())
name String @unique
// this is a comment
@@index([name])
}
prisma-ast can sort the schema for you. The default sort order is ['generator', 'datasource', 'model', 'enum']
and will sort objects of the same type alphabetically.
print(options?: {
sort: boolean,
locales?: string | string[],
sortOrder?: Array<'generator' | 'datasource' | 'model' | 'enum'>
})
You can optionally set your own sort order, or change the locale used by the sort.
// sort with default parameters
builder.print({ sort: true })
// sort with options
builder.print({
sort: true, locales: 'en-US', sortOrder: ['datasource', 'generator', 'model', 'enum']
});
Since a schema can only have one datasource, calling this command will override the existing datasource if the schema already has one, or create a datasource block if it doesn't.
datasource(provider: string, url: string | { env: string })
You can set a datasource by passing in the provider and url parameters.
builder.datasource('postgresql', { env: 'DATABASE_URL' })
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
If you want to perform a custom action that there isn't a Builder method for, you can access the underlying schema object programmatically.
import { Datasource } from '@mrleebo/prisma-ast'
// rename the datasource programmatically
builder.datasource('postgresql', { env: 'DATABASE_URL' }).then<Datasource>((datasource) => {
datasource.name = "DS"
})
datasource DS {
provider = "postgresql"
url = env("DATABASE_URL")
}
generator(name: string, provider: string)
If the schema already has a generator with the given name, it will be updated. Otherwise, a new generator will be created.
builder.generator('nexusPrisma', 'nexus-prisma')
generator nexusPrisma {
provider = "nexus-prisma"
}
assignment(key: string, value: string)
If your generator accepts additional assignments, they can be added by chaining .assignment() calls to your generator.
builder.generator('client', 'prisma-client-js').assignment('output', 'db.js')
generator client {
provider = "prisma-client-js"
output = "db.js"
}
If you want to perform a custom action that there isn't a Builder method for, you can access the underlying schema object programmatically.
generator client {
provider = "prisma-client-js"
output = "db.js"
}
import { Generator } from '@mrleebo/prisma-ast'
// rename the generator programmatically
builder.generator('client').then<Generator>((generator) => {
generator.name = "foo"
})
generator foo {
provider = "prisma-client-js"
output = "db.js"
}
If the model with that name already exists in the schema, it will be selected and any fields that follow will be appended to the model. Otherwise, the model will be created and added to the schema.
builder.model('Project').field('name', 'String')
model Project {
name String
}
If you want to perform a custom action that there isn't a Builder method for, you can access the underlying schema object programmatically.
model Project {
name String
}
import { Model } from '@mrleebo/prisma-ast'
// rename the datasource programmatically
builder.model('Project').then<Model>((model) => {
model.name = "Task"
})
model Task {
name String
}
If the entered model name already exists, that model will be used as the subject for any field and attribute calls that follow.
model Project {
name String
}
builder.model('Project').field('projectCode', 'String').attribute('unique')
model Project {
name String
projectCode String @unique
}
If the field already exists, you can add new attributes to it by making calls to .attribute()
.
model Project {
name String
projectCode String @unique
}
builder.model('Project').field('name').attribute('unique')
model Project {
name String @unique
projectCode String @unique
}
You can remove an existing field with .removeField()
.
model Project {
name String
projectCode String @unique
}
builder.model('Project').removeField('projectCode')
model Project {
name String
}
You can remove an attribute from a field with .removeAttribute()
.
model Project {
name String
projectCode String @unique
}
builder.model('Project').field('projectCode').removeAttribute('unique')
model Project {
name String
projectCode String
}
If you want to perform a custom action that there isn't a Builder method for, you can access the underlying schema object programmatically.
model TaskMessage {
createdAt DateTime? @db.Timestamptz(6)
}
import { Field, Attribute } from '@mrleebo/prisma-ast'
// Replace the @db.Timestamptz(6) attribute with @default(now())
builder
.model('TaskMessage')
.field('createdAt')
.then<Field>(field => {
const attribute: Attribute = {
type: 'attribute',
kind: 'field',
name: 'default',
args: [{ type: 'attributeArgument', value: 'now()' }],
};
field.attributes = [attribute];
});
model TaskMessage {
createdAt DateTime? @default(now())
}
model Project {
name String
projectCode String @unique
}
builder.model('Project').blockAttribute('index', ['name'])
model Project {
name String
projectCode String @unique
@@index([name])
}
builder.enum('Role', ['USER', 'ADMIN'])
enum Role {
USER
ADMIN
}
Additional enumerators can also be added to an existing Enum
builder.enum('Role').break().comment("New role added for feature #12").enumerator('ORGANIZATION')
enum Role {
USER
ADMIN
// New role added for feature #12
ORGANIZATION
}
builder
.model("Project")
.break()
.comment("I wish I could add a color to your rainbow")
model Project {
name String
projectCode String @unique
@@index([name])
// I wish I could add a color to your rainbow
}
The produceSchema
and createPrismaSchemaBuilder
functions are intended to be your interface for interacting with the prisma schema, but you can also get direct access to the AST representation if you need to edit the schema for more advanced usages that aren't covered by the methods above.
The shape of the AST is not fully documented, and it is more likely to change than the builder API.
import { getSchema } from '@mrleebo/prisma-ast'
const source = `
model User {
id Int @id @default(autoincrement())
name String @unique
}
`
const schema = getSchema(source)
This is what builder.print()
calls internally, and is what you'd use to print if you called getSchema()
.
import { printSchema } from '@mrleebo/prisma-ast'
const source = printSchema(schema)
You can optionally re-sort the schema. The default sort order is ['generator', 'datasource', 'model', 'enum']
, and objects with the same type are sorted alphabetically, but the sort order can be overridden.
const source = printSchema(schema, { sort: true, locales: "en-US", sortOrder: ["datasource", "generator", "model", "enum"] })
FAQs
This library uses an abstract syntax tree to parse schema.prisma files into an object in JavaScript. It is similar to [@prisma/sdk](https://github.com/prisma/prisma/tree/master/src/packages/sdk) except that it preserves comments and model attributes.
The npm package @mrleebo/prisma-ast receives a total of 120,640 weekly downloads. As such, @mrleebo/prisma-ast popularity was classified as popular.
We found that @mrleebo/prisma-ast demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Research
The Socket Research Team breaks down a malicious wrapper package that uses obfuscation to harvest credentials and exfiltrate sensitive data.
Research
Security News
Attackers used a malicious npm package typosquatting a popular ESLint plugin to steal sensitive data, execute commands, and exploit developer systems.
Security News
The Ultralytics' PyPI Package was compromised four times in one weekend through GitHub Actions cache poisoning and failure to rotate previously compromised API tokens.