-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Fix/17514 #17786
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
Fix/17514 #17786
Changes from all commits
5a84f21
d789eda
4af573c
6525e24
ca281cc
cb97187
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -61,3 +61,6 @@ packages/gatsby/gatsby-node.d.ts | |
# intellij | ||
*.iml | ||
/**/.wrangler/* | ||
|
||
#junit reports | ||
packages/**/*.junit.xml |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
import { type DurableObjectState, type ExecutionContext } from '@cloudflare/workers-types'; | ||
|
||
type ContextType = ExecutionContext | DurableObjectState; | ||
type OverridesStore<T extends ContextType> = Map<keyof T, (...args: unknown[]) => unknown>; | ||
|
||
/** | ||
* Creates a new copy of the given execution context, optionally overriding methods. | ||
* | ||
* @param {ContextType|void} ctx - The execution context to be copied. Can be of type `ContextType` or `void`. | ||
* @return {ContextType|void} A new execution context with the same properties and overridden methods if applicable. | ||
*/ | ||
export function copyExecutionContext<T extends ContextType>(ctx: T): T { | ||
if (!ctx) return ctx; | ||
|
||
const overrides: OverridesStore<T> = new Map(); | ||
const contextPrototype = Object.getPrototypeOf(ctx); | ||
const methodNames = Object.getOwnPropertyNames(contextPrototype) as unknown as (keyof T)[]; | ||
const descriptors = methodNames.reduce((prevDescriptors, methodName) => { | ||
if (methodName === 'constructor') return prevDescriptors; | ||
if (typeof ctx[methodName] !== 'function') return prevDescriptors; | ||
const overridableDescriptor = makeOverridableDescriptor(overrides, ctx, methodName); | ||
return { | ||
...prevDescriptors, | ||
[methodName]: overridableDescriptor, | ||
}; | ||
}, {}); | ||
|
||
return Object.create(ctx, descriptors); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Bug: Override System Excludes Own Properties and SymbolsThe |
||
} | ||
|
||
/** | ||
* Creates a property descriptor that allows overriding of a method on the given context object. | ||
* | ||
* This descriptor supports property overriding with functions only. It delegates method calls to | ||
* the provided store if an override exists or to the original method on the context otherwise. | ||
* | ||
* @param {OverridesStore<ContextType>} store - The storage for overridden methods specific to the context type. | ||
* @param {ContextType} ctx - The context object that contains the method to be overridden. | ||
* @param {keyof ContextType} method - The method on the context object to create the overridable descriptor for. | ||
* @return {PropertyDescriptor} A property descriptor enabling the overriding of the specified method. | ||
*/ | ||
function makeOverridableDescriptor<T extends ContextType>( | ||
store: OverridesStore<T>, | ||
ctx: T, | ||
method: keyof T, | ||
): PropertyDescriptor { | ||
return { | ||
configurable: true, | ||
enumerable: true, | ||
set: newValue => { | ||
if (typeof newValue !== 'function') throw new Error('Cannot override non-function'); | ||
store.set(method, newValue); | ||
return true; | ||
}, | ||
|
||
get: () => { | ||
if (store.has(method)) return store.get(method); | ||
const methodFunction = Reflect.get(ctx, method); | ||
if (typeof methodFunction !== 'function') return methodFunction; | ||
// We should do bind() to make sure that the method is bound to the context object - otherwise it will not work | ||
return methodFunction.bind(ctx); | ||
}, | ||
}; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
import { type Mocked, describe, expect, it, vi } from 'vitest'; | ||
import { copyExecutionContext } from '../src/utils/copyExecutionContext'; | ||
|
||
describe('Copy of the execution context', () => { | ||
describe.for([ | ||
'waitUntil', | ||
'passThroughOnException', | ||
'acceptWebSocket', | ||
'blockConcurrencyWhile', | ||
'getWebSockets', | ||
'arbitraryMethod', | ||
'anythingElse', | ||
])('%s', method => { | ||
it('Override without changing original', async () => { | ||
const context = { | ||
[method]: vi.fn(), | ||
} as any; | ||
const copy = copyExecutionContext(context); | ||
copy[method] = vi.fn(); | ||
expect(context[method]).not.toBe(copy[method]); | ||
}); | ||
|
||
it('Overridden method was called', async () => { | ||
const context = { | ||
[method]: vi.fn(), | ||
} as any; | ||
const copy = copyExecutionContext(context); | ||
const overridden = vi.fn(); | ||
copy[method] = overridden; | ||
copy[method](); | ||
expect(overridden).toBeCalled(); | ||
expect(context[method]).not.toBeCalled(); | ||
}); | ||
}); | ||
|
||
it('No side effects', async () => { | ||
const context = makeExecutionContextMock(); | ||
expect(() => copyExecutionContext(Object.freeze(context))).not.toThrow( | ||
/Cannot define property \w+, object is not extensible/, | ||
); | ||
}); | ||
it('Respects symbols', async () => { | ||
const s = Symbol('test'); | ||
const context = makeExecutionContextMock<ExecutionContext & { [s]: unknown }>(); | ||
context[s] = {}; | ||
const copy = copyExecutionContext(context); | ||
expect(copy[s]).toBe(context[s]); | ||
}); | ||
}); | ||
|
||
function makeExecutionContextMock<T extends ExecutionContext>() { | ||
return { | ||
waitUntil: vi.fn(), | ||
passThroughOnException: vi.fn(), | ||
} as unknown as Mocked<T>; | ||
} |