bunja 0.0.1 → 0.0.3

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
@@ -15,8 +15,8 @@ Bunja is a library designed to address these weaknesses.\
15
15
  Each state defined with Bunja has a lifetime that begins when it is first depended on somewhere in the render tree and ends when all dependencies disappear.
16
16
 
17
17
  Therefore, when writing a state to manage a WebSocket,
18
- you only need to create a mount handler that establishes the WebSocket connection and an unmount handler that terminates the connection.\
19
- The library automatically tracks the actual usage period and calls the mount and unmount handlers as needed.
18
+ you only need to create a function that establishes the WebSocket connection and an dispose handler that terminates the connection.\
19
+ The library automatically tracks the actual usage period and calls the init and dispose as needed.
20
20
 
21
21
  ## So, do I no longer need jotai or other state management libraries?
22
22
 
@@ -33,27 +33,27 @@ You can use `bunja` to define a state with a finite lifetime and use the `useBun
33
33
  You can define a bunja using the `bunja` function. When you access the defined bunja with the `useBunja` hook, a bunja instance is created.\
34
34
  If all components in the render tree that refer to the bunja disappear, the bunja instance is automatically destroyed.
35
35
 
36
- You can also register functions to be called when the dependency on the bunja starts and ends by using the `mount` and `unmount` fields in the init function's return value.
36
+ If you want to clean up resources when the bunja's lifetime ends, you can use the `Symbol.dispose` field.
37
37
 
38
38
  ```ts
39
39
  const countBunja = bunja([], () => {
40
40
  const countAtom = atom(0);
41
41
  return {
42
- value: countAtom,
43
- mount: () => console.log("mounted"),
44
- unmount: () => console.log("unmounted"),
42
+ countAtom,
43
+ [Symbol.dispose]() {
44
+ console.log("disposed");
45
+ },
45
46
  };
46
47
  });
47
48
 
48
49
  function MyComponent() {
49
- const countAtom = useBunja(countBunja);
50
+ const { countAtom } = useBunja(countBunja);
50
51
  const [count, setCount] = useAtom(countAtom);
51
52
  // Your component logic here
52
53
  }
53
54
  ```
54
55
 
55
56
  This code snippet defines a bunja that creates a `countAtom`.\
56
- The `mount` function is logged when the bunja instance is first accessed,
57
- and the `unmount` function is logged when it is no longer referenced by any component in the render tree.
57
+ The `Symbol.dispose` method is used when the bunja instance is no longer referenced by any component in the render tree, allowing you to clean up resources appropriately.
58
58
 
59
59
  TODO: context
package/bunja.ts CHANGED
@@ -1,5 +1,9 @@
1
1
  import * as React from "react";
2
2
 
3
+ disposePolyfill: {
4
+ (Symbol as any).dispose ??= Symbol("Symbol.dispose");
5
+ }
6
+
3
7
  type Dep<T> = React.Context<T> | Bunja<T>;
4
8
 
5
9
  class Bunja<T> {
@@ -7,32 +11,24 @@ class Bunja<T> {
7
11
  public id: number,
8
12
  public deps: Dep<any>[],
9
13
  public contexts: React.Context<any>[],
10
- public init: (args: any[], dispose: () => void) => BunjaInstance<T>
14
+ public init: (...args: any[]) => T
11
15
  ) {}
12
16
  }
13
17
 
14
- interface BunjaInitResult<T> {
15
- value: T;
16
- mount?: () => void;
17
- unmount?: () => void;
18
- }
19
-
20
- export function bunja<T>(deps: [], init: () => BunjaInitResult<T>): Bunja<T>;
21
- export function bunja<T, U>(
22
- deps: [Dep<U>],
23
- init: (u: U) => BunjaInitResult<T>
24
- ): Bunja<T>;
18
+ const bunjas: Record<string, BunjaInstance<any>> = {};
19
+ export function bunja<T>(deps: [], init: () => T): Bunja<T>;
20
+ export function bunja<T, U>(deps: [Dep<U>], init: (u: U) => T): Bunja<T>;
25
21
  export function bunja<T, U, V>(
26
22
  deps: [Dep<U>, Dep<V>],
27
- init: (u: U, v: V) => BunjaInitResult<T>
23
+ init: (u: U, v: V) => T
28
24
  ): Bunja<T>;
29
25
  export function bunja<T, U, V, W>(
30
26
  deps: [Dep<U>, Dep<V>, Dep<W>],
31
- init: (u: U, v: V, w: W) => BunjaInitResult<T>
27
+ init: (u: U, v: V, w: W) => T
32
28
  ): Bunja<T>;
33
29
  export function bunja<T, const U extends any[]>(
34
30
  deps: { [K in keyof U]: Dep<U[K]> },
35
- init: (...args: U) => BunjaInitResult<T>
31
+ init: (...args: U) => T
36
32
  ): Bunja<T> {
37
33
  const contexts = deps.filter(
38
34
  (dep) => !(dep instanceof Bunja)
@@ -41,17 +37,12 @@ export function bunja<T, const U extends any[]>(
41
37
  const dedupedContexts = Array.from(
42
38
  new Set([...contexts, ...bunjas.flatMap((def) => def.contexts)])
43
39
  );
44
- return new Bunja(bunja.counter++, deps, dedupedContexts, (args, dispose) => {
45
- const noop = () => {};
46
- const { value, mount = noop, unmount = noop } = init(...(args as U));
47
- mount();
48
- return new BunjaInstance(() => (dispose(), unmount()), value);
49
- });
40
+ return new Bunja(bunja.counter++, deps, dedupedContexts, init);
50
41
  }
51
42
  bunja.counter = 0;
52
43
 
53
44
  export function useBunja<T>(bunja: Bunja<T>): T {
54
- const { id, deps, contexts } = bunja;
45
+ const { id, deps, contexts, init } = bunja;
55
46
  const rid = useRid();
56
47
  const tuples = contexts.map((c) => [c, React.useContext(c)] as const);
57
48
  const scopes = tuples.map(([context, value]) => getScope(context, value));
@@ -64,28 +55,21 @@ export function useBunja<T>(bunja: Bunja<T>): T {
64
55
  .map(({ id }) => id)
65
56
  .sort()
66
57
  .join(",")}`;
67
- const bunjaInstance = getBunjaInstance(bunja, biid, args);
58
+ const instance = (bunjas[biid] ??= new BunjaInstance(biid, init(...args)));
68
59
  React.useEffect(() => {
69
- bunjaInstance.reg(rid);
70
- return () => bunjaInstance.dereg(rid);
71
- }, [bunjaInstance]);
60
+ instance.reg(rid);
61
+ return () => instance.dereg(rid);
62
+ }, [instance]);
72
63
  React.useEffect(() => {
73
64
  scopes.forEach((scope) => scope.reg(rid));
74
65
  return () => scopes.forEach((scope) => scope.dereg(rid));
75
66
  }, [rid, ...scopes]);
76
- return bunjaInstance.value;
67
+ return instance.value;
77
68
  }
78
69
 
79
70
  const useRid = () => React.useState(() => useRid.counter++)[0];
80
71
  useRid.counter = 0;
81
72
 
82
- const bunjas: Record<string, BunjaInstance<any>> = {};
83
- function getBunjaInstance<T>(bunja: Bunja<T>, biid: string, args: any[]) {
84
- return (bunjas[biid] ??= bunja.init(args, () => {
85
- delete bunjas[biid];
86
- }));
87
- }
88
-
89
73
  const scopes = new WeakMap<React.Context<any>, Map<any, Scope>>();
90
74
  function getScope(context: React.Context<any>, value: any) {
91
75
  const m = scopes.get(context) ?? scopes.set(context, new Map()).get(context)!;
@@ -98,7 +82,6 @@ getScope.counter = 0;
98
82
  class RefCounter<T = number> {
99
83
  #disposed = false;
100
84
  refs = new Set<T>();
101
- constructor(public dispose: () => void) {}
102
85
  reg(reference: T) {
103
86
  this.refs.add(reference);
104
87
  }
@@ -108,15 +91,19 @@ class RefCounter<T = number> {
108
91
  if (this.#disposed) return;
109
92
  if (this.refs.size < 1) {
110
93
  this.#disposed = true;
111
- this.dispose();
94
+ this[Symbol.dispose]();
112
95
  }
113
96
  });
114
97
  }
115
98
  }
116
99
 
117
100
  class BunjaInstance<T> extends RefCounter {
118
- constructor(dispose: () => void, public value: T) {
119
- super(dispose);
101
+ constructor(public biid: string, public value: T) {
102
+ super();
103
+ }
104
+ [Symbol.dispose]() {
105
+ delete bunjas[this.biid];
106
+ this.value[Symbol.dispose]?.();
120
107
  }
121
108
  }
122
109
 
@@ -127,6 +114,7 @@ class Scope extends RefCounter {
127
114
  public value: any,
128
115
  public id: number
129
116
  ) {
130
- super(dispose);
117
+ super();
118
+ this[Symbol.dispose] = dispose;
131
119
  }
132
120
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bunja",
3
- "version": "0.0.1",
3
+ "version": "0.0.3",
4
4
  "description": "State Lifetime Manager for React",
5
5
  "main": "bunja.ts",
6
6
  "scripts": {},
package/tsconfig.json ADDED
@@ -0,0 +1,5 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ESNext"
4
+ }
5
+ }