Skip to content

fix: handling of bytes/data/bigint/decimal data types #59

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Nov 7, 2022
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.2.11",
"version": "0.2.12",
"description": "",
"scripts": {
"build": "pnpm -r build",
Expand Down
5 changes: 3 additions & 2 deletions packages/internal/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@zenstackhq/internal",
"version": "0.2.11",
"version": "0.2.12",
"displayName": "ZenStack Internal Library",
"description": "ZenStack internal runtime library. This package is for supporting runtime functionality of ZenStack and not supposed to be used directly.",
"repository": {
Expand All @@ -27,12 +27,13 @@
"bcryptjs": "^2.4.3",
"colors": "^1.4.0",
"cuid": "^2.1.8",
"decimal.js": "^10.4.2",
"deepcopy": "^2.1.0",
"swr": "^1.3.0"
},
"peerDependencies": {
"@prisma/client": "^4.4.0",
"next": "12.3.1",
"next": "^12.3.1",
"react": "^17.0.2 || ^18",
"react-dom": "^17.0.2 || ^18"
},
Expand Down
51 changes: 47 additions & 4 deletions packages/internal/src/handler/data/policy-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,40 @@ 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 @@ -596,13 +630,11 @@ export async function preprocessWritePayload(
fieldData: any,
parentData: any
) => {
if (fieldInfo.type !== 'String') {
return true;
}
// process @password field
const pwdAttr = fieldInfo.attributes?.find(
(attr) => attr.name === '@password'
);
if (pwdAttr) {
if (pwdAttr && fieldInfo.type !== 'String') {
// hash password value
let salt: string | number | undefined = pwdAttr.args.find(
(arg) => arg.name === 'salt'
Expand All @@ -616,6 +648,17 @@ export async function preprocessWritePayload(
}
parentData[fieldInfo.name] = hashSync(fieldData, salt);
}

// deserialize Buffer field
if (fieldInfo.type === 'Bytes' && Array.isArray(fieldData.data)) {
parentData[fieldInfo.name] = Buffer.from(fieldData.data);
}

// deserialize BigInt field
if (fieldInfo.type === 'BigInt' && typeof fieldData === 'string') {
parentData[fieldInfo.name] = BigInt(fieldData);
}

return true;
};

Expand Down
71 changes: 70 additions & 1 deletion packages/internal/src/request.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,71 @@
import Decimal from 'decimal.js';
import useSWR, { useSWRConfig } from 'swr';
import type {
MutatorCallback,
MutatorOptions,
SWRResponse,
} from 'swr/dist/types';

type BufferShape = { type: 'Buffer'; data: number[] };
function isBuffer(value: unknown): value is BufferShape {
return (
!!value &&
(value as BufferShape).type === 'Buffer' &&
Array.isArray((value as BufferShape).data)
);
}

type BigIntShape = { type: 'BigInt'; data: string };
function isBigInt(value: unknown): value is BigIntShape {
return (
!!value &&
(value as BigIntShape).type === 'BigInt' &&
typeof (value as BigIntShape).data === 'string'
);
}

type DateShape = { type: 'Date'; data: string };
function isDate(value: unknown): value is BigIntShape {
return (
!!value &&
(value as DateShape).type === 'Date' &&
typeof (value as DateShape).data === 'string'
);
}

type DecmalShape = { type: 'Decimal'; data: string };
function isDecimal(value: unknown): value is DecmalShape {
return (
!!value &&
(value as DecmalShape).type === 'Decimal' &&
typeof (value as DateShape).data === 'string'
);
}

const dataReviver = (key: string, value: unknown) => {
// Buffer
if (isBuffer(value)) {
return Buffer.from(value.data);
}

// BigInt
if (isBigInt(value)) {
return BigInt(value.data);
}

// Date
if (isDate(value)) {
return new Date(value.data);
}

// Decimal
if (isDecimal(value)) {
return new Decimal(value.data);
}

return value;
};

const fetcher = async (url: string, options?: RequestInit) => {
const res = await fetch(url, options);
if (!res.ok) {
Expand All @@ -15,7 +76,15 @@ const fetcher = async (url: string, options?: RequestInit) => {
error.status = res.status;
throw error;
}
return res.json();

const textResult = await res.text();
console.log;
try {
return JSON.parse(textResult, dataReviver);
} catch (err) {
console.error(`Unable to deserialize data:`, textResult);
throw err;
}
};

function makeUrl(url: string, args: unknown) {
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": "0.2.11",
"version": "0.2.12",
"description": "This package contains runtime library for consuming client and server side code generated by ZenStack.",
"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": "ZenStack is a toolkit that simplifies full-stack development",
"version": "0.2.11",
"version": "0.2.12",
"author": {
"name": "ZenStack Team"
},
Expand Down
38 changes: 19 additions & 19 deletions packages/schema/src/res/stdlib.zmodel
Original file line number Diff line number Diff line change
Expand Up @@ -10,33 +10,33 @@ enum ReferentialAction {
}

/*
* Reads value from an environment variable
* Reads value from an environment variable.
*/
function env(name: String): String {}

/*
* Gets thec current login user
* Gets thec current login user.
*/
function auth(): Any {}

/*
* Gets current date-time (as DateTime type)
* Gets current date-time (as DateTime type).
*/
function now(): DateTime {}

/*
* Generate a globally unique identifier based on the UUID spec
* Generates a globally unique identifier based on the UUID specs.
*/
function uuid(): String {}

/*
* Generate a globally unique identifier based on the CUID spec
* Generates a globally unique identifier based on the CUID spec.
*/
function cuid(): String {}

/*
* Create a sequence of integers in the underlying database and assign the incremented
* values to the ID values of the created records based on the sequence
* Creates a sequence of integers in the underlying database and assign the incremented
* values to the ID values of the created records based on the sequence.
*/
function autoincrement(): Int {}

Expand All @@ -46,57 +46,57 @@ function autoincrement(): Int {}
function dbgenerated(expr: String): Any {}

/*
* Defines an ID on the model
* Defines an ID on the model.
*/
attribute @id(map: String?)

/*
* Defines a default value for a field
* Defines a default value for a field.
*/
attribute @default(_ value: ContextType)

/*
* Defines a unique constraint for this field
* Defines a unique constraint for this field.
*/
attribute @unique(map: String?)

/*
* Defines a compound unique constraint for the specified fields
* Defines a compound unique constraint for the specified fields.
*/
attribute @@unique(_ fields: FieldReference[], name: String?, map: String?)

/*
* Defines an index in the database
* Defines an index in the database.
*/
attribute @@index(_ fields: FieldReference[], map: String?)

/*
* Defines meta information about the relation
* Defines meta information about the relation.
*/
attribute @relation(_ name: String?, fields: FieldReference[]?, references: FieldReference[]?, onDelete: ReferentialAction?, onUpdate: ReferentialAction?, map: String?)

/*
* Maps a field name or enum value from the schema to a column with a different name in the database
* Maps a field name or enum value from the schema to a column with a different name in the database.
*/
attribute @map(_ name: String)

/*
* Maps the schema model name to a table with a different name, or an enum name to a different underlying enum in the database
* Maps the schema model name to a table with a different name, or an enum name to a different underlying enum in the database.
*/
attribute @@map(_ name: String)

/*
* Automatically stores the time when a record was last updated
* Automatically stores the time when a record was last updated.
*/
attribute @updatedAt()

/*
* Defines an access policy that allows a set of operations when the given condition is true
* Defines an access policy that allows a set of operations when the given condition is true.
*/
attribute @@allow(_ operation: String, _ condition: Boolean)

/*
* Defines an access policy that denies a set of operations when the given condition is true
* Defines an access policy that denies a set of operations when the given condition is true.
*/
attribute @@deny(_ operation: String, _ condition: Boolean)

Expand All @@ -113,6 +113,6 @@ attribute @@deny(_ operation: String, _ condition: Boolean)
attribute @password(saltLength: Int?, salt: String?)

/*
* Indicates that the field should be omitted when read from the generated services
* Indicates that the field should be omitted when read from the generated services.
*/
attribute @omit()
Loading