Skip to content
Merged
4 changes: 3 additions & 1 deletion .eslintrc.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@ plugins:

rules:
"@typescript-eslint/no-non-null-assertion": off
"@typescript-eslint/no-explicit-any": off
"@typescript-eslint/no-explicit-any": ["error", {"ignoreRestArgs": true}]
no-unused-vars: off
"@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_" }]
prettier/prettier: error
no-console: warn
require-atomic-updates: warn
Expand Down
101 changes: 66 additions & 35 deletions api/api.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ export interface IPNode {

/** Forces the node to be finalized. You generaly don't need to call this. */
finalize(): void;

/** The result of the node's callback, finalizing it if needed. */
readonly result: unknown;
}

/** A probed component of which the return type is known. */
Expand All @@ -35,32 +38,17 @@ export interface PNode<T> extends IPNode {
}

/** A function that implements the probe() functionality for a given set of intrinsics. */
export type ProbingFunction<I extends Intrinsics<I>> = <T extends keyof I | ((...args: any[]) => any)>(
export type ProbingFunction<I extends FuncMap> = <T extends IKeys<I> | ((...args: any[]) => unknown)>(
cb: T,
...args: ProbedParams<T, I>
) => AsPNode<ProbedResult<T, I>>;

/**
* Creates a probe() function that is bound against a set of intrinsic components.
*
* ex: const probe = createProber({"hi": x=>x+5, "bye": x=>x*2});
* probe("hi", 12);
*/
export declare function createProber<I extends Intrinsics<I>>(intrinsics: I): ProbingFunction<I>;

/** Default probe() function not bound to any intrinsic. */
// eslint-disable-next-line @typescript-eslint/ban-types
export declare function probe<T extends (...args: any[]) => any>(cb: T, ...args: Parameters<T>): AsPNode<ReturnType<T>>;

// ***************** Hooks ***************** //

/** Registers a callback that will be invoked when component currently being probed is disposed. */
export declare function useOnDispose(op: () => void): void;

// ***************** Dynamics ***************** //
/** Meta information available to components. */
export interface ProbingContext {
componentName: string; // The name of the component.
}

/** Determines at runtime if a value is static or dynamic. */
export declare function isDynamic<T>(val: T | DynamicReader<T>): val is DynamicReader<T>;
export type Component<Args extends unknown[], Ret> = (...args: Args) => Ret;

// ************ Dynamics - Readers ************ //

Expand All @@ -70,6 +58,9 @@ export interface DynamicReaderBase<T> {
removeListener(lst: (v: T) => void): void;

readonly current: T;

/** gets string representation */
toString(): string;
}

/** Consumer API for a dynamic primitive. */
Expand Down Expand Up @@ -105,6 +96,40 @@ export interface DynamicList<T extends Array<unknown>> extends DynamicBase<T> {
push(v: ListValueType<T>): void;
}

/**
* Creates a probe() function that is bound against a set of intrinsic components.
*
* ex: const probe = createProber({"hi": x=>x+5, "bye": x=>x*2});
* probe("hi", 12);
*/
export function createProber<I extends Intrinsics<I>>(intrinsics: I): ProbingFunction<I>;
export function createProber<I extends Intrinsics<I>>(
intrinsics: Partial<I>,
fallback: IntrinsicFallback<I>,
): ProbingFunction<I>;

/** Default probe() function not bound to any intrinsic. */
// eslint-disable-next-line @typescript-eslint/ban-types
export declare function probe<T extends (...args: any[]) => unknown>(
cb: T,
...args: Parameters<T>
): AsPNode<ReturnType<T>>;

// ***************** Hooks ***************** //

/** Registers a callback that will be invoked when component currently being probed is disposed. */
export declare function useOnDispose(op: () => void): void;

/** Obtains the probing context for the node being probed */
export declare function useProbingContext(): ProbingContext;

// ***************** Dynamics ***************** //

/** Determines at runtime if a value is static or dynamic. */
export declare function isDynamic<T>(val: T | DynamicReader<T>): val is DynamicReader<T>;

// ************ Dynamics - Readers ************ //

/** Creates a new Dynamic value. */
export declare function dynamic<T>(init: T[]): DynamicList<T[]>;
export declare function dynamic<T>(init: T): DynamicValue<T>;
Expand All @@ -124,31 +149,37 @@ export declare function valType<T>(v: Reader<T>): string;

// ***************** Utility / not user-facing ***************** //

type DynamicReader<T> = DynamicListReader<T extends Array<unknown> ? T : never> | DynamicValueReader<T>;

type AsPNode<T> = T extends PNode<infer U> ? PNode<U> : PNode<T>;
export type DynamicReader<T> = DynamicListReader<T extends Array<unknown> ? T : never> | DynamicValueReader<T>;

type Component<ArgsT extends any[] = any[], RetT = any> = (...arg: ArgsT) => RetT;
export type AsPNode<T> = T extends PNode<infer U> ? PNode<U> : PNode<T>;

type Intrinsics<I> = {
[_ in keyof I]: Component;
export type Intrinsics<T> = {
[K in keyof T as T[K] extends (...args: any[]) => unknown ? K : never]: T[K];
};

type IntrinsicParams<K extends keyof I, I extends Intrinsics<I>> = Parameters<I[K]>;
type IntrinsicResult<K extends keyof I, I extends Intrinsics<I>> = ReturnType<I[K]>;
export type IKeys<T> = keyof T;

type Probed<I extends Intrinsics<I> = Record<string, never>> = keyof I | Component;
export type IntrinsicParams<K extends IKeys<I>, I extends FuncMap> = Parameters<I[K]>;
export type IntrinsicResult<K extends IKeys<I>, I extends FuncMap> = ReturnType<I[K]>;

type ProbedParams<T extends Probed<I>, I extends Intrinsics<I> = Record<string, never>> = T extends keyof I
export type Probed<I extends FuncMap> = IKeys<I> | ((...args: any[]) => unknown);

export type ProbedParams<T extends Probed<I>, I extends FuncMap> = T extends IKeys<I>
? IntrinsicParams<T, I>
: T extends Component<infer P, any>
: T extends (...arg: infer P) => unknown
? P
: never;

type ProbedResult<T extends Probed<I>, I extends Intrinsics<I> = Record<string, never>> = T extends keyof I
export type ProbedResult<T extends Probed<I>, I extends FuncMap> = T extends IKeys<I>
? IntrinsicResult<T, I>
: T extends Component<any[], infer P>
: T extends (...arg: any[]) => infer P
? P
: never;

type ListValueType<ArrayType extends Array<unknown>> = ArrayType[number];
export type FuncMap = Record<string, (...args: any[]) => unknown>;

type FallbackParams<T extends FuncMap> = Parameters<T[keyof T]>;
type FallbackResult<T extends FuncMap> = ReturnType<T[keyof T]>;
export type IntrinsicFallback<T extends FuncMap> = (...args: FallbackParams<T>) => FallbackResult<T>;

export type ListValueType<ArrayType extends Array<unknown>> = ArrayType[number];
6 changes: 3 additions & 3 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,17 @@ const fullConfig = {
projects: [
{
displayName: 'prod',
setupFiles: ['./tests/variants/prod.js'],
setupFiles: ['./tests/setup/prod.js'],
...baseConfig,
},
{
displayName: 'dev',
setupFiles: ['./tests/variants/dev.js'],
setupFiles: ['./tests/setup/dev.js'],
...baseConfig,
},
{
displayName: 'check',
setupFiles: ['./tests/variants/check.js'],
setupFiles: ['./tests/setup/check.js'],
...baseConfig,
},
],
Expand Down
40 changes: 28 additions & 12 deletions src/ApiTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ export interface IPNode {

/** Forces the node to be finalized. You generaly don't need to call this. */
finalize(): void;

/** The result of the node's callback, finalizing it if needed. */
readonly result: unknown;
}

/** A probed component of which the return type is known. */
Expand All @@ -35,11 +38,18 @@ export interface PNode<T> extends IPNode {
}

/** A function that implements the probe() functionality for a given set of intrinsics. */
export type ProbingFunction<I extends Intrinsics<I>> = <T extends keyof I | ((...args: any[]) => any)>(
export type ProbingFunction<I extends FuncMap> = <T extends IKeys<I> | ((...args: any[]) => unknown)>(
cb: T,
...args: ProbedParams<T, I>
) => AsPNode<ProbedResult<T, I>>;

/** Meta information available to components. */
export interface ProbingContext {
componentName: string; // The name of the component.
}

export type Component<Args extends unknown[], Ret> = (...args: Args) => Ret;

// ************ Dynamics - Readers ************ //

/** Consumer API for a dynamic value. */
Expand Down Expand Up @@ -92,27 +102,33 @@ export type DynamicReader<T> = DynamicListReader<T extends Array<unknown> ? T :

export type AsPNode<T> = T extends PNode<infer U> ? PNode<U> : PNode<T>;

export type Component<ArgsT extends any[] = any[], RetT = any> = (...arg: ArgsT) => RetT;

export type Intrinsics<I> = {
[_ in keyof I]: Component;
export type Intrinsics<T> = {
[K in keyof T as T[K] extends (...args: any[]) => unknown ? K : never]: T[K];
};

export type IntrinsicParams<K extends keyof I, I extends Intrinsics<I>> = Parameters<I[K]>;
export type IntrinsicResult<K extends keyof I, I extends Intrinsics<I>> = ReturnType<I[K]>;
export type IKeys<T> = keyof T;

export type IntrinsicParams<K extends IKeys<I>, I extends FuncMap> = Parameters<I[K]>;
export type IntrinsicResult<K extends IKeys<I>, I extends FuncMap> = ReturnType<I[K]>;

export type Probed<I extends Intrinsics<I> = Record<string, never>> = keyof I | Component;
export type Probed<I extends FuncMap> = IKeys<I> | ((...args: any[]) => unknown);

export type ProbedParams<T extends Probed<I>, I extends Intrinsics<I> = Record<string, never>> = T extends keyof I
export type ProbedParams<T extends Probed<I>, I extends FuncMap> = T extends IKeys<I>
? IntrinsicParams<T, I>
: T extends Component<infer P, any>
: T extends (...arg: infer P) => unknown
? P
: never;

export type ProbedResult<T extends Probed<I>, I extends Intrinsics<I> = Record<string, never>> = T extends keyof I
export type ProbedResult<T extends Probed<I>, I extends FuncMap> = T extends IKeys<I>
? IntrinsicResult<T, I>
: T extends Component<any[], infer P>
: T extends (...arg: any[]) => infer P
? P
: never;

export type FuncMap = Record<string, (...args: any[]) => unknown>;

type FallbackParams<T extends FuncMap> = Parameters<T[keyof T]>;
type FallbackResult<T extends FuncMap> = ReturnType<T[keyof T]>;
export type IntrinsicFallback<T extends FuncMap> = (...args: FallbackParams<T>) => FallbackResult<T>;

export type ListValueType<ArrayType extends Array<unknown>> = ArrayType[number];
16 changes: 16 additions & 0 deletions src/Environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,12 @@
* limitations under the License.
*/

import { ProbingContext } from './ApiTypes';

export type DisposeOp = () => void;
export interface Environment {
_onDispose: (op: DisposeOp) => void;
_getProbingContext: () => ProbingContext | undefined;
}

let _currentEnv: Environment | null = null;
Expand All @@ -41,3 +44,16 @@ export const useOnDispose = (op: DisposeOp): void => {

_currentEnv!._onDispose(op);
};

export function useProbingContext(): ProbingContext {
if (process.env.NODE_ENV !== 'production' && !_currentEnv) {
throw new Error('Environment underflow');
}

const result = _currentEnv!._getProbingContext();
if (process.env.NODE_ENV !== 'production' && !result) {
throw new Error('There is no active probing context');
}

return result!;
}
9 changes: 6 additions & 3 deletions src/Node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,21 @@
* limitations under the License.
*/

import { IPNode } from './ApiTypes';
import { IPNode, ProbingContext } from './ApiTypes';
import { DisposeOp } from './Environment';

export interface IProber {
_finalize(target: IPNode): void;
}

export interface NodeBuildData {
_cb: (...arg: any[]) => any;
_args: any[];
_cb: (...arg: unknown[]) => unknown;
_args: unknown[];

_next?: IPNode;
_resolveAs?: IPNode;
_prober: IProber;
_context: ProbingContext;
}

export abstract class BaseNode implements IPNode {
Expand All @@ -48,6 +49,8 @@ export abstract class BaseNode implements IPNode {
}
}

abstract get result(): unknown;

_result?: unknown;
_probed_pnodetype?: number;
_onDispose?: DisposeOp[];
Expand Down
Loading