Skip to content

Commit

Permalink
dynamic link
Browse files Browse the repository at this point in the history
  • Loading branch information
unnoq committed Dec 29, 2024
1 parent 9bb7616 commit f66ab3b
Show file tree
Hide file tree
Showing 4 changed files with 76 additions and 0 deletions.
21 changes: 21 additions & 0 deletions packages/client/src/dynamic-link.test-d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import type { ClientLink } from './types'
import { DynamicLink } from './dynamic-link'

describe('dynamicLink', () => {
it('pass correct context', () => {
void new DynamicLink<{ batch?: boolean }>((path, input, options) => {
expectTypeOf(options.context).toEqualTypeOf<{ batch?: boolean } >()

return {} as any
})
})

it('required return a another link', () => {
void new DynamicLink(() => ({} as ClientLink<any>))
void new DynamicLink<{ batch?: boolean }>(() => ({} as ClientLink<{ batch?: boolean }>))
// @ts-expect-error - context is mismatch
void new DynamicLink<{ batch?: boolean }>(() => ({} as ClientLink<{ batch?: string }>))
// @ts-expect-error - must return a ClientLink
void new DynamicLink<{ batch?: boolean }>(() => ({}))
})
})
26 changes: 26 additions & 0 deletions packages/client/src/dynamic-link.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import type { ProcedureClientOptions } from '@orpc/server'
import { describe, expect, it, vi } from 'vitest'
import { DynamicLink } from './dynamic-link'

describe('dynamicLink', () => {
it('works', async () => {
const mockedLink = { call: vi.fn().mockResolvedValue('__mocked__') }
const mockLinkResolver = vi.fn().mockResolvedValue(mockedLink)
const link = new DynamicLink(mockLinkResolver)

const path = ['users', 'getProfile']
const input = { id: 123 }
const options: ProcedureClientOptions<any> = {
context: {
batch: true,
},
}

expect(await link.call(path, input, options)).toEqual('__mocked__')

expect(mockLinkResolver).toHaveBeenCalledTimes(1)
expect(mockLinkResolver).toHaveBeenCalledWith(path, input, options)
expect(mockedLink.call).toHaveBeenCalledTimes(1)
expect(mockedLink.call).toHaveBeenCalledWith(path, input, options)
})
})
27 changes: 27 additions & 0 deletions packages/client/src/dynamic-link.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import type { ProcedureClientOptions } from '@orpc/server'
import type { Promisable } from '@orpc/shared'
import type { ClientLink } from './types'

/**
* DynamicLink provides a way to dynamically resolve and delegate calls to other ClientLinks
* based on the request path, input, and context.
*/
export class DynamicLink<TClientContext> implements ClientLink<TClientContext> {
constructor(
private readonly linkResolver: (
path: readonly string[],
input: unknown,
options: ProcedureClientOptions<TClientContext> & { context: TClientContext },
) => Promisable<ClientLink<TClientContext>>,
) {
}

async call(path: readonly string[], input: unknown, options: ProcedureClientOptions<TClientContext>): Promise<unknown> {
// Since the context is only optional when the context is undefinable, we can safely cast it
const resolvedLink = await this.linkResolver(path, input, options as typeof options & { context: TClientContext })

const output = await resolvedLink.call(path, input, options)

return output
}
}
2 changes: 2 additions & 0 deletions packages/client/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
/** unnoq */

export * from './client'
export * from './dynamic-link'
export * from './types'

export * from '@orpc/shared/error'

0 comments on commit f66ab3b

Please sign in to comment.