Skip to content

Commit

Permalink
Merge branch 'release/release/0.13.20'
Browse files Browse the repository at this point in the history
  • Loading branch information
holtwick committed Oct 11, 2023
2 parents f1ae3cc + eab70a4 commit e4c3e6e
Show file tree
Hide file tree
Showing 3 changed files with 89 additions and 128 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "zeed",
"type": "module",
"version": "0.13.19",
"version": "0.13.20",
"description": "🌱 Simple foundation library",
"author": {
"name": "Dirk Holtwick",
Expand Down
9 changes: 5 additions & 4 deletions src/common/msg/rpc.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { MessageChannel } from 'worker_threads'
import { useRPC, useRPCHub } from './rpc'
import { decodeJson, encodeJson } from '../bin'


let bobCount = 0

const Bob = {
Expand Down Expand Up @@ -39,14 +40,14 @@ describe('rpc', () => {
const serialize = (data: any) => encodeJson(data)
const deserialize = (data: any) => decodeJson(data)

const bob = useRPC<AliceFunctions>(Bob, {
const bob = useRPC<BobFunctions, AliceFunctions>(Bob, {
post: data => channel.port1.postMessage(data),
on: data => channel.port1.on('message', data),
serialize,
deserialize,
})

const alice = useRPC<BobFunctions>(Alice, {
const alice = useRPC<AliceFunctions, BobFunctions>(Alice, {
// mark bob's `bump` as an event without response
eventNames: ['bump'],
post: data => channel.port2.postMessage(data),
Expand Down Expand Up @@ -82,9 +83,9 @@ describe('rpc', () => {
deserialize,
})

let bob = bobHub<AliceFunctions>(Bob)
let bob = bobHub<BobFunctions, AliceFunctions>(Bob)

const alice = useRPC<BobFunctions>(Alice, {
const alice = useRPC<AliceFunctions, BobFunctions>(Alice, {
// mark bob's `bump` as an event without response
eventNames: ['bump'],
post: data => channel.port2.postMessage(data),
Expand Down
206 changes: 83 additions & 123 deletions src/common/msg/rpc.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
// From https://github.com/antfu/birpc/blob/main/src/index.ts MIT

import { LoggerInterface } from "../log/log-base"

export type ArgumentsType<T> = T extends (...args: infer A) => any ? A : never
export type ReturnType<T> = T extends (...args: any) => infer R ? R : never

Expand All @@ -14,6 +16,8 @@ export interface RPCOptionsBasic {
serialize?: (data: any) => any
/** Custom function to deserialize data */
deserialize?: (data: any) => any
/** Custom logger */
log?: LoggerInterface
}

export interface RPCOptions<Remote> extends RPCOptionsBasic {
Expand Down Expand Up @@ -53,16 +57,13 @@ type RPCMessage = [
const defaultSerialize = (i: any) => i
const defaultDeserialize = defaultSerialize

export function useRPC<RemoteFunctions = object, LocalFunctions = object>(
functions: LocalFunctions,
options: RPCOptions<RemoteFunctions>,
): RPCReturn<RemoteFunctions> {
function setupRPCBasic(options: RPCOptionsBasic, functions: any, eventNames: string[] = []) {
const {
post,
on,
eventNames = [],
serialize = defaultSerialize,
deserialize = defaultDeserialize,
log,
} = options

const rpcPromiseMap = new Map<
Expand All @@ -71,150 +72,109 @@ export function useRPC<RemoteFunctions = object, LocalFunctions = object>(
>()

on(async (data) => {
const msg = deserialize(data) as RPCMessage
const [mode, args, id, method] = msg
if (mode === RPCMode.request || mode === RPCMode.event) {
let result, error: any
if (method != null) {
try {
const fn = (functions as any)[method] // todo
result = await fn(...args)
try {
const msg = deserialize(data) as RPCMessage
const [mode, args, id, method] = msg
if (mode === RPCMode.request || mode === RPCMode.event) {
let result, error: any
if (method != null) {
try {
const fn = functions[method]
result = await fn(...args)
}
catch (e) {
error = String(e)
}
}
catch (e) {
error = String(e)
else {
error = 'Method implementation missing'
}
}
else {
error = 'Method implementation missing'
}
if (mode === RPCMode.request && id) {
if (error)
post(serialize([RPCMode.reject, error, id]))
else
post(serialize([RPCMode.resolve, result, id]))
log?.warn('error', msg, error)
if (mode === RPCMode.request && id) {
if (error)
post(serialize([RPCMode.reject, error, id]))
else
post(serialize([RPCMode.resolve, result, id]))
}
}
}
else if (id) {
const promise = rpcPromiseMap.get(id)
if (promise != null) {
if (mode === RPCMode.reject)
promise.reject(args)
else promise.resolve(args)
else if (id) {
const promise = rpcPromiseMap.get(id)
if (promise != null) {
if (mode === RPCMode.reject)
promise.reject(args)
else promise.resolve(args)
}
rpcPromiseMap.delete(id)
}
rpcPromiseMap.delete(id)
}
catch (err) {
log?.warn('Error on handling RPC data. Invalid?', err, data)
}
})

return new Proxy(
{},
{
get(_, method) {
const sendEvent = (...args: any[]) => {
post(serialize([RPCMode.event, args, null, method]))
}
if (options.onlyEvents || eventNames.includes(method as any)) {
sendEvent.asEvent = sendEvent
return sendEvent
}
const sendCall = (...args: any[]) => {
return new Promise((resolve, reject) => {
const id = rpcCounter++
rpcPromiseMap.set(id, { resolve, reject })
post(serialize([RPCMode.request, args, id, method]))
})
}
sendCall.asEvent = sendEvent
return sendCall
},
const proxyHandler = {
get(_: any, method: string) {
const sendEvent = (...args: any[]) => {
post(serialize([RPCMode.event, args, null, method]))
}
if (options.onlyEvents || eventNames.includes(method)) {
sendEvent.asEvent = sendEvent
return sendEvent
}
const sendCall = (...args: any[]) => {
return new Promise((resolve, reject) => {
const id = rpcCounter++
rpcPromiseMap.set(id, { resolve, reject })
post(serialize([RPCMode.request, args, id, method]))
})
}
sendCall.asEvent = sendEvent
return sendCall
},
) as any
}

return { post, serialize, rpcPromiseMap, proxyHandler }
}

export function useRPCHub(options: RPCOptionsBasic) {
const {
post,
on,
serialize = defaultSerialize,
deserialize = defaultDeserialize,
} = options
export function useRPC<LocalFunctions, RemoteFunctions = LocalFunctions>(
functions: LocalFunctions,
options: RPCOptions<RemoteFunctions>,
): RPCReturn<RemoteFunctions> {
const { eventNames = [] } = options
const { proxyHandler } = setupRPCBasic(options, functions, eventNames as any)

return new Proxy({}, proxyHandler)
}

export function useRPCHub(options: RPCOptionsBasic) {
const eventNames: string[] = []
const functions: Record<string, any> = {}

const rpcPromiseMap = new Map<
number,
{ resolve: (...args: any) => any; reject: (...args: any) => any }
>()

on(async (data) => {
const msg = deserialize(data) as RPCMessage
const [mode, args, id, method] = msg
if (mode === RPCMode.request || mode === RPCMode.event) {
let result, error: any
if (method != null) {
try {
const fn = functions[method]
result = await fn(...args)
}
catch (e) {
error = String(e)
}
}
else {
error = 'Method implementation missing'
}
if (mode === RPCMode.request && id) {
if (error)
post(serialize([RPCMode.reject, error, id]))
else
post(serialize([RPCMode.resolve, result, id]))
}
}
else if (id) {
const promise = rpcPromiseMap.get(id)
if (promise != null) {
if (mode === RPCMode.reject)
promise.reject(args)
else promise.resolve(args)
}
rpcPromiseMap.delete(id)
}
})
const { proxyHandler } = setupRPCBasic(options, functions)

function createRPCProxy() {
return new Proxy(
{},
{
get(_, method) {
const sendEvent = (...args: any[]) => {
post(serialize([RPCMode.event, args, null, method]))
}
if (options.onlyEvents || eventNames.includes(method as any)) {
sendEvent.asEvent = sendEvent
return sendEvent
}
const sendCall = (...args: any[]) => {
return new Promise((resolve, reject) => {
const id = rpcCounter++
rpcPromiseMap.set(id, { resolve, reject })
post(serialize([RPCMode.request, args, id, method]))
})
}
sendCall.asEvent = sendEvent
return sendCall
},
},
) as any
return new Proxy({}, proxyHandler)
}

return function<RemoteFunctions = object, LocalFunctions = object>(
return function<LocalFunctions, RemoteFunctions = LocalFunctions>(
additionalFunctions?: LocalFunctions,
additionalEventNames: string[] = [],
): RPCReturn<RemoteFunctions> {
Object.assign(functions, additionalFunctions ?? {})
// log(`Registered functions:\n${Object.keys(functions).join('\n')}`)
eventNames.push(...additionalEventNames)
return createRPCProxy()
}
}

export type UseRPCHubType = ReturnType<typeof useRPCHub>

// Syntax test case
// const hub: UseRPCHubType = {} as any
// const x = hub({
// test(name: string): string {
// return name
// },
// })
// await x.test('dsd')

0 comments on commit e4c3e6e

Please sign in to comment.