Skip to content
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

feat: support for edge runtime #1209

Merged
merged 12 commits into from
Apr 5, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
4 changes: 4 additions & 0 deletions packages/plugins/trpc/tests/projects/t3-trpc-v10/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,14 @@
"@trpc/next": "^10.43.6",
"@trpc/react-query": "^10.43.6",
"@trpc/server": "^10.43.6",
"@zenstackhq/language": "file:../../../../../../.build/zenstackhq-language-2.0.0-beta.5.tgz",
"@zenstackhq/runtime": "file:../../../../../../.build/zenstackhq-runtime-2.0.0-beta.5.tgz",
"@zenstackhq/sdk": "file:../../../../../../.build/zenstackhq-sdk-2.0.0-beta.5.tgz",
"next": "^14.0.4",
"react": "18.2.0",
"react-dom": "18.2.0",
"superjson": "^2.2.1",
"zenstack": "file:../../../../../../.build/zenstack-2.0.0-beta.5.tgz",
"zod": "^3.22.4"
},
"devDependencies": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export function createTRPCNext<
TRouter extends AnyRouter,
TPath extends string | undefined = undefined,
TSSRContext extends NextPageContext = NextPageContext,
TFlags = null,
TFlags = null
>(opts: Parameters<typeof _createTRPCNext>[0]) {
const r: CreateTRPCNext<TRouter, TSSRContext, TFlags> = _createTRPCNext<TRouter, TSSRContext, TFlags>(opts);
return r as DeepOverrideAtPath<CreateTRPCNext<TRouter, TSSRContext, TFlags>, ClientType<TRouter>, TPath>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ export type DeepOverride<T, R> = T extends Primitive
: R extends Primitive
? R
: {
[K in keyof T]: K extends keyof R ? DeepOverride<T[K], R[K]> : T[K];
} & {
[K in Exclude<keyof R, keyof T>]: R[K];
};
[K in keyof T]: K extends keyof R ? DeepOverride<T[K], R[K]> : T[K];
} & {
[K in Exclude<keyof R, keyof T>]: R[K];
};

/**
* Traverse to `Path` (denoted by dot separated string literal type) in `T`, and starting from there,
Expand All @@ -25,8 +25,8 @@ export type DeepOverrideAtPath<T, R, Path extends string | undefined = undefined
? DeepOverride<T, R>
: Path extends `${infer P1}.${infer P2}`
? P1 extends keyof T
? Omit<T, P1> & Record<P1, DeepOverride<T[P1], DeepOverrideAtPath<T[P1], R, P2>>>
: never
? Omit<T, P1> & Record<P1, DeepOverride<T[P1], DeepOverrideAtPath<T[P1], R, P2>>>
: never
: Path extends keyof T
? Omit<T, Path> & Record<Path, DeepOverride<T[Path], R>>
: never;
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export async function checkMutate<T>(promise: Promise<T>): Promise<T | undefined
throw err;
}
}

}

export async function checkRead<T>(promise: Promise<T>): Promise<T> {
Expand Down Expand Up @@ -58,10 +59,11 @@ export async function checkRead<T>(promise: Promise<T>): Promise<T> {
code: 'BAD_REQUEST',
message: err.message,
cause: err,
});
})
}
} else {
throw err;
}
}

}

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,25 +1,17 @@
/* eslint-disable */
import {
unsetMarker,
type AnyRouter,
type AnyRootConfig,
type CreateRouterInner,
type Procedure,
type ProcedureBuilder,
type ProcedureParams,
type ProcedureRouterRecord,
type ProcedureType,
} from '@trpc/server';
import { type PrismaClient } from '@prisma/client';
import createUserRouter from './User.router';
import createPostRouter from './Post.router';
import { ClientType as UserClientType } from './User.router';
import { ClientType as PostClientType } from './Post.router';
import { unsetMarker, type AnyRouter, type AnyRootConfig, type CreateRouterInner, type Procedure, type ProcedureBuilder, type ProcedureParams, type ProcedureRouterRecord, type ProcedureType } from "@trpc/server";
import { type PrismaClient } from "@prisma/client";
import createUserRouter from "./User.router";
import createPostRouter from "./Post.router";
import { ClientType as UserClientType } from "./User.router";
import { ClientType as PostClientType } from "./Post.router";

export type BaseConfig = AnyRootConfig;

export type RouterFactory<Config extends BaseConfig> = <ProcRouterRecord extends ProcedureRouterRecord>(
procedures: ProcRouterRecord,
export type RouterFactory<Config extends BaseConfig> = <
ProcRouterRecord extends ProcedureRouterRecord
>(
procedures: ProcRouterRecord
) => CreateRouterInner<Config, ProcRouterRecord>;

export type UnsetMarker = typeof unsetMarker;
Expand All @@ -39,7 +31,8 @@ export function createRouter<Config extends BaseConfig>(router: RouterFactory<Co
return router({
user: createUserRouter(router, procedure),
post: createPostRouter(router, procedure),
});
}
);
}

export interface ClientType<AppRouter extends AnyRouter> {
Expand Down
5 changes: 4 additions & 1 deletion packages/runtime/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@
"require": "./cross/index.js",
"default": "./cross/index.js"
},
"./model-meta": {
"types": "./model-meta.d.ts",
"default": "./model-meta.js"
},
"./prisma": {
"types": "./prisma.d.ts"
}
Expand All @@ -59,7 +63,6 @@
"bcryptjs": "^2.4.3",
"buffer": "^6.0.3",
"change-case": "^4.1.2",
"colors": "1.4.0",
"decimal.js": "^10.4.2",
"deepcopy": "^2.1.0",
"deepmerge": "^4.3.1",
Expand Down
1 change: 1 addition & 0 deletions packages/runtime/res/model-meta.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * '.zenstack/model-meta';
ymc9 marked this conversation as resolved.
Show resolved Hide resolved
10 changes: 10 additions & 0 deletions packages/runtime/res/model-meta.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });

try {
exports.default = require('.zenstack/model-meta').default;
ymc9 marked this conversation as resolved.
Show resolved Hide resolved
} catch {
exports.default = function () {
throw new Error('Generated model meta not found. Please run `zenstack generate` first.');
};
}
9 changes: 4 additions & 5 deletions packages/runtime/src/enhancements/create-enhancement.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import colors from 'colors';
import semver from 'semver';
import { PRISMA_MINIMUM_VERSION } from '../constants';
import { isDelegateModel, type ModelMeta } from '../cross';
import type { AuthUser } from '../types';
import { withDefaultAuth } from './default-auth';
import { withDelegate } from './delegate';
import { Logger } from './logger';
import { withOmit } from './omit';
import { withPassword } from './password';
import { withPolicy } from './policy';
Expand Down Expand Up @@ -151,10 +151,9 @@ export function createEnhancement<DbClient extends object>(
// and `updateMany` to plain `delete` and `update`
if (Object.values(options.modelMeta.models).some((model) => isDelegateModel(options.modelMeta, model.name))) {
if (!kinds.includes('delegate')) {
console.warn(
colors.yellow(
'Your ZModel contains delegate models but "delegate" enhancement kind is not enabled. This may result in unexpected behavior.'
)
const logger = new Logger(prisma);
logger.warn(
'Your ZModel contains delegate models but "delegate" enhancement kind is not enabled. This may result in unexpected behavior.'
);
} else {
result = withDelegate(result, options);
Expand Down
10 changes: 9 additions & 1 deletion packages/runtime/src/enhancements/logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,15 @@ export class Logger {
const engine = (this.prisma as any)._engine;
this.emitter = engine ? (engine.logEmitter as EventEmitter) : undefined;
if (this.emitter) {
this.eventNames = this.emitter.eventNames();
if (typeof this.emitter.eventNames === 'function') {
// Node.js
this.eventNames = this.emitter.eventNames();
} else if ('events' in this.emitter) {
// edge runtime
this.eventNames = Object.keys((this.emitter as any).events);
} else {
this.eventNames = [];
}
}
}

Expand Down
4 changes: 2 additions & 2 deletions packages/runtime/src/enhancements/password.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/no-unused-vars */

import { hash } from 'bcryptjs';
import { hashSync } from 'bcryptjs';
import { DEFAULT_PASSWORD_SALT_LENGTH } from '../constants';
import { NestedWriteVisitor, type PrismaWriteActionType } from '../cross';
import { DbClientContract } from '../types';
Expand Down Expand Up @@ -53,7 +53,7 @@ class PasswordHandler extends DefaultPrismaProxyHandler {
if (!salt) {
salt = DEFAULT_PASSWORD_SALT_LENGTH;
}
context.parent[field.name] = await hash(data, salt);
context.parent[field.name] = hashSync(data, salt);
}
},
});
Expand Down
15 changes: 14 additions & 1 deletion packages/schema/src/plugins/plugin-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,10 @@ export function getNodeModulesFolder(startPath?: string): string | undefined {
*/
export function ensureDefaultOutputFolder(options: PluginRunnerOptions) {
const output = options.output ? path.resolve(options.output) : getDefaultOutputFolder();
if (output && !fs.existsSync(output)) {
if (output) {
if (fs.existsSync(output)) {
fs.rmSync(output, { recursive: true });
}
fs.mkdirSync(output, { recursive: true });
if (!options.output) {
const pkgJson = {
Expand All @@ -55,11 +58,21 @@ export function ensureDefaultOutputFolder(options: PluginRunnerOptions) {
types: './zod/objects/index.d.ts',
default: './zod/objects/index.js',
},
'./model-meta': {
types: './model-meta.d.ts',
default: './model-meta.js',
},
'./prisma': {
types: './prisma.d.ts',
},
},
};

for (const zodFolder of ['models', 'input', 'objects']) {
fs.mkdirSync(path.join(output, 'zod', zodFolder), { recursive: true });
fs.writeFileSync(path.join(output, 'zod', zodFolder, 'index.js'), '');
}

fs.writeFileSync(path.join(output, 'package.json'), JSON.stringify(pkgJson, undefined, 4));
}
}
Expand Down
2 changes: 1 addition & 1 deletion packages/server/src/api/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export abstract class APIHandlerBase {

constructor() {
try {
this.defaultModelMeta = getDefaultModelMeta(undefined);
this.defaultModelMeta = getDefaultModelMeta();
} catch {
// noop
}
Expand Down
85 changes: 7 additions & 78 deletions packages/server/src/shared.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
/* eslint-disable @typescript-eslint/no-var-requires */
import { DEFAULT_RUNTIME_LOAD_PATH, type ModelMeta, type PolicyDef, type ZodSchemas } from '@zenstackhq/runtime';
import path from 'path';
import type { ModelMeta, ZodSchemas } from '@zenstackhq/runtime';
import { AdapterBaseOptions } from './types';

export function loadAssets(options: AdapterBaseOptions) {
// model metadata
const modelMeta = options.modelMeta ?? getDefaultModelMeta(options.loadPath);
const modelMeta = options.modelMeta ?? getDefaultModelMeta();

// zod schemas
let zodSchemas: ZodSchemas | undefined;
if (typeof options.zodSchemas === 'object') {
zodSchemas = options.zodSchemas;
} else if (options.zodSchemas === true) {
zodSchemas = getDefaultZodSchemas(options.loadPath);
zodSchemas = getDefaultZodSchemas();
if (!zodSchemas) {
throw new Error('Unable to load zod schemas from default location');
}
Expand All @@ -27,94 +26,24 @@ export function loadAssets(options: AdapterBaseOptions) {
* @param loadPath The path to load model metadata from. If not provided,
* will use default load path.
*/
export function getDefaultModelMeta(loadPath: string | undefined): ModelMeta {
export function getDefaultModelMeta(): ModelMeta {
try {
if (loadPath) {
const toLoad = path.resolve(loadPath, 'model-meta');
return require(toLoad).default;
} else {
// model-meta should be resolved relative to the runtime
const metaPath = require.resolve('.zenstack/model-meta', {
paths: [require.resolve('@zenstackhq/runtime')],
});
return require(metaPath).default;
}
return require('@zenstackhq/runtime/model-meta').default;
} catch {
if (process.env.ZENSTACK_TEST === '1' && !loadPath) {
try {
// special handling for running as tests, try resolving relative to CWD
return require(path.join(process.cwd(), 'node_modules', DEFAULT_RUNTIME_LOAD_PATH, 'model-meta'))
.default;
} catch {
throw new Error('Model meta cannot be loaded. Please make sure "zenstack generate" has been run.');
}
}
throw new Error('Model meta cannot be loaded. Please make sure "zenstack generate" has been run.');
}
}

/**
* Load access policies.
*
* @param loadPath The path to load access policies from. If not provided,
* will use default load path.
*/
export function getDefaultPolicy(loadPath: string | undefined): PolicyDef {
try {
if (loadPath) {
const toLoad = path.resolve(loadPath, 'policy');
return require(toLoad).default;
} else {
// policy should be resolved relative to the runtime
const policyPath = require.resolve('.zenstack/policy', {
paths: [require.resolve('@zenstackhq/runtime')],
});
return require(policyPath).default;
}
} catch {
if (process.env.ZENSTACK_TEST === '1' && !loadPath) {
try {
// special handling for running as tests, try resolving relative to CWD
return require(path.join(process.cwd(), 'node_modules', DEFAULT_RUNTIME_LOAD_PATH, 'policy')).default;
} catch {
throw new Error(
'Policy definition cannot be loaded from default location. Please make sure "zenstack generate" has been run.'
);
}
}
throw new Error(
'Policy definition cannot be loaded from default location. Please make sure "zenstack generate" has been run.'
);
}
}

/**
* Load zod schemas.
*
* @param loadPath The path to load zod schemas from. If not provided,
* will use default load path.
*/
export function getDefaultZodSchemas(loadPath: string | undefined): ZodSchemas | undefined {
export function getDefaultZodSchemas(): ZodSchemas | undefined {
try {
if (loadPath) {
const toLoad = path.resolve(loadPath, 'zod');
return require(toLoad);
} else {
// policy should be resolved relative to the runtime
const zodPath = require.resolve('.zenstack/zod', {
paths: [require.resolve('@zenstackhq/runtime')],
});
return require(zodPath);
}
return require('@zenstackhq/runtime/zod');
} catch {
if (process.env.ZENSTACK_TEST === '1' && !loadPath) {
try {
// special handling for running as tests, try resolving relative to CWD
return require(path.join(process.cwd(), 'node_modules', DEFAULT_RUNTIME_LOAD_PATH, 'zod'));
} catch {
return undefined;
}
}
return undefined;
}
}
11 changes: 3 additions & 8 deletions packages/server/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,9 @@ export interface AdapterBaseOptions {
logger?: LoggerConfig;

/**
* Model metadata. By default loaded from the standard output location
* of the `@zenstackhq/model-meta` plugin. You can pass it in explicitly
* if you configured the plugin to output to a different location.
* Model metadata. By default loaded from the `node_module/.zenstack/model-meta`
* module. You can pass it in explicitly if you configured ZenStack to output to
* a different location.
*/
modelMeta?: ModelMeta;

Expand All @@ -48,11 +48,6 @@ export interface AdapterBaseOptions {
*/
zodSchemas?: ZodSchemas | boolean;

/**
* Path to load model metadata and zod schemas from. Defaults to `node_modules/.zenstack`.
*/
loadPath?: string;

/**
* Api request handler function. Can be created using `@zenstackhq/server/api/rest` or `@zenstackhq/server/api/rpc` factory functions.
* Defaults to RPC-style API handler created with `/api/rpc`.
Expand Down
Loading
Loading