rask-ui 0.18.3 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -48,7 +48,10 @@ RASK provides a set of reactive hooks for building interactive UIs. These hooks
48
48
  - **`useState`** - Create reactive state objects
49
49
  - **`useEffect`** - Run side effects when dependencies change
50
50
  - **`useDerived`** - Derive values from state with automatic caching
51
- - **`useAsync`** - Manage async operations (fetch, mutations, polling, etc.)
51
+ - **`useAsync`** - Fetch async values with automatic observation and cancellation
52
+ - **`useAction`** - Perform async operations with queuing and retry support
53
+ - **`useSuspend`** - Suspend until multiple async values resolve
54
+ - **`useCatchError`** - Catch and handle component errors (hook-based error boundary)
52
55
  - **`useRouter`** - Type-safe client-side routing
53
56
  - **`createContext`** / **`useContext`** - Share data through the component tree
54
57
  - **`useView`** - Compose state and methods into reusable objects
@@ -1 +1 @@
1
- {"version":3,"file":"component.d.ts","sourceRoot":"","sources":["../src/component.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,EACL,SAAS,EACT,KAAK,EACL,WAAW,EACZ,MAAM,SAAS,CAAC;AAEjB,OAAO,EAAsB,QAAQ,EAAU,MAAM,eAAe,CAAC;AAIrE,MAAM,MAAM,8BAA8B,CAAC,CAAC,SAAS,KAAK,CAAC,GAAG,CAAC,IAC3D,CAAC,MAAM,KAAK,CAAC,GACb,CAAC,CAAC,KAAK,EAAE,CAAC,KAAK,KAAK,CAAC,CAAC;AAE1B,qBAAa,sBAAuB,SAAQ,SAAS;IAC3C,QAAQ,EAAE,8BAA8B,CAAC,GAAG,CAAC,CAAC;IACtD,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,aAAa,CAAS;IAC9B,QAAQ,WAML;IACH,OAAO,CAAC,aAAa,CAAc;IAEnC,qBAAqB,IAAI,OAAO;IAMhC,kBAAkB,IAAI,IAAI;IAG1B,yBAAyB,CAAC,SAAS,EAAE,GAAG,GAAG,IAAI;IAe/C,MAAM;CAOP;AAID,wBAAgB,mBAAmB,2CAElC;AAED,wBAAgB,cAAc,CAAC,EAAE,EAAE,MAAM,IAAI,QAM5C;AAED,wBAAgB,UAAU,CAAC,EAAE,EAAE,MAAM,IAAI,QAMxC;AAED,MAAM,MAAM,6BAA6B,CAAC,CAAC,SAAS,KAAK,CAAC,GAAG,CAAC,IAC1D,CAAC,MAAM,MAAM,KAAK,CAAC,GACnB,CAAC,CAAC,KAAK,EAAE,CAAC,KAAK,MAAM,KAAK,CAAC,CAAC;AAEhC,qBAAa,qBAAqB,CAAC,CAAC,SAAS,KAAK,CAAC,GAAG,CAAC,CAAE,SAAQ,SAAS,CAAC,CAAC,CAAC;IACnE,KAAK,EAAE,6BAA6B,CAAC,CAAC,CAAC,CAAC;IAChD,OAAO,CAAC,QAAQ,CAAC,CAAc;IAC/B,OAAO,CAAC,aAAa,CAAC,CAAa;IAwBnC,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,aAAa,CAAS;IAE9B,QAAQ,WAOL;IAEH,WAAW,UAAS;IACpB,OAAO,EAAE,KAAK,CAAC;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,GAAG,EAAE,MAAM,IAAI,CAAA;KAAE,CAAC,CAAM;IAC3D,QAAQ,gBAAa;IACrB,eAAe;IAUf,QAAQ,EAAE,KAAK,CAAC,MAAM,IAAI,CAAC,CAAM;IACjC,UAAU,EAAE,KAAK,CAAC,MAAM,IAAI,CAAC,CAAM;IAEnC,iBAAiB,IAAI,IAAI;IAGzB,oBAAoB,IAAI,IAAI;IAI5B,yBAAyB,CACvB,SAAS,EAAE,QAAQ,CAAC;QAAE,QAAQ,CAAC,EAAE,WAAW,CAAA;KAAE,GAAG,CAAC,CAAC,GAClD,IAAI;IAcP,qBAAqB,IAAI,OAAO;IAMhC,MAAM;CAyCP;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,EAAE,GAAG,CAAC,EAAE,MAAM,SAO9D"}
1
+ {"version":3,"file":"component.d.ts","sourceRoot":"","sources":["../src/component.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,EACL,SAAS,EACT,KAAK,EACL,WAAW,EACZ,MAAM,SAAS,CAAC;AAEjB,OAAO,EAAsB,QAAQ,EAAU,MAAM,eAAe,CAAC;AAIrE,MAAM,MAAM,8BAA8B,CAAC,CAAC,SAAS,KAAK,CAAC,GAAG,CAAC,IAC3D,CAAC,MAAM,KAAK,CAAC,GACb,CAAC,CAAC,KAAK,EAAE,CAAC,KAAK,KAAK,CAAC,CAAC;AAE1B,qBAAa,sBAAuB,SAAQ,SAAS;IAC3C,QAAQ,EAAE,8BAA8B,CAAC,GAAG,CAAC,CAAC;IACtD,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,aAAa,CAAS;IAC9B,QAAQ,WAML;IACH,OAAO,CAAC,aAAa,CAAc;IAEnC,qBAAqB,IAAI,OAAO;IAMhC,kBAAkB,IAAI,IAAI;IAG1B,yBAAyB,CAAC,SAAS,EAAE,GAAG,GAAG,IAAI;IAe/C,MAAM;CAOP;AAID,wBAAgB,mBAAmB,2CAElC;AAED,wBAAgB,cAAc,CAAC,EAAE,EAAE,MAAM,IAAI,QAM5C;AAED,wBAAgB,UAAU,CAAC,EAAE,EAAE,MAAM,IAAI,QAMxC;AAED,MAAM,MAAM,6BAA6B,CAAC,CAAC,SAAS,KAAK,CAAC,GAAG,CAAC,IAC1D,CAAC,MAAM,MAAM,KAAK,CAAC,GACnB,CAAC,CAAC,KAAK,EAAE,CAAC,KAAK,MAAM,KAAK,CAAC,CAAC;AAEhC,qBAAa,qBAAqB,CAAC,CAAC,SAAS,KAAK,CAAC,GAAG,CAAC,CAAE,SAAQ,SAAS,CAAC,CAAC,CAAC;IACnE,KAAK,EAAE,6BAA6B,CAAC,CAAC,CAAC,CAAC;IAChD,OAAO,CAAC,QAAQ,CAAC,CAAc;IAC/B,OAAO,CAAC,aAAa,CAAC,CAAa;IAwBnC,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,aAAa,CAAS;IAE9B,QAAQ,WAOL;IAEH,WAAW,UAAS;IACpB,OAAO,EAAE,KAAK,CAAC;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,GAAG,EAAE,MAAM,IAAI,CAAA;KAAE,CAAC,CAAM;IAC3D,QAAQ,gBAAa;IACrB,eAAe;IAUf,QAAQ,EAAE,KAAK,CAAC,MAAM,IAAI,CAAC,CAAM;IACjC,UAAU,EAAE,KAAK,CAAC,MAAM,IAAI,CAAC,CAAM;IAEnC,iBAAiB,IAAI,IAAI;IAGzB,oBAAoB,IAAI,IAAI;IAI5B,yBAAyB,CACvB,SAAS,EAAE,QAAQ,CAAC;QAAE,QAAQ,CAAC,EAAE,WAAW,CAAA;KAAE,GAAG,CAAC,CAAC,GAClD,IAAI;IAcP,qBAAqB,IAAI,OAAO;IAMhC,MAAM;CA6CP;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,EAAE,GAAG,CAAC,EAAE,MAAM,SAO9D"}
package/dist/component.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { createComponentVNode, Component, } from "inferno";
2
2
  import { getCurrentObserver, Observer, Signal } from "./observation";
3
3
  import { syncBatch } from "./batch";
4
+ import { CatchErrorContext } from "./useCatchError";
4
5
  export class RaskStatelessComponent extends Component {
5
6
  isNotified = false;
6
7
  isReconciling = false;
@@ -157,10 +158,12 @@ export class RaskStatefulComponent extends Component {
157
158
  this.isRendering = false;
158
159
  }
159
160
  catch (error) {
160
- if (typeof this.context.notifyError !== "function") {
161
+ const notifyError = this.contexts.get(CatchErrorContext) ||
162
+ this.context.getContext?.(CatchErrorContext);
163
+ if (typeof notifyError !== "function") {
161
164
  throw error;
162
165
  }
163
- this.context.notifyError(error);
166
+ notifyError(error);
164
167
  }
165
168
  finally {
166
169
  stopObserving();
package/dist/error.d.ts CHANGED
@@ -1,16 +1,5 @@
1
- import { Component, VNode } from "inferno";
2
- export declare class ErrorBoundary extends Component<{
3
- children: any;
4
- error: (error: unknown) => VNode;
5
- }, {
1
+ export declare const CatchErrorContext: import("./createContext").Context<(error: unknown) => void>;
2
+ export declare function useCatchError(): {
6
3
  error: unknown;
7
- }> {
8
- getChildContext(): {
9
- notifyError: (error: unknown) => void;
10
- };
11
- state: {
12
- error: null;
13
- };
14
- render(): any;
15
- }
4
+ };
16
5
  //# sourceMappingURL=error.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"error.d.ts","sourceRoot":"","sources":["../src/error.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAE3C,qBAAa,aAAc,SAAQ,SAAS,CAC1C;IAAE,QAAQ,EAAE,GAAG,CAAC;IAAC,KAAK,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,KAAK,CAAA;CAAE,EACnD;IAAE,KAAK,EAAE,OAAO,CAAA;CAAE,CACnB;IACC,eAAe;6BAEU,OAAO;;IAKhC,KAAK;;MAAmB;IAExB,MAAM;CAOP"}
1
+ {"version":3,"file":"error.d.ts","sourceRoot":"","sources":["../src/error.tsx"],"names":[],"mappings":"AAIA,eAAO,MAAM,iBAAiB,4CAAyB,OAAO,KAAK,IAAI,CAAG,CAAC;AAE3E,wBAAgB,aAAa;WAQK,OAAO;EAOxC"}
package/dist/error.js CHANGED
@@ -1,17 +1,16 @@
1
- import { Component } from "inferno";
2
- export class ErrorBoundary extends Component {
3
- getChildContext() {
4
- return {
5
- notifyError: (error) => {
6
- this.setState({ error });
7
- },
8
- };
9
- }
10
- state = { error: null };
11
- render() {
12
- if (this.state.error) {
13
- return this.props.error(this.state.error);
14
- }
15
- return this.props.children;
1
+ import { useState } from "./useState";
2
+ import { createContext, useInjectContext } from "./createContext";
3
+ import { getCurrentComponent } from "./component";
4
+ export const CatchErrorContext = createContext();
5
+ export function useCatchError() {
6
+ const currentComponent = getCurrentComponent();
7
+ if (!currentComponent || currentComponent.isRendering) {
8
+ throw new Error("Only use the useCatchError hook in setup");
16
9
  }
10
+ const inject = useInjectContext(CatchErrorContext);
11
+ const state = useState({
12
+ error: null,
13
+ });
14
+ inject((error) => (state.error = error));
15
+ return state;
17
16
  }
package/dist/index.d.ts CHANGED
@@ -3,7 +3,9 @@ export { useCleanup, useMountEffect, RaskStatefulComponent, RaskStatelessCompone
3
3
  export { createContext, useContext, useInjectContext } from "./createContext";
4
4
  export { useState, assignState } from "./useState";
5
5
  export { useAsync, Async } from "./useAsync";
6
- export { ErrorBoundary } from "./error";
6
+ export { useAction, Action } from "./useAction";
7
+ export { useSuspend } from "./useSuspend";
8
+ export { useCatchError } from "./useCatchError";
7
9
  export { useRef } from "./useRef";
8
10
  export { useView } from "./useView";
9
11
  export { useEffect } from "./useEffect";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,OAAO,EACL,UAAU,EACV,cAAc,EACd,qBAAqB,EACrB,sBAAsB,GACvB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAC9E,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACnD,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACxC,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACnD,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAGhD,OAAO,EACL,WAAW,EACX,oBAAoB,EACpB,cAAc,EACd,eAAe,EACf,cAAc,EACd,SAAS,GACV,MAAM,SAAS,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,OAAO,EACL,UAAU,EACV,cAAc,EACd,qBAAqB,EACrB,sBAAsB,GACvB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAC9E,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACnD,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAC7C,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACnD,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAGhD,OAAO,EACL,WAAW,EACX,oBAAoB,EACpB,cAAc,EACd,eAAe,EACf,cAAc,EACd,SAAS,GACV,MAAM,SAAS,CAAC"}
package/dist/index.js CHANGED
@@ -3,7 +3,9 @@ export { useCleanup, useMountEffect, RaskStatefulComponent, RaskStatelessCompone
3
3
  export { createContext, useContext, useInjectContext } from "./createContext";
4
4
  export { useState, assignState } from "./useState";
5
5
  export { useAsync } from "./useAsync";
6
- export { ErrorBoundary } from "./error";
6
+ export { useAction } from "./useAction";
7
+ export { useSuspend } from "./useSuspend";
8
+ export { useCatchError } from "./useCatchError";
7
9
  export { useRef } from "./useRef";
8
10
  export { useView } from "./useView";
9
11
  export { useEffect } from "./useEffect";
@@ -0,0 +1,18 @@
1
+ export type QueuedAction<P> = {
2
+ params: P;
3
+ error: Error | null;
4
+ retry(): void;
5
+ cancel(): void;
6
+ };
7
+ export type ActionState<P> = {
8
+ isPending: boolean;
9
+ queue: QueuedAction<P>[];
10
+ };
11
+ export type Action<P = null> = [
12
+ ActionState<P>,
13
+ [
14
+ P
15
+ ] extends [null] ? () => void : (params: P) => void
16
+ ];
17
+ export declare function useAction<P = null>(fn: [P] extends [null] ? () => Promise<void> : (params: P) => Promise<void>): Action<P>;
18
+ //# sourceMappingURL=useAction.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useAction.d.ts","sourceRoot":"","sources":["../src/useAction.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,YAAY,CAAC,CAAC,IAAI;IAC5B,MAAM,EAAE,CAAC,CAAC;IACV,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,KAAK,IAAI,IAAI,CAAC;IACd,MAAM,IAAI,IAAI,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,WAAW,CAAC,CAAC,IAAI;IAC3B,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,EAAE,YAAY,CAAC,CAAC,CAAC,EAAE,CAAC;CAC1B,CAAC;AAEF,MAAM,MAAM,MAAM,CAAC,CAAC,GAAG,IAAI,IAAI;IAC7B,WAAW,CAAC,CAAC,CAAC;IACd;QAAC,CAAC;KAAC,SAAS,CAAC,IAAI,CAAC,GAAG,MAAM,IAAI,GAAG,CAAC,MAAM,EAAE,CAAC,KAAK,IAAI;CACtD,CAAC;AAEF,wBAAgB,SAAS,CAAC,CAAC,GAAG,IAAI,EAChC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,GAC1E,MAAM,CAAC,CAAC,CAAC,CAkDX"}
@@ -0,0 +1,43 @@
1
+ import { useState } from "./useState";
2
+ export function useAction(fn) {
3
+ const state = useState({
4
+ isPending: false,
5
+ queue: [],
6
+ });
7
+ const processQueue = () => {
8
+ const next = state.queue[0];
9
+ if (!next) {
10
+ state.isPending = false;
11
+ return;
12
+ }
13
+ state.isPending = true;
14
+ fn(next.params)
15
+ .then(() => {
16
+ state.queue.shift();
17
+ processQueue();
18
+ })
19
+ .catch((error) => {
20
+ next.error = error;
21
+ });
22
+ };
23
+ const run = (params) => {
24
+ params = (params || null);
25
+ let actionProxy;
26
+ const index = state.queue.push({
27
+ params,
28
+ error: null,
29
+ retry() {
30
+ processQueue();
31
+ },
32
+ cancel() {
33
+ state.queue.splice(state.queue.indexOf(actionProxy), 1);
34
+ processQueue();
35
+ },
36
+ }) - 1;
37
+ actionProxy = state.queue[index];
38
+ if (index === 0) {
39
+ processQueue();
40
+ }
41
+ };
42
+ return [state, run];
43
+ }
@@ -1,27 +1,33 @@
1
- export type AsyncState<P, T, I = null> = {
2
- isPending: false;
3
- params: null;
4
- value: I;
5
- error: null;
1
+ export type AsyncState<T> = {
2
+ error: Error;
3
+ isLoading: true;
4
+ isRefreshing: false;
5
+ value: null;
6
+ } | {
7
+ error: Error;
8
+ isLoading: false;
9
+ isRefreshing: true;
10
+ value: T;
6
11
  } | {
7
- isPending: true;
8
- value: T | I;
9
- params: P;
10
12
  error: null;
13
+ isLoading: true;
14
+ isRefreshing: false;
15
+ value: null;
11
16
  } | {
12
- isPending: false;
13
- params: null;
14
- value: T;
15
17
  error: null;
18
+ isLoading: false;
19
+ isRefreshing: true;
20
+ value: T;
16
21
  } | {
17
- isPending: false;
18
- params: null;
19
- value: T | I;
20
- error: string;
22
+ error: null;
23
+ isLoading: false;
24
+ isRefreshing: false;
25
+ value: T;
21
26
  };
22
- export type Async<A, B = never, I = null> = [B] extends [never] ? [AsyncState<null, A, I>, () => void] : [AsyncState<A, B, I>, (params: A) => void];
23
- export declare function useAsync<T>(initialValue: T, fn: (params: undefined, signal: AbortSignal) => Promise<T>): Async<T, never, T>;
24
- export declare function useAsync<P, T>(initialValue: T, fn: (params: P, signal: AbortSignal) => Promise<T>): Async<P, T, T>;
25
- export declare function useAsync<T>(fn: (params: undefined, signal: AbortSignal) => Promise<T>): Async<T>;
26
- export declare function useAsync<P, T>(fn: (params: P, signal: AbortSignal) => Promise<T>): Async<P, T>;
27
+ export type Async<T extends NonNullable<any>> = [
28
+ AsyncState<T>,
29
+ () => Promise<void>
30
+ ];
31
+ export declare function isAsync(value: unknown): boolean;
32
+ export declare function useAsync<T extends NonNullable<any>>(fn: (signal?: AbortSignal) => Promise<T>): Async<T>;
27
33
  //# sourceMappingURL=useAsync.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"useAsync.d.ts","sourceRoot":"","sources":["../src/useAsync.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,UAAU,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,GAAG,IAAI,IACjC;IACE,SAAS,EAAE,KAAK,CAAC;IACjB,MAAM,EAAE,IAAI,CAAC;IACb,KAAK,EAAE,CAAC,CAAC;IACT,KAAK,EAAE,IAAI,CAAC;CACb,GACD;IACE,SAAS,EAAE,IAAI,CAAC;IAChB,KAAK,EAAE,CAAC,GAAG,CAAC,CAAC;IACb,MAAM,EAAE,CAAC,CAAC;IACV,KAAK,EAAE,IAAI,CAAC;CACb,GACD;IACE,SAAS,EAAE,KAAK,CAAC;IACjB,MAAM,EAAE,IAAI,CAAC;IACb,KAAK,EAAE,CAAC,CAAC;IACT,KAAK,EAAE,IAAI,CAAC;CACb,GACD;IACE,SAAS,EAAE,KAAK,CAAC;IACjB,MAAM,EAAE,IAAI,CAAC;IACb,KAAK,EAAE,CAAC,GAAG,CAAC,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEN,MAAM,MAAM,KAAK,CAAC,CAAC,EAAE,CAAC,GAAG,KAAK,EAAE,CAAC,GAAG,IAAI,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,GAC3D,CAAC,UAAU,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,IAAI,CAAC,GACpC,CAAC,UAAU,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,IAAI,CAAC,CAAC;AAE/C,wBAAgB,QAAQ,CAAC,CAAC,EACxB,YAAY,EAAE,CAAC,EACf,EAAE,EAAE,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,WAAW,KAAK,OAAO,CAAC,CAAC,CAAC,GACzD,KAAK,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC;AACtB,wBAAgB,QAAQ,CAAC,CAAC,EAAE,CAAC,EAC3B,YAAY,EAAE,CAAC,EACf,EAAE,EAAE,CAAC,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,WAAW,KAAK,OAAO,CAAC,CAAC,CAAC,GACjD,KAAK,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;AAClB,wBAAgB,QAAQ,CAAC,CAAC,EACxB,EAAE,EAAE,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,WAAW,KAAK,OAAO,CAAC,CAAC,CAAC,GACzD,KAAK,CAAC,CAAC,CAAC,CAAC;AACZ,wBAAgB,QAAQ,CAAC,CAAC,EAAE,CAAC,EAC3B,EAAE,EAAE,CAAC,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,WAAW,KAAK,OAAO,CAAC,CAAC,CAAC,GACjD,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC"}
1
+ {"version":3,"file":"useAsync.d.ts","sourceRoot":"","sources":["../src/useAsync.ts"],"names":[],"mappings":"AAKA,MAAM,MAAM,UAAU,CAAC,CAAC,IACpB;IACE,KAAK,EAAE,KAAK,CAAC;IACb,SAAS,EAAE,IAAI,CAAC;IAChB,YAAY,EAAE,KAAK,CAAC;IACpB,KAAK,EAAE,IAAI,CAAC;CACb,GACD;IACE,KAAK,EAAE,KAAK,CAAC;IACb,SAAS,EAAE,KAAK,CAAC;IACjB,YAAY,EAAE,IAAI,CAAC;IACnB,KAAK,EAAE,CAAC,CAAC;CACV,GACD;IACE,KAAK,EAAE,IAAI,CAAC;IACZ,SAAS,EAAE,IAAI,CAAC;IAChB,YAAY,EAAE,KAAK,CAAC;IACpB,KAAK,EAAE,IAAI,CAAC;CACb,GACD;IACE,KAAK,EAAE,IAAI,CAAC;IACZ,SAAS,EAAE,KAAK,CAAC;IACjB,YAAY,EAAE,IAAI,CAAC;IACnB,KAAK,EAAE,CAAC,CAAC;CACV,GACD;IACE,KAAK,EAAE,IAAI,CAAC;IACZ,SAAS,EAAE,KAAK,CAAC;IACjB,YAAY,EAAE,KAAK,CAAC;IACpB,KAAK,EAAE,CAAC,CAAC;CACV,CAAC;AAEN,MAAM,MAAM,KAAK,CAAC,CAAC,SAAS,WAAW,CAAC,GAAG,CAAC,IAAI;IAC9C,UAAU,CAAC,CAAC,CAAC;IACb,MAAM,OAAO,CAAC,IAAI,CAAC;CACpB,CAAC;AAEF,wBAAgB,OAAO,CAAC,KAAK,EAAE,OAAO,WAWrC;AAED,wBAAgB,QAAQ,CAAC,CAAC,SAAS,WAAW,CAAC,GAAG,CAAC,EACjD,EAAE,EAAE,CAAC,MAAM,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,CAAC,CAAC,GA0InC,KAAK,CAAC,CAAC,CAAC,CACd"}
package/dist/useAsync.js CHANGED
@@ -1,72 +1,131 @@
1
+ import { syncBatch } from "./batch";
1
2
  import { useCleanup, getCurrentComponent } from "./component";
3
+ import { Observer } from "./observation";
2
4
  import { assignState, useState } from "./useState";
3
- export function useAsync(...args) {
5
+ export function isAsync(value) {
6
+ if (value === null || typeof value !== "object") {
7
+ return false;
8
+ }
9
+ return Boolean("isLoading" in value &&
10
+ "isRefreshing" in value &&
11
+ "error" in value &&
12
+ "value" in value);
13
+ }
14
+ export function useAsync(fn) {
4
15
  const currentComponent = getCurrentComponent();
5
16
  if (!currentComponent || currentComponent.isRendering) {
6
- throw new Error("Only use useTask in component setup");
17
+ throw new Error("Only use useAsync in component setup");
7
18
  }
8
- const value = args.length === 2 ? args[0] : null;
9
- const fn = args.length === 2 ? args[1] : args[0];
10
19
  const state = useState({
11
- isPending: false,
12
- value,
20
+ isLoading: true,
21
+ isRefreshing: false,
22
+ value: null,
13
23
  error: null,
14
- params: null,
15
24
  });
25
+ const refreshResolvers = [];
16
26
  let currentAbortController;
17
- const fetch = (params) => {
27
+ const refresh = () => {
18
28
  currentAbortController?.abort();
19
29
  const abortController = (currentAbortController = new AbortController());
20
- const promise = fn(params, abortController.signal);
30
+ const stopObserving = observer.observe();
31
+ const promise = fn(abortController.signal);
32
+ stopObserving();
21
33
  promise
22
34
  .then((result) => {
23
35
  if (abortController.signal.aborted) {
24
36
  return;
25
37
  }
26
- assignState(state, {
27
- isPending: false,
28
- value: result,
29
- error: null,
30
- params: null,
38
+ syncBatch(() => {
39
+ assignState(state, {
40
+ isLoading: false,
41
+ isRefreshing: false,
42
+ value: result,
43
+ error: null,
44
+ });
31
45
  });
46
+ refreshResolvers.forEach((resolver) => resolver.resolve());
47
+ refreshResolvers.length = 0;
32
48
  })
33
49
  .catch((error) => {
34
50
  if (abortController.signal.aborted) {
35
51
  return;
36
52
  }
37
- assignState(state, {
38
- isPending: false,
39
- value: state.value,
40
- error: String(error),
41
- params: null,
53
+ syncBatch(() => {
54
+ assignState(state, {
55
+ isLoading: state.isLoading,
56
+ isRefreshing: state.isRefreshing,
57
+ value: state.value,
58
+ error,
59
+ });
42
60
  });
61
+ refreshResolvers.forEach((resolver) => resolver.reject(error));
62
+ refreshResolvers.length = 0;
43
63
  });
44
64
  return promise;
45
65
  };
46
- useCleanup(() => currentAbortController?.abort());
66
+ const observer = new Observer(() => {
67
+ syncBatch(() => {
68
+ if (state.isLoading) {
69
+ refresh();
70
+ }
71
+ else if (state.error && state.value === null) {
72
+ assignState(state, {
73
+ isLoading: true,
74
+ isRefreshing: false,
75
+ value: state.value,
76
+ error: null,
77
+ });
78
+ refresh();
79
+ }
80
+ else {
81
+ assignState(state, {
82
+ isLoading: false,
83
+ isRefreshing: true,
84
+ value: state.value,
85
+ error: null,
86
+ });
87
+ refresh();
88
+ }
89
+ });
90
+ });
91
+ useCleanup(() => {
92
+ currentAbortController?.abort();
93
+ observer.dispose();
94
+ });
95
+ refresh();
47
96
  return [
48
- {
49
- get isPending() {
50
- return state.isPending;
51
- },
52
- get value() {
53
- return state.value;
54
- },
55
- get error() {
56
- return state.error;
57
- },
58
- get params() {
59
- return state.params;
60
- },
61
- },
62
- (params) => {
63
- fetch(params);
64
- assignState(state, {
65
- isPending: true,
66
- value: state.value,
67
- error: null,
68
- params: (params || null),
97
+ state,
98
+ async () => {
99
+ if (state.isLoading && !state.error) {
100
+ return;
101
+ }
102
+ syncBatch(() => {
103
+ if (state.error && state.value === null) {
104
+ assignState(state, {
105
+ isLoading: true,
106
+ isRefreshing: false,
107
+ value: state.value,
108
+ error: null,
109
+ });
110
+ }
111
+ else {
112
+ assignState(state, {
113
+ isLoading: false,
114
+ isRefreshing: true,
115
+ value: state.value,
116
+ error: null,
117
+ });
118
+ }
119
+ });
120
+ let resolve;
121
+ let reject;
122
+ const promise = new Promise((res, rej) => {
123
+ resolve = res;
124
+ reject = rej;
69
125
  });
126
+ refreshResolvers.push({ resolve, reject });
127
+ refresh();
128
+ return promise;
70
129
  },
71
130
  ];
72
131
  }
@@ -0,0 +1,5 @@
1
+ export declare const CatchErrorContext: import("./createContext").Context<(error: unknown) => void>;
2
+ export declare function useCatchError(): {
3
+ error: unknown;
4
+ };
5
+ //# sourceMappingURL=useCatchError.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useCatchError.d.ts","sourceRoot":"","sources":["../src/useCatchError.tsx"],"names":[],"mappings":"AAIA,eAAO,MAAM,iBAAiB,4CAAyB,OAAO,KAAK,IAAI,CAAG,CAAC;AAE3E,wBAAgB,aAAa;WAQK,OAAO;EAOxC"}
@@ -0,0 +1,16 @@
1
+ import { useState } from "./useState";
2
+ import { createContext, useInjectContext } from "./createContext";
3
+ import { getCurrentComponent } from "./component";
4
+ export const CatchErrorContext = createContext();
5
+ export function useCatchError() {
6
+ const currentComponent = getCurrentComponent();
7
+ if (!currentComponent || currentComponent.isRendering) {
8
+ throw new Error("Only use the useCatchError hook in setup");
9
+ }
10
+ const inject = useInjectContext(CatchErrorContext);
11
+ const state = useState({
12
+ error: null,
13
+ });
14
+ inject((error) => (state.error = error));
15
+ return state;
16
+ }
@@ -0,0 +1,40 @@
1
+ import { AsyncState } from "./useAsync";
2
+ type SuspendState<T extends Record<string, () => any>> = {
3
+ error: Error;
4
+ isLoading: true;
5
+ isRefreshing: false;
6
+ values: {
7
+ [K in keyof T]: ReturnType<T[K]> extends AsyncState<any> ? ReturnType<T[K]>["value"] : ReturnType<T[K]>;
8
+ };
9
+ } | {
10
+ error: Error;
11
+ isLoading: false;
12
+ isRefreshing: true;
13
+ values: {
14
+ [K in keyof T]: ReturnType<T[K]> extends AsyncState<any> ? NonNullable<ReturnType<T[K]>["value"]> : ReturnType<T[K]>;
15
+ };
16
+ } | {
17
+ error: null;
18
+ isLoading: true;
19
+ isRefreshing: false;
20
+ values: {
21
+ [K in keyof T]: ReturnType<T[K]> extends AsyncState<any> ? ReturnType<T[K]>["value"] : ReturnType<T[K]>;
22
+ };
23
+ } | {
24
+ error: null;
25
+ isLoading: false;
26
+ isRefreshing: true;
27
+ values: {
28
+ [K in keyof T]: ReturnType<T[K]> extends AsyncState<any> ? NonNullable<ReturnType<T[K]>["value"]> : ReturnType<T[K]>;
29
+ };
30
+ } | {
31
+ error: null;
32
+ isLoading: false;
33
+ isRefreshing: false;
34
+ values: {
35
+ [K in keyof T]: ReturnType<T[K]> extends AsyncState<any> ? NonNullable<ReturnType<T[K]>["value"]> : ReturnType<T[K]>;
36
+ };
37
+ };
38
+ export declare function useSuspend<T extends Record<string, any>>(asyncs: T): SuspendState<T>;
39
+ export {};
40
+ //# sourceMappingURL=useSuspend.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useSuspend.d.ts","sourceRoot":"","sources":["../src/useSuspend.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAW,MAAM,YAAY,CAAC;AAIjD,KAAK,YAAY,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,CAAC,IACjD;IACE,KAAK,EAAE,KAAK,CAAC;IACb,SAAS,EAAE,IAAI,CAAC;IAChB,YAAY,EAAE,KAAK,CAAC;IACpB,MAAM,EAAE;SACL,CAAC,IAAI,MAAM,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,UAAU,CAAC,GAAG,CAAC,GACpD,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,GACzB,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;KACrB,CAAC;CACH,GACD;IACE,KAAK,EAAE,KAAK,CAAC;IACb,SAAS,EAAE,KAAK,CAAC;IACjB,YAAY,EAAE,IAAI,CAAC;IACnB,MAAM,EAAE;SACL,CAAC,IAAI,MAAM,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,UAAU,CAAC,GAAG,CAAC,GACpD,WAAW,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,GACtC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;KACrB,CAAC;CACH,GACD;IACE,KAAK,EAAE,IAAI,CAAC;IACZ,SAAS,EAAE,IAAI,CAAC;IAChB,YAAY,EAAE,KAAK,CAAC;IACpB,MAAM,EAAE;SACL,CAAC,IAAI,MAAM,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,UAAU,CAAC,GAAG,CAAC,GACpD,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,GACzB,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;KACrB,CAAC;CACH,GACD;IACE,KAAK,EAAE,IAAI,CAAC;IACZ,SAAS,EAAE,KAAK,CAAC;IACjB,YAAY,EAAE,IAAI,CAAC;IACnB,MAAM,EAAE;SACL,CAAC,IAAI,MAAM,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,UAAU,CAAC,GAAG,CAAC,GACpD,WAAW,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,GACtC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;KACrB,CAAC;CACH,GACD;IACE,KAAK,EAAE,IAAI,CAAC;IACZ,SAAS,EAAE,KAAK,CAAC;IACjB,YAAY,EAAE,KAAK,CAAC;IACpB,MAAM,EAAE;SACL,CAAC,IAAI,MAAM,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,UAAU,CAAC,GAAG,CAAC,GACpD,WAAW,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,GACtC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;KACrB,CAAC;CACH,CAAC;AAEN,wBAAgB,UAAU,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,EAAE,CAAC,mBAsDlE"}
@@ -0,0 +1,48 @@
1
+ import { isAsync } from "./useAsync";
2
+ import { useEffect } from "./useEffect";
3
+ import { assignState, useState } from "./useState";
4
+ export function useSuspend(asyncs) {
5
+ const state = useState({
6
+ isLoading: true,
7
+ isRefreshing: false,
8
+ error: null,
9
+ values: Object.keys(asyncs).reduce((aggr, key) => {
10
+ let value = asyncs[key]();
11
+ if (isAsync(value)) {
12
+ value = value.value;
13
+ }
14
+ aggr[key] = value;
15
+ return aggr;
16
+ }, {}),
17
+ });
18
+ useEffect(() => {
19
+ let isLoading = false;
20
+ let isRefreshing = false;
21
+ let error;
22
+ const values = {};
23
+ for (const key in asyncs) {
24
+ let value = asyncs[key]();
25
+ if (!isAsync(value)) {
26
+ values[key] = value;
27
+ continue;
28
+ }
29
+ values[key] = value.value;
30
+ if (value.isLoading) {
31
+ isLoading = true;
32
+ }
33
+ else if (value.isRefreshing) {
34
+ isRefreshing = true;
35
+ }
36
+ if (value.error) {
37
+ error = value.error;
38
+ }
39
+ }
40
+ state.isLoading = isLoading;
41
+ state.isRefreshing = isLoading ? false : isRefreshing;
42
+ state.error = error || null;
43
+ if (!state.isLoading && !state.isRefreshing && !error) {
44
+ assignState(state.values, values);
45
+ }
46
+ });
47
+ return state;
48
+ }
@@ -0,0 +1,18 @@
1
+ import { AsyncState } from "./useAsync";
2
+ type SuspendAsyncState<T extends Record<string, AsyncState<any>>> = {
3
+ values: {
4
+ [K in keyof T]: T[K]["value"];
5
+ };
6
+ } & ({
7
+ isPending: true;
8
+ error: null;
9
+ } | {
10
+ isPending: false;
11
+ error: string;
12
+ } | {
13
+ isPending: false;
14
+ error: null;
15
+ });
16
+ export declare function useSuspendAsync<T extends Record<string, AsyncState<any>>>(asyncs: T): SuspendAsyncState<T>;
17
+ export {};
18
+ //# sourceMappingURL=useSuspendAsync.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useSuspendAsync.d.ts","sourceRoot":"","sources":["../src/useSuspendAsync.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAIxC,KAAK,iBAAiB,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,GAAG,CAAC,CAAC,IAAI;IAClE,MAAM,EAAE;SACL,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;KAC9B,CAAC;CACH,GAAG,CACA;IACE,SAAS,EAAE,IAAI,CAAC;IAChB,KAAK,EAAE,IAAI,CAAC;CACb,GACD;IACE,SAAS,EAAE,KAAK,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;CACf,GACD;IACE,SAAS,EAAE,KAAK,CAAC;IACjB,KAAK,EAAE,IAAI,CAAC;CACb,CACJ,CAAC;AAEF,wBAAgB,eAAe,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,GAAG,CAAC,CAAC,EACvE,MAAM,EAAE,CAAC,wBAyCV"}
@@ -0,0 +1,37 @@
1
+ import { useEffect } from "./useEffect";
2
+ import { useState } from "./useState";
3
+ export function useSuspendAsync(asyncs) {
4
+ const state = useState({
5
+ isPending: false,
6
+ error: null,
7
+ values: Object.keys(asyncs).reduce((aggr, key) => ({ ...aggr, [key]: asyncs[key].value }), {}),
8
+ });
9
+ useEffect(() => {
10
+ let isPending = false;
11
+ let error = "";
12
+ for (const key in asyncs) {
13
+ if (asyncs[key].error) {
14
+ error += asyncs[key].error + "\n";
15
+ continue;
16
+ }
17
+ if (asyncs[key].isPending) {
18
+ isPending = true;
19
+ continue;
20
+ }
21
+ }
22
+ if (error) {
23
+ state.isPending = false;
24
+ state.error = error;
25
+ return;
26
+ }
27
+ if (isPending) {
28
+ state.isPending = false;
29
+ state.error = null;
30
+ return;
31
+ }
32
+ for (const key in asyncs) {
33
+ state.values[key] = asyncs[key].value;
34
+ }
35
+ });
36
+ return state;
37
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rask-ui",
3
- "version": "0.18.3",
3
+ "version": "0.19.0",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",