rask-ui 0.6.0 → 0.7.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
@@ -10,6 +10,29 @@ A lightweight reactive component library that combines the simplicity of observa
10
10
  npm install rask-ui
11
11
  ```
12
12
 
13
+ ---
14
+
15
+ ## 📢 Open For Feedback
16
+
17
+ **RASK is feature-complete and ready for community feedback!**
18
+
19
+ The core implementation is finished, including:
20
+ - ✅ Inferno-based reconciler for powerful UI expression
21
+ - ✅ JSX transformation plugin (Inferno JSX + stateful components)
22
+ - ✅ Reactive primitives for simple state management
23
+
24
+ **Before the official release, we need your input:**
25
+ - Would a library like this be valuable for your projects?
26
+ - Is the API clear and intuitive?
27
+ - Is the documentation helpful and complete?
28
+ - Does the approach resonate with you?
29
+
30
+ **[Share your feedback by creating an issue →](https://github.com/christianalfoni/rask/issues/new)**
31
+
32
+ Your feedback will directly shape the final release. All perspectives welcome!
33
+
34
+ ---
35
+
13
36
  ## The Itch with Modern UI Frameworks
14
37
 
15
38
  Modern UI frameworks present developers with a fundamental tradeoff between state management and UI expression:
@@ -195,7 +218,7 @@ When `state.count` changes in Parent, only Child re-renders because it accesses
195
218
 
196
219
  ### One Rule To Accept
197
220
 
198
- **RASK has observable primitives**: Never destructure reactive objects (state, props, context values, async, query, mutation). Destructuring extracts plain values and breaks reactivity.
221
+ **RASK has observable primitives**: Never destructure reactive objects (state, props, context values, tasks). Destructuring extracts plain values and breaks reactivity.
199
222
 
200
223
  ```tsx
201
224
  // ❌ BAD - Destructuring breaks reactivity
@@ -241,9 +264,7 @@ Reactive objects are implemented using JavaScript Proxies. When you access a pro
241
264
  - `createState()` - Never destructure state objects
242
265
  - Props - Never destructure component props
243
266
  - `createContext().get()` - Never destructure context values
244
- - `createAsync()` - Never destructure async state
245
- - `createQuery()` - Never destructure query objects
246
- - `createMutation()` - Never destructure mutation objects
267
+ - `createTask()` - Never destructure task objects
247
268
  - `createView()` - Never destructure view objects
248
269
  - `createComputed()` - Never destructure computed objects
249
270
 
@@ -661,58 +682,38 @@ function Child() {
661
682
 
662
683
  ### Async Data Management
663
684
 
664
- #### `createAsync<T>(promise)`
685
+ #### `createTask<T, P>(task)`
665
686
 
666
- Creates reactive state for async operations with loading and error states.
687
+ Creates a low-level reactive primitive for managing async operations with loading, error, and result states. This is a generic primitive that gives you full control over async state management without prescribing patterns.
667
688
 
668
689
  ```tsx
669
- import { createAsync } from "rask-ui";
690
+ import { createTask } from "rask-ui";
670
691
 
692
+ // Simple task without parameters - auto-runs on creation
671
693
  function UserProfile() {
672
- const user = createAsync(fetch("/api/user").then((r) => r.json()));
694
+ const user = createTask(() => fetch("/api/user").then((r) => r.json()));
673
695
 
674
- return () => }
675
- if (user.isPending) {
676
- return <p>Loading...</p>
696
+ return () => {
697
+ if (user.isRunning) {
698
+ return <p>Loading...</p>;
677
699
  }
678
700
 
679
701
  if (user.error) {
680
- return <p>Error: {user.error}</p>
702
+ return <p>Error: {user.error}</p>;
681
703
  }
682
704
 
683
- return <p>Hello, {user.value.name}!</p>
705
+ return <p>Hello, {user.result.name}!</p>;
706
+ };
684
707
  }
685
- ```
686
-
687
- **Parameters:**
688
-
689
- - `promise: Promise<T>` - The promise to track
690
-
691
- **Returns:** Reactive object with:
692
-
693
- - `isPending: boolean` - True while promise is pending
694
- - `value: T | null` - Resolved value (null while pending or on error)
695
- - `error: string | null` - Error message (null while pending or on success)
696
-
697
- **States:**
698
-
699
- - `{ isPending: true, value: null, error: null }` - Loading
700
- - `{ isPending: false, value: T, error: null }` - Success
701
- - `{ isPending: false, value: null, error: string }` - Error
702
-
703
- ---
704
-
705
- #### `createQuery<T>(fetcher)`
706
-
707
- Creates a query with refetch capability and request cancellation.
708
-
709
- ```tsx
710
- import { createQuery } from "rask-ui";
711
708
 
709
+ // Task with parameters - control when to run
712
710
  function Posts() {
713
- const posts = createQuery(() => fetch("/api/posts").then((r) => r.json()));
711
+ const posts = createTask((page: number) =>
712
+ fetch(`/api/posts?page=${page}`).then((r) => r.json())
713
+ );
714
+
714
715
  const renderPosts = () => {
715
- if (posts.isPending) {
716
+ if (posts.isRunning) {
716
717
  return <p>Loading...</p>;
717
718
  }
718
719
 
@@ -720,61 +721,37 @@ function Posts() {
720
721
  return <p>Error: {posts.error}</p>;
721
722
  }
722
723
 
723
- return posts.data.map((post) => (
724
+ if (!posts.result) {
725
+ return <p>No posts loaded</p>;
726
+ }
727
+
728
+ return posts.result.map((post) => (
724
729
  <article key={post.id}>{post.title}</article>
725
730
  ));
726
731
  };
727
732
 
728
733
  return () => (
729
734
  <div>
730
- <button onClick={() => posts.fetch()}>Refresh</button>
731
- <button onClick={() => posts.fetch(true)}>Force Refresh</button>
735
+ <button onClick={() => posts.run(1)}>Load Page 1</button>
736
+ <button onClick={() => posts.rerun(1)}>Reload Page 1</button>
732
737
  {renderPosts()}
733
738
  </div>
734
739
  );
735
740
  }
736
- ```
737
-
738
- **Parameters:**
739
-
740
- - `fetcher: () => Promise<T>` - Function that returns a promise
741
-
742
- **Returns:** Query object with:
743
-
744
- - `isPending: boolean` - True while fetching
745
- - `data: T | null` - Fetched data
746
- - `error: string | null` - Error message
747
- - `fetch(force?: boolean)` - Refetch data
748
- - `force: false` (default) - Keep existing data while refetching
749
- - `force: true` - Clear data before refetching
750
-
751
- **Features:**
752
-
753
- - Automatic request cancellation on refetch
754
- - Keeps old data by default during refetch
755
- - Automatically fetches on creation
756
-
757
- ---
758
-
759
- #### `createMutation<T>(mutator)`
760
-
761
- Creates a mutation for data updates with pending and error states.
762
-
763
- ```tsx
764
- import { createMutation } from "rask-ui";
765
741
 
742
+ // Mutation-style usage
766
743
  function CreatePost() {
767
744
  const state = createState({ title: "", body: "" });
768
745
 
769
- const create = createMutation((data) =>
746
+ const create = createTask((data: { title: string; body: string }) =>
770
747
  fetch("/api/posts", {
771
748
  method: "POST",
772
749
  body: JSON.stringify(data),
773
- }))
750
+ }).then((r) => r.json())
774
751
  );
775
752
 
776
753
  const handleSubmit = () => {
777
- create.mutate({ title: state.title, body: state.body });
754
+ create.run({ title: state.title, body: state.body });
778
755
  };
779
756
 
780
757
  return () => (
@@ -787,8 +764,8 @@ function CreatePost() {
787
764
  value={state.body}
788
765
  onInput={(e) => (state.body = e.target.value)}
789
766
  />
790
- <button disabled={create.isPending}>
791
- {create.isPending ? "Creating..." : "Create"}
767
+ <button disabled={create.isRunning}>
768
+ {create.isRunning ? "Creating..." : "Create"}
792
769
  </button>
793
770
  {create.error && <p>Error: {create.error}</p>}
794
771
  </form>
@@ -796,22 +773,72 @@ function CreatePost() {
796
773
  }
797
774
  ```
798
775
 
799
- **Parameters:**
776
+ **Type Signatures:**
777
+
778
+ ```tsx
779
+ // Task without parameters - auto-runs on creation
780
+ createTask<T>(task: () => Promise<T>): Task<T, never> & {
781
+ run(): Promise<T>;
782
+ rerun(): Promise<T>;
783
+ }
784
+
785
+ // Task with parameters - manual control
786
+ createTask<T, P>(task: (params: P) => Promise<T>): Task<T, P> & {
787
+ run(params: P): Promise<T>;
788
+ rerun(params: P): Promise<T>;
789
+ }
790
+ ```
791
+
792
+ **Returns:** Task object with reactive state and methods:
800
793
 
801
- - `mutator: (params: T) => Promise<T>` - Function that performs the mutation
794
+ **State Properties:**
795
+ - `isRunning: boolean` - True while task is executing
796
+ - `result: T | null` - Result of successful execution (null if not yet run, running, or error)
797
+ - `error: string | null` - Error message from failed execution (null if successful or running)
798
+ - `params: P | null` - Current parameters while running (null when idle)
802
799
 
803
- **Returns:** Mutation object with:
800
+ **Methods:**
801
+ - `run(params?: P): Promise<T>` - Execute the task, clearing previous result
802
+ - `rerun(params?: P): Promise<T>` - Re-execute the task, keeping previous result until new one arrives
804
803
 
805
- - `isPending: boolean` - True while mutation is in progress
806
- - `params: T | null` - Current mutation parameters
807
- - `error: string | null` - Error message
808
- - `mutate(params: T)` - Execute the mutation
804
+ **State Transitions:**
805
+
806
+ Initial state (no params):
807
+ ```tsx
808
+ { isRunning: false, params: null, result: null, error: null }
809
+ ```
810
+
811
+ While running:
812
+ ```tsx
813
+ { isRunning: true, result: T | null, params: P, error: null }
814
+ ```
815
+
816
+ Success:
817
+ ```tsx
818
+ { isRunning: false, params: null, result: T, error: null }
819
+ ```
820
+
821
+ Error:
822
+ ```tsx
823
+ { isRunning: false, params: null, result: null, error: string }
824
+ ```
809
825
 
810
826
  **Features:**
811
827
 
812
- - Automatic request cancellation if mutation called again
813
- - Tracks mutation parameters
814
- - Resets state after successful completion
828
+ - **Automatic cancellation** - Previous executions are cancelled when a new one starts
829
+ - **Flexible control** - Use `run()` to clear old data or `rerun()` to keep it during loading
830
+ - **Type-safe** - Full TypeScript inference for parameters and results
831
+ - **Auto-run support** - Tasks without parameters run automatically on creation
832
+ - **Generic primitive** - Build your own patterns on top (queries, mutations, etc.)
833
+
834
+ **Usage Patterns:**
835
+
836
+ Use `createTask` as a building block for various async patterns:
837
+ - **Queries**: Tasks that fetch data and can be refetched
838
+ - **Mutations**: Tasks that modify server state
839
+ - **Polling**: Tasks that run periodically
840
+ - **Debounced searches**: Tasks that run based on user input
841
+ - **File uploads**: Tasks that track upload progress
815
842
 
816
843
  ---
817
844
 
@@ -0,0 +1,30 @@
1
+ export type Task<T, P> = {
2
+ isRunning: false;
3
+ params: null;
4
+ result: null;
5
+ error: null;
6
+ } | {
7
+ isRunning: true;
8
+ result: T | null;
9
+ params: P;
10
+ error: null;
11
+ } | {
12
+ isRunning: false;
13
+ params: null;
14
+ result: T;
15
+ error: null;
16
+ } | {
17
+ isRunning: false;
18
+ params: null;
19
+ result: null;
20
+ error: string;
21
+ };
22
+ export declare function createTask<T>(task: () => Promise<T>): Task<T, never> & {
23
+ run(): Promise<T>;
24
+ rerun(): Promise<T>;
25
+ };
26
+ export declare function createTask<T, P>(task: (params: P) => Promise<T>): Task<T, P> & {
27
+ run(params: P): Promise<T>;
28
+ rerun(params: P): Promise<T>;
29
+ };
30
+ //# sourceMappingURL=createTask.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createTask.d.ts","sourceRoot":"","sources":["../src/createTask.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,IAAI,CAAC,CAAC,EAAE,CAAC,IACjB;IACE,SAAS,EAAE,KAAK,CAAC;IACjB,MAAM,EAAE,IAAI,CAAC;IACb,MAAM,EAAE,IAAI,CAAC;IACb,KAAK,EAAE,IAAI,CAAC;CACb,GACD;IACE,SAAS,EAAE,IAAI,CAAC;IAChB,MAAM,EAAE,CAAC,GAAG,IAAI,CAAC;IACjB,MAAM,EAAE,CAAC,CAAC;IACV,KAAK,EAAE,IAAI,CAAC;CACb,GACD;IACE,SAAS,EAAE,KAAK,CAAC;IACjB,MAAM,EAAE,IAAI,CAAC;IACb,MAAM,EAAE,CAAC,CAAC;IACV,KAAK,EAAE,IAAI,CAAC;CACb,GACD;IACE,SAAS,EAAE,KAAK,CAAC;IACjB,MAAM,EAAE,IAAI,CAAC;IACb,MAAM,EAAE,IAAI,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEN,wBAAgB,UAAU,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,EAAE,KAAK,CAAC,GAAG;IACtE,GAAG,IAAI,OAAO,CAAC,CAAC,CAAC,CAAC;IAClB,KAAK,IAAI,OAAO,CAAC,CAAC,CAAC,CAAC;CACrB,CAAC;AACF,wBAAgB,UAAU,CAAC,CAAC,EAAE,CAAC,EAC7B,IAAI,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,GAC9B,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG;IACd,GAAG,CAAC,MAAM,EAAE,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IAC3B,KAAK,CAAC,MAAM,EAAE,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;CAC9B,CAAC"}
@@ -0,0 +1,77 @@
1
+ import { createState } from "./createState";
2
+ export function createTask(task) {
3
+ const state = createState({
4
+ isRunning: false,
5
+ result: null,
6
+ error: null,
7
+ params: null,
8
+ });
9
+ const assign = (newState) => {
10
+ Object.assign(state, newState);
11
+ };
12
+ let currentAbortController;
13
+ const fetch = (params) => {
14
+ currentAbortController?.abort();
15
+ const abortController = (currentAbortController = new AbortController());
16
+ const promise = task(params);
17
+ promise
18
+ .then((result) => {
19
+ if (abortController.signal.aborted) {
20
+ return;
21
+ }
22
+ assign({
23
+ isRunning: false,
24
+ result,
25
+ error: null,
26
+ params: null,
27
+ });
28
+ })
29
+ .catch((error) => {
30
+ if (abortController.signal.aborted) {
31
+ return;
32
+ }
33
+ assign({
34
+ isRunning: false,
35
+ result: null,
36
+ error: String(error),
37
+ params: null,
38
+ });
39
+ });
40
+ return promise;
41
+ };
42
+ fetch();
43
+ return {
44
+ get isRunning() {
45
+ return state.isRunning;
46
+ },
47
+ get result() {
48
+ return state.result;
49
+ },
50
+ get error() {
51
+ return state.error;
52
+ },
53
+ get params() {
54
+ return state.params;
55
+ },
56
+ run(params) {
57
+ const promise = fetch(params);
58
+ assign({
59
+ isRunning: true,
60
+ result: null,
61
+ error: null,
62
+ params: (params || null),
63
+ });
64
+ return promise;
65
+ },
66
+ rerun(params) {
67
+ const promise = fetch(params);
68
+ assign({
69
+ isRunning: true,
70
+ result: state.result,
71
+ error: null,
72
+ params: (params || null),
73
+ });
74
+ return promise;
75
+ },
76
+ };
77
+ }
package/dist/index.d.ts CHANGED
@@ -2,10 +2,8 @@ export { render } from "./render";
2
2
  export { createCleanup, createMountEffect, RaskComponent } from "./component";
3
3
  export { createContext } from "./createContext";
4
4
  export { createState } from "./createState";
5
- export { createAsync } from "./createAsync";
5
+ export { createTask } from "./createTask";
6
6
  export { ErrorBoundary } from "./error";
7
- export { createQuery } from "./createQuery";
8
- export { createMutation } from "./createMutation";
9
7
  export { createRef } from "inferno";
10
8
  export { createView } from "./createView";
11
9
  export { createEffect } from "./createEffect";
@@ -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,EAAE,aAAa,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC9E,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACxC,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAGpC,OAAO,EACL,WAAW,EACX,oBAAoB,EACpB,cAAc,EACd,eAAe,EACf,cAAc,GACf,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,EAAE,aAAa,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC9E,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACxC,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAGpC,OAAO,EACL,WAAW,EACX,oBAAoB,EACpB,cAAc,EACd,eAAe,EACf,cAAc,GACf,MAAM,SAAS,CAAC"}
package/dist/index.js CHANGED
@@ -2,10 +2,8 @@ export { render } from "./render";
2
2
  export { createCleanup, createMountEffect, RaskComponent } from "./component";
3
3
  export { createContext } from "./createContext";
4
4
  export { createState } from "./createState";
5
- export { createAsync } from "./createAsync";
5
+ export { createTask } from "./createTask";
6
6
  export { ErrorBoundary } from "./error";
7
- export { createQuery } from "./createQuery";
8
- export { createMutation } from "./createMutation";
9
7
  export { createRef } from "inferno";
10
8
  export { createView } from "./createView";
11
9
  export { createEffect } from "./createEffect";
package/logo.png ADDED
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rask-ui",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -25,10 +25,11 @@
25
25
  "files": [
26
26
  "dist",
27
27
  "swc-plugin",
28
+ "logo.png",
28
29
  "README.md"
29
30
  ],
30
31
  "scripts": {
31
- "build": "npm run build:plugin && tsc && cp ../../README.md .",
32
+ "build": "npm run build:plugin && tsc && cp ../../README.md . && cp ../../logo.png .",
32
33
  "build:plugin": "cd swc-plugin && cargo build --release --target wasm32-wasip1",
33
34
  "dev": "tsc --watch",
34
35
  "test": "vitest",
@@ -1 +1 @@
1
- /Users/christianalfoni/Development/snabbdom-components/packages/core/swc-plugin/target/wasm32-wasip1/release/libswc_plugin_rask_component.rlib: /Users/christianalfoni/Development/snabbdom-components/packages/core/swc-plugin/src/lib.rs
1
+ /Users/christianalfoni/Development/rask-ui/packages/core/swc-plugin/target/wasm32-wasip1/release/libswc_plugin_rask_component.rlib: /Users/christianalfoni/Development/rask-ui/packages/core/swc-plugin/src/lib.rs
@@ -1 +1 @@
1
- /Users/christianalfoni/Development/snabbdom-components/packages/core/swc-plugin/target/wasm32-wasip1/release/swc_plugin_rask_component.wasm: /Users/christianalfoni/Development/snabbdom-components/packages/core/swc-plugin/src/lib.rs
1
+ /Users/christianalfoni/Development/rask-ui/packages/core/swc-plugin/target/wasm32-wasip1/release/swc_plugin_rask_component.wasm: /Users/christianalfoni/Development/rask-ui/packages/core/swc-plugin/src/lib.rs