bunja 0.0.2 → 0.0.4

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,36 +1,55 @@
1
1
  import * as React from "react";
2
2
 
3
- disposePolyfill: {
4
- const S = Symbol as any;
5
- S.dispose ??= Symbol.for("dispose");
6
- S.asyncDispose ??= Symbol.for("asyncDispose");
7
- }
8
-
9
- type Dep<T> = React.Context<T> | Bunja<T>;
3
+ export type Dep<T> = React.Context<T> | Bunja<T>;
10
4
 
11
- class Bunja<T> {
5
+ export class Bunja<T> {
12
6
  constructor(
13
7
  public id: number,
14
8
  public deps: Dep<any>[],
15
9
  public contexts: React.Context<any>[],
16
- public init: (...args: any[]) => T
10
+ public init: (...args: any[]) => T & BunjaValue
17
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
+ }
18
27
  }
19
28
 
20
- const bunjas: Record<string, BunjaInstance<any>> = {};
21
- export function bunja<T>(deps: [], init: () => T): Bunja<T>;
22
- 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>;
23
42
  export function bunja<T, U, V>(
24
43
  deps: [Dep<U>, Dep<V>],
25
- init: (u: U, v: V) => T
44
+ init: (u: U, v: V) => T & BunjaValue
26
45
  ): Bunja<T>;
27
46
  export function bunja<T, U, V, W>(
28
47
  deps: [Dep<U>, Dep<V>, Dep<W>],
29
- init: (u: U, v: V, w: W) => T
48
+ init: (u: U, v: V, w: W) => T & BunjaValue
30
49
  ): Bunja<T>;
31
50
  export function bunja<T, const U extends any[]>(
32
51
  deps: { [K in keyof U]: Dep<U[K]> },
33
- init: (...args: U) => T
52
+ init: (...args: U) => T & BunjaValue
34
53
  ): Bunja<T> {
35
54
  const contexts = deps.filter(
36
55
  (dep) => !(dep instanceof Bunja)
@@ -44,7 +63,8 @@ export function bunja<T, const U extends any[]>(
44
63
  bunja.counter = 0;
45
64
 
46
65
  export function useBunja<T>(bunja: Bunja<T>): T {
47
- const { id, deps, contexts, init } = bunja;
66
+ const { id, deps, contexts } = bunja;
67
+ const store = React.useContext(BunjaStoreContext);
48
68
  const rid = useRid();
49
69
  const tuples = contexts.map((c) => [c, React.useContext(c)] as const);
50
70
  const scopes = tuples.map(([context, value]) => getScope(context, value));
@@ -57,7 +77,7 @@ export function useBunja<T>(bunja: Bunja<T>): T {
57
77
  .map(({ id }) => id)
58
78
  .sort()
59
79
  .join(",")}`;
60
- const instance = (bunjas[biid] ??= new BunjaInstance(biid, init(...args)));
80
+ const instance = store.get(bunja, biid, args);
61
81
  React.useEffect(() => {
62
82
  instance.reg(rid);
63
83
  return () => instance.dereg(rid);
@@ -66,7 +86,7 @@ export function useBunja<T>(bunja: Bunja<T>): T {
66
86
  scopes.forEach((scope) => scope.reg(rid));
67
87
  return () => scopes.forEach((scope) => scope.dereg(rid));
68
88
  }, [rid, ...scopes]);
69
- return instance.value;
89
+ return instance.value as T;
70
90
  }
71
91
 
72
92
  const useRid = () => React.useState(() => useRid.counter++)[0];
@@ -81,7 +101,7 @@ function getScope(context: React.Context<any>, value: any) {
81
101
  }
82
102
  getScope.counter = 0;
83
103
 
84
- class RefCounter<T = number> {
104
+ abstract class RefCounter<T = number> {
85
105
  #disposed = false;
86
106
  refs = new Set<T>();
87
107
  reg(reference: T) {
@@ -93,30 +113,40 @@ class RefCounter<T = number> {
93
113
  if (this.#disposed) return;
94
114
  if (this.refs.size < 1) {
95
115
  this.#disposed = true;
96
- this[Symbol.dispose]();
116
+ this.dispose();
97
117
  }
98
118
  });
99
119
  }
120
+ abstract dispose: () => void;
100
121
  }
101
122
 
102
- class BunjaInstance<T> extends RefCounter {
103
- 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
+ ) {
104
131
  super();
105
132
  }
106
- [Symbol.dispose]() {
107
- delete bunjas[this.biid];
108
- (this.value[Symbol.asyncDispose] ?? this.value[Symbol.dispose])?.();
133
+ reg(reference: number) {
134
+ this.#cleanup ??= this.value[Bunja.effect]?.() ?? noop;
135
+ super.reg(reference);
109
136
  }
137
+ dispose = () => {
138
+ this.#cleanup?.();
139
+ this.store.delete(this.biid);
140
+ };
110
141
  }
111
142
 
112
143
  class Scope extends RefCounter {
113
144
  constructor(
114
- dispose: () => void,
145
+ public dispose: () => void,
115
146
  public context: React.Context<any>,
116
147
  public value: any,
117
148
  public id: number
118
149
  ) {
119
150
  super();
120
- this[Symbol.dispose] = dispose;
121
151
  }
122
152
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bunja",
3
- "version": "0.0.2",
3
+ "version": "0.0.4",
4
4
  "description": "State Lifetime Manager for React",
5
5
  "main": "bunja.ts",
6
6
  "scripts": {},
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
  }