bunja 1.0.0 → 2.0.0-alpha.2

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/dist/bunja.d.ts CHANGED
@@ -1,55 +1,64 @@
1
+ export interface BunjaFn {
2
+ <T>(init: () => T): Bunja<T>;
3
+ use: BunjaUseFn;
4
+ effect: BunjaEffectFn;
5
+ }
6
+ export declare const bunja: BunjaFn;
7
+ export type BunjaUseFn = <T>(dep: Dep<T>) => T;
8
+ export type BunjaEffectFn = (callback: BunjaEffectCallback) => void;
9
+ export type BunjaEffectCallback = () => (() => void) | void;
10
+ export declare function createScope<T>(hash?: HashFn<T>): Scope<T>;
11
+ export declare function createBunjaStore(): BunjaStore;
1
12
  export type Dep<T> = Bunja<T> | Scope<T>;
2
- declare const bunjaEffectSymbol: unique symbol;
3
- type BunjaEffectSymbol = typeof bunjaEffectSymbol;
13
+ export declare class BunjaStore {
14
+ #private;
15
+ dispose(): void;
16
+ get<T>(bunja: Bunja<T>, readScope: ReadScope): BunjaStoreGetResult<T>;
17
+ }
18
+ export type ReadScope = <T>(scope: Scope<T>) => T;
19
+ export interface BunjaStoreGetResult<T> {
20
+ value: T;
21
+ mount: () => () => void;
22
+ deps: unknown[];
23
+ }
4
24
  export declare class Bunja<T> {
5
- deps: Dep<any>[];
6
- parents: Bunja<any>[];
7
- relatedBunjas: Bunja<any>[];
8
- relatedScopes: Scope<any>[];
9
- init: (...args: any[]) => T & BunjaValue;
10
- static readonly bunjas: Bunja<any>[];
11
- readonly id: number;
25
+ #private;
26
+ init: () => T;
27
+ private static counter;
28
+ readonly id: string;
12
29
  debugLabel: string;
13
- constructor(deps: Dep<any>[], // one depth dependencies
14
- parents: Bunja<any>[], // one depth parents
15
- relatedBunjas: Bunja<any>[], // toposorted parents without self
16
- relatedScopes: Scope<any>[], // deduped
17
- init: (...args: any[]) => T & BunjaValue);
18
- static readonly effect: BunjaEffectSymbol;
30
+ constructor(init: () => T);
31
+ get baked(): boolean;
32
+ get parents(): Bunja<unknown>[];
33
+ get relatedBunjas(): Bunja<unknown>[];
34
+ get relatedScopes(): Scope<unknown>[];
35
+ addParent(bunja: Bunja<unknown>): void;
36
+ addScope(scope: Scope<unknown>): void;
37
+ bake(): void;
38
+ calcInstanceId(scopeInstanceMap: Map<Scope<unknown>, ScopeInstance>): string;
19
39
  toString(): string;
20
40
  }
21
- export type HashFn<T = any, U = any> = (value: T) => U;
22
41
  export declare class Scope<T> {
23
- readonly hash: HashFn;
24
- static readonly scopes: Scope<any>[];
25
- readonly id: number;
42
+ readonly hash: HashFn<T>;
43
+ private static counter;
44
+ readonly id: string;
26
45
  debugLabel: string;
27
- constructor(hash?: HashFn);
46
+ constructor(hash?: HashFn<T>);
47
+ private static identity;
28
48
  toString(): string;
29
49
  }
30
- export type ReadScope = <T>(scope: Scope<T>) => T;
31
- export declare class BunjaStore {
50
+ export type HashFn<T> = (value: T) => unknown;
51
+ declare abstract class RefCounter {
32
52
  #private;
33
- get<T>(bunja: Bunja<T>, readScope: ReadScope): {
34
- value: T;
35
- mount: () => () => void;
36
- deps: any[];
37
- };
53
+ abstract dispose(): void;
54
+ add(): void;
55
+ sub(): void;
38
56
  }
39
- export declare const createBunjaStore: () => BunjaStore;
40
- export type BunjaEffectFn = () => () => void;
41
- export interface BunjaValue {
42
- [Bunja.effect]?: BunjaEffectFn;
57
+ declare class ScopeInstance extends RefCounter {
58
+ readonly value: unknown;
59
+ readonly dispose: () => void;
60
+ private static counter;
61
+ readonly id: string;
62
+ constructor(value: unknown, dispose: () => void);
43
63
  }
44
- export declare const bunja: {
45
- <T>(deps: [], init: () => T & BunjaValue): Bunja<T>;
46
- <T, U>(deps: [Dep<U>], init: (u: U) => T & BunjaValue): Bunja<T>;
47
- <T, U, V>(deps: [Dep<U>, Dep<V>], init: (u: U, v: V) => T & BunjaValue): Bunja<T>;
48
- <T, U, V, W>(deps: [Dep<U>, Dep<V>, Dep<W>], init: (u: U, v: V, w: W) => T & BunjaValue): Bunja<T>;
49
- <T, U, V, W, X>(deps: [Dep<U>, Dep<V>, Dep<W>, Dep<X>], init: (u: U, v: V, w: W, x: X) => T & BunjaValue): Bunja<T>;
50
- <T, U, V, W, X, Y>(deps: [Dep<U>, Dep<V>, Dep<W>, Dep<X>, Dep<Y>], init: (u: U, v: V, w: W, x: X, y: Y) => T & BunjaValue): Bunja<T>;
51
- <T, U, V, W, X, Y, Z>(deps: [Dep<U>, Dep<V>, Dep<W>, Dep<X>, Dep<Y>, Dep<Z>], init: (u: U, v: V, w: W, x: X, y: Y, z: Z) => T & BunjaValue): Bunja<T>;
52
- readonly effect: BunjaEffectSymbol;
53
- };
54
- export declare function createScope<T>(hash?: HashFn): Scope<T>;
55
64
  export {};
package/dist/bunja.js CHANGED
@@ -1,3 +1,3 @@
1
- import { Bunja, BunjaStore, Scope, bunja, createBunjaStore, createScope } from "./bunja-fHIhQAuL.js";
1
+ import { Bunja, BunjaStore, Scope, bunja, createBunjaStore, createScope } from "./bunja-wcx846sL.js";
2
2
 
3
3
  export { Bunja, BunjaStore, Scope, bunja, createBunjaStore, createScope };
package/dist/react.cjs CHANGED
@@ -22,11 +22,19 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
22
22
  }) : target, mod));
23
23
 
24
24
  //#endregion
25
- const require_bunja = require('./bunja-Q0ZusYIM.cjs');
26
- const { createContext, useContext, useEffect } = __toESM(require("react"));
25
+ const require_bunja = require('./bunja-bUA1rGXy.cjs');
26
+ const { createContext, createElement, use, useEffect, useState } = __toESM(require("react"));
27
27
 
28
28
  //#region react.ts
29
29
  const BunjaStoreContext = createContext(require_bunja.createBunjaStore());
30
+ function BunjaStoreProvider({ children }) {
31
+ const [value] = useState(require_bunja.createBunjaStore);
32
+ useEffect(() => () => value.dispose(), [value]);
33
+ return createElement(BunjaStoreContext, {
34
+ value,
35
+ children
36
+ });
37
+ }
30
38
  const scopeContextMap = new Map();
31
39
  function bindScope(scope, context) {
32
40
  scopeContextMap.set(scope, context);
@@ -38,10 +46,10 @@ function createScopeFromContext(context, hash) {
38
46
  }
39
47
  const defaultReadScope = (scope) => {
40
48
  const context = scopeContextMap.get(scope);
41
- return useContext(context);
49
+ return use(context);
42
50
  };
43
51
  function useBunja(bunja, readScope = defaultReadScope) {
44
- const store = useContext(BunjaStoreContext);
52
+ const store = use(BunjaStoreContext);
45
53
  const { value, mount, deps } = store.get(bunja, readScope);
46
54
  useEffect(mount, deps);
47
55
  return value;
@@ -52,12 +60,13 @@ function inject(overrideTable) {
52
60
  if (map.has(scope)) return map.get(scope);
53
61
  const context = scopeContextMap.get(scope);
54
62
  if (!context) throw new Error("Unable to read the scope. Please inject the value explicitly or bind scope to the React context.");
55
- return useContext(context);
63
+ return use(context);
56
64
  };
57
65
  }
58
66
 
59
67
  //#endregion
60
68
  exports.BunjaStoreContext = BunjaStoreContext
69
+ exports.BunjaStoreProvider = BunjaStoreProvider
61
70
  exports.bindScope = bindScope
62
71
  exports.createScopeFromContext = createScopeFromContext
63
72
  exports.inject = inject
package/dist/react.d.cts CHANGED
@@ -1,9 +1,10 @@
1
- import { type Context } from "react";
1
+ import { type Context, type PropsWithChildren } from "react";
2
2
  import { type Bunja, type BunjaStore, type HashFn, type ReadScope, type Scope } from "./bunja.ts";
3
3
  export declare const BunjaStoreContext: Context<BunjaStore>;
4
- export declare const scopeContextMap: Map<Scope<any>, Context<any>>;
5
- export declare function bindScope(scope: Scope<any>, context: Context<any>): void;
4
+ export declare function BunjaStoreProvider({ children }: PropsWithChildren): React.JSX.Element;
5
+ export declare const scopeContextMap: Map<Scope<unknown>, Context<unknown>>;
6
+ export declare function bindScope<T>(scope: Scope<T>, context: Context<T>): void;
6
7
  export declare function createScopeFromContext<T>(context: Context<T>, hash?: HashFn<T>): Scope<T>;
7
8
  export declare function useBunja<T>(bunja: Bunja<T>, readScope?: ReadScope): T;
8
9
  export type ScopePair<T> = [Scope<T>, T];
9
- export declare function inject<const T extends ScopePair<any>[]>(overrideTable: T): ReadScope;
10
+ export declare function inject<const T extends ScopePair<unknown>[]>(overrideTable: T): ReadScope;
package/dist/react.d.ts CHANGED
@@ -1,9 +1,10 @@
1
- import { type Context } from "react";
1
+ import { type Context, type PropsWithChildren } from "react";
2
2
  import { type Bunja, type BunjaStore, type HashFn, type ReadScope, type Scope } from "./bunja.ts";
3
3
  export declare const BunjaStoreContext: Context<BunjaStore>;
4
- export declare const scopeContextMap: Map<Scope<any>, Context<any>>;
5
- export declare function bindScope(scope: Scope<any>, context: Context<any>): void;
4
+ export declare function BunjaStoreProvider({ children }: PropsWithChildren): React.JSX.Element;
5
+ export declare const scopeContextMap: Map<Scope<unknown>, Context<unknown>>;
6
+ export declare function bindScope<T>(scope: Scope<T>, context: Context<T>): void;
6
7
  export declare function createScopeFromContext<T>(context: Context<T>, hash?: HashFn<T>): Scope<T>;
7
8
  export declare function useBunja<T>(bunja: Bunja<T>, readScope?: ReadScope): T;
8
9
  export type ScopePair<T> = [Scope<T>, T];
9
- export declare function inject<const T extends ScopePair<any>[]>(overrideTable: T): ReadScope;
10
+ export declare function inject<const T extends ScopePair<unknown>[]>(overrideTable: T): ReadScope;
package/dist/react.js CHANGED
@@ -1,8 +1,16 @@
1
- import { createBunjaStore, createScope } from "./bunja-fHIhQAuL.js";
2
- import { createContext, useContext, useEffect } from "react";
1
+ import { createBunjaStore, createScope } from "./bunja-wcx846sL.js";
2
+ import { createContext, createElement, use, useEffect, useState } from "react";
3
3
 
4
4
  //#region react.ts
5
5
  const BunjaStoreContext = createContext(createBunjaStore());
6
+ function BunjaStoreProvider({ children }) {
7
+ const [value] = useState(createBunjaStore);
8
+ useEffect(() => () => value.dispose(), [value]);
9
+ return createElement(BunjaStoreContext, {
10
+ value,
11
+ children
12
+ });
13
+ }
6
14
  const scopeContextMap = new Map();
7
15
  function bindScope(scope, context) {
8
16
  scopeContextMap.set(scope, context);
@@ -14,10 +22,10 @@ function createScopeFromContext(context, hash) {
14
22
  }
15
23
  const defaultReadScope = (scope) => {
16
24
  const context = scopeContextMap.get(scope);
17
- return useContext(context);
25
+ return use(context);
18
26
  };
19
27
  function useBunja(bunja, readScope = defaultReadScope) {
20
- const store = useContext(BunjaStoreContext);
28
+ const store = use(BunjaStoreContext);
21
29
  const { value, mount, deps } = store.get(bunja, readScope);
22
30
  useEffect(mount, deps);
23
31
  return value;
@@ -28,9 +36,9 @@ function inject(overrideTable) {
28
36
  if (map.has(scope)) return map.get(scope);
29
37
  const context = scopeContextMap.get(scope);
30
38
  if (!context) throw new Error("Unable to read the scope. Please inject the value explicitly or bind scope to the React context.");
31
- return useContext(context);
39
+ return use(context);
32
40
  };
33
41
  }
34
42
 
35
43
  //#endregion
36
- export { BunjaStoreContext, bindScope, createScopeFromContext, inject, scopeContextMap, useBunja };
44
+ export { BunjaStoreContext, BunjaStoreProvider, bindScope, createScopeFromContext, inject, scopeContextMap, useBunja };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "bunja",
3
3
  "type": "module",
4
- "version": "1.0.0",
4
+ "version": "2.0.0-alpha.2",
5
5
  "description": "State Lifetime Manager",
6
6
  "main": "dist/bunja.cjs",
7
7
  "module": "dist/bunja.js",
@@ -49,14 +49,14 @@
49
49
  "author": "JongChan Choi <jong@chan.moe>",
50
50
  "license": "Zlib",
51
51
  "devDependencies": {
52
- "@types/react": "^18",
53
- "react": "^18",
52
+ "@types/react": "^19",
53
+ "react": "^19",
54
54
  "tsdown": "^0.2.17",
55
55
  "typescript": "^5.6.3"
56
56
  },
57
57
  "peerDependencies": {
58
58
  "@types/react": "*",
59
- "react": ">=17"
59
+ "react": ">=19"
60
60
  },
61
61
  "peerDependenciesMeta": {
62
62
  "@types/react": {
package/react.ts CHANGED
@@ -1,4 +1,12 @@
1
- import { type Context, createContext, useContext, useEffect } from "react";
1
+ import {
2
+ type Context,
3
+ createContext,
4
+ createElement,
5
+ type PropsWithChildren,
6
+ use,
7
+ useEffect,
8
+ useState,
9
+ } from "react";
2
10
  import {
3
11
  type Bunja,
4
12
  type BunjaStore,
@@ -13,30 +21,38 @@ export const BunjaStoreContext: Context<BunjaStore> = createContext(
13
21
  createBunjaStore(),
14
22
  );
15
23
 
16
- export const scopeContextMap: Map<Scope<any>, Context<any>> = new Map();
17
- export function bindScope(scope: Scope<any>, context: Context<any>): void {
18
- scopeContextMap.set(scope, context);
24
+ export function BunjaStoreProvider(
25
+ { children }: PropsWithChildren,
26
+ ): React.JSX.Element {
27
+ const [value] = useState(createBunjaStore);
28
+ useEffect(() => () => value.dispose(), [value]);
29
+ return createElement(BunjaStoreContext, { value, children });
30
+ }
31
+
32
+ export const scopeContextMap: Map<Scope<unknown>, Context<unknown>> = new Map();
33
+ export function bindScope<T>(scope: Scope<T>, context: Context<T>): void {
34
+ scopeContextMap.set(scope as Scope<unknown>, context as Context<unknown>);
19
35
  }
20
36
 
21
37
  export function createScopeFromContext<T>(
22
38
  context: Context<T>,
23
39
  hash?: HashFn<T>,
24
40
  ): Scope<T> {
25
- const scope = createScope(hash);
41
+ const scope = createScope<T>(hash);
26
42
  bindScope(scope, context);
27
43
  return scope;
28
44
  }
29
45
 
30
- const defaultReadScope: ReadScope = (scope) => {
31
- const context = scopeContextMap.get(scope)!;
32
- return useContext(context);
46
+ const defaultReadScope: ReadScope = <T>(scope: Scope<T>) => {
47
+ const context = scopeContextMap.get(scope as Scope<unknown>)!;
48
+ return use(context) as T;
33
49
  };
34
50
 
35
51
  export function useBunja<T>(
36
52
  bunja: Bunja<T>,
37
53
  readScope: ReadScope = defaultReadScope,
38
54
  ): T {
39
- const store = useContext(BunjaStoreContext);
55
+ const store = use(BunjaStoreContext);
40
56
  const { value, mount, deps } = store.get(bunja, readScope);
41
57
  useEffect(mount, deps);
42
58
  return value;
@@ -44,18 +60,20 @@ export function useBunja<T>(
44
60
 
45
61
  export type ScopePair<T> = [Scope<T>, T];
46
62
 
47
- export function inject<const T extends ScopePair<any>[]>(
63
+ export function inject<const T extends ScopePair<unknown>[]>(
48
64
  overrideTable: T,
49
65
  ): ReadScope {
50
66
  const map = new Map(overrideTable);
51
- return (scope) => {
52
- if (map.has(scope)) return map.get(scope);
53
- const context = scopeContextMap.get(scope);
67
+ return <T>(scope: Scope<T>) => {
68
+ if (map.has(scope as Scope<unknown>)) {
69
+ return map.get(scope as Scope<unknown>) as T;
70
+ }
71
+ const context = scopeContextMap.get(scope as Scope<unknown>);
54
72
  if (!context) {
55
73
  throw new Error(
56
74
  "Unable to read the scope. Please inject the value explicitly or bind scope to the React context.",
57
75
  );
58
76
  }
59
- return useContext(context);
77
+ return use(context) as T;
60
78
  };
61
79
  }
package/test.ts CHANGED
@@ -2,10 +2,9 @@ import { assertEquals } from "jsr:@std/assert";
2
2
  import { assertSpyCalls, spy } from "jsr:@std/testing/mock";
3
3
  import { FakeTime } from "jsr:@std/testing/time";
4
4
 
5
- import { createBunjaStore, createScope } from "./bunja.ts";
6
- import { bunja } from "./bunja.ts";
5
+ import { bunja, createBunjaStore, createScope } from "./bunja.ts";
7
6
 
8
- const readNull = () => (null as any);
7
+ const readNull = <T>() => (null as T);
9
8
 
10
9
  Deno.test({
11
10
  name: "basic",
@@ -13,7 +12,7 @@ Deno.test({
13
12
  using time = new FakeTime();
14
13
  const store = createBunjaStore();
15
14
  const myBunjaInstance = {};
16
- const myBunja = bunja([], () => myBunjaInstance);
15
+ const myBunja = bunja(() => myBunjaInstance);
17
16
  const { value, mount } = store.get(myBunja, readNull);
18
17
  const cleanup = mount();
19
18
  cleanup();
@@ -29,12 +28,12 @@ Deno.test({
29
28
  const store = createBunjaStore();
30
29
  const mountSpy = spy();
31
30
  const unmountSpy = spy();
32
- const myBunja = bunja([], () => ({
33
- [bunja.effect]() {
31
+ const myBunja = bunja(() => {
32
+ bunja.effect(() => {
34
33
  mountSpy();
35
34
  return unmountSpy;
36
- },
37
- }));
35
+ });
36
+ });
38
37
  assertSpyCalls(mountSpy, 0);
39
38
  const { mount } = store.get(myBunja, readNull);
40
39
  assertSpyCalls(mountSpy, 0);
@@ -55,20 +54,22 @@ Deno.test({
55
54
  const store = createBunjaStore();
56
55
  const [aMountSpy, aUnmountSpy] = [spy(), spy()];
57
56
  const [bMountSpy, bUnmountSpy] = [spy(), spy()];
58
- const aBunjaInstance = {
59
- [bunja.effect]() {
57
+ const aBunjaInstance = {};
58
+ const aBunja = bunja(() => {
59
+ bunja.effect(() => {
60
60
  aMountSpy();
61
61
  return aUnmountSpy;
62
- },
63
- };
64
- const aBunja = bunja([], () => aBunjaInstance);
65
- const bBunja = bunja([aBunja], (a) => ({
66
- a,
67
- [bunja.effect]() {
62
+ });
63
+ return aBunjaInstance;
64
+ });
65
+ const bBunja = bunja(() => {
66
+ const a = bunja.use(aBunja);
67
+ bunja.effect(() => {
68
68
  bMountSpy();
69
69
  return bUnmountSpy;
70
- },
71
- }));
70
+ });
71
+ return { a };
72
+ });
72
73
  assertSpyCalls(aMountSpy, 0);
73
74
  assertSpyCalls(bMountSpy, 0);
74
75
  const { value, mount } = store.get(bBunja, readNull);
@@ -96,12 +97,19 @@ Deno.test({
96
97
  const store = createBunjaStore();
97
98
  const [aMountSpy, aUnmountSpy] = [spy(), spy()];
98
99
  const [bMountSpy, bUnmountSpy] = [spy(), spy()];
99
- const aBunja = bunja([], () => ({
100
- [bunja.effect]: () => (aMountSpy(), aUnmountSpy),
101
- }));
102
- const bBunja = bunja([aBunja], () => ({
103
- [bunja.effect]: () => (bMountSpy(), bUnmountSpy),
104
- }));
100
+ const aBunja = bunja(() => {
101
+ bunja.effect(() => {
102
+ aMountSpy();
103
+ return aUnmountSpy;
104
+ });
105
+ });
106
+ const bBunja = bunja(() => {
107
+ bunja.use(aBunja);
108
+ bunja.effect(() => {
109
+ bMountSpy();
110
+ return bUnmountSpy;
111
+ });
112
+ });
105
113
  const { mount: m1 } = store.get(aBunja, readNull);
106
114
  const c1 = m1();
107
115
  assertSpyCalls(aMountSpy, 1);
@@ -130,12 +138,19 @@ Deno.test({
130
138
  const store = createBunjaStore();
131
139
  const [aMountSpy, aUnmountSpy] = [spy(), spy()];
132
140
  const [bMountSpy, bUnmountSpy] = [spy(), spy()];
133
- const aBunja = bunja([], () => ({
134
- [bunja.effect]: () => (aMountSpy(), aUnmountSpy),
135
- }));
136
- const bBunja = bunja([aBunja], () => ({
137
- [bunja.effect]: () => (bMountSpy(), bUnmountSpy),
138
- }));
141
+ const aBunja = bunja(() => {
142
+ bunja.effect(() => {
143
+ aMountSpy();
144
+ return aUnmountSpy;
145
+ });
146
+ });
147
+ const bBunja = bunja(() => {
148
+ bunja.use(aBunja);
149
+ bunja.effect(() => {
150
+ bMountSpy();
151
+ return bUnmountSpy;
152
+ });
153
+ });
139
154
  const { mount: m1 } = store.get(bBunja, readNull);
140
155
  const c1 = m1();
141
156
  assertSpyCalls(aMountSpy, 1);
@@ -163,7 +178,10 @@ Deno.test({
163
178
  using time = new FakeTime();
164
179
  const store = createBunjaStore();
165
180
  const myScope = createScope<string>();
166
- const myBunja = bunja([myScope], (scopeValue) => ({ scopeValue }));
181
+ const myBunja = bunja(() => {
182
+ const scopeValue = bunja.use(myScope);
183
+ return { scopeValue };
184
+ });
167
185
  const readScope = <T>(): T => "injected value" as T;
168
186
  const { value: { scopeValue }, mount } = store.get(myBunja, readScope);
169
187
  const cleanup = mount();
@@ -179,7 +197,10 @@ Deno.test({
179
197
  using time = new FakeTime();
180
198
  const store = createBunjaStore();
181
199
  const myScope = createScope<string>(({ length }) => length);
182
- const myBunja = bunja([myScope], (scopeValue) => ({ scopeValue }));
200
+ const myBunja = bunja(() => {
201
+ const scopeValue = bunja.use(myScope);
202
+ return { scopeValue };
203
+ });
183
204
  const { value: { scopeValue: scopeValue1 }, mount: mount1 } = store.get(
184
205
  myBunja,
185
206
  <T>() => "foo" as T,