bunja 0.0.3 → 0.0.5

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
@@ -25,7 +25,7 @@ You can typically use jotai or something, and when lifetime management becomes n
25
25
 
26
26
  ## How to use
27
27
 
28
- Bunja provides two functions: `bunja` and `useBunja`.\
28
+ Bunja basically provides two functions: `bunja` and `useBunja`.\
29
29
  You can use `bunja` to define a state with a finite lifetime and use the `useBunja` hook to access that state.
30
30
 
31
31
  ### Defining a Bunja
@@ -33,15 +33,18 @@ 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
- If you want to clean up resources when the bunja's lifetime ends, you can use the `Symbol.dispose` field.
36
+ If you want to trigger effects when the lifetime of a bunja starts and ends, you can use the `Bunja.effect` field.
37
37
 
38
38
  ```ts
39
+ import { bunja, Bunja, useBunja } from "bunja";
40
+
39
41
  const countBunja = bunja([], () => {
40
42
  const countAtom = atom(0);
41
43
  return {
42
44
  countAtom,
43
- [Symbol.dispose]() {
44
- console.log("disposed");
45
+ [Bunja.effect]() {
46
+ console.log("mounted");
47
+ return () => console.log("unmounted");
45
48
  },
46
49
  };
47
50
  });
@@ -53,7 +56,4 @@ function MyComponent() {
53
56
  }
54
57
  ```
55
58
 
56
- This code snippet defines a bunja that creates a `countAtom`.\
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
-
59
59
  TODO: context
package/bunja.ts CHANGED
@@ -1,34 +1,55 @@
1
1
  import * as React from "react";
2
2
 
3
- disposePolyfill: {
4
- (Symbol as any).dispose ??= Symbol("Symbol.dispose");
5
- }
6
-
7
- type Dep<T> = React.Context<T> | Bunja<T>;
3
+ export type Dep<T> = React.Context<T> | Bunja<T>;
8
4
 
9
- class Bunja<T> {
5
+ export class Bunja<T> {
10
6
  constructor(
11
7
  public id: number,
12
8
  public deps: Dep<any>[],
13
9
  public contexts: React.Context<any>[],
14
- public init: (...args: any[]) => T
10
+ public init: (...args: any[]) => T & BunjaValue
15
11
  ) {}
12
+ static readonly effect = Symbol("Bunja.effect");
13
+ }
14
+
15
+ export class BunjaStore {
16
+ #bunjas: Record<string, BunjaInstance> = {};
17
+ get(bunja: Bunja<any>, biid: string, args: any[]) {
18
+ return (this.#bunjas[biid] ??= new BunjaInstance(
19
+ this,
20
+ biid,
21
+ bunja.init(...args)
22
+ ));
23
+ }
24
+ delete(biid: string) {
25
+ delete this.#bunjas[biid];
26
+ }
16
27
  }
17
28
 
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>;
29
+ export const createBunjaStore = () => new BunjaStore();
30
+ export const BunjaStoreContext = React.createContext(createBunjaStore());
31
+
32
+ export type BunjaEffectFn = () => () => void;
33
+ export interface BunjaValue {
34
+ [Bunja.effect]?: BunjaEffectFn;
35
+ }
36
+
37
+ export function bunja<T>(deps: [], init: () => T & BunjaValue): Bunja<T>;
38
+ export function bunja<T, U>(
39
+ deps: [Dep<U>],
40
+ init: (u: U) => T & BunjaValue
41
+ ): Bunja<T>;
21
42
  export function bunja<T, U, V>(
22
43
  deps: [Dep<U>, Dep<V>],
23
- init: (u: U, v: V) => T
44
+ init: (u: U, v: V) => T & BunjaValue
24
45
  ): Bunja<T>;
25
46
  export function bunja<T, U, V, W>(
26
47
  deps: [Dep<U>, Dep<V>, Dep<W>],
27
- init: (u: U, v: V, w: W) => T
48
+ init: (u: U, v: V, w: W) => T & BunjaValue
28
49
  ): Bunja<T>;
29
50
  export function bunja<T, const U extends any[]>(
30
51
  deps: { [K in keyof U]: Dep<U[K]> },
31
- init: (...args: U) => T
52
+ init: (...args: U) => T & BunjaValue
32
53
  ): Bunja<T> {
33
54
  const contexts = deps.filter(
34
55
  (dep) => !(dep instanceof Bunja)
@@ -42,7 +63,8 @@ export function bunja<T, const U extends any[]>(
42
63
  bunja.counter = 0;
43
64
 
44
65
  export function useBunja<T>(bunja: Bunja<T>): T {
45
- const { id, deps, contexts, init } = bunja;
66
+ const { id, deps, contexts } = bunja;
67
+ const store = React.useContext(BunjaStoreContext);
46
68
  const rid = useRid();
47
69
  const tuples = contexts.map((c) => [c, React.useContext(c)] as const);
48
70
  const scopes = tuples.map(([context, value]) => getScope(context, value));
@@ -55,7 +77,7 @@ export function useBunja<T>(bunja: Bunja<T>): T {
55
77
  .map(({ id }) => id)
56
78
  .sort()
57
79
  .join(",")}`;
58
- const instance = (bunjas[biid] ??= new BunjaInstance(biid, init(...args)));
80
+ const instance = store.get(bunja, biid, args);
59
81
  React.useEffect(() => {
60
82
  instance.reg(rid);
61
83
  return () => instance.dereg(rid);
@@ -64,7 +86,7 @@ export function useBunja<T>(bunja: Bunja<T>): T {
64
86
  scopes.forEach((scope) => scope.reg(rid));
65
87
  return () => scopes.forEach((scope) => scope.dereg(rid));
66
88
  }, [rid, ...scopes]);
67
- return instance.value;
89
+ return instance.value as T;
68
90
  }
69
91
 
70
92
  const useRid = () => React.useState(() => useRid.counter++)[0];
@@ -79,7 +101,7 @@ function getScope(context: React.Context<any>, value: any) {
79
101
  }
80
102
  getScope.counter = 0;
81
103
 
82
- class RefCounter<T = number> {
104
+ abstract class RefCounter<T = number> {
83
105
  #disposed = false;
84
106
  refs = new Set<T>();
85
107
  reg(reference: T) {
@@ -91,30 +113,40 @@ class RefCounter<T = number> {
91
113
  if (this.#disposed) return;
92
114
  if (this.refs.size < 1) {
93
115
  this.#disposed = true;
94
- this[Symbol.dispose]();
116
+ this.dispose();
95
117
  }
96
118
  });
97
119
  }
120
+ abstract dispose: () => void;
98
121
  }
99
122
 
100
- class BunjaInstance<T> extends RefCounter {
101
- constructor(public biid: string, public value: T) {
123
+ const noop = () => {};
124
+ class BunjaInstance extends RefCounter {
125
+ #cleanup: (() => void) | undefined;
126
+ constructor(
127
+ public store: BunjaStore,
128
+ public biid: string,
129
+ public value: BunjaValue
130
+ ) {
102
131
  super();
103
132
  }
104
- [Symbol.dispose]() {
105
- delete bunjas[this.biid];
106
- this.value[Symbol.dispose]?.();
133
+ reg(reference: number) {
134
+ this.#cleanup ??= this.value[Bunja.effect]?.() ?? noop;
135
+ super.reg(reference);
107
136
  }
137
+ dispose = () => {
138
+ this.#cleanup?.();
139
+ this.store.delete(this.biid);
140
+ };
108
141
  }
109
142
 
110
143
  class Scope extends RefCounter {
111
144
  constructor(
112
- dispose: () => void,
145
+ public dispose: () => void,
113
146
  public context: React.Context<any>,
114
147
  public value: any,
115
148
  public id: number
116
149
  ) {
117
150
  super();
118
- this[Symbol.dispose] = dispose;
119
151
  }
120
152
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bunja",
3
- "version": "0.0.3",
3
+ "version": "0.0.5",
4
4
  "description": "State Lifetime Manager for React",
5
5
  "main": "bunja.ts",
6
6
  "scripts": {},
@@ -16,6 +16,12 @@
16
16
  "react": "^18"
17
17
  },
18
18
  "peerDependencies": {
19
+ "@types/react": "*",
19
20
  "react": ">=17"
21
+ },
22
+ "peerDependenciesMeta": {
23
+ "@types/react": {
24
+ "optional": true
25
+ }
20
26
  }
21
27
  }
package/tsconfig.json CHANGED
@@ -1,5 +1,6 @@
1
1
  {
2
2
  "compilerOptions": {
3
- "target": "ESNext"
3
+ "target": "ESNext",
4
+ "strict": true
4
5
  }
5
6
  }