reactant-share
Version:
A framework for building shared web applications with Reactant
279 lines (256 loc) • 6.57 kB
text/typescript
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable no-use-before-define */
import type {
BaseInteraction,
EmitParameter,
MergeInteraction,
Transport,
TransportOptions,
} from 'data-transport';
import type { Config as BaseConfig, App, Renderer } from 'reactant';
import type { ILastActionData } from 'reactant-last-action';
import type { RouterState } from 'reactant-router';
import {
isClientName,
lastActionName,
loadFullStateActionName,
preloadedStateActionName,
proxyClientActionName,
proxyServerActionName,
syncRouterName,
syncWorkerRouterName,
syncToClientsName,
syncClientIdsFromClientsName,
syncClientIdToServerName,
removeClientIdToServerName,
proxyExecutorKey,
} from './constants';
export type { Transport } from 'data-transport';
export type Port = 'server' | 'client';
export interface Transports {
/**
* Server Transport
*/
server?: ServerTransport;
/**
* Client Transport
*/
client?: ClientTransport;
}
export type ServerTransport<T extends BaseInteraction = {}> = Transport<
MergeInteraction<{ emit: ServerEvents; listen: ClientEvents }, T>
>;
export type ClientTransport<T extends BaseInteraction = {}> = Transport<
MergeInteraction<
{
emit: ClientEvents;
listen: ServerEvents;
},
T
>
>;
export interface ISharedAppOptions {
/**
* Reactant shared app name.
*/
name: string;
/**
* Reactant shared app type.
*/
type: 'SharedTab' | 'SharedWorker' | 'Base';
/**
* Shared app's transports
*/
transports?: Transports;
/**
* Specify 'client' or 'server' port.
*/
port?: Port;
/**
* Specify a port name for routing and fork() args.
*/
portName?: string;
/**
* Specify a SharedWorker URL
*/
workerURL?: string;
/**
* Specify a SharedWorker
*/
worker?: SharedWorker;
/**
* Enable patches filter to support minimized modules collections in client port.
*/
enablePatchesFilter?: boolean;
/**
* Enable patches checker to support minimized patches in server port.
*/
enablePatchesChecker?: boolean;
/**
* Enable transport debugger for shared app.
*/
enableTransportDebugger?: boolean;
/**
* Transport logger for shared app.
*/
transportLogger?: TransportOptions['logger'];
/**
* Transform client/server port
*/
transform?: Transform;
/**
* Forced Sync for all client, enabled by default.
*
* If forcedSyncClient is false, then only the client's visibilityState is visible will the state be synchronized from server port.
*
* `forcedSyncClient` is only true in `SharedTab` type.
*/
forcedSyncClient?: boolean;
/**
* forced share, disabled by default.
*/
forcedShare?: boolean;
}
export interface Config<T, S extends any[], R extends Renderer<S>>
extends BaseConfig<T, S, R> {
/**
* Reactant shared app options.
*/
share: ISharedAppOptions;
}
export type Transform = (changedPort: Port) => void;
export type Callback = () => void | Promise<void>;
export type CallbackWithHook<T extends Transport = Transport<any>> = (
/**
* Shared app's transport
*/
transport: T
) => void | (() => void);
export type PortApp = Partial<Record<Port, App<any, any, any>>>;
export interface ProxyExecParams {
/**
* module name
*/
module: string;
/**
* method name
*/
method: string;
/**
* method arguments
*/
args: any[];
/**
* hook execution options
*/
hook?: string;
}
export type ClientEvents = {
[proxyClientActionName](options: ProxyExecParams): Promise<[number, any]>;
[preloadedStateActionName](): Promise<Record<string, any>>;
[loadFullStateActionName](
sequence: number
): Promise<Record<string, any> | null | undefined>;
[isClientName](): Promise<boolean>;
[syncRouterName](
name: string,
timestamp: number,
router?: RouterState
): Promise<RouterState>;
[syncClientIdToServerName](clientId: string): void;
[removeClientIdToServerName](clientId: string): void;
};
export type ServerEvents = {
[proxyServerActionName](
options: ProxyExecParams & {
portName?: string;
clientIds?: string[];
excludeClientIds?: string[];
}
): Promise<void>;
[lastActionName](options: ILastActionData): Promise<void>;
[syncToClientsName](
options: Record<string, any> | null | undefined
): Promise<void>;
[syncWorkerRouterName](name: string): Promise<RouterState | undefined>;
[syncClientIdsFromClientsName](): Promise<void>;
};
export interface HandleServerOptions {
app: App<any, any, any>;
transport: Transports['server'];
disposeClient?: () => void;
disposeServer?: () => void;
enablePatchesChecker?: boolean;
}
export interface HandleClientOptions {
app: App<any, any, any>;
transport: Transports['client'];
disposeServer?: () => void;
disposeClient?: () => void;
enablePatchesFilter?: boolean;
preloadedState?: Record<string, any>;
}
export type FunctionKeys<T> = Exclude<
{
[K in keyof T]: T[K] extends Function ? K : never;
}[keyof T],
void
>;
type ProxyArgs<
F extends (...args: any[]) => any,
O extends EmitParameter<any>['respond']
> = Parameters<F> extends []
? [
/**
* Pass in the parameters for this method.
*/
args?: Parameters<F>,
/**
* proxy execution options
*/
options?: { respond?: O; portName?: string; clientIds?: string[] } & Pick<
EmitParameter<any>,
Exclude<keyof EmitParameter<any>, 'name' | 'respond'>
>
]
: [
/**
* Pass in the parameters for this method.
*/
args: Parameters<F>,
/**
* proxy execution options
*/
options?: { respond?: O; portName?: string; clientIds?: string[] } & Pick<
EmitParameter<any>,
Exclude<keyof EmitParameter<any>, 'name' | 'respond'>
>
];
export type ProxyExec = <
T extends Record<string | number | symbol, any>,
K extends FunctionKeys<T>,
O extends EmitParameter<any>['respond']
>(
/**
* Designate an execution module from the server side.
*/
module: T,
/**
* Specify the name of a method in this module.
*/
key: K,
...args: ProxyArgs<T[K], O>
) => O extends false
? void
: ReturnType<T[K]> extends Promise<infer R>
? Promise<R>
: Promise<ReturnType<T[K]>>;
export type SymmetricTransport<
T extends Record<string, (...args: any[]) => any>
> = Transport<{
emit: T;
listen: T;
}>;
export interface ProxyExecutor {
[proxyExecutorKey]?: (params: ProxyExecParams) => Promise<unknown>;
}