Skip to content

Commit

Permalink
merge dev to main (v2.11.0) (#1943)
Browse files Browse the repository at this point in the history
  • Loading branch information
ymc9 authored Jan 7, 2025
2 parents 689d013 + 4605278 commit 70a81c6
Show file tree
Hide file tree
Showing 62 changed files with 2,046 additions and 165 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": "2.10.2",
"version": "2.11.0",
"description": "",
"scripts": {
"build": "pnpm -r --filter=\"!./packages/ide/*\" build",
Expand Down
2 changes: 1 addition & 1 deletion packages/ide/jetbrains/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ plugins {
}

group = "dev.zenstack"
version = "2.10.2"
version = "2.11.0"

repositories {
mavenCentral()
Expand Down
2 changes: 1 addition & 1 deletion packages/ide/jetbrains/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "jetbrains",
"version": "2.10.2",
"version": "2.11.0",
"displayName": "ZenStack JetBrains IDE Plugin",
"description": "ZenStack JetBrains IDE plugin",
"homepage": "https://zenstack.dev",
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": "2.10.2",
"version": "2.11.0",
"displayName": "ZenStack modeling language compiler",
"description": "ZenStack modeling language compiler",
"homepage": "https://zenstack.dev",
Expand Down
2 changes: 1 addition & 1 deletion packages/misc/redwood/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@zenstackhq/redwood",
"displayName": "ZenStack RedwoodJS Integration",
"version": "2.10.2",
"version": "2.11.0",
"description": "CLI and runtime for integrating ZenStack with RedwoodJS projects.",
"repository": {
"type": "git",
Expand Down
2 changes: 1 addition & 1 deletion packages/plugins/openapi/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@zenstackhq/openapi",
"displayName": "ZenStack Plugin and Runtime for OpenAPI",
"version": "2.10.2",
"version": "2.11.0",
"description": "ZenStack plugin and runtime supporting OpenAPI",
"main": "index.js",
"repository": {
Expand Down
12 changes: 10 additions & 2 deletions packages/plugins/openapi/src/rest-generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -906,16 +906,24 @@ export class RESTfulOpenAPIGenerator extends OpenAPIGeneratorBase {
},
};

let idFieldSchema: OAPI.SchemaObject = { type: 'string' };
if (idFields.length === 1) {
// FIXME: JSON:API actually requires id field to be a string,
// but currently the RESTAPIHandler returns the original data
// type as declared in the ZModel schema.
idFieldSchema = this.fieldTypeToOpenAPISchema(idFields[0].type);
}

if (mode === 'create') {
// 'id' is required if there's no default value
const idFields = model.fields.filter((f) => isIdField(f));
if (idFields.length === 1 && !hasAttribute(idFields[0], '@default')) {
properties = { id: { type: 'string' }, ...properties };
properties = { id: idFieldSchema, ...properties };
toplevelRequired.unshift('id');
}
} else {
// 'id' always required for read and update
properties = { id: { type: 'string' }, ...properties };
properties = { id: idFieldSchema, ...properties };
toplevelRequired.unshift('id');
}

Expand Down
26 changes: 24 additions & 2 deletions packages/plugins/openapi/tests/openapi-restful.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ model Bar {

const { name: output } = tmp.fileSync({ postfix: '.yaml' });

const options = buildOptions(model, modelFile, output, '3.1.0');
const options = buildOptions(model, modelFile, output, specVersion);
await generate(model, options, dmmf);

console.log(`OpenAPI specification generated for ${specVersion}: ${output}`);
Expand Down Expand Up @@ -324,7 +324,7 @@ model Foo {

const { name: output } = tmp.fileSync({ postfix: '.yaml' });

const options = buildOptions(model, modelFile, output, '3.1.0');
const options = buildOptions(model, modelFile, output, specVersion);
await generate(model, options, dmmf);

console.log(`OpenAPI specification generated for ${specVersion}: ${output}`);
Expand All @@ -340,6 +340,28 @@ model Foo {
}
});

it('int field as id', async () => {
const { model, dmmf, modelFile } = await loadZModelAndDmmf(`
plugin openapi {
provider = '${normalizePath(path.resolve(__dirname, '../dist'))}'
}
model Foo {
id Int @id @default(autoincrement())
}
`);

const { name: output } = tmp.fileSync({ postfix: '.yaml' });

const options = buildOptions(model, modelFile, output, '3.0.0');
await generate(model, options, dmmf);
console.log(`OpenAPI specification generated: ${output}`);
await OpenAPIParser.validate(output);

const parsed = YAML.parse(fs.readFileSync(output, 'utf-8'));
expect(parsed.components.schemas.Foo.properties.id.type).toBe('integer');
});

it('exposes individual fields from a compound id as attributes', async () => {
const { model, dmmf, modelFile } = await loadZModelAndDmmf(`
plugin openapi {
Expand Down
2 changes: 1 addition & 1 deletion packages/plugins/swr/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@zenstackhq/swr",
"displayName": "ZenStack plugin for generating SWR hooks",
"version": "2.10.2",
"version": "2.11.0",
"description": "ZenStack plugin for generating SWR hooks",
"main": "index.js",
"repository": {
Expand Down
2 changes: 1 addition & 1 deletion packages/plugins/tanstack-query/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@zenstackhq/tanstack-query",
"displayName": "ZenStack plugin for generating tanstack-query hooks",
"version": "2.10.2",
"version": "2.11.0",
"description": "ZenStack plugin for generating tanstack-query hooks",
"main": "index.js",
"exports": {
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": "2.10.2",
"version": "2.11.0",
"description": "ZenStack plugin for tRPC",
"main": "index.js",
"repository": {
Expand Down
6 changes: 5 additions & 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": "2.10.2",
"version": "2.11.0",
"description": "Runtime of ZenStack for both client-side and server-side environments.",
"repository": {
"type": "git",
Expand Down Expand Up @@ -80,6 +80,10 @@
"types": "./zod-utils.d.ts",
"default": "./zod-utils.js"
},
"./encryption": {
"types": "./encryption/index.d.ts",
"default": "./encryption/index.js"
},
"./package.json": {
"default": "./package.json"
}
Expand Down
12 changes: 12 additions & 0 deletions packages/runtime/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,15 @@ export const PRISMA_MINIMUM_VERSION = '5.0.0';
* Prefix for auxiliary relation field generated for delegated models
*/
export const DELEGATE_AUX_RELATION_PREFIX = 'delegate_aux';

/**
* Prisma actions that can have a write payload
*/
export const ACTIONS_WITH_WRITE_PAYLOAD = [
'create',
'createMany',
'createManyAndReturn',
'update',
'updateMany',
'upsert',
];
48 changes: 25 additions & 23 deletions packages/runtime/src/cross/nested-write-visitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import type { FieldInfo, ModelMeta } from './model-meta';
import { resolveField } from './model-meta';
import { MaybePromise, PrismaWriteActionType, PrismaWriteActions } from './types';
import { getModelFields } from './utils';
import { enumerate, getModelFields } from './utils';

type NestingPathItem = { field?: FieldInfo; model: string; where: any; unique: boolean };

Expand Down Expand Up @@ -310,31 +310,33 @@ export class NestedWriteVisitor {
payload: any,
nestingPath: NestingPathItem[]
) {
for (const field of getModelFields(payload)) {
const fieldInfo = resolveField(this.modelMeta, model, field);
if (!fieldInfo) {
continue;
}
for (const item of enumerate(payload)) {
for (const field of getModelFields(item)) {
const fieldInfo = resolveField(this.modelMeta, model, field);
if (!fieldInfo) {
continue;
}

if (fieldInfo.isDataModel) {
if (payload[field]) {
// recurse into nested payloads
for (const [subAction, subData] of Object.entries<any>(payload[field])) {
if (this.isPrismaWriteAction(subAction) && subData) {
await this.doVisit(fieldInfo.type, subAction, subData, payload[field], fieldInfo, [
...nestingPath,
]);
if (fieldInfo.isDataModel) {
if (item[field]) {
// recurse into nested payloads
for (const [subAction, subData] of Object.entries<any>(item[field])) {
if (this.isPrismaWriteAction(subAction) && subData) {
await this.doVisit(fieldInfo.type, subAction, subData, item[field], fieldInfo, [
...nestingPath,
]);
}
}
}
}
} else {
// visit plain field
if (this.callback.field) {
await this.callback.field(fieldInfo, action, payload[field], {
parent: payload,
nestingPath,
field: fieldInfo,
});
} else {
// visit plain field
if (this.callback.field) {
await this.callback.field(fieldInfo, action, item[field], {
parent: item,
nestingPath,
field: fieldInfo,
});
}
}
}
}
Expand Down
67 changes: 67 additions & 0 deletions packages/runtime/src/encryption/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { _decrypt, _encrypt, ENCRYPTION_KEY_BYTES, getKeyDigest, loadKey } from './utils';

/**
* Default encrypter
*/
export class Encrypter {
private key: CryptoKey | undefined;
private keyDigest: string | undefined;

constructor(private readonly encryptionKey: Uint8Array) {
if (encryptionKey.length !== ENCRYPTION_KEY_BYTES) {
throw new Error(`Encryption key must be ${ENCRYPTION_KEY_BYTES} bytes`);
}
}

/**
* Encrypts the given data
*/
async encrypt(data: string): Promise<string> {
if (!this.key) {
this.key = await loadKey(this.encryptionKey, ['encrypt']);
}

if (!this.keyDigest) {
this.keyDigest = await getKeyDigest(this.encryptionKey);
}

return _encrypt(data, this.key, this.keyDigest);
}
}

/**
* Default decrypter
*/
export class Decrypter {
private keys: Array<{ key: CryptoKey; digest: string }> = [];

constructor(private readonly decryptionKeys: Uint8Array[]) {
if (decryptionKeys.length === 0) {
throw new Error('At least one decryption key must be provided');
}

for (const key of decryptionKeys) {
if (key.length !== ENCRYPTION_KEY_BYTES) {
throw new Error(`Decryption key must be ${ENCRYPTION_KEY_BYTES} bytes`);
}
}
}

/**
* Decrypts the given data
*/
async decrypt(data: string): Promise<string> {
if (this.keys.length === 0) {
this.keys = await Promise.all(
this.decryptionKeys.map(async (key) => ({
key: await loadKey(key, ['decrypt']),
digest: await getKeyDigest(key),
}))
);
}

return _decrypt(data, async (digest) =>
this.keys.filter((entry) => entry.digest === digest).map((entry) => entry.key)
);
}
}
Loading

0 comments on commit 70a81c6

Please sign in to comment.