bunja 2.0.0-alpha.8 → 2.0.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 ADDED
@@ -0,0 +1,277 @@
1
+ # Bunja
2
+
3
+ Bunja is lightweight State Lifetime Manager.\
4
+ Heavily inspired by [Bunshi](https://github.com/saasquatch/bunshi).
5
+
6
+ > Definition: Bunja (分子 / 분자) - Korean for molecule, member or element.
7
+
8
+ ## Why is managing the lifetime of state necessary?
9
+
10
+ Global state managers like jotai or signals offer the advantage of declaratively
11
+ describing state and effectively reducing render counts, but they lack suitable
12
+ methods for managing resources with a defined start and end.\
13
+ For example, consider establishing and closing a WebSocket connection or a modal
14
+ form UI that appears temporarily and then disappears.
15
+
16
+ Bunja is a library designed to address these weaknesses.\
17
+ Each state defined with Bunja has a lifetime that begins when it is first
18
+ depended on somewhere in the render tree and ends when all dependencies
19
+ disappear.
20
+
21
+ Therefore, when writing a state to manage a WebSocket, you only need to create a
22
+ function that establishes the WebSocket connection and a disposal handler that
23
+ terminates the connection.\
24
+ The library automatically tracks the actual usage period and calls the init and
25
+ dispose as needed.
26
+
27
+ ## So, do I no longer need jotai or other state management libraries?
28
+
29
+ No. Bunja focuses solely on managing the lifetime of state, so jotai and other
30
+ state management libraries are still valuable.\
31
+ You can typically use jotai or something, and when lifetime management becomes
32
+ necessary, you can wrap those states with bunja.
33
+
34
+ ## How to use
35
+
36
+ Bunja basically provides two functions: `bunja` and `useBunja`.\
37
+ You can use `bunja` to define a state with a finite lifetime and use the
38
+ `useBunja` hook to access that state.
39
+
40
+ ### Defining a Bunja
41
+
42
+ You can define a bunja using the `bunja` function. When you access the defined
43
+ bunja with the `useBunja` hook, a bunja instance is created.\
44
+ If all components in the render tree that refer to the bunja disappear, the
45
+ bunja instance is automatically destroyed.
46
+
47
+ If you want to trigger effects when the lifetime of a bunja starts and ends, you
48
+ can use the `bunja.effect` function.
49
+
50
+ ```ts
51
+ import { bunja } from "bunja";
52
+ import { useBunja } from "bunja/react";
53
+
54
+ const countBunja = bunja(() => {
55
+ const countAtom = atom(0);
56
+
57
+ bunja.effect(() => {
58
+ console.log("mounted");
59
+ return () => console.log("unmounted");
60
+ });
61
+
62
+ return { countAtom };
63
+ });
64
+
65
+ function MyComponent() {
66
+ const { countAtom } = useBunja(countBunja);
67
+ const [count, setCount] = useAtom(countAtom);
68
+ // Your component logic here
69
+ }
70
+ ```
71
+
72
+ ### Defining a Bunja that relies on other Bunja
73
+
74
+ If you want to manage a state with a broad lifetime and another state with a
75
+ narrower lifetime, you can create a (narrower) bunja that depends on a (broader)
76
+ bunja. For example, you can think of a bunja that manages the WebSocket
77
+ connection and disconnection, and another bunja that subscribes to a specific
78
+ resource over the connected WebSocket.
79
+
80
+ In an application composed of multiple pages, you might want to subscribe to the
81
+ Foo resource on page A and the Bar resource on page B, while using the same
82
+ WebSocket connection regardless of which page you're on. In such a case, you can
83
+ write the following code.
84
+
85
+ ```ts
86
+ // To simplify the example, code for buffering and reconnection has been omitted.
87
+ const websocketBunja = bunja(() => {
88
+ let socket;
89
+ const send = (message) => socket.send(JSON.stringify(message));
90
+
91
+ const emitter = new EventEmitter();
92
+ const on = (handler) => {
93
+ emitter.on("message", handler);
94
+ return () => emitter.off("message", handler);
95
+ };
96
+
97
+ bunja.effect(() => {
98
+ socket = new WebSocket("...");
99
+ socket.onmessage = (e) => emitter.emit("message", JSON.parse(e.data));
100
+ return () => socket.close();
101
+ });
102
+
103
+ return { send, on };
104
+ });
105
+
106
+ const resourceFooBunja = bunja(() => {
107
+ const { send, on } = bunja.use(websocketBunja);
108
+ const resourceFooAtom = atom();
109
+
110
+ bunja.effect(() => {
111
+ const off = on((message) => {
112
+ if (message.type === "foo") store.set(resourceAtom, message.value);
113
+ });
114
+ send("subscribe-foo");
115
+ return () => {
116
+ send("unsubscribe-foo");
117
+ off();
118
+ };
119
+ });
120
+
121
+ return { resourceFooAtom };
122
+ });
123
+
124
+ const resourceBarBunja = bunja(() => {
125
+ const { send, on } = bunja.use(websocketBunja);
126
+ const resourceBarAtom = atom();
127
+ // ...
128
+ });
129
+
130
+ function PageA() {
131
+ const { resourceFooAtom } = useBunja(resourceFooBunja);
132
+ const resourceFoo = useAtomValue(resourceFooAtom);
133
+ // ...
134
+ }
135
+
136
+ function PageB() {
137
+ const { resourceBarAtom } = useBunja(resourceBarBunja);
138
+ const resourceBar = useAtomValue(resourceBarAtom);
139
+ // ...
140
+ }
141
+ ```
142
+
143
+ Notice that `websocketBunja` is not directly `useBunja`-ed. When you `useBunja`
144
+ either `resourceFooBunja` or `resourceBarBunja`, since they depend on
145
+ `websocketBunja`, it has the same effect as if `websocketBunja` were also
146
+ `useBunja`-ed.
147
+
148
+ > [!NOTE]
149
+ > When a bunja starts, the initialization effect of the bunja with a broader
150
+ > lifetime is called first.\
151
+ > Similarly, when a bunja ends, the cleanup effect of the bunja with the broader
152
+ > lifetime is called first.\
153
+ > This behavior is aligned with how React's `useEffect` cleanup function is
154
+ > invoked, where the parent’s cleanup is executed before the child’s in the
155
+ > render tree.
156
+ >
157
+ > See: <https://github.com/facebook/react/issues/16728>
158
+
159
+ ### Dependency injection using Scope
160
+
161
+ You can use a bunja for local state management.\
162
+ When you specify a scope as a dependency of the bunja, separate bunja instances
163
+ are created based on the values injected into the scope.
164
+
165
+ ```ts
166
+ import { bunja, createScope } from "bunja";
167
+
168
+ const UrlScope = createScope();
169
+
170
+ const fetchBunja = bunja(() => {
171
+ const url = bunja.use(UrlScope);
172
+
173
+ const queryAtom = atomWithQuery((get) => ({
174
+ queryKey: [url],
175
+ queryFn: async () => (await fetch(url)).json(),
176
+ }));
177
+
178
+ return { queryAtom };
179
+ });
180
+ ```
181
+
182
+ #### Injecting dependencies via React context
183
+
184
+ If you bind a scope to a React context, bunjas that depend on the scope can
185
+ retrieve values from the corresponding React context.
186
+
187
+ In the example below, there are two React instances (`<ChildComponent />`) that
188
+ reference the same `fetchBunja`, but since each looks at a different context
189
+ value, two separate bunja instances are also created.
190
+
191
+ ```tsx
192
+ import { createContext } from "react";
193
+ import { bunja, createScope } from "bunja";
194
+ import { bindScope } from "bunja/react";
195
+
196
+ const UrlContext = createContext("https://example.com/");
197
+ const UrlScope = createScope();
198
+ bindScope(UrlScope, UrlContext);
199
+
200
+ const fetchBunja = bunja(() => {
201
+ const url = bunja.use(UrlScope);
202
+
203
+ const queryAtom = atomWithQuery((get) => ({
204
+ queryKey: [url],
205
+ queryFn: async () => (await fetch(url)).json(),
206
+ }));
207
+
208
+ return { queryAtom };
209
+ });
210
+
211
+ function ParentComponent() {
212
+ return (
213
+ <>
214
+ <UrlContext value="https://example.com/foo">
215
+ <ChildComponent />
216
+ </UrlContext>
217
+ <UrlContext value="https://example.com/bar">
218
+ <ChildComponent />
219
+ </UrlContext>
220
+ </>
221
+ );
222
+ }
223
+
224
+ function ChildComponent() {
225
+ const { queryAtom } = useBunja(fetchBunja);
226
+ const { data, isPending, isError } = useAtomValue(queryAtom);
227
+ // Your component logic here
228
+ }
229
+ ```
230
+
231
+ You can use the `createScopeFromContext` function to handle both the creation of
232
+ the scope and the binding to the context in one step.
233
+
234
+ ```ts
235
+ import { createContext } from "react";
236
+ import { createScopeFromContext } from "bunja/react";
237
+
238
+ const UrlContext = createContext("https://example.com/");
239
+ const UrlScope = createScopeFromContext(UrlContext);
240
+ ```
241
+
242
+ #### Injecting dependencies directly into the scope
243
+
244
+ You might want to use a bunja directly within a React component where the values
245
+ to be injected into the scope are created.
246
+
247
+ In such cases, you can use the second parameter of `useBunja` hook to inject
248
+ values into the scope without wrapping the context separately.
249
+
250
+ ```tsx
251
+ function MyComponent() {
252
+ const { queryAtom } = useBunja(
253
+ fetchBunja,
254
+ [UrlScope.bind("https://example.com/")],
255
+ );
256
+ const { data, isPending, isError } = useAtomValue(queryAtom);
257
+ // Your component logic here
258
+ }
259
+ ```
260
+
261
+ ##### Doing the same thing inside a bunja
262
+
263
+ You can use `bunja.fork` to inject scope values from within a bunja
264
+ initialization function.
265
+
266
+ ```ts
267
+ const myBunja = bunja(() => {
268
+ const fooData = bunja.fork(fetchBunja, [
269
+ UrlScope.bind("https://example.com/foo"),
270
+ ]);
271
+ const barData = bunja.fork(fetchBunja, [
272
+ UrlScope.bind("https://example.com/bar"),
273
+ ]);
274
+
275
+ return { fooData, barData };
276
+ });
277
+ ```
package/bunja.ts CHANGED
@@ -1,3 +1,7 @@
1
+ // @ts-ignore dev
2
+ // deno-lint-ignore no-process-global
3
+ const __DEV__ = process.env.NODE_ENV !== "production";
4
+
1
5
  export interface BunjaFn {
2
6
  <T>(init: () => T): Bunja<T>;
3
7
  use: BunjaUseFn;
@@ -61,6 +65,11 @@ interface BunjaStoreGetContext {
61
65
  type BunjaInstanceMap = Map<Bunja<unknown>, BunjaInstance>;
62
66
  type ScopeInstanceMap = Map<Scope<unknown>, ScopeInstance>;
63
67
 
68
+ interface InternalState {
69
+ bunjas: Record<string, BunjaInstance>;
70
+ scopes: Map<Scope<unknown>, Map<unknown, ScopeInstance>>;
71
+ }
72
+
64
73
  interface BunjaBakingContext {
65
74
  currentBunja: Bunja<unknown>;
66
75
  }
@@ -69,10 +78,22 @@ export type WrapInstanceFn = <T>(fn: (dispose: () => void) => T) => T;
69
78
  const defaultWrapInstanceFn: WrapInstanceFn = (fn) => fn(noop);
70
79
 
71
80
  export class BunjaStore {
81
+ private static counter: number = 0;
82
+ readonly id: string = String(BunjaStore.counter++);
72
83
  #bunjas: Record<string, BunjaInstance> = {};
73
84
  #scopes: Map<Scope<unknown>, Map<unknown, ScopeInstance>> = new Map();
74
85
  #bakingContext: BunjaBakingContext | undefined;
75
86
  wrapInstance: WrapInstanceFn = defaultWrapInstanceFn;
87
+ constructor() {
88
+ if (__DEV__) {
89
+ devtoolsGlobalHook.stores[this.id] = this;
90
+ devtoolsGlobalHook.emit("storeCreated", { storeId: this.id });
91
+ }
92
+ }
93
+ get _internalState(): InternalState | undefined {
94
+ if (__DEV__) return { bunjas: this.#bunjas, scopes: this.#scopes };
95
+ return undefined;
96
+ }
76
97
  dispose(): void {
77
98
  for (const instance of Object.values(this.#bunjas)) instance.dispose();
78
99
  for (const instanceMap of Object.values(this.#scopes)) {
@@ -80,6 +101,10 @@ export class BunjaStore {
80
101
  }
81
102
  this.#bunjas = {};
82
103
  this.#scopes = new Map();
104
+ if (__DEV__) {
105
+ devtoolsGlobalHook.emit("storeDisposed", { storeId: this.id });
106
+ delete devtoolsGlobalHook.stores[this.id];
107
+ }
83
108
  }
84
109
  get<T>(bunja: Bunja<T>, readScope: ReadScope): BunjaStoreGetResult<T> {
85
110
  const originalUse = bunjaFn.use;
@@ -87,7 +112,7 @@ export class BunjaStore {
87
112
  const { bunjaInstance, bunjaInstanceMap, scopeInstanceMap } = bunja.baked
88
113
  ? this.#getBaked(bunja, readScope)
89
114
  : this.#getUnbaked(bunja, readScope);
90
- return {
115
+ const result: BunjaStoreGetResult<T> = {
91
116
  value: bunjaInstance.value as T,
92
117
  mount: () => {
93
118
  bunjaInstanceMap.forEach((instance) => instance.add());
@@ -102,6 +127,14 @@ export class BunjaStore {
102
127
  },
103
128
  deps: Array.from(scopeInstanceMap.values()).map(({ value }) => value),
104
129
  };
130
+ if (__DEV__) {
131
+ result.bunjaInstance = bunjaInstance;
132
+ devtoolsGlobalHook.emit("getCalled", {
133
+ storeId: this.id,
134
+ bunjaInstanceId: bunjaInstance.id,
135
+ });
136
+ }
137
+ return result;
105
138
  } finally {
106
139
  bunjaFn.use = originalUse;
107
140
  }
@@ -225,7 +258,16 @@ export class BunjaStore {
225
258
  return instanceMap.get(key) ??
226
259
  instanceMap.set(
227
260
  key,
228
- new ScopeInstance(value, () => instanceMap.delete(key)),
261
+ this.#createScopeInstance(scope, key, value, () => {
262
+ instanceMap.delete(key);
263
+ if (__DEV__) {
264
+ devtoolsGlobalHook.emit("scopeInstanceUnmounted", {
265
+ storeId: this.id,
266
+ scope,
267
+ key,
268
+ });
269
+ }
270
+ }),
229
271
  ).get(key)!;
230
272
  }
231
273
  #createBunjaInstance(
@@ -241,12 +283,39 @@ export class BunjaStore {
241
283
  return () => cleanups.forEach((cleanup) => cleanup());
242
284
  };
243
285
  const bunjaInstance = new BunjaInstance(id, value, effect, () => {
286
+ if (__DEV__) {
287
+ devtoolsGlobalHook.emit("bunjaInstanceUnmounted", {
288
+ storeId: this.id,
289
+ bunjaInstanceId: id,
290
+ });
291
+ }
244
292
  dispose();
245
293
  delete this.#bunjas[id];
246
294
  });
247
295
  this.#bunjas[id] = bunjaInstance;
296
+ if (__DEV__) {
297
+ devtoolsGlobalHook.emit("bunjaInstanceMounted", {
298
+ storeId: this.id,
299
+ bunjaInstanceId: id,
300
+ });
301
+ }
248
302
  return bunjaInstance;
249
303
  }
304
+ #createScopeInstance(
305
+ scope: Scope<unknown>,
306
+ key: unknown,
307
+ value: unknown,
308
+ dispose: () => void,
309
+ ): ScopeInstance {
310
+ if (__DEV__) {
311
+ devtoolsGlobalHook.emit("scopeInstanceMounted", {
312
+ storeId: this.id,
313
+ scope,
314
+ key,
315
+ });
316
+ }
317
+ return new ScopeInstance(value, dispose);
318
+ }
250
319
  }
251
320
 
252
321
  export type ReadScope = <T>(scope: Scope<T>) => T;
@@ -267,6 +336,7 @@ export interface BunjaStoreGetResult<T> {
267
336
  value: T;
268
337
  mount: () => () => void;
269
338
  deps: unknown[];
339
+ bunjaInstance?: BunjaInstance;
270
340
  }
271
341
 
272
342
  export function delayUnmount(
@@ -431,3 +501,64 @@ function toposort<T extends Toposortable>(nodes: T[]): T[] {
431
501
  }
432
502
 
433
503
  const noop = () => {};
504
+
505
+ export interface BunjaDevtoolsGlobalHook {
506
+ stores: Record<string, BunjaStore>;
507
+ listeners: Record<
508
+ BunjaDevtoolsEventType,
509
+ Set<(event: any) => void>
510
+ >;
511
+ emit<T extends BunjaDevtoolsEventType>(
512
+ type: T,
513
+ event: BunjaDevtoolsEvent[T],
514
+ ): void;
515
+ on<T extends BunjaDevtoolsEventType>(
516
+ type: T,
517
+ listener: (event: BunjaDevtoolsEvent[T]) => void,
518
+ ): () => void;
519
+ }
520
+ export interface BunjaDevtoolsEvent {
521
+ storeCreated: { storeId: string };
522
+ storeDisposed: { storeId: string };
523
+ getCalled: { storeId: string; bunjaInstanceId: string };
524
+ bunjaInstanceMounted: { storeId: string; bunjaInstanceId: string };
525
+ bunjaInstanceUnmounted: { storeId: string; bunjaInstanceId: string };
526
+ scopeInstanceMounted: {
527
+ storeId: string;
528
+ scope: Scope<unknown>;
529
+ key: unknown;
530
+ };
531
+ scopeInstanceUnmounted: {
532
+ storeId: string;
533
+ scope: Scope<unknown>;
534
+ key: unknown;
535
+ };
536
+ }
537
+ export type BunjaDevtoolsEventType = keyof BunjaDevtoolsEvent;
538
+ let devtoolsGlobalHook: BunjaDevtoolsGlobalHook;
539
+ if (__DEV__) {
540
+ if ((globalThis as any).__BUNJA_DEVTOOLS_GLOBAL_HOOK__) {
541
+ devtoolsGlobalHook = (globalThis as any).__BUNJA_DEVTOOLS_GLOBAL_HOOK__;
542
+ } else {
543
+ devtoolsGlobalHook = {
544
+ stores: {},
545
+ listeners: {
546
+ storeCreated: new Set(),
547
+ storeDisposed: new Set(),
548
+ getCalled: new Set(),
549
+ bunjaInstanceMounted: new Set(),
550
+ bunjaInstanceUnmounted: new Set(),
551
+ scopeInstanceMounted: new Set(),
552
+ scopeInstanceUnmounted: new Set(),
553
+ },
554
+ emit: (type, event) => {
555
+ for (const fn of devtoolsGlobalHook.listeners[type]) fn(event);
556
+ },
557
+ on: (type, listener) => {
558
+ devtoolsGlobalHook.listeners[type].add(listener);
559
+ return () => devtoolsGlobalHook.listeners[type].delete(listener);
560
+ },
561
+ };
562
+ (globalThis as any).__BUNJA_DEVTOOLS_GLOBAL_HOOK__ = devtoolsGlobalHook;
563
+ }
564
+ }
package/deno.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@disjukr/bunja",
3
- "version": "2.0.0-alpha.7",
3
+ "version": "2.0.0",
4
4
  "license": "Zlib",
5
5
  "exports": {
6
6
  ".": "./bunja.ts",
@@ -1,5 +1,6 @@
1
1
 
2
2
  //#region bunja.ts
3
+ const __DEV__ = process.env.NODE_ENV !== "production";
3
4
  const bunja = bunjaFn;
4
5
  function bunjaFn(init) {
5
6
  return new Bunja(init);
@@ -26,22 +27,41 @@ function invalidEffect() {
26
27
  throw new Error("`bunja.effect` can only be used inside a bunja init function.");
27
28
  }
28
29
  const defaultWrapInstanceFn = (fn) => fn(noop);
29
- var BunjaStore = class {
30
+ var BunjaStore = class BunjaStore {
31
+ static counter = 0;
32
+ id = String(BunjaStore.counter++);
30
33
  #bunjas = {};
31
34
  #scopes = /* @__PURE__ */ new Map();
32
35
  #bakingContext;
33
36
  wrapInstance = defaultWrapInstanceFn;
37
+ constructor() {
38
+ if (__DEV__) {
39
+ devtoolsGlobalHook.stores[this.id] = this;
40
+ devtoolsGlobalHook.emit("storeCreated", { storeId: this.id });
41
+ }
42
+ }
43
+ get _internalState() {
44
+ if (__DEV__) return {
45
+ bunjas: this.#bunjas,
46
+ scopes: this.#scopes
47
+ };
48
+ return void 0;
49
+ }
34
50
  dispose() {
35
51
  for (const instance of Object.values(this.#bunjas)) instance.dispose();
36
52
  for (const instanceMap of Object.values(this.#scopes)) for (const instance of instanceMap.values()) instance.dispose();
37
53
  this.#bunjas = {};
38
54
  this.#scopes = /* @__PURE__ */ new Map();
55
+ if (__DEV__) {
56
+ devtoolsGlobalHook.emit("storeDisposed", { storeId: this.id });
57
+ delete devtoolsGlobalHook.stores[this.id];
58
+ }
39
59
  }
40
60
  get(bunja$1, readScope) {
41
61
  const originalUse = bunjaFn.use;
42
62
  try {
43
63
  const { bunjaInstance, bunjaInstanceMap, scopeInstanceMap } = bunja$1.baked ? this.#getBaked(bunja$1, readScope) : this.#getUnbaked(bunja$1, readScope);
44
- return {
64
+ const result = {
45
65
  value: bunjaInstance.value,
46
66
  mount: () => {
47
67
  bunjaInstanceMap.forEach((instance) => instance.add());
@@ -56,6 +76,14 @@ var BunjaStore = class {
56
76
  },
57
77
  deps: Array.from(scopeInstanceMap.values()).map(({ value }) => value)
58
78
  };
79
+ if (__DEV__) {
80
+ result.bunjaInstance = bunjaInstance;
81
+ devtoolsGlobalHook.emit("getCalled", {
82
+ storeId: this.id,
83
+ bunjaInstanceId: bunjaInstance.id
84
+ });
85
+ }
86
+ return result;
59
87
  } finally {
60
88
  bunjaFn.use = originalUse;
61
89
  }
@@ -80,14 +108,14 @@ var BunjaStore = class {
80
108
  const bunjaInstanceMap = /* @__PURE__ */ new Map();
81
109
  const scopeInstanceMap = /* @__PURE__ */ new Map();
82
110
  function getUse(map, addDep, getInstance) {
83
- return (dep) => {
111
+ return ((dep) => {
84
112
  const d = dep;
85
113
  addDep(d);
86
114
  if (map.has(d)) return map.get(d).value;
87
115
  const instance = getInstance(d);
88
116
  map.set(d, instance);
89
117
  return instance.value;
90
- };
118
+ });
91
119
  }
92
120
  const useScope = getUse(scopeInstanceMap, (dep) => this.#bakingContext.currentBunja.addScope(dep), (dep) => this.#getScopeInstance(dep, readScope(dep)));
93
121
  const useBunja = getUse(bunjaInstanceMap, (dep) => this.#bakingContext.currentBunja.addParent(dep), (dep) => {
@@ -150,7 +178,14 @@ var BunjaStore = class {
150
178
  #getScopeInstance(scope, value) {
151
179
  const key = scope.hash(value);
152
180
  const instanceMap = this.#scopes.get(scope) ?? this.#scopes.set(scope, /* @__PURE__ */ new Map()).get(scope);
153
- return instanceMap.get(key) ?? instanceMap.set(key, new ScopeInstance(value, () => instanceMap.delete(key))).get(key);
181
+ return instanceMap.get(key) ?? instanceMap.set(key, this.#createScopeInstance(scope, key, value, () => {
182
+ instanceMap.delete(key);
183
+ if (__DEV__) devtoolsGlobalHook.emit("scopeInstanceUnmounted", {
184
+ storeId: this.id,
185
+ scope,
186
+ key
187
+ });
188
+ })).get(key);
154
189
  }
155
190
  #createBunjaInstance(id, value, effects, dispose) {
156
191
  const effect = () => {
@@ -158,12 +193,28 @@ var BunjaStore = class {
158
193
  return () => cleanups.forEach((cleanup) => cleanup());
159
194
  };
160
195
  const bunjaInstance = new BunjaInstance(id, value, effect, () => {
196
+ if (__DEV__) devtoolsGlobalHook.emit("bunjaInstanceUnmounted", {
197
+ storeId: this.id,
198
+ bunjaInstanceId: id
199
+ });
161
200
  dispose();
162
201
  delete this.#bunjas[id];
163
202
  });
164
203
  this.#bunjas[id] = bunjaInstance;
204
+ if (__DEV__) devtoolsGlobalHook.emit("bunjaInstanceMounted", {
205
+ storeId: this.id,
206
+ bunjaInstanceId: id
207
+ });
165
208
  return bunjaInstance;
166
209
  }
210
+ #createScopeInstance(scope, key, value, dispose) {
211
+ if (__DEV__) devtoolsGlobalHook.emit("scopeInstanceMounted", {
212
+ storeId: this.id,
213
+ scope,
214
+ key
215
+ });
216
+ return new ScopeInstance(value, dispose);
217
+ }
167
218
  };
168
219
  function createReadScopeFn(scopeValuePairs, readScope) {
169
220
  const map = new Map(scopeValuePairs);
@@ -306,6 +357,30 @@ function toposort(nodes) {
306
357
  return result;
307
358
  }
308
359
  const noop = () => {};
360
+ let devtoolsGlobalHook;
361
+ if (__DEV__) if (globalThis.__BUNJA_DEVTOOLS_GLOBAL_HOOK__) devtoolsGlobalHook = globalThis.__BUNJA_DEVTOOLS_GLOBAL_HOOK__;
362
+ else {
363
+ devtoolsGlobalHook = {
364
+ stores: {},
365
+ listeners: {
366
+ storeCreated: /* @__PURE__ */ new Set(),
367
+ storeDisposed: /* @__PURE__ */ new Set(),
368
+ getCalled: /* @__PURE__ */ new Set(),
369
+ bunjaInstanceMounted: /* @__PURE__ */ new Set(),
370
+ bunjaInstanceUnmounted: /* @__PURE__ */ new Set(),
371
+ scopeInstanceMounted: /* @__PURE__ */ new Set(),
372
+ scopeInstanceUnmounted: /* @__PURE__ */ new Set()
373
+ },
374
+ emit: (type, event) => {
375
+ for (const fn of devtoolsGlobalHook.listeners[type]) fn(event);
376
+ },
377
+ on: (type, listener) => {
378
+ devtoolsGlobalHook.listeners[type].add(listener);
379
+ return () => devtoolsGlobalHook.listeners[type].delete(listener);
380
+ }
381
+ };
382
+ globalThis.__BUNJA_DEVTOOLS_GLOBAL_HOOK__ = devtoolsGlobalHook;
383
+ }
309
384
 
310
385
  //#endregion
311
386
  Object.defineProperty(exports, 'Bunja', {
@@ -16,10 +16,18 @@ interface CreateBunjaStoreConfig {
16
16
  }
17
17
  declare function createBunjaStore(config?: CreateBunjaStoreConfig): BunjaStore;
18
18
  type Dep<T> = Bunja<T> | Scope<T>;
19
+ interface InternalState {
20
+ bunjas: Record<string, BunjaInstance>;
21
+ scopes: Map<Scope<unknown>, Map<unknown, ScopeInstance>>;
22
+ }
19
23
  type WrapInstanceFn = <T>(fn: (dispose: () => void) => T) => T;
20
24
  declare class BunjaStore {
21
25
  #private;
26
+ private static counter;
27
+ readonly id: string;
22
28
  wrapInstance: WrapInstanceFn;
29
+ constructor();
30
+ get _internalState(): InternalState | undefined;
23
31
  dispose(): void;
24
32
  get<T>(bunja: Bunja<T>, readScope: ReadScope): BunjaStoreGetResult<T>;
25
33
  }
@@ -29,6 +37,7 @@ interface BunjaStoreGetResult<T> {
29
37
  value: T;
30
38
  mount: () => () => void;
31
39
  deps: unknown[];
40
+ bunjaInstance?: BunjaInstance;
32
41
  }
33
42
  declare function delayUnmount(mount: () => () => void, ms?: number): () => () => void;
34
43
  declare class Bunja<T> {
@@ -66,6 +75,16 @@ declare abstract class RefCounter {
66
75
  add(): void;
67
76
  sub(): void;
68
77
  }
78
+ declare class BunjaInstance extends RefCounter {
79
+ #private;
80
+ readonly id: string;
81
+ readonly value: unknown;
82
+ readonly effect: BunjaEffectCallback;
83
+ private readonly _dispose;
84
+ constructor(id: string, value: unknown, effect: BunjaEffectCallback, _dispose: () => void);
85
+ dispose(): void;
86
+ add(): void;
87
+ }
69
88
  declare class ScopeInstance extends RefCounter {
70
89
  readonly value: unknown;
71
90
  readonly dispose: () => void;
@@ -73,5 +92,42 @@ declare class ScopeInstance extends RefCounter {
73
92
  readonly id: string;
74
93
  constructor(value: unknown, dispose: () => void);
75
94
  }
95
+ interface BunjaDevtoolsGlobalHook {
96
+ stores: Record<string, BunjaStore>;
97
+ listeners: Record<BunjaDevtoolsEventType, Set<(event: any) => void>>;
98
+ emit<T extends BunjaDevtoolsEventType>(type: T, event: BunjaDevtoolsEvent[T]): void;
99
+ on<T extends BunjaDevtoolsEventType>(type: T, listener: (event: BunjaDevtoolsEvent[T]) => void): () => void;
100
+ }
101
+ interface BunjaDevtoolsEvent {
102
+ storeCreated: {
103
+ storeId: string;
104
+ };
105
+ storeDisposed: {
106
+ storeId: string;
107
+ };
108
+ getCalled: {
109
+ storeId: string;
110
+ bunjaInstanceId: string;
111
+ };
112
+ bunjaInstanceMounted: {
113
+ storeId: string;
114
+ bunjaInstanceId: string;
115
+ };
116
+ bunjaInstanceUnmounted: {
117
+ storeId: string;
118
+ bunjaInstanceId: string;
119
+ };
120
+ scopeInstanceMounted: {
121
+ storeId: string;
122
+ scope: Scope<unknown>;
123
+ key: unknown;
124
+ };
125
+ scopeInstanceUnmounted: {
126
+ storeId: string;
127
+ scope: Scope<unknown>;
128
+ key: unknown;
129
+ };
130
+ }
131
+ type BunjaDevtoolsEventType = keyof BunjaDevtoolsEvent;
76
132
  //#endregion
77
- export { Bunja, BunjaEffectCallback, BunjaEffectFn, BunjaFn, BunjaForkFn, BunjaStore, BunjaStoreGetResult, BunjaUseFn, CreateBunjaStoreConfig, Dep, HashFn, ReadScope, Scope, ScopeValuePair, WrapInstanceFn, bunja, createBunjaStore, createReadScopeFn, createScope, delayUnmount };
133
+ export { Bunja, BunjaDevtoolsEvent, BunjaDevtoolsEventType, BunjaDevtoolsGlobalHook, BunjaEffectCallback, BunjaEffectFn, BunjaFn, BunjaForkFn, BunjaStore, BunjaStoreGetResult, BunjaUseFn, CreateBunjaStoreConfig, Dep, HashFn, ReadScope, Scope, ScopeValuePair, WrapInstanceFn, bunja, createBunjaStore, createReadScopeFn, createScope, delayUnmount };
@@ -16,10 +16,18 @@ interface CreateBunjaStoreConfig {
16
16
  }
17
17
  declare function createBunjaStore(config?: CreateBunjaStoreConfig): BunjaStore;
18
18
  type Dep<T> = Bunja<T> | Scope<T>;
19
+ interface InternalState {
20
+ bunjas: Record<string, BunjaInstance>;
21
+ scopes: Map<Scope<unknown>, Map<unknown, ScopeInstance>>;
22
+ }
19
23
  type WrapInstanceFn = <T>(fn: (dispose: () => void) => T) => T;
20
24
  declare class BunjaStore {
21
25
  #private;
26
+ private static counter;
27
+ readonly id: string;
22
28
  wrapInstance: WrapInstanceFn;
29
+ constructor();
30
+ get _internalState(): InternalState | undefined;
23
31
  dispose(): void;
24
32
  get<T>(bunja: Bunja<T>, readScope: ReadScope): BunjaStoreGetResult<T>;
25
33
  }
@@ -29,6 +37,7 @@ interface BunjaStoreGetResult<T> {
29
37
  value: T;
30
38
  mount: () => () => void;
31
39
  deps: unknown[];
40
+ bunjaInstance?: BunjaInstance;
32
41
  }
33
42
  declare function delayUnmount(mount: () => () => void, ms?: number): () => () => void;
34
43
  declare class Bunja<T> {
@@ -66,6 +75,16 @@ declare abstract class RefCounter {
66
75
  add(): void;
67
76
  sub(): void;
68
77
  }
78
+ declare class BunjaInstance extends RefCounter {
79
+ #private;
80
+ readonly id: string;
81
+ readonly value: unknown;
82
+ readonly effect: BunjaEffectCallback;
83
+ private readonly _dispose;
84
+ constructor(id: string, value: unknown, effect: BunjaEffectCallback, _dispose: () => void);
85
+ dispose(): void;
86
+ add(): void;
87
+ }
69
88
  declare class ScopeInstance extends RefCounter {
70
89
  readonly value: unknown;
71
90
  readonly dispose: () => void;
@@ -73,5 +92,42 @@ declare class ScopeInstance extends RefCounter {
73
92
  readonly id: string;
74
93
  constructor(value: unknown, dispose: () => void);
75
94
  }
95
+ interface BunjaDevtoolsGlobalHook {
96
+ stores: Record<string, BunjaStore>;
97
+ listeners: Record<BunjaDevtoolsEventType, Set<(event: any) => void>>;
98
+ emit<T extends BunjaDevtoolsEventType>(type: T, event: BunjaDevtoolsEvent[T]): void;
99
+ on<T extends BunjaDevtoolsEventType>(type: T, listener: (event: BunjaDevtoolsEvent[T]) => void): () => void;
100
+ }
101
+ interface BunjaDevtoolsEvent {
102
+ storeCreated: {
103
+ storeId: string;
104
+ };
105
+ storeDisposed: {
106
+ storeId: string;
107
+ };
108
+ getCalled: {
109
+ storeId: string;
110
+ bunjaInstanceId: string;
111
+ };
112
+ bunjaInstanceMounted: {
113
+ storeId: string;
114
+ bunjaInstanceId: string;
115
+ };
116
+ bunjaInstanceUnmounted: {
117
+ storeId: string;
118
+ bunjaInstanceId: string;
119
+ };
120
+ scopeInstanceMounted: {
121
+ storeId: string;
122
+ scope: Scope<unknown>;
123
+ key: unknown;
124
+ };
125
+ scopeInstanceUnmounted: {
126
+ storeId: string;
127
+ scope: Scope<unknown>;
128
+ key: unknown;
129
+ };
130
+ }
131
+ type BunjaDevtoolsEventType = keyof BunjaDevtoolsEvent;
76
132
  //#endregion
77
- export { Bunja, BunjaEffectCallback, BunjaEffectFn, BunjaFn, BunjaForkFn, BunjaStore, BunjaStoreGetResult, BunjaUseFn, CreateBunjaStoreConfig, Dep, HashFn, ReadScope, Scope, ScopeValuePair, WrapInstanceFn, bunja, createBunjaStore, createReadScopeFn, createScope, delayUnmount };
133
+ export { Bunja, BunjaDevtoolsEvent, BunjaDevtoolsEventType, BunjaDevtoolsGlobalHook, BunjaEffectCallback, BunjaEffectFn, BunjaFn, BunjaForkFn, BunjaStore, BunjaStoreGetResult, BunjaUseFn, CreateBunjaStoreConfig, Dep, HashFn, ReadScope, Scope, ScopeValuePair, WrapInstanceFn, bunja, createBunjaStore, createReadScopeFn, createScope, delayUnmount };
@@ -1,4 +1,5 @@
1
1
  //#region bunja.ts
2
+ const __DEV__ = process.env.NODE_ENV !== "production";
2
3
  const bunja = bunjaFn;
3
4
  function bunjaFn(init) {
4
5
  return new Bunja(init);
@@ -25,22 +26,41 @@ function invalidEffect() {
25
26
  throw new Error("`bunja.effect` can only be used inside a bunja init function.");
26
27
  }
27
28
  const defaultWrapInstanceFn = (fn) => fn(noop);
28
- var BunjaStore = class {
29
+ var BunjaStore = class BunjaStore {
30
+ static counter = 0;
31
+ id = String(BunjaStore.counter++);
29
32
  #bunjas = {};
30
33
  #scopes = /* @__PURE__ */ new Map();
31
34
  #bakingContext;
32
35
  wrapInstance = defaultWrapInstanceFn;
36
+ constructor() {
37
+ if (__DEV__) {
38
+ devtoolsGlobalHook.stores[this.id] = this;
39
+ devtoolsGlobalHook.emit("storeCreated", { storeId: this.id });
40
+ }
41
+ }
42
+ get _internalState() {
43
+ if (__DEV__) return {
44
+ bunjas: this.#bunjas,
45
+ scopes: this.#scopes
46
+ };
47
+ return void 0;
48
+ }
33
49
  dispose() {
34
50
  for (const instance of Object.values(this.#bunjas)) instance.dispose();
35
51
  for (const instanceMap of Object.values(this.#scopes)) for (const instance of instanceMap.values()) instance.dispose();
36
52
  this.#bunjas = {};
37
53
  this.#scopes = /* @__PURE__ */ new Map();
54
+ if (__DEV__) {
55
+ devtoolsGlobalHook.emit("storeDisposed", { storeId: this.id });
56
+ delete devtoolsGlobalHook.stores[this.id];
57
+ }
38
58
  }
39
59
  get(bunja$1, readScope) {
40
60
  const originalUse = bunjaFn.use;
41
61
  try {
42
62
  const { bunjaInstance, bunjaInstanceMap, scopeInstanceMap } = bunja$1.baked ? this.#getBaked(bunja$1, readScope) : this.#getUnbaked(bunja$1, readScope);
43
- return {
63
+ const result = {
44
64
  value: bunjaInstance.value,
45
65
  mount: () => {
46
66
  bunjaInstanceMap.forEach((instance) => instance.add());
@@ -55,6 +75,14 @@ var BunjaStore = class {
55
75
  },
56
76
  deps: Array.from(scopeInstanceMap.values()).map(({ value }) => value)
57
77
  };
78
+ if (__DEV__) {
79
+ result.bunjaInstance = bunjaInstance;
80
+ devtoolsGlobalHook.emit("getCalled", {
81
+ storeId: this.id,
82
+ bunjaInstanceId: bunjaInstance.id
83
+ });
84
+ }
85
+ return result;
58
86
  } finally {
59
87
  bunjaFn.use = originalUse;
60
88
  }
@@ -79,14 +107,14 @@ var BunjaStore = class {
79
107
  const bunjaInstanceMap = /* @__PURE__ */ new Map();
80
108
  const scopeInstanceMap = /* @__PURE__ */ new Map();
81
109
  function getUse(map, addDep, getInstance) {
82
- return (dep) => {
110
+ return ((dep) => {
83
111
  const d = dep;
84
112
  addDep(d);
85
113
  if (map.has(d)) return map.get(d).value;
86
114
  const instance = getInstance(d);
87
115
  map.set(d, instance);
88
116
  return instance.value;
89
- };
117
+ });
90
118
  }
91
119
  const useScope = getUse(scopeInstanceMap, (dep) => this.#bakingContext.currentBunja.addScope(dep), (dep) => this.#getScopeInstance(dep, readScope(dep)));
92
120
  const useBunja = getUse(bunjaInstanceMap, (dep) => this.#bakingContext.currentBunja.addParent(dep), (dep) => {
@@ -149,7 +177,14 @@ var BunjaStore = class {
149
177
  #getScopeInstance(scope, value) {
150
178
  const key = scope.hash(value);
151
179
  const instanceMap = this.#scopes.get(scope) ?? this.#scopes.set(scope, /* @__PURE__ */ new Map()).get(scope);
152
- return instanceMap.get(key) ?? instanceMap.set(key, new ScopeInstance(value, () => instanceMap.delete(key))).get(key);
180
+ return instanceMap.get(key) ?? instanceMap.set(key, this.#createScopeInstance(scope, key, value, () => {
181
+ instanceMap.delete(key);
182
+ if (__DEV__) devtoolsGlobalHook.emit("scopeInstanceUnmounted", {
183
+ storeId: this.id,
184
+ scope,
185
+ key
186
+ });
187
+ })).get(key);
153
188
  }
154
189
  #createBunjaInstance(id, value, effects, dispose) {
155
190
  const effect = () => {
@@ -157,12 +192,28 @@ var BunjaStore = class {
157
192
  return () => cleanups.forEach((cleanup) => cleanup());
158
193
  };
159
194
  const bunjaInstance = new BunjaInstance(id, value, effect, () => {
195
+ if (__DEV__) devtoolsGlobalHook.emit("bunjaInstanceUnmounted", {
196
+ storeId: this.id,
197
+ bunjaInstanceId: id
198
+ });
160
199
  dispose();
161
200
  delete this.#bunjas[id];
162
201
  });
163
202
  this.#bunjas[id] = bunjaInstance;
203
+ if (__DEV__) devtoolsGlobalHook.emit("bunjaInstanceMounted", {
204
+ storeId: this.id,
205
+ bunjaInstanceId: id
206
+ });
164
207
  return bunjaInstance;
165
208
  }
209
+ #createScopeInstance(scope, key, value, dispose) {
210
+ if (__DEV__) devtoolsGlobalHook.emit("scopeInstanceMounted", {
211
+ storeId: this.id,
212
+ scope,
213
+ key
214
+ });
215
+ return new ScopeInstance(value, dispose);
216
+ }
166
217
  };
167
218
  function createReadScopeFn(scopeValuePairs, readScope) {
168
219
  const map = new Map(scopeValuePairs);
@@ -305,6 +356,30 @@ function toposort(nodes) {
305
356
  return result;
306
357
  }
307
358
  const noop = () => {};
359
+ let devtoolsGlobalHook;
360
+ if (__DEV__) if (globalThis.__BUNJA_DEVTOOLS_GLOBAL_HOOK__) devtoolsGlobalHook = globalThis.__BUNJA_DEVTOOLS_GLOBAL_HOOK__;
361
+ else {
362
+ devtoolsGlobalHook = {
363
+ stores: {},
364
+ listeners: {
365
+ storeCreated: /* @__PURE__ */ new Set(),
366
+ storeDisposed: /* @__PURE__ */ new Set(),
367
+ getCalled: /* @__PURE__ */ new Set(),
368
+ bunjaInstanceMounted: /* @__PURE__ */ new Set(),
369
+ bunjaInstanceUnmounted: /* @__PURE__ */ new Set(),
370
+ scopeInstanceMounted: /* @__PURE__ */ new Set(),
371
+ scopeInstanceUnmounted: /* @__PURE__ */ new Set()
372
+ },
373
+ emit: (type, event) => {
374
+ for (const fn of devtoolsGlobalHook.listeners[type]) fn(event);
375
+ },
376
+ on: (type, listener) => {
377
+ devtoolsGlobalHook.listeners[type].add(listener);
378
+ return () => devtoolsGlobalHook.listeners[type].delete(listener);
379
+ }
380
+ };
381
+ globalThis.__BUNJA_DEVTOOLS_GLOBAL_HOOK__ = devtoolsGlobalHook;
382
+ }
308
383
 
309
384
  //#endregion
310
385
  export { Bunja, BunjaStore, Scope, bunja, createBunjaStore, createReadScopeFn, createScope, delayUnmount };
package/dist/bunja.cjs CHANGED
@@ -1,4 +1,4 @@
1
- const require_bunja = require('./bunja-bhXTtuLY.cjs');
1
+ const require_bunja = require('./bunja-CaephaLd.cjs');
2
2
 
3
3
  exports.Bunja = require_bunja.Bunja;
4
4
  exports.BunjaStore = require_bunja.BunjaStore;
package/dist/bunja.d.cts CHANGED
@@ -1,2 +1,2 @@
1
- import { Bunja, BunjaEffectCallback, BunjaEffectFn, BunjaFn, BunjaForkFn, BunjaStore, BunjaStoreGetResult, BunjaUseFn, CreateBunjaStoreConfig, Dep, HashFn, ReadScope, Scope, ScopeValuePair, WrapInstanceFn, bunja, createBunjaStore, createReadScopeFn, createScope, delayUnmount } from "./bunja-P0kiwZQC.cjs";
2
- export { Bunja, BunjaEffectCallback, BunjaEffectFn, BunjaFn, BunjaForkFn, BunjaStore, BunjaStoreGetResult, BunjaUseFn, CreateBunjaStoreConfig, Dep, HashFn, ReadScope, Scope, ScopeValuePair, WrapInstanceFn, bunja, createBunjaStore, createReadScopeFn, createScope, delayUnmount };
1
+ import { Bunja, BunjaDevtoolsEvent, BunjaDevtoolsEventType, BunjaDevtoolsGlobalHook, BunjaEffectCallback, BunjaEffectFn, BunjaFn, BunjaForkFn, BunjaStore, BunjaStoreGetResult, BunjaUseFn, CreateBunjaStoreConfig, Dep, HashFn, ReadScope, Scope, ScopeValuePair, WrapInstanceFn, bunja, createBunjaStore, createReadScopeFn, createScope, delayUnmount } from "./bunja-DXtbhAmJ.cjs";
2
+ export { Bunja, BunjaDevtoolsEvent, BunjaDevtoolsEventType, BunjaDevtoolsGlobalHook, BunjaEffectCallback, BunjaEffectFn, BunjaFn, BunjaForkFn, BunjaStore, BunjaStoreGetResult, BunjaUseFn, CreateBunjaStoreConfig, Dep, HashFn, ReadScope, Scope, ScopeValuePair, WrapInstanceFn, bunja, createBunjaStore, createReadScopeFn, createScope, delayUnmount };
package/dist/bunja.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- import { Bunja, BunjaEffectCallback, BunjaEffectFn, BunjaFn, BunjaForkFn, BunjaStore, BunjaStoreGetResult, BunjaUseFn, CreateBunjaStoreConfig, Dep, HashFn, ReadScope, Scope, ScopeValuePair, WrapInstanceFn, bunja, createBunjaStore, createReadScopeFn, createScope, delayUnmount } from "./bunja-CqyhNWOx.js";
2
- export { Bunja, BunjaEffectCallback, BunjaEffectFn, BunjaFn, BunjaForkFn, BunjaStore, BunjaStoreGetResult, BunjaUseFn, CreateBunjaStoreConfig, Dep, HashFn, ReadScope, Scope, ScopeValuePair, WrapInstanceFn, bunja, createBunjaStore, createReadScopeFn, createScope, delayUnmount };
1
+ import { Bunja, BunjaDevtoolsEvent, BunjaDevtoolsEventType, BunjaDevtoolsGlobalHook, BunjaEffectCallback, BunjaEffectFn, BunjaFn, BunjaForkFn, BunjaStore, BunjaStoreGetResult, BunjaUseFn, CreateBunjaStoreConfig, Dep, HashFn, ReadScope, Scope, ScopeValuePair, WrapInstanceFn, bunja, createBunjaStore, createReadScopeFn, createScope, delayUnmount } from "./bunja-D_SKFBCD.js";
2
+ export { Bunja, BunjaDevtoolsEvent, BunjaDevtoolsEventType, BunjaDevtoolsGlobalHook, BunjaEffectCallback, BunjaEffectFn, BunjaFn, BunjaForkFn, BunjaStore, BunjaStoreGetResult, BunjaUseFn, CreateBunjaStoreConfig, Dep, HashFn, ReadScope, Scope, ScopeValuePair, WrapInstanceFn, bunja, createBunjaStore, createReadScopeFn, createScope, delayUnmount };
package/dist/bunja.js CHANGED
@@ -1,3 +1,3 @@
1
- import { Bunja, BunjaStore, Scope, bunja, createBunjaStore, createReadScopeFn, createScope, delayUnmount } from "./bunja-BB7ru8D0.js";
1
+ import { Bunja, BunjaStore, Scope, bunja, createBunjaStore, createReadScopeFn, createScope, delayUnmount } from "./bunja-EJqDbU0A.js";
2
2
 
3
3
  export { Bunja, BunjaStore, Scope, bunja, createBunjaStore, createReadScopeFn, createScope, delayUnmount };
package/dist/react.cjs CHANGED
@@ -2,10 +2,11 @@
2
2
 
3
3
 
4
4
  const require_chunk = require('./chunk-CUT6urMc.cjs');
5
- const require_bunja = require('./bunja-bhXTtuLY.cjs');
5
+ const require_bunja = require('./bunja-CaephaLd.cjs');
6
6
  const react = require_chunk.__toESM(require("react"));
7
7
 
8
8
  //#region react.ts
9
+ const __DEV__ = process.env.NODE_ENV !== "production";
9
10
  const BunjaStoreContext = (0, react.createContext)(require_bunja.createBunjaStore());
10
11
  function BunjaStoreProvider({ children }) {
11
12
  const [value] = (0, react.useState)(require_bunja.createBunjaStore);
@@ -30,13 +31,25 @@ const defaultReadScope = (scope) => {
30
31
  };
31
32
  function useBunja(bunja, scopeValuePairs) {
32
33
  const store = (0, react.use)(BunjaStoreContext);
33
- const readScope = scopeValuePairs ? require_bunja.createReadScopeFn(scopeValuePairs, (scope) => {
34
- const context = scopeContextMap.get(scope);
35
- return (0, react.use)(context);
36
- }) : defaultReadScope;
37
- const { value, mount, deps } = store.get(bunja, readScope);
38
- (0, react.useEffect)(require_bunja.delayUnmount(mount), deps);
39
- return value;
34
+ const readScope = scopeValuePairs ? require_bunja.createReadScopeFn(scopeValuePairs, defaultReadScope) : defaultReadScope;
35
+ if (__DEV__) {
36
+ const { value, mount, deps, bunjaInstance } = store.get(bunja, readScope);
37
+ (0, react.useEffect)(require_bunja.delayUnmount(mount), deps);
38
+ (0, react.useMemo)(() => ({
39
+ bunja,
40
+ scopeValuePairs,
41
+ bunjaInstance
42
+ }), [
43
+ bunja,
44
+ scopeValuePairs,
45
+ bunjaInstance
46
+ ]);
47
+ return value;
48
+ } else {
49
+ const { value, mount, deps } = store.get(bunja, readScope);
50
+ (0, react.useEffect)(require_bunja.delayUnmount(mount), deps);
51
+ return value;
52
+ }
40
53
  }
41
54
 
42
55
  //#endregion
package/dist/react.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { Bunja, BunjaStore, HashFn, Scope, ScopeValuePair } from "./bunja-P0kiwZQC.cjs";
1
+ import { Bunja, BunjaStore, HashFn, Scope, ScopeValuePair } from "./bunja-DXtbhAmJ.cjs";
2
2
  import { Context, PropsWithChildren } from "react";
3
3
 
4
4
  //#region react.d.ts
package/dist/react.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Bunja, BunjaStore, HashFn, Scope, ScopeValuePair } from "./bunja-CqyhNWOx.js";
1
+ import { Bunja, BunjaStore, HashFn, Scope, ScopeValuePair } from "./bunja-D_SKFBCD.js";
2
2
  import { Context, PropsWithChildren } from "react";
3
3
 
4
4
  //#region react.d.ts
package/dist/react.js CHANGED
@@ -1,10 +1,11 @@
1
1
  "use client";
2
2
 
3
3
 
4
- import { createBunjaStore, createReadScopeFn, createScope, delayUnmount } from "./bunja-BB7ru8D0.js";
5
- import { createContext, createElement, use, useEffect, useState } from "react";
4
+ import { createBunjaStore, createReadScopeFn, createScope, delayUnmount } from "./bunja-EJqDbU0A.js";
5
+ import { createContext, createElement, use, useEffect, useMemo, useState } from "react";
6
6
 
7
7
  //#region react.ts
8
+ const __DEV__ = process.env.NODE_ENV !== "production";
8
9
  const BunjaStoreContext = createContext(createBunjaStore());
9
10
  function BunjaStoreProvider({ children }) {
10
11
  const [value] = useState(createBunjaStore);
@@ -29,13 +30,25 @@ const defaultReadScope = (scope) => {
29
30
  };
30
31
  function useBunja(bunja, scopeValuePairs) {
31
32
  const store = use(BunjaStoreContext);
32
- const readScope = scopeValuePairs ? createReadScopeFn(scopeValuePairs, (scope) => {
33
- const context = scopeContextMap.get(scope);
34
- return use(context);
35
- }) : defaultReadScope;
36
- const { value, mount, deps } = store.get(bunja, readScope);
37
- useEffect(delayUnmount(mount), deps);
38
- return value;
33
+ const readScope = scopeValuePairs ? createReadScopeFn(scopeValuePairs, defaultReadScope) : defaultReadScope;
34
+ if (__DEV__) {
35
+ const { value, mount, deps, bunjaInstance } = store.get(bunja, readScope);
36
+ useEffect(delayUnmount(mount), deps);
37
+ useMemo(() => ({
38
+ bunja,
39
+ scopeValuePairs,
40
+ bunjaInstance
41
+ }), [
42
+ bunja,
43
+ scopeValuePairs,
44
+ bunjaInstance
45
+ ]);
46
+ return value;
47
+ } else {
48
+ const { value, mount, deps } = store.get(bunja, readScope);
49
+ useEffect(delayUnmount(mount), deps);
50
+ return value;
51
+ }
39
52
  }
40
53
 
41
54
  //#endregion
package/dist/solid.cjs CHANGED
@@ -1,5 +1,5 @@
1
1
  const require_chunk = require('./chunk-CUT6urMc.cjs');
2
- const require_bunja = require('./bunja-bhXTtuLY.cjs');
2
+ const require_bunja = require('./bunja-CaephaLd.cjs');
3
3
  const solid_js = require_chunk.__toESM(require("solid-js"));
4
4
 
5
5
  //#region solid.ts
@@ -36,11 +36,11 @@ const defaultReadScope = (scope) => {
36
36
  };
37
37
  function useBunja(bunja, scopeValuePairs) {
38
38
  const store = (0, solid_js.useContext)(BunjaStoreContext);
39
- const readScope = scopeValuePairs ? require_bunja.createReadScopeFn(scopeValuePairs, (scope) => {
40
- const context = scopeContextMap.get(scope);
41
- return access((0, solid_js.useContext)(context));
42
- }) : defaultReadScope;
43
- const entry = (0, solid_js.createMemo)(() => store.get(access(bunja), readScope));
39
+ const readScope = (0, solid_js.createMemo)(() => {
40
+ const pairs = access(scopeValuePairs);
41
+ return pairs ? require_bunja.createReadScopeFn(pairs, defaultReadScope) : defaultReadScope;
42
+ });
43
+ const entry = (0, solid_js.createMemo)(() => store.get(access(bunja), readScope()));
44
44
  (0, solid_js.createEffect)(() => {
45
45
  const cleanup = entry().mount();
46
46
  (0, solid_js.onCleanup)(() => setTimeout(cleanup));
package/dist/solid.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { Bunja, BunjaStore, HashFn, Scope, ScopeValuePair } from "./bunja-P0kiwZQC.cjs";
1
+ import { Bunja, BunjaStore, HashFn, Scope, ScopeValuePair } from "./bunja-DXtbhAmJ.cjs";
2
2
  import { Accessor, Context, JSX, ParentProps } from "solid-js";
3
3
 
4
4
  //#region solid.d.ts
@@ -9,6 +9,6 @@ declare function BunjaStoreProvider(props: ParentProps): JSX.Element;
9
9
  declare const scopeContextMap: Map<Scope<unknown>, Context<MaybeAccessor<unknown>>>;
10
10
  declare function bindScope<T>(scope: Scope<T>, context: Context<MaybeAccessor<T>>): void;
11
11
  declare function createScopeFromContext<T>(context: Context<T>, hash?: HashFn<AccessedValue<T>>): Scope<AccessedValue<T>>;
12
- declare function useBunja<T>(bunja: MaybeAccessor<Bunja<T>>, scopeValuePairs?: ScopeValuePair<any>[]): Accessor<T>;
12
+ declare function useBunja<T>(bunja: MaybeAccessor<Bunja<T>>, scopeValuePairs?: MaybeAccessor<ScopeValuePair<any>[]>): Accessor<T>;
13
13
  //#endregion
14
14
  export { BunjaStoreContext, BunjaStoreProvider, bindScope, createScopeFromContext, scopeContextMap, useBunja };
package/dist/solid.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Bunja, BunjaStore, HashFn, Scope, ScopeValuePair } from "./bunja-CqyhNWOx.js";
1
+ import { Bunja, BunjaStore, HashFn, Scope, ScopeValuePair } from "./bunja-D_SKFBCD.js";
2
2
  import { Accessor, Context, JSX, ParentProps } from "solid-js";
3
3
 
4
4
  //#region solid.d.ts
@@ -9,6 +9,6 @@ declare function BunjaStoreProvider(props: ParentProps): JSX.Element;
9
9
  declare const scopeContextMap: Map<Scope<unknown>, Context<MaybeAccessor<unknown>>>;
10
10
  declare function bindScope<T>(scope: Scope<T>, context: Context<MaybeAccessor<T>>): void;
11
11
  declare function createScopeFromContext<T>(context: Context<T>, hash?: HashFn<AccessedValue<T>>): Scope<AccessedValue<T>>;
12
- declare function useBunja<T>(bunja: MaybeAccessor<Bunja<T>>, scopeValuePairs?: ScopeValuePair<any>[]): Accessor<T>;
12
+ declare function useBunja<T>(bunja: MaybeAccessor<Bunja<T>>, scopeValuePairs?: MaybeAccessor<ScopeValuePair<any>[]>): Accessor<T>;
13
13
  //#endregion
14
14
  export { BunjaStoreContext, BunjaStoreProvider, bindScope, createScopeFromContext, scopeContextMap, useBunja };
package/dist/solid.js CHANGED
@@ -1,4 +1,4 @@
1
- import { createBunjaStore, createReadScopeFn, createScope } from "./bunja-BB7ru8D0.js";
1
+ import { createBunjaStore, createReadScopeFn, createScope } from "./bunja-EJqDbU0A.js";
2
2
  import { createComponent, createContext, createEffect, createMemo, createRoot, getOwner, onCleanup, useContext } from "solid-js";
3
3
 
4
4
  //#region solid.ts
@@ -35,11 +35,11 @@ const defaultReadScope = (scope) => {
35
35
  };
36
36
  function useBunja(bunja, scopeValuePairs) {
37
37
  const store = useContext(BunjaStoreContext);
38
- const readScope = scopeValuePairs ? createReadScopeFn(scopeValuePairs, (scope) => {
39
- const context = scopeContextMap.get(scope);
40
- return access(useContext(context));
41
- }) : defaultReadScope;
42
- const entry = createMemo(() => store.get(access(bunja), readScope));
38
+ const readScope = createMemo(() => {
39
+ const pairs = access(scopeValuePairs);
40
+ return pairs ? createReadScopeFn(pairs, defaultReadScope) : defaultReadScope;
41
+ });
42
+ const entry = createMemo(() => store.get(access(bunja), readScope()));
43
43
  createEffect(() => {
44
44
  const cleanup = entry().mount();
45
45
  onCleanup(() => setTimeout(cleanup));
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "bunja",
3
3
  "type": "module",
4
- "version": "2.0.0-alpha.8",
4
+ "version": "2.0.0",
5
5
  "description": "State Lifetime Manager",
6
6
  "main": "dist/bunja.cjs",
7
7
  "module": "dist/bunja.js",
@@ -52,6 +52,7 @@
52
52
  }
53
53
  },
54
54
  "scripts": {
55
+ "clean": "rm -rf ./dist",
55
56
  "build": "tsdown"
56
57
  },
57
58
  "keywords": [
package/react.ts CHANGED
@@ -7,6 +7,7 @@ import {
7
7
  type PropsWithChildren,
8
8
  use,
9
9
  useEffect,
10
+ useMemo,
10
11
  useState,
11
12
  } from "react";
12
13
  import {
@@ -22,6 +23,10 @@ import {
22
23
  type ScopeValuePair,
23
24
  } from "./bunja.ts";
24
25
 
26
+ // @ts-ignore dev
27
+ // deno-lint-ignore no-process-global
28
+ const __DEV__ = process.env.NODE_ENV !== "production";
29
+
25
30
  export const BunjaStoreContext: Context<BunjaStore> = createContext(
26
31
  createBunjaStore(),
27
32
  );
@@ -59,12 +64,19 @@ export function useBunja<T>(
59
64
  ): T {
60
65
  const store = use(BunjaStoreContext);
61
66
  const readScope = scopeValuePairs
62
- ? createReadScopeFn(scopeValuePairs, <T>(scope: Scope<T>) => {
63
- const context = scopeContextMap.get(scope as Scope<unknown>)!;
64
- return use(context) as T;
65
- })
67
+ ? createReadScopeFn(scopeValuePairs, defaultReadScope)
66
68
  : defaultReadScope;
67
- const { value, mount, deps } = store.get(bunja, readScope);
68
- useEffect(delayUnmount(mount), deps);
69
- return value;
69
+ if (__DEV__) {
70
+ const { value, mount, deps, bunjaInstance } = store.get(bunja, readScope);
71
+ useEffect(delayUnmount(mount), deps);
72
+ useMemo(
73
+ () => ({ bunja, scopeValuePairs, bunjaInstance }),
74
+ [bunja, scopeValuePairs, bunjaInstance],
75
+ );
76
+ return value;
77
+ } else {
78
+ const { value, mount, deps } = store.get(bunja, readScope);
79
+ useEffect(delayUnmount(mount), deps);
80
+ return value;
81
+ }
70
82
  }
package/solid.ts CHANGED
@@ -83,16 +83,16 @@ const defaultReadScope: ReadScope = <T>(scope: Scope<T>) => {
83
83
 
84
84
  export function useBunja<T>(
85
85
  bunja: MaybeAccessor<Bunja<T>>,
86
- scopeValuePairs?: ScopeValuePair<any>[],
86
+ scopeValuePairs?: MaybeAccessor<ScopeValuePair<any>[]>,
87
87
  ): Accessor<T> {
88
88
  const store = useContext(BunjaStoreContext);
89
- const readScope = scopeValuePairs
90
- ? createReadScopeFn(scopeValuePairs, <T>(scope: Scope<T>) => {
91
- const context = scopeContextMap.get(scope as Scope<unknown>)!;
92
- return access(useContext(context)) as T;
93
- })
94
- : defaultReadScope;
95
- const entry = createMemo(() => store.get(access(bunja), readScope));
89
+ const readScope = createMemo(() => {
90
+ const pairs = access(scopeValuePairs);
91
+ return pairs
92
+ ? createReadScopeFn(pairs, defaultReadScope)
93
+ : defaultReadScope;
94
+ });
95
+ const entry = createMemo(() => store.get(access(bunja), readScope()));
96
96
  createEffect(() => {
97
97
  const cleanup = entry().mount();
98
98
  onCleanup(() => setTimeout(cleanup));