npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

zen-stack-ts

v1.4.4

Published

Generate ZenStack from TypeScript

Downloads

11

Readme

zen-stack-ts

npm version

Generate ZenStack from TypeScript, based on Refract.

Installation

npm i -D zen-stack-ts
yarn add -D zen-stack-ts

Usage

See here for a full demo.


Use the ZenStackTs default export of this package to generate a Prisma file.

// schema.ts

// Import the entry-point
import ZenStackTs from 'zen-stack-ts';
// Import your custom Models
import { Roles, User, Posts } from './models';

ZenStackTs({
  // Supply models/enums for generation
  schema: [Roles, User, Posts],
  // https://www.prisma.io/docs/reference/api-reference/prisma-schema-reference#datasource
  datasource: {
    provider: 'postgresql',
    url: 'env("DATABASE_URL")',
    shadowDatabaseUrl: 'env("DATABASE_SHADOW_URL")',
    referentialIntegrity: 'prisma',
  },
  // https://www.prisma.io/docs/reference/api-reference/prisma-schema-reference#generator
  generators: [
    {
      provider: 'prisma-client-js',
      previewFeatures: ['referentialIntegrity'],
      engineType: 'library',
      binaryTargets: ['native'],
    },
  ],
  // Define output path for generated Prisma file
  output: path.join(process.cwd(), 'schema.prisma'),
});

A command like npx ts-node schema.ts will run this TypeScript code & generate the resulting Prisma file at the output path.

Models

const User = Model('User', 'This is an optional comment');

User.Field('id', Int(Id, Default('autoincrement()')), 'The primary key');

// // This is an optional comment
// model User {
//    // The primary key
//    id  Int @id @default(autoincrement())
// }

Model uses a fluid interface, so you can chain the following methods:

  • .Field(name, scalar): Add a scalar column to a Model
  • .Relation(name, relation): Add a relationship to a Model
  • .Block(compound): Add a block field, e.g. @@id, @@unique, @@map
  • .Mixin(mixin): Inherit columns from a Mixin for compositional Models
  • .Raw(value): Escape hatch into writing raw Prisma

Scalars

Scalars are the types of data that the column contains, Int, String etc. You can define & re-use Scalars wherever in your models

const PrimaryKey = Int(Id, Default('autoincrement()'));

// id Int   @id @default("autoincrement()")
m.Field('id', PrimaryKey);

Modifiers

Modifiers are functions/objects that append attributes to a column e.g.

// String? @default("Hello World")
String(Default('Hello World'), Nullable);

// Int @id @unique @default(autoincrement())
Int(Id, Unique, Default('autoincrement()'));

// DateTime @default(now()) @updatedAt
DateTime(Default('now()'), UpdatedAt);

Certain modifiers are constrained to certain scalars, the mapping is:

  • String: Unique, Id, Default(string | 'auto()'), Limit(number)
  • Int: Unique, Id, Default('cuid' | 'autoincrement()' | 'uuid()' | number)
  • Float: Unique, Default(number)
  • BigInt: Unique, Default(BigInt)
  • Bytes: Unique
  • Decimal: Unique
  • Boolean: Unique
  • DateTime: Default('now()'), UpdatedAt
  • Unsupported

Additionally all scalars can use: Nullable, Map, Ignore, Raw & Array modifiers.

The Raw() modifier can be used as an escape hatch:

// String  @db.ObjectId
String(Raw('@db.ObjectId'));

@db attributes

Currently there's support for mysql, postgresql, cockroachdb & mongodb @db attributes, and can be used like all the other modifiers.

import { MySql as db } from 'zen-stack-ts';

// email String @db.VarChar(255)
m.Field('email', String(db.VarChar(255)));

Check src/public/db/mysql.ts (mongo.ts/postgresql.ts/cockroach.ts) for list of mappings between scalar types & attributes.

Relationships

  • OneToMany (model, name?, ...modifiers)
    • Nullable
  • OneToOne (model, name?, fields, references, ...modifiers)
  • OneToOne (model, name?, ...modifiers)
    • Nullable, OnUpdate(Action), OnDelete(Action)
  • ManyToOne (model, name?, fields, references, ...modifiers)
    • Nullable, OnUpdate(Action), OnDelete(Action)

Where Action is one of: Cascade, Restrict, NoAction, SetNull, SetDefault

Examples

OneToOne

const User = Model('User');
const Something = Model('Something');

Something
  .Field('id', PrimaryKey)
  // Holds foreign key
  .Field('userId', Int())
  .Relation('user', OneToOne(User, Fields('userId'), References('id')));
  // Alternatively you can do Fields('userId', Int()) to avoid the extra
  // .Field() call, this'll add the column to the model for you

User
  .Field('id', PrimaryKey)
  .Relation('thingy', OneToOne(Something));

Implicit ManyToMany

https://www.prisma.io/docs/concepts/components/prisma-schema/relations/many-to-many-relations#implicit-many-to-many-relations

const Post = Model('Post');
const Category = Model('Category');

Post
  .Field('id',            Int(Id, Default('autoincrement()')))
  .Relation('categories', OneToMany(Category));

Category
  .Field('id',            Int(Id, Default('autoincrement()')))
  .Relation('posts',      OneToMany(Post));

Ambiguous relations

The 2nd parameter of the Relation can be a string & explicitly denote the name of the relation.

// pinnedBy   User?   @relation(name: "PinnedPost", fields: [pinnedById], references: [id])
m.Relation(
  'pinnedBy',
  OneToOne(
    User,
    'PinnedPost',
    Fields('pinnedById'),
    References('id'),
    Nullable,
  ),
);

Referentials Actions

OnUpdate & OnDelete modifiers can be used as follows:

// tag    Tag?  @relation(fields: [tagId], references: [id], onUpdate: Cascade, onDelete: Cascade)
m.Relation(
  'tag',
  ManyToOne(
    Fields('tagId'),
    References('id'),
    OnUpdate('Cascade'),
    OnDelete('Cascade'),
    Nullable,
  ),
);

Enums

Composed of two parts:

  • Enum(name, comment?, ...Key)
  • Key(value, ...modifiers, comment?)
    • Map
const Animal = Enum(
  'Animal',
  Key('Seacow'),
  Key('Capybara'),
  Key('Otter', Map('otter')),
);

// fave  Animal @default(Seacow)
// null  Animal?
model
  .Field('fave', Animal('Seacow'))
  .Field('null', Animal());

const WithComment = Enum(
  "Foo", "This is with a comment",
  Key("Bar", "Another comment")
);
// // This is with a comment
// enum Foo {
//  // Another comment
//  Bar
// }

Blocks

Used for adding fields like @@map, @@id, @@fulltext etc.

import { Compound, Mongo as db } from 'zen-stack-ts';

// Creating a compound index
model
  .Field('id', Int(Id, Default('autoincrement()')))
  .Field('authorId', Int())
  .Relation('author', ManyToOne(User, Fields('authorId'), References('id')))
  .Block(Compound.Id('id', 'authorId'));

// e.g. in MongoDB schemas
Model('User')
  .Field('id', String(Id, db.ObjectId, Map('_id')))
  .Block(Compound.Map('users'));

Mixins

Allows you to re-use groups of fields, compositional models.

const Timestamps = Mixin()
  .Field('createdAt', DateTime(Default('now()')))
  .Field('updatedAt', DateTime(Nullable, UpdatedAt));

const User = Model('User').Field('id', PrimaryKey).Mixin(Timestamps);

// User will now have `createdAt` & `updatedAt` columns

Programmatic usage

const prisma = ZenStackTs.generate({
  datasource: {...},
  generators: [...],
  schema
})

console.log(prisma); // schema.prisma contents

Handling circular relationships

At some point you'll want to split the schema across files, which introduces issues with circular relationships when you're importing for .Relation()s in Node

One way to get around this is to have a file with all the models/enums defined, and have files import those & apply the fields, e.g.

// models.ts ------------------------------
const User = Model("User");
const Post = Model("Posts");
// ... and all the other Models

// users.ts ------------------------------
import { User, Post } from './models'

User
  .Field("id",        Int(Id, Default("autoincrement()")))
  .Relation("posts",  OneToMany(Post))

// posts.ts  ------------------------------
import { User, Post } from './models'

Post
  .Field("id",        Int(Id, Default("autoincrement()")))
  .Field("authorId",  Int())
  .Relation("author", ManyToOne(User, Fields("authorId"), References("id")))

// zen-stack.ts ------------------------------
import * as schema from './models'

// IMPORTANT: import the model files which performs the `.Field()`, `.Relation()`
// etc. calls, thereby adding the columns to the models
import "./posts";
import "./users";

ZenStackTs({
  datasource: {...},
  generators: [...],
  schema
})