Skip to content

Commit

Permalink
merge dev to main (#253)
Browse files Browse the repository at this point in the history
  • Loading branch information
ymc9 authored Mar 9, 2023
2 parents d0bb796 + b9b05c6 commit bf9f4cb
Show file tree
Hide file tree
Showing 21 changed files with 155 additions and 51 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "zenstack-monorepo",
"version": "1.0.0-alpha.60",
"version": "1.0.0-alpha.62",
"description": "",
"scripts": {
"build": "pnpm -r build",
Expand Down
2 changes: 1 addition & 1 deletion packages/language/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@zenstackhq/language",
"version": "1.0.0-alpha.60",
"version": "1.0.0-alpha.62",
"displayName": "ZenStack modeling language compiler",
"description": "ZenStack modeling language compiler",
"homepage": "https://zenstack.dev",
Expand Down
2 changes: 1 addition & 1 deletion packages/language/src/generated/ast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ export interface DataSourceField extends AstNode {
readonly $container: DataSource;
readonly $type: 'DataSourceField';
name: string
value: InvocationExpr | LiteralExpr
value: ArrayExpr | InvocationExpr | LiteralExpr
}

export const DataSourceField = 'DataSourceField';
Expand Down
7 changes: 7 additions & 0 deletions packages/language/src/generated/grammar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,13 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel
"$ref": "#/rules@16"
},
"arguments": []
},
{
"$type": "RuleCall",
"rule": {
"$ref": "#/rules@10"
},
"arguments": []
}
]
}
Expand Down
2 changes: 1 addition & 1 deletion packages/language/src/zmodel.langium
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ DataSource:
TRIPLE_SLASH_COMMENT* 'datasource' name=ID '{' (fields+=DataSourceField)* '}';

DataSourceField:
TRIPLE_SLASH_COMMENT* name=ID '=' value=(LiteralExpr|InvocationExpr);
TRIPLE_SLASH_COMMENT* name=ID '=' value=(LiteralExpr | InvocationExpr | ArrayExpr);

// generator
GeneratorDecl:
Expand Down
2 changes: 1 addition & 1 deletion packages/next/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@zenstackhq/next",
"version": "1.0.0-alpha.60",
"version": "1.0.0-alpha.62",
"displayName": "ZenStack Next.js integration",
"description": "ZenStack Next.js integration",
"homepage": "https://zenstack.dev",
Expand Down
2 changes: 1 addition & 1 deletion packages/plugins/react/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@zenstackhq/react",
"displayName": "ZenStack plugin and runtime for ReactJS",
"version": "1.0.0-alpha.60",
"version": "1.0.0-alpha.62",
"description": "ZenStack plugin and runtime for ReactJS",
"main": "index.js",
"repository": {
Expand Down
2 changes: 1 addition & 1 deletion packages/plugins/trpc/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@zenstackhq/trpc",
"displayName": "ZenStack plugin for tRPC",
"version": "1.0.0-alpha.60",
"version": "1.0.0-alpha.62",
"description": "ZenStack plugin for tRPC",
"main": "index.js",
"repository": {
Expand Down
2 changes: 1 addition & 1 deletion packages/runtime/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@zenstackhq/runtime",
"displayName": "ZenStack Runtime Library",
"version": "1.0.0-alpha.60",
"version": "1.0.0-alpha.62",
"description": "Runtime of ZenStack for both client-side and server-side environments.",
"repository": {
"type": "git",
Expand Down
2 changes: 1 addition & 1 deletion packages/schema/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"publisher": "zenstack",
"displayName": "ZenStack Language Tools",
"description": "A toolkit for building secure CRUD apps with Next.js + Typescript",
"version": "1.0.0-alpha.60",
"version": "1.0.0-alpha.62",
"author": {
"name": "ZenStack Team"
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,17 @@ import { ValidationAcceptor } from 'langium';
import { getStringLiteral, validateDuplicatedDeclarations } from './utils';
import { SUPPORTED_PROVIDERS } from '../constants';

const supportedFields = ['provider', 'url', 'shadowDatabaseUrl', 'relationMode'];

/**
* Validates data source declarations.
*/
export default class DataSourceValidator implements AstValidator<DataSource> {
validate(ds: DataSource, accept: ValidationAcceptor): void {
validateDuplicatedDeclarations(ds.fields, accept);
this.validateFields(ds, accept);
this.validateProvider(ds, accept);
this.validateUrl(ds, accept);
this.validateRelationMode(ds, accept);
}

private validateFields(ds: DataSource, accept: ValidationAcceptor) {
ds.fields.forEach((f) => {
if (!supportedFields.includes(f.name)) {
accept('error', `Unexpected field "${f.name}"`, { node: f });
}
});
}

private validateProvider(ds: DataSource, accept: ValidationAcceptor) {
const provider = ds.fields.find((f) => f.name === 'provider');
if (!provider) {
Expand Down
6 changes: 4 additions & 2 deletions packages/schema/src/plugins/plugin-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export const RUNTIME_PACKAGE = '@zenstackhq/runtime';
export const ALL_OPERATION_KINDS: PolicyOperationKind[] = ['create', 'update', 'postUpdate', 'read', 'delete'];

/**
* Gets the nearest "node_modules" folder by walking up froma start path.
* Gets the nearest "node_modules" folder by walking up from start path.
*/
export function getNodeModulesFolder(startPath?: string): string | undefined {
startPath = startPath ?? process.cwd();
Expand All @@ -25,7 +25,9 @@ export function getNodeModulesFolder(startPath?: string): string | undefined {
* @returns
*/
export function getDefaultOutputFolder() {
const modulesFolder = getNodeModulesFolder();
// Find the real runtime module path, it might be a symlink in pnpm
const runtimeModulePath = require.resolve('@zenstackhq/runtime');
const modulesFolder = getNodeModulesFolder(runtimeModulePath);
return modulesFolder ? path.join(modulesFolder, '.zenstack') : undefined;
}

Expand Down
27 changes: 22 additions & 5 deletions packages/schema/src/plugins/prisma/prisma-builder.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import indentString from './indent-string';

/**
* Field used by datasource and generator declarations.
*/
export type SimpleField = { name: string; value: string | string[] };

/**
* Prisma schema builder
*/
Expand All @@ -9,13 +14,19 @@ export class PrismaModel {
private models: Model[] = [];
private enums: Enum[] = [];

addDataSource(name: string, provider: string, url: DataSourceUrl, shadowDatabaseUrl?: DataSourceUrl): DataSource {
const ds = new DataSource(name, provider, url, shadowDatabaseUrl);
addDataSource(
name: string,
provider: string,
url: DataSourceUrl,
shadowDatabaseUrl?: DataSourceUrl,
restFields: SimpleField[] = []
): DataSource {
const ds = new DataSource(name, provider, url, shadowDatabaseUrl, restFields);
this.datasources.push(ds);
return ds;
}

addGenerator(name: string, fields: Array<{ name: string; value: string | string[] }>): Generator {
addGenerator(name: string, fields: SimpleField[]): Generator {
const generator = new Generator(name, fields);
this.generators.push(generator);
return generator;
Expand Down Expand Up @@ -45,15 +56,21 @@ export class DataSource {
public name: string,
public provider: string,
public url: DataSourceUrl,
public shadowDatabaseUrl?: DataSourceUrl
public shadowDatabaseUrl?: DataSourceUrl,
public restFields: SimpleField[] = []
) {}

toString(): string {
const restFields =
this.restFields.length > 0
? this.restFields.map((f) => indentString(`${f.name} = ${JSON.stringify(f.value)}`)).join('\n')
: '';
return (
`datasource ${this.name} {\n` +
indentString(`provider="${this.provider}"\n`) +
indentString(`url=${this.url}\n`) +
(this.shadowDatabaseUrl ? indentString(`shadowDatabaseurl=${this.shadowDatabaseUrl}\n`) : '') +
(restFields ? restFields + '\n' : '') +
`}`
);
}
Expand All @@ -68,7 +85,7 @@ export class DataSourceUrl {
}

export class Generator {
constructor(public name: string, public fields: Array<{ name: string; value: string | string[] }>) {}
constructor(public name: string, public fields: SimpleField[]) {}

toString(): string {
return (
Expand Down
28 changes: 24 additions & 4 deletions packages/schema/src/plugins/prisma/schema-generator.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {
ArrayExpr,
AstNode,
AttributeArg,
DataModel,
Expand Down Expand Up @@ -48,6 +49,7 @@ import {
ModelFieldType,
PassThroughAttribute as PrismaPassThroughAttribute,
PrismaModel,
SimpleField,
} from './prisma-builder';
import ZModelCodeGenerator from './zmodel-code-generator';

Expand Down Expand Up @@ -96,14 +98,19 @@ export default class PrismaSchemaGenerator {
}
await writeFile(outFile, this.PRELUDE + prisma.toString());

// run 'prisma generate'
await execSync(`npx prisma generate --schema ${outFile}`);
const generateClient = options.generateClient !== false;

if (generateClient) {
// run 'prisma generate'
await execSync(`npx prisma generate --schema ${outFile}`);
}
}

private generateDataSource(prisma: PrismaModel, dataSource: DataSource) {
let provider: string | undefined = undefined;
let url: PrismaDataSourceUrl | undefined = undefined;
let shadowDatabaseUrl: PrismaDataSourceUrl | undefined = undefined;
const restFields: SimpleField[] = [];

for (const f of dataSource.fields) {
switch (f.name) {
Expand Down Expand Up @@ -133,6 +140,19 @@ export default class PrismaSchemaGenerator {
shadowDatabaseUrl = r;
break;
}

default: {
// rest fields
const value = isArrayExpr(f.value) ? getLiteralArray(f.value) : getLiteral(f.value);
if (value === undefined) {
throw new PluginError(
`Invalid value for datasource field ${f.name}: value must be a string or an array of strings`
);
} else {
restFields.push({ name: f.name, value });
}
break;
}
}
}

Expand All @@ -143,10 +163,10 @@ export default class PrismaSchemaGenerator {
throw new PluginError('Datasource is missing "url" field');
}

prisma.addDataSource(dataSource.name, provider, url, shadowDatabaseUrl);
prisma.addDataSource(dataSource.name, provider, url, shadowDatabaseUrl, restFields);
}

private extractDataSourceUrl(fieldValue: LiteralExpr | InvocationExpr) {
private extractDataSourceUrl(fieldValue: LiteralExpr | InvocationExpr | ArrayExpr) {
if (this.isStringLiteral(fieldValue)) {
return new PrismaDataSourceUrl(fieldValue.value as string, false);
} else if (
Expand Down
5 changes: 5 additions & 0 deletions packages/schema/src/res/stdlib.zmodel
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,11 @@ attribute @db.Blob() @@@targetField([BytesField]) @@@prisma
attribute @db.MediumBlob() @@@targetField([BytesField]) @@@prisma
attribute @db.Image() @@@targetField([BytesField]) @@@prisma

/*
* Specifies the schema to use in a multi-schema database. https://www.prisma.io/docs/guides/database/multi-schema.
*/
attribute @@schema(_ name: String) @@@prisma

/*
* Defines an access policy that allows a set of operations when the given condition is true.
*/
Expand Down
32 changes: 26 additions & 6 deletions packages/schema/src/utils/pkg-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,36 @@ import { execSync } from './exec-utils';

export type PackageManagers = 'npm' | 'yarn' | 'pnpm';

function findUp(names: string[], cwd: string): string | undefined {
let dir = cwd;
// eslint-disable-next-line no-constant-condition
while (true) {
const target = names.find((name) => fs.existsSync(path.join(dir, name)));
if (target) return target;

const up = path.resolve(dir, '..');
if (up === dir) return undefined; // it'll fail anyway
dir = up;
}
}

function getPackageManager(projectPath = '.'): PackageManagers {
if (fs.existsSync(path.join(projectPath, 'yarn.lock'))) {
return 'yarn';
} else if (fs.existsSync(path.join(projectPath, 'pnpm-lock.yaml'))) {
return 'pnpm';
} else {
const lockFile = findUp(['yarn.lock', 'pnpm-lock.yaml', 'package-lock.json'], projectPath);

if (!lockFile) {
// default use npm
return 'npm';
}
}

switch (path.basename(lockFile)) {
case 'yarn.lock':
return 'yarn';
case 'pnpm-lock.yaml':
return 'pnpm';
default:
return 'npm';
}
}
export function installPackage(
pkg: string,
dev: boolean,
Expand Down
54 changes: 54 additions & 0 deletions packages/schema/tests/generator/prisma-generator.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
/// <reference types="@types/jest" />

import { getDMMF } from '@prisma/internals';
import fs from 'fs';
import tmp from 'tmp';
Expand Down Expand Up @@ -137,4 +139,56 @@ describe('Prisma generator test', () => {
expect(content).toContain('@unique()');
expect(content).toContain('@@index([x, y])');
});

it('multi schema', async () => {
const model = await loadModel(`
datasource db {
provider = 'postgresql'
url = env('URL')
schemas = ['base', 'transactional']
}
generator client {
provider = "prisma-client-js"
previewFeatures = ["multiSchema"]
}
model User {
id Int @id
orders Order[]
@@schema("base")
}
model Order {
id Int @id
user User @relation(fields: [id], references: [id])
user_id Int
@@schema("transactional")
}
enum Size {
Small
Medium
Large
@@schema("transactional")
}
`);

const { name } = tmp.fileSync({ postfix: '.prisma' });
await new PrismaSchemaGenerator().generate(model, {
provider: '@zenstack/prisma',
schemaPath: 'schema.zmodel',
output: name,
generateClient: false,
});

const content = fs.readFileSync(name, 'utf-8');
await getDMMF({ datamodel: content });
expect(content).toContain('@@schema("base")');
expect(content).toContain('@@schema("base")');
expect(content).toContain('schemas = ["base","transactional"]');
});
});
Loading

0 comments on commit bf9f4cb

Please sign in to comment.