Skip to content

Commit

Permalink
feat: Passing change handlers to host
Browse files Browse the repository at this point in the history
  • Loading branch information
cmath10 committed Oct 30, 2024
1 parent bd21691 commit 9043dc7
Show file tree
Hide file tree
Showing 5 changed files with 69 additions and 88 deletions.
20 changes: 6 additions & 14 deletions src/context/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ declare module 'pinia' {

export const injectAccessor = <M extends ContextSchemaMap>(endpoint: Endpoint<ContextAccessor<M>>) => {
return (context: PiniaPluginContext) => {
context.store.endpoint = endpoint
context.store.endpoint = endpoint as Endpoint<ContextAccessor>
}
}

Expand Down Expand Up @@ -65,10 +65,13 @@ export const defineContext = <Id extends string, S extends ContextSchema>(

keysOf(schema).forEach(field => {
context[field] = state[field]
endpoint.call.on(id, `change:${String(field)}` as keyof EventSchema<S>, (value) => {
context[field] = value as TypeOf<S[typeof field]>
})
})
},

set<F extends keyof Mutable<S>>(field: F, value: TypeOf<S[F]>, synchronize = true) {
set<F extends keyof Mutable<S>>(field: F, value: TypeOf<S[F]>) {
if (!(field in schema)) {
throw new Error(`[crm:embed:remote] Field ${String(field)} is not present in context ${id}`)
}
Expand All @@ -87,17 +90,7 @@ export const defineContext = <Id extends string, S extends ContextSchema>(
}>>

context[field] = value

if (synchronize) {
endpoint.call.set(id, field, value)
}
},

on<E extends keyof EventSchema<S>>(event: E, payload: EventSchema<S>[E]) {
const [, field] = String(event).split(':')
if (field in schema) {
this.set(field, payload as TypeOf<S[typeof field]>, false)
}
endpoint.call.set(id, field, value)
},
},
})
Expand All @@ -113,7 +106,6 @@ export const useField = <S extends ContextSchema, F extends keyof S>(
}, {
initialize(): Promise<void>;
set<F extends keyof Mutable<S>>(field: F, value: TypeOf<S[F]>): void;
on<E extends keyof EventSchema<S>>(event: E, payload: EventSchema<S>[E]): void;
}>,
field: F
): Computed<S, F> => {
Expand Down
31 changes: 14 additions & 17 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,31 @@
import type { MessageEndpoint } from '@remote-ui/rpc'

import type {
Endpoint,
ExtensionApi,
} from '~types/endpoint'
MessageEndpoint,
} from '@remote-ui/rpc'

import type { ContextAccessor } from '~types/context/schema'
import type { ExtensionApi } from '~types/endpoint'

import type { OverallContextAccessor } from '~types/context'
import { SchemaMap } from '~types/context'

import {
createEndpoint,
release,
retain,
} from '@remote-ui/rpc'

import type { AnyFunction, None } from '~types/scaffolding'
import type { AnyFunction } from '~types/scaffolding'

export {
defineContext,
useField,
} from './context/store'

export const createWidgetEndpoint = <
M extends Record<string, AnyFunction> = None,
E extends Record<string, unknown> = None
>(api: ExtensionApi<M, E>, messenger: MessageEndpoint): Endpoint<M, E> => {
const endpoint = createEndpoint<OverallContextAccessor>(messenger)
export const createWidgetEndpoint = <M extends Record<string, AnyFunction>>(
api: ExtensionApi<M>,
messenger: MessageEndpoint
): Endpoint<ContextAccessor<SchemaMap>> => {
const endpoint = createEndpoint<ContextAccessor<SchemaMap>>(messenger)

let onRelease = () => {}

Expand All @@ -47,11 +48,7 @@ export const createWidgetEndpoint = <
onRelease()
onRelease = () => {}
},
} as ExtensionApi<M> as unknown as Record<string, AnyFunction | undefined>)

on (context, event, payload) {
api.on(context, event, payload)
},
} as ExtensionApi<M, E> as unknown as Record<string, AnyFunction | undefined>)

return endpoint as Endpoint<M, E>
return endpoint
}
53 changes: 31 additions & 22 deletions tests/order/card.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,7 @@ import type { Channel } from '@omnicajs/vue-remote/host'
import type { ExtensionApi } from '~types/endpoint'
import type { None } from '~types/scaffolding'

import type {
ContextAccessor,
EventSchema,
TypeOf,
} from '~types/context/schema'
import type { ContextAccessor, TypeOf } from '~types/context/schema'

import type { Schema } from '~types/context/order/card'
import type { SchemaMap } from '~types/context'
Expand Down Expand Up @@ -36,14 +32,15 @@ import { createWidgetEndpoint } from '@/index'
import {
createEndpoint,
fromMessagePort,
retain,
} from '@remote-ui/rpc'

import { injectAccessor } from '@/context/store'

import { useContext } from '@/context/order/card'
import { useField } from '@/context/store'

const createOrderContext = () => {
const createHostContext = () => {
const order = reactive({
customer: {
email: '[email protected]',
Expand Down Expand Up @@ -97,6 +94,28 @@ const createOrderContext = () => {
throw new Error(`Field ${field} is not supported`)
}
},

on (context: string, event: string, handler: (payload: unknown) => void) {
if (context !== 'order/card') {
throw new Error(`[crm:embed:host] Context ${context} is not supported`)
}

retain(handler)

switch (event) {
case 'change:customer.email':
watch(() => order.customer.email, handler)
break
case 'change:customer.phone':
watch(() => order.customer.phone, handler)
break
case 'change:delivery.address':
watch(() => order.delivery.address, handler)
break
default:
throw new Error(`Event ${event} is not supported`)
}
},
} as ContextAccessor<SchemaMap>

return {
Expand All @@ -112,36 +131,26 @@ describe('order/card', () => {
port1.start()
port2.start()

const { order, accessor } = createOrderContext()
const { order, accessor } = createHostContext()

const host = createEndpoint<ExtensionApi<None, EventSchema<Schema>>>(fromMessagePort(port1))
const host = createEndpoint<ExtensionApi<None>>(fromMessagePort(port1))

host.expose(accessor)

const app = createApp({ template: '<div />' })
const pinia = createPinia()

pinia.use(injectAccessor<SchemaMap>(createWidgetEndpoint<None, EventSchema<Schema>>({
async run () {},
pinia.use(injectAccessor<SchemaMap>(createWidgetEndpoint<None>({
async run () {
const app = createApp({ template: '<div />' })

on: (contextId, event, payload) => {
const context = useContext()
if (contextId === 'order/card') {
context.on(event, payload)
}
app.use(pinia)
},

release () {},
}, fromMessagePort(port2))))

app.use(pinia)

await host.call.run(null as unknown as Channel, 'order/card:customer', {})

watch(() => order.customer.email, (email) => host.call.on('order/card', 'change:customer.email', email))
watch(() => order.customer.phone, (phone) => host.call.on('order/card', 'change:customer.phone', phone))
watch(() => order.delivery.address, (address) => host.call.on('order/card', 'change:delivery.address', address))

const wrapper = mount({
setup () {
const orderContext = useContext()
Expand Down
27 changes: 16 additions & 11 deletions types/context/schema.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,18 @@ export type Mutable<S extends ContextSchema> = {
[F in keyof S]: IsReadonly<S[F]> extends true ? never : S[F];
}

export type EventName<
Key extends string,
F extends Field<unknown, boolean>
> = IsReadonly<F> extends true ? never : `change:${Key}`

export type EventSchema<S extends ContextSchema> = {
[K in keyof S as EventName<
K & string,
S[K]
>]: TypeOf<S[K]>;
}

export type ContextAccessor<M extends ContextSchemaMap = ContextSchemaMap> = {
get <
C extends keyof M
Expand All @@ -40,16 +52,9 @@ export type ContextAccessor<M extends ContextSchemaMap = ContextSchemaMap> = {
C extends keyof M,
F extends keyof Mutable<M[C]>
>(context: C, field: F, value: TypeOf<M[C][F]>): void;
}

export type EventName<
Key extends string,
F extends Field<unknown, boolean>
> = IsReadonly<F> extends true ? never : `change:${Key}`

export type EventSchema<S extends ContextSchema> = {
[K in keyof S as EventName<
K & string,
S[K]
>]: TypeOf<S[K]>;
on<
C extends keyof M,
E extends keyof EventSchema<M[C]>
>(context: C, event: E, handler: (payload: EventSchema<M[C]>[E]) => void): void;
}
26 changes: 2 additions & 24 deletions types/endpoint.d.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,6 @@
import type {
AnyFunction,
None,
} from './scaffolding'
import type { AnyFunction } from './scaffolding'

import type { Channel } from '@omnicajs/vue-remote/host'
import type { Endpoint as RemoteEndpoint } from '@remote-ui/rpc'

import type { OverallContextAccessor } from './context'

export type Target =
| 'customer/card:main'
Expand All @@ -16,28 +10,12 @@ export type Target =
| 'order/card:customer.phone'
| 'order/card:delivery.address'

export interface ExtensionApi<
M extends Record<string, AnyFunction> = None,
E extends Record<string, unknown> = None
> {
export interface ExtensionApi<M extends Record<string, AnyFunction>> {
run (
channel: Channel,
target: Target,
api: M,
): Promise<void>;

release (): void;

on <K extends keyof E>(
context: string,
event: K,
payload: E[K]
): void;
}

export type Endpoint<
M extends Record<string, AnyFunction> = None,
E extends Record<string, unknown> = None
> = RemoteEndpoint<OverallContextAccessor> & {
expose (api: ExtensionApi<M, E>): void
}

0 comments on commit 9043dc7

Please sign in to comment.