Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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": "0.4.1",
"version": "0.4.2",
"description": "",
"scripts": {
"build": "pnpm -r build",
Expand Down
2 changes: 1 addition & 1 deletion packages/next-auth/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@zenstackhq/next-auth",
"displayName": "ZenStack next-auth integration library",
"version": "0.4.1",
"version": "0.4.2",
"description": "ZenStack adapter for integrating with next-auth",
"repository": {
"type": "git",
Expand Down
7 changes: 4 additions & 3 deletions 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": "0.4.1",
"version": "0.4.2",
"description": "Runtime of ZenStack for both client-side and server-side environments.",
"repository": {
"type": "git",
Expand All @@ -20,14 +20,15 @@
"linkDirectory": true
},
"dependencies": {
"@types/bcryptjs": "^2.4.2",
"bcryptjs": "^2.4.3",
"colors": "1.4.0",
"cuid": "^2.1.8",
"decimal.js": "^10.4.2",
"deepcopy": "^2.1.0",
"superjson": "^1.11.0",
"swr": "^1.3.0",
"tslib": "^2.4.1",
"@types/bcryptjs": "^2.4.2",
"bcryptjs": "^2.4.3",
"zod": "^3.19.1",
"zod-validation-error": "^0.2.1"
},
Expand Down
13 changes: 10 additions & 3 deletions packages/runtime/src/handler/data/crud.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import cuid from 'cuid';
import superjson from 'superjson';
import { TRANSACTION_FIELD_NAME } from '../../constants';
import {
DbClientContract,
Expand Down Expand Up @@ -122,7 +123,9 @@ export class CRUD<DbClient> {

// conduct the create
this.service.verbose(
`Conducting create: ${model}:\n${JSON.stringify(args)}`
`Conducting create: ${model}:\n${superjson.stringify(
args
)}`
);
const createResult = (await tx[model].create(args)) as {
id: string;
Expand Down Expand Up @@ -245,7 +248,9 @@ export class CRUD<DbClient> {

// conduct the update
this.service.verbose(
`Conducting update: ${model}:\n${JSON.stringify(args)}`
`Conducting update: ${model}:\n${superjson.stringify(
args
)}`
);
await tx[model].update(args);

Expand Down Expand Up @@ -356,7 +361,9 @@ export class CRUD<DbClient> {

// conduct the deletion
this.service.verbose(
`Conducting delete ${model}:\n${JSON.stringify(args)}`
`Conducting delete ${model}:\n${superjson.stringify(
args
)}`
);
await tx[model].delete(args);

Expand Down
49 changes: 38 additions & 11 deletions packages/runtime/src/handler/data/handler.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
import { NextApiRequest, NextApiResponse } from 'next';
import superjson from 'superjson';
import { RequestHandlerOptions } from '../../request-handler';
import { registerSerializers } from '../../serialization-utils';
import {
DbClientContract,
QueryContext,
ServerErrorCode,
Service,
} from '../../types';
import { RequestHandler, CRUDError } from '../types';
import { CRUDError, RequestHandler } from '../types';
import { CRUD } from './crud';

registerSerializers();

/**
* Request handler for /data endpoint which processes data CRUD requests.
*/
Expand Down Expand Up @@ -36,7 +40,9 @@ export default class DataHandler<DbClient extends DbClientContract>

this.service.verbose(`Data request: ${method} ${path}`);
if (req.body) {
this.service.verbose(`Request body: ${JSON.stringify(req.body)}`);
this.service.verbose(
`Request body: ${superjson.stringify(req.body)}`
);
}

try {
Expand Down Expand Up @@ -100,6 +106,18 @@ export default class DataHandler<DbClient extends DbClientContract>
}
}

private marshal(value: unknown) {
return JSON.parse(superjson.stringify(value));
}

private unmarshal(value: unknown) {
if (typeof value === 'string') {
return superjson.parse(value);
} else {
return superjson.parse(JSON.stringify(value));
}
}

private async get(
req: NextApiRequest,
res: NextApiResponse,
Expand All @@ -108,19 +126,19 @@ export default class DataHandler<DbClient extends DbClientContract>
context: QueryContext
) {
// parse additional query args from "q" parameter
const args = req.query.q ? JSON.parse(req.query.q as string) : {};
const args = req.query.q ? this.unmarshal(req.query.q as string) : {};

if (id) {
// GET <model>/:id, make sure "id" is injected
const result = await this.crud.get(model, id, args, context);
if (!result) {
throw new CRUDError(ServerErrorCode.ENTITY_NOT_FOUND);
}
res.status(200).send(result);
res.status(200).send(this.marshal(result));
} else {
// GET <model>/, get list
const result = await this.crud.find(model, args, context);
res.status(200).send(result);
res.status(200).send(this.marshal(result));
}
}

Expand All @@ -130,8 +148,12 @@ export default class DataHandler<DbClient extends DbClientContract>
model: string,
context: QueryContext
) {
const result = await this.crud.create(model, req.body, context);
res.status(201).send(result);
const result = await this.crud.create(
model,
this.unmarshal(req.body),
context
);
res.status(201).send(this.marshal(result));
}

private async put(
Expand All @@ -148,8 +170,13 @@ export default class DataHandler<DbClient extends DbClientContract>
);
}

const result = await this.crud.update(model, id, req.body, context);
res.status(200).send(result);
const result = await this.crud.update(
model,
id,
this.unmarshal(req.body),
context
);
res.status(200).send(this.marshal(result));
}

private async del(
Expand All @@ -166,8 +193,8 @@ export default class DataHandler<DbClient extends DbClientContract>
);
}

const args = req.query.q ? JSON.parse(req.query.q as string) : {};
const args = req.query.q ? this.unmarshal(req.query.q as string) : {};
const result = await this.crud.del(model, id, args, context);
res.status(200).send(result);
res.status(200).send(this.marshal(result));
}
}
45 changes: 6 additions & 39 deletions packages/runtime/src/handler/data/policy-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { hashSync } from 'bcryptjs';
import deepcopy from 'deepcopy';
import superjson from 'superjson';
import {
DEFAULT_PASSWORD_SALT_LENGTH,
GUARD_FIELD_NAME,
Expand All @@ -15,7 +16,7 @@ import {
ServerErrorCode,
Service,
} from '../../types';
import { PrismaWriteActionType, CRUDError } from '../types';
import { CRUDError, PrismaWriteActionType } from '../types';
import { NestedWriteVisitor } from './nested-write-vistor';

//#region General helpers
Expand Down Expand Up @@ -103,7 +104,7 @@ export async function readWithCheck(
await injectNestedReadConditions(model, args, service, context);

service.verbose(
`Reading with validation for ${model}: ${JSON.stringify(args)}`
`Reading with validation for ${model}: ${superjson.stringify(args)}`
);
const result = await db[model].findMany(args);

Expand Down Expand Up @@ -187,40 +188,6 @@ async function postProcessForRead(
if (await shouldOmit(service, model, field)) {
delete entityData[field];
}

const fieldValue = entityData[field];

if (typeof fieldValue === 'bigint') {
// serialize BigInt with typing info
entityData[field] = {
type: 'BigInt',
data: fieldValue.toString(),
};
}

if (fieldValue instanceof Date) {
// serialize Date with typing info
entityData[field] = {
type: 'Date',
data: fieldValue.toISOString(),
};
}

if (typeof fieldValue === 'object') {
const fieldInfo = await service.resolveField(model, field);
if (fieldInfo?.type === 'Decimal') {
// serialize Decimal with typing info
entityData[field] = {
type: 'Decimal',
data: fieldValue.toString(),
};
} else if (fieldInfo?.type === 'Bytes') {
entityData[field] = {
type: 'Bytes',
data: Array.from(fieldValue as Buffer),
};
}
}
}

const injectTarget = args.select ?? args.include;
Expand Down Expand Up @@ -462,15 +429,15 @@ async function checkPolicyForSelectionPath(
const query = buildChainedSelectQuery(id, selectionPath);

service.verbose(
`Query for selection path: model ${model}, path ${JSON.stringify(
`Query for selection path: model ${model}, path ${superjson.stringify(
selectionPath
)}, query ${JSON.stringify(query)}`
)}, query ${superjson.stringify(query)}`
);
const r = await db[model].findUnique(query);

// collect ids at the end of the path
const ids: string[] = collectTerminalEntityIds(selectionPath, r);
service.verbose(`Collected leaf ids: ${JSON.stringify(ids)}`);
service.verbose(`Collected leaf ids: ${superjson.stringify(ids)}`);

if (ids.length === 0) {
return;
Expand Down
Loading