Skip to content

Commit

Permalink
safe object callable
Browse files Browse the repository at this point in the history
  • Loading branch information
unnoq committed Dec 18, 2024
1 parent 5664122 commit 57f6953
Show file tree
Hide file tree
Showing 3 changed files with 39 additions and 17 deletions.
25 changes: 8 additions & 17 deletions packages/server/src/implementer-chainable.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { Middleware } from './middleware'
import type { Context, MergeContext, WELL_CONTEXT } from './types'
import { type ContractProcedure, type ContractRouter, isContractProcedure } from '@orpc/contract'
import { createCallableObject } from '@orpc/shared'
import { ProcedureImplementer } from './procedure-implementer'
import { RouterImplementer } from './router-implementer'

Expand Down Expand Up @@ -31,38 +32,28 @@ export function createChainableImplementer<
return implementer as any
}

const chainable: Record<string, unknown> = {}
const chainable = {} as ChainableImplementer<TContext, TExtraContext, TContract>

for (const key in contract) {
chainable[key] = createChainableImplementer(contract[key]!, middlewares)
(chainable as any)[key] = createChainableImplementer(contract[key]!, middlewares)
}

const routerImplementer = new RouterImplementer({ contract, middlewares })

const merged = new Proxy(chainable, {
get(target, key) {
const next = Reflect.get(target, key)
const next = Reflect.get(target, key) as ChainableImplementer<TContext, TExtraContext, TContract> | undefined
const method = Reflect.get(routerImplementer, key)

if (typeof key !== 'string' || typeof method !== 'function') {
return next
}

return new Proxy(method.bind(routerImplementer), {
get(target, key) {
// TODO: create own utils for object callable proxy
if (
typeof key !== 'string'
|| !next
|| (typeof next !== 'object' && typeof next !== 'function')
|| !(key in next)
) {
return Reflect.get(target, key)
}
if (!next) {
return method.bind(routerImplementer)
}

return next[key]
},
})
return createCallableObject(next, method.bind(routerImplementer))
},
})

Expand Down
1 change: 1 addition & 0 deletions packages/shared/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export * from './function'
export * from './hook'
export * from './json'
export * from './object'
export * from './proxy'
export * from './value'

export { isPlainObject } from 'is-what'
Expand Down
30 changes: 30 additions & 0 deletions packages/shared/src/proxy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import type { AnyFunction } from './function'

export function createCallableObject<TObject extends object, TFunc extends AnyFunction>(obj: TObject, func: TFunc): TObject & TFunc {
const proxy = new Proxy(func, {
has(target, key) {
return Reflect.has(obj, key) || Reflect.has(target, key)
},
ownKeys(target) {
return Array.from(new Set(Reflect.ownKeys(obj).concat(...Reflect.ownKeys(target))))
},
get(target, key) {
if (!Reflect.has(target, key) || Reflect.has(obj, key)) {
return Reflect.get(obj, key)
}

return Reflect.get(target, key)
},
defineProperty(_, key, descriptor) {
return Reflect.defineProperty(obj, key, descriptor)
},
set(_, key, value) {
return Reflect.set(obj, key, value)
},
deleteProperty(target, key) {
return Reflect.deleteProperty(target, key) && Reflect.deleteProperty(obj, key)
},
})

return proxy as any
}

0 comments on commit 57f6953

Please sign in to comment.