sinclairzx81 / typebox

Json Schema Type Builder with Static Type Resolution for TypeScript
Other
5.08k stars 161 forks source link

Support for Cloudflare Worker Environment #1095

Closed foreseaz closed 3 days ago

foreseaz commented 3 days ago

The TypeBox library does not currently support execution within the Cloudflare Worker environment. This is due to the use of the Function() constructor, which is restricted in Cloudflare Workers. The restriction is documented in the Cloudflare Workers JavaScript standards, which state that code generation from strings (e.g., Function(), eval()) is not allowed due to security reasons.

When attempting to use TypeBox in a Worker, it throws the following error:

Uncaught Error: EvalError: Code generation from strings disallowed for this context

Relevant Source Code The issue originates from the invocation of Function() in the following line of the compiler.ts file: https://github.com/sinclairzx81/typebox/blob/b05c59c13bc95679a5ec1c202e7007e2aa127f1a/src/compiler/compiler.ts#L631

Is there any plan to support environments like Cloudflare Workers in the future? Would it be possible to replace or refactor the usage of Function() to ensure compatibility?

sinclairzx81 commented 3 days ago

@foreseaz Hi,

You can use TypeBox validators in CloudFlare, but you will need to swap from the TypeCompiler to the Value.* functions.

https://github.com/sinclairzx81/typebox?tab=readme-ov-file#check

import { Value } from '@sinclair/typebox/value'

const T = Type.Object({ 
  x: Type.Number(), 
  y: Type.Number(), 
  z: Type.Number() 
})

// Safe to Use in Cloudflare
const R2 = Value.Check(T, { x: 1, y: 2, z: 3 })

Performance

The Value.Check function works the same as compiled version, but is non-optimized so you will see a significant reduction of validation performance, however the performance should still be more than adequate for most applications. You can find a performance comparison between Dynamic (Value) and Compiled (TypeCompiler, Ajv) checking at the link below.

https://github.com/sinclairzx81/typebox?tab=readme-ov-file#benchmark-validate

Conditional Compile

If you want the best possible validation performance for your environment, you can implement a custom compile function that will fallback to Value.Check in environments where Eval is restricted.

TypeScript Link Here

// -----------------------------------------------------------
// SafeCompile: Tries to compile with Eval, falls back to
// dynamic checking on fail.
// -----------------------------------------------------------
function SafeCompile<Type extends TSchema>(type: Type, references: TSchema[] = []): TypeCheck<Type> {
  try {
    // Try to compile using Eval (this will throw on Cloudflare)
    return TypeCompiler.Compile(type)
  } catch {
    // Use Dynamic Checking when Eval unavailable
    return new TypeCheck(type, references, (value) => Value.Check(type, references, value), '')
  }
}

// ...

const T = Type.Object({
  x: Type.Number(),
  y: Type.Number(),
  z: Type.Number()
})

const R = SafeCompile(T).Check({ x: 1, y: 2, z: 3 })

So yes, it's possible to use TypeBox on Cloudflare, but unfortunately, the Content Security Policy for Eval means that applications running on there have to pay a validation performance cost. TypeBox does offer a third code generation (AOT) option with TypeCompiler.Code where you would generate the validation logic and write that to disk as a module. This can be an option for performance critical applications on Cloudflare, but would only be required if your application needs to handle 100's of millions of validations per second (most applications don't need this).

Information on Code Generation can be found on the TypeCompiler documentation in the readme.

https://github.com/sinclairzx81/typebox?tab=readme-ov-file#typecompiler

Hope this helps S

sinclairzx81 commented 3 days ago

@foreseaz Heya,

Will close up this issue for now, but give Value.Check a try and see how you go. If you run into any problems, feel free to ping on this thread.

Cheers S

foreseaz commented 3 days ago

Thanks for the prompt response regarding this issue! I’ve run some benchmarks comparing TypeBox's Value validation with other candidates.

TypeBox Compiled Validation               x 143,038,827 ops/sec ±1.38% (98 runs sampled)
TypeBox Value(un-optimized) Validation    x   1,979,232 ops/sec ±6.19% (82 runs sampled)  (CF Worker compatible)
Ajv Validation                            x  27,530,079 ops/sec ±1.49% (91 runs sampled)
Superstruct Validation                    x     166,232 ops/sec ±3.96% (90 runs sampled)
Yup Validation                            x     111,987 ops/sec ±3.52% (88 runs sampled)  (CF Worker compatible)
Zod Validation                            x     875,983 ops/sec ±0.70% (94 runs sampled)
ArkType Validation                        x  52,927,504 ops/sec ±3.80% (89 runs sampled)  (CF Worker compatible)

While the compiled version of TypeBox delivers excellent performance, the Value validation performance was not quite sufficient. Looking forward to potentially supporting Workers with the compiled validation in the future, like arktype does.

sinclairzx81 commented 3 days ago

@foreseaz That's fine

While the compiled version of TypeBox delivers excellent performance, the Value validation performance was not quite sufficient. Looking forward to potentially supporting Workers with the compiled validation in the future, like arktype does.

There is no way to support the compiled versions on CF workers because of the Content Security Policy for Eval. This is going to be true for all validators (unless there is something I am missing)

Just be mindful that Ajv and ArkType will most likely be falling back to dynamic checking internally (as with the SafeCompile function above). If performance is a concern, you will want to run these benchmarks on the CF worker to be sure.

Good luck! S

sinclairzx81 commented 3 days ago

@foreseaz Fyi, had to check.

https://github.com/arktypeio/arktype/blob/main/ark/util/functions.ts#L94-L109

/**
 * Checks if the environment has Content Security Policy (CSP) enabled,
 * preventing JIT-optimized code from being compiled via new Function().
 *
 * @returns `true` if a function created using new Function() can be
 * successfully invoked in the environment, `false` otherwise.
 *
 * The result is cached for subsequent invocations.
 */
export const envHasCsp = cached((): boolean => {
    try {
        return new Function("return false")()
    } catch {
        return true
    }
})

TypeBox prefers to make the above explicit (as per SafeCompile) as I find it lends insights into the actual performance. Unfortunately, it's still not possible to get JIT compiled validation on CF Workers. It would be nice if JS runtimes could provide some facilities for safe evaluation of JS code outside the global context. Maybe with Shadow Realm proposal.

https://github.com/tc39/proposal-shadowrealm