bunja 0.1.0 → 1.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/bunja.ts +11 -7
- package/deno.json +1 -1
- package/deno.lock +38 -0
- package/dist/{bunja-QjknYXY-.cjs → bunja-Q0ZusYIM.cjs} +12 -9
- package/dist/{bunja-3B0nQ1vI.js → bunja-fHIhQAuL.js} +12 -9
- package/dist/bunja.cjs +1 -1
- package/dist/bunja.d.cts +5 -3
- package/dist/bunja.d.ts +5 -3
- package/dist/bunja.js +1 -1
- package/dist/react.cjs +3 -3
- package/dist/react.d.cts +2 -2
- package/dist/react.d.ts +2 -2
- package/dist/react.js +3 -3
- package/package.json +1 -1
- package/presentations/2024-11-28-en.pdf +0 -0
- package/presentations/2024-11-28.pdf +0 -0
- package/presentations/README.md +9 -0
- package/react.ts +9 -4
- package/test.ts +206 -0
package/bunja.ts
CHANGED
|
@@ -24,11 +24,13 @@ export class Bunja<T> {
|
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
export type HashFn<T = any, U = any> = (value: T) => U;
|
|
28
|
+
|
|
27
29
|
export class Scope<T> {
|
|
28
30
|
public static readonly scopes: Scope<any>[] = [];
|
|
29
31
|
public readonly id: number;
|
|
30
32
|
public debugLabel: string = "";
|
|
31
|
-
constructor() {
|
|
33
|
+
constructor(public readonly hash: HashFn = id) {
|
|
32
34
|
this.id = Scope.scopes.length;
|
|
33
35
|
Scope.scopes.push(this);
|
|
34
36
|
}
|
|
@@ -48,7 +50,7 @@ export class BunjaStore {
|
|
|
48
50
|
readScope: ReadScope,
|
|
49
51
|
): {
|
|
50
52
|
value: T;
|
|
51
|
-
mount: () => void;
|
|
53
|
+
mount: () => () => void;
|
|
52
54
|
deps: any[];
|
|
53
55
|
} {
|
|
54
56
|
const scopeInstanceMap = new Map(
|
|
@@ -108,18 +110,19 @@ export class BunjaStore {
|
|
|
108
110
|
return bunjaInstance;
|
|
109
111
|
}
|
|
110
112
|
#getScopeInstance(scope: Scope<any>, value: any): ScopeInstance {
|
|
113
|
+
const key = scope.hash(value);
|
|
111
114
|
const scopeInstanceMap = this.#scopes.get(scope) ??
|
|
112
115
|
this.#scopes.set(scope, new Map()).get(scope)!;
|
|
113
116
|
const init = () =>
|
|
114
117
|
new ScopeInstance(
|
|
115
|
-
() => scopeInstanceMap.delete(
|
|
118
|
+
() => scopeInstanceMap.delete(key),
|
|
116
119
|
ScopeInstance.counter++,
|
|
117
120
|
scope,
|
|
118
121
|
value,
|
|
119
122
|
);
|
|
120
123
|
return (
|
|
121
|
-
scopeInstanceMap.get(
|
|
122
|
-
scopeInstanceMap.set(
|
|
124
|
+
scopeInstanceMap.get(key) ??
|
|
125
|
+
scopeInstanceMap.set(key, init()).get(key)!
|
|
123
126
|
);
|
|
124
127
|
}
|
|
125
128
|
}
|
|
@@ -171,8 +174,8 @@ export const bunja: {
|
|
|
171
174
|
readonly effect: BunjaEffectSymbol;
|
|
172
175
|
} = bunjaImpl;
|
|
173
176
|
|
|
174
|
-
export function createScope<T>(): Scope<T> {
|
|
175
|
-
return new Scope();
|
|
177
|
+
export function createScope<T>(hash?: HashFn): Scope<T> {
|
|
178
|
+
return new Scope(hash);
|
|
176
179
|
}
|
|
177
180
|
|
|
178
181
|
abstract class RefCounter {
|
|
@@ -194,6 +197,7 @@ abstract class RefCounter {
|
|
|
194
197
|
abstract dispose(): void;
|
|
195
198
|
}
|
|
196
199
|
|
|
200
|
+
const id = <T>(x: T): T => x;
|
|
197
201
|
const noop = () => {};
|
|
198
202
|
class BunjaInstance extends RefCounter {
|
|
199
203
|
#cleanup: (() => void) | undefined;
|
package/deno.json
CHANGED
package/deno.lock
CHANGED
|
@@ -1,11 +1,49 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": "4",
|
|
3
3
|
"specifiers": {
|
|
4
|
+
"jsr:@std/assert@*": "1.0.8",
|
|
5
|
+
"jsr:@std/assert@^1.0.8": "1.0.8",
|
|
6
|
+
"jsr:@std/async@^1.0.8": "1.0.8",
|
|
7
|
+
"jsr:@std/data-structures@^1.0.4": "1.0.4",
|
|
8
|
+
"jsr:@std/internal@^1.0.4": "1.0.5",
|
|
9
|
+
"jsr:@std/internal@^1.0.5": "1.0.5",
|
|
10
|
+
"jsr:@std/testing@*": "1.0.5",
|
|
4
11
|
"npm:@types/react@18": "18.3.12",
|
|
5
12
|
"npm:react@18": "18.3.1",
|
|
6
13
|
"npm:tsdown@~0.2.17": "0.2.17_typescript@5.6.3",
|
|
7
14
|
"npm:typescript@^5.6.3": "5.6.3"
|
|
8
15
|
},
|
|
16
|
+
"jsr": {
|
|
17
|
+
"@std/assert@1.0.6": {
|
|
18
|
+
"integrity": "1904c05806a25d94fe791d6d883b685c9e2dcd60e4f9fc30f4fc5cf010c72207",
|
|
19
|
+
"dependencies": [
|
|
20
|
+
"jsr:@std/internal@^1.0.4"
|
|
21
|
+
]
|
|
22
|
+
},
|
|
23
|
+
"@std/assert@1.0.8": {
|
|
24
|
+
"integrity": "ebe0bd7eb488ee39686f77003992f389a06c3da1bbd8022184804852b2fa641b",
|
|
25
|
+
"dependencies": [
|
|
26
|
+
"jsr:@std/internal@^1.0.5"
|
|
27
|
+
]
|
|
28
|
+
},
|
|
29
|
+
"@std/async@1.0.8": {
|
|
30
|
+
"integrity": "c057c5211a0f1d12e7dcd111ab430091301b8d64b4250052a79d277383bc3ba7"
|
|
31
|
+
},
|
|
32
|
+
"@std/data-structures@1.0.4": {
|
|
33
|
+
"integrity": "fa0e20c11eb9ba673417450915c750a0001405a784e2a4e0c3725031681684a0"
|
|
34
|
+
},
|
|
35
|
+
"@std/internal@1.0.5": {
|
|
36
|
+
"integrity": "54a546004f769c1ac9e025abd15a76b6671ddc9687e2313b67376125650dc7ba"
|
|
37
|
+
},
|
|
38
|
+
"@std/testing@1.0.5": {
|
|
39
|
+
"integrity": "6e693cbec94c81a1ad3df668685c7ba8e20742bb10305bc7137faa5cf16d2ec4",
|
|
40
|
+
"dependencies": [
|
|
41
|
+
"jsr:@std/assert@^1.0.8",
|
|
42
|
+
"jsr:@std/async",
|
|
43
|
+
"jsr:@std/data-structures"
|
|
44
|
+
]
|
|
45
|
+
}
|
|
46
|
+
},
|
|
9
47
|
"npm": {
|
|
10
48
|
"@antfu/utils@0.7.10": {
|
|
11
49
|
"integrity": "sha512-+562v9k4aI80m1+VuMHehNJWLOFjBnXn3tdOitzD0il5b7smkSBal4+a3oKiQTbrwMmN/TBUMDvbdoWDehgOww=="
|
|
@@ -17,21 +17,22 @@ var Bunja = class Bunja {
|
|
|
17
17
|
}
|
|
18
18
|
static effect = bunjaEffectSymbol;
|
|
19
19
|
toString() {
|
|
20
|
-
const { id, debugLabel } = this;
|
|
21
|
-
return `[Bunja:${id}${debugLabel && ` - ${debugLabel}`}]`;
|
|
20
|
+
const { id: id$1, debugLabel } = this;
|
|
21
|
+
return `[Bunja:${id$1}${debugLabel && ` - ${debugLabel}`}]`;
|
|
22
22
|
}
|
|
23
23
|
};
|
|
24
24
|
var Scope = class Scope {
|
|
25
25
|
static scopes = [];
|
|
26
26
|
id;
|
|
27
27
|
debugLabel = "";
|
|
28
|
-
constructor() {
|
|
28
|
+
constructor(hash = id) {
|
|
29
|
+
this.hash = hash;
|
|
29
30
|
this.id = Scope.scopes.length;
|
|
30
31
|
Scope.scopes.push(this);
|
|
31
32
|
}
|
|
32
33
|
toString() {
|
|
33
|
-
const { id, debugLabel } = this;
|
|
34
|
-
return `[Scope:${id}${debugLabel && ` - ${debugLabel}`}]`;
|
|
34
|
+
const { id: id$1, debugLabel } = this;
|
|
35
|
+
return `[Scope:${id$1}${debugLabel && ` - ${debugLabel}`}]`;
|
|
35
36
|
}
|
|
36
37
|
};
|
|
37
38
|
var BunjaStore = class {
|
|
@@ -72,9 +73,10 @@ var BunjaStore = class {
|
|
|
72
73
|
return bunjaInstance;
|
|
73
74
|
}
|
|
74
75
|
#getScopeInstance(scope, value) {
|
|
76
|
+
const key = scope.hash(value);
|
|
75
77
|
const scopeInstanceMap = this.#scopes.get(scope) ?? this.#scopes.set(scope, new Map()).get(scope);
|
|
76
|
-
const init = () => new ScopeInstance(() => scopeInstanceMap.delete(
|
|
77
|
-
return scopeInstanceMap.get(
|
|
78
|
+
const init = () => new ScopeInstance(() => scopeInstanceMap.delete(key), ScopeInstance.counter++, scope, value);
|
|
79
|
+
return scopeInstanceMap.get(key) ?? scopeInstanceMap.set(key, init()).get(key);
|
|
78
80
|
}
|
|
79
81
|
};
|
|
80
82
|
const createBunjaStore = () => new BunjaStore();
|
|
@@ -87,8 +89,8 @@ function bunjaImpl(deps, init) {
|
|
|
87
89
|
}
|
|
88
90
|
bunjaImpl.effect = Bunja.effect;
|
|
89
91
|
const bunja = bunjaImpl;
|
|
90
|
-
function createScope() {
|
|
91
|
-
return new Scope();
|
|
92
|
+
function createScope(hash) {
|
|
93
|
+
return new Scope(hash);
|
|
92
94
|
}
|
|
93
95
|
var RefCounter = class {
|
|
94
96
|
#disposed = false;
|
|
@@ -107,6 +109,7 @@ var RefCounter = class {
|
|
|
107
109
|
});
|
|
108
110
|
}
|
|
109
111
|
};
|
|
112
|
+
const id = (x) => x;
|
|
110
113
|
const noop = () => {};
|
|
111
114
|
var BunjaInstance = class extends RefCounter {
|
|
112
115
|
#cleanup;
|
|
@@ -16,21 +16,22 @@ var Bunja = class Bunja {
|
|
|
16
16
|
}
|
|
17
17
|
static effect = bunjaEffectSymbol;
|
|
18
18
|
toString() {
|
|
19
|
-
const { id, debugLabel } = this;
|
|
20
|
-
return `[Bunja:${id}${debugLabel && ` - ${debugLabel}`}]`;
|
|
19
|
+
const { id: id$1, debugLabel } = this;
|
|
20
|
+
return `[Bunja:${id$1}${debugLabel && ` - ${debugLabel}`}]`;
|
|
21
21
|
}
|
|
22
22
|
};
|
|
23
23
|
var Scope = class Scope {
|
|
24
24
|
static scopes = [];
|
|
25
25
|
id;
|
|
26
26
|
debugLabel = "";
|
|
27
|
-
constructor() {
|
|
27
|
+
constructor(hash = id) {
|
|
28
|
+
this.hash = hash;
|
|
28
29
|
this.id = Scope.scopes.length;
|
|
29
30
|
Scope.scopes.push(this);
|
|
30
31
|
}
|
|
31
32
|
toString() {
|
|
32
|
-
const { id, debugLabel } = this;
|
|
33
|
-
return `[Scope:${id}${debugLabel && ` - ${debugLabel}`}]`;
|
|
33
|
+
const { id: id$1, debugLabel } = this;
|
|
34
|
+
return `[Scope:${id$1}${debugLabel && ` - ${debugLabel}`}]`;
|
|
34
35
|
}
|
|
35
36
|
};
|
|
36
37
|
var BunjaStore = class {
|
|
@@ -71,9 +72,10 @@ var BunjaStore = class {
|
|
|
71
72
|
return bunjaInstance;
|
|
72
73
|
}
|
|
73
74
|
#getScopeInstance(scope, value) {
|
|
75
|
+
const key = scope.hash(value);
|
|
74
76
|
const scopeInstanceMap = this.#scopes.get(scope) ?? this.#scopes.set(scope, new Map()).get(scope);
|
|
75
|
-
const init = () => new ScopeInstance(() => scopeInstanceMap.delete(
|
|
76
|
-
return scopeInstanceMap.get(
|
|
77
|
+
const init = () => new ScopeInstance(() => scopeInstanceMap.delete(key), ScopeInstance.counter++, scope, value);
|
|
78
|
+
return scopeInstanceMap.get(key) ?? scopeInstanceMap.set(key, init()).get(key);
|
|
77
79
|
}
|
|
78
80
|
};
|
|
79
81
|
const createBunjaStore = () => new BunjaStore();
|
|
@@ -86,8 +88,8 @@ function bunjaImpl(deps, init) {
|
|
|
86
88
|
}
|
|
87
89
|
bunjaImpl.effect = Bunja.effect;
|
|
88
90
|
const bunja = bunjaImpl;
|
|
89
|
-
function createScope() {
|
|
90
|
-
return new Scope();
|
|
91
|
+
function createScope(hash) {
|
|
92
|
+
return new Scope(hash);
|
|
91
93
|
}
|
|
92
94
|
var RefCounter = class {
|
|
93
95
|
#disposed = false;
|
|
@@ -106,6 +108,7 @@ var RefCounter = class {
|
|
|
106
108
|
});
|
|
107
109
|
}
|
|
108
110
|
};
|
|
111
|
+
const id = (x) => x;
|
|
109
112
|
const noop = () => {};
|
|
110
113
|
var BunjaInstance = class extends RefCounter {
|
|
111
114
|
#cleanup;
|
package/dist/bunja.cjs
CHANGED
package/dist/bunja.d.cts
CHANGED
|
@@ -18,11 +18,13 @@ export declare class Bunja<T> {
|
|
|
18
18
|
static readonly effect: BunjaEffectSymbol;
|
|
19
19
|
toString(): string;
|
|
20
20
|
}
|
|
21
|
+
export type HashFn<T = any, U = any> = (value: T) => U;
|
|
21
22
|
export declare class Scope<T> {
|
|
23
|
+
readonly hash: HashFn;
|
|
22
24
|
static readonly scopes: Scope<any>[];
|
|
23
25
|
readonly id: number;
|
|
24
26
|
debugLabel: string;
|
|
25
|
-
constructor();
|
|
27
|
+
constructor(hash?: HashFn);
|
|
26
28
|
toString(): string;
|
|
27
29
|
}
|
|
28
30
|
export type ReadScope = <T>(scope: Scope<T>) => T;
|
|
@@ -30,7 +32,7 @@ export declare class BunjaStore {
|
|
|
30
32
|
#private;
|
|
31
33
|
get<T>(bunja: Bunja<T>, readScope: ReadScope): {
|
|
32
34
|
value: T;
|
|
33
|
-
mount: () => void;
|
|
35
|
+
mount: () => () => void;
|
|
34
36
|
deps: any[];
|
|
35
37
|
};
|
|
36
38
|
}
|
|
@@ -49,5 +51,5 @@ export declare const bunja: {
|
|
|
49
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>;
|
|
50
52
|
readonly effect: BunjaEffectSymbol;
|
|
51
53
|
};
|
|
52
|
-
export declare function createScope<T>(): Scope<T>;
|
|
54
|
+
export declare function createScope<T>(hash?: HashFn): Scope<T>;
|
|
53
55
|
export {};
|
package/dist/bunja.d.ts
CHANGED
|
@@ -18,11 +18,13 @@ export declare class Bunja<T> {
|
|
|
18
18
|
static readonly effect: BunjaEffectSymbol;
|
|
19
19
|
toString(): string;
|
|
20
20
|
}
|
|
21
|
+
export type HashFn<T = any, U = any> = (value: T) => U;
|
|
21
22
|
export declare class Scope<T> {
|
|
23
|
+
readonly hash: HashFn;
|
|
22
24
|
static readonly scopes: Scope<any>[];
|
|
23
25
|
readonly id: number;
|
|
24
26
|
debugLabel: string;
|
|
25
|
-
constructor();
|
|
27
|
+
constructor(hash?: HashFn);
|
|
26
28
|
toString(): string;
|
|
27
29
|
}
|
|
28
30
|
export type ReadScope = <T>(scope: Scope<T>) => T;
|
|
@@ -30,7 +32,7 @@ export declare class BunjaStore {
|
|
|
30
32
|
#private;
|
|
31
33
|
get<T>(bunja: Bunja<T>, readScope: ReadScope): {
|
|
32
34
|
value: T;
|
|
33
|
-
mount: () => void;
|
|
35
|
+
mount: () => () => void;
|
|
34
36
|
deps: any[];
|
|
35
37
|
};
|
|
36
38
|
}
|
|
@@ -49,5 +51,5 @@ export declare const bunja: {
|
|
|
49
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>;
|
|
50
52
|
readonly effect: BunjaEffectSymbol;
|
|
51
53
|
};
|
|
52
|
-
export declare function createScope<T>(): Scope<T>;
|
|
54
|
+
export declare function createScope<T>(hash?: HashFn): Scope<T>;
|
|
53
55
|
export {};
|
package/dist/bunja.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { Bunja, BunjaStore, Scope, bunja, createBunjaStore, createScope } from "./bunja-
|
|
1
|
+
import { Bunja, BunjaStore, Scope, bunja, createBunjaStore, createScope } from "./bunja-fHIhQAuL.js";
|
|
2
2
|
|
|
3
3
|
export { Bunja, BunjaStore, Scope, bunja, createBunjaStore, createScope };
|
package/dist/react.cjs
CHANGED
|
@@ -22,7 +22,7 @@ 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-
|
|
25
|
+
const require_bunja = require('./bunja-Q0ZusYIM.cjs');
|
|
26
26
|
const { createContext, useContext, useEffect } = __toESM(require("react"));
|
|
27
27
|
|
|
28
28
|
//#region react.ts
|
|
@@ -31,8 +31,8 @@ const scopeContextMap = new Map();
|
|
|
31
31
|
function bindScope(scope, context) {
|
|
32
32
|
scopeContextMap.set(scope, context);
|
|
33
33
|
}
|
|
34
|
-
function createScopeFromContext(context) {
|
|
35
|
-
const scope = require_bunja.createScope();
|
|
34
|
+
function createScopeFromContext(context, hash) {
|
|
35
|
+
const scope = require_bunja.createScope(hash);
|
|
36
36
|
bindScope(scope, context);
|
|
37
37
|
return scope;
|
|
38
38
|
}
|
package/dist/react.d.cts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { type Context } from "react";
|
|
2
|
-
import { type Bunja, type BunjaStore, type ReadScope, type Scope } from "./bunja.ts";
|
|
2
|
+
import { type Bunja, type BunjaStore, type HashFn, type ReadScope, type Scope } from "./bunja.ts";
|
|
3
3
|
export declare const BunjaStoreContext: Context<BunjaStore>;
|
|
4
4
|
export declare const scopeContextMap: Map<Scope<any>, Context<any>>;
|
|
5
5
|
export declare function bindScope(scope: Scope<any>, context: Context<any>): void;
|
|
6
|
-
export declare function createScopeFromContext<T>(context: Context<T>): Scope<T>;
|
|
6
|
+
export declare function createScopeFromContext<T>(context: Context<T>, hash?: HashFn<T>): Scope<T>;
|
|
7
7
|
export declare function useBunja<T>(bunja: Bunja<T>, readScope?: ReadScope): T;
|
|
8
8
|
export type ScopePair<T> = [Scope<T>, T];
|
|
9
9
|
export declare function inject<const T extends ScopePair<any>[]>(overrideTable: T): ReadScope;
|
package/dist/react.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { type Context } from "react";
|
|
2
|
-
import { type Bunja, type BunjaStore, type ReadScope, type Scope } from "./bunja.ts";
|
|
2
|
+
import { type Bunja, type BunjaStore, type HashFn, type ReadScope, type Scope } from "./bunja.ts";
|
|
3
3
|
export declare const BunjaStoreContext: Context<BunjaStore>;
|
|
4
4
|
export declare const scopeContextMap: Map<Scope<any>, Context<any>>;
|
|
5
5
|
export declare function bindScope(scope: Scope<any>, context: Context<any>): void;
|
|
6
|
-
export declare function createScopeFromContext<T>(context: Context<T>): Scope<T>;
|
|
6
|
+
export declare function createScopeFromContext<T>(context: Context<T>, hash?: HashFn<T>): Scope<T>;
|
|
7
7
|
export declare function useBunja<T>(bunja: Bunja<T>, readScope?: ReadScope): T;
|
|
8
8
|
export type ScopePair<T> = [Scope<T>, T];
|
|
9
9
|
export declare function inject<const T extends ScopePair<any>[]>(overrideTable: T): ReadScope;
|
package/dist/react.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createBunjaStore, createScope } from "./bunja-
|
|
1
|
+
import { createBunjaStore, createScope } from "./bunja-fHIhQAuL.js";
|
|
2
2
|
import { createContext, useContext, useEffect } from "react";
|
|
3
3
|
|
|
4
4
|
//#region react.ts
|
|
@@ -7,8 +7,8 @@ const scopeContextMap = new Map();
|
|
|
7
7
|
function bindScope(scope, context) {
|
|
8
8
|
scopeContextMap.set(scope, context);
|
|
9
9
|
}
|
|
10
|
-
function createScopeFromContext(context) {
|
|
11
|
-
const scope = createScope();
|
|
10
|
+
function createScopeFromContext(context, hash) {
|
|
11
|
+
const scope = createScope(hash);
|
|
12
12
|
bindScope(scope, context);
|
|
13
13
|
return scope;
|
|
14
14
|
}
|
package/package.json
CHANGED
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Presentations
|
|
2
|
+
|
|
3
|
+
- React에서 불필요한 웹소켓 재연결을 줄이는 방법 (How to reduce unnecessary
|
|
4
|
+
WebSocket reconnections in React)
|
|
5
|
+
- Conference: 2024 블록체인 진흥주간 x 웹 3.0 컨퍼런스 (2024 Blockchain
|
|
6
|
+
Promotion Week x Web 3.0 Conference)
|
|
7
|
+
- Date: 2024-11-28
|
|
8
|
+
- Slide: [Korean](./2024-11-28.pdf), [English](./2024-11-28-en.pdf)
|
|
9
|
+
- Video(YouTube): [Link](https://www.youtube.com/watch?v=KejJgClvVic)
|
package/react.ts
CHANGED
|
@@ -4,20 +4,25 @@ import {
|
|
|
4
4
|
type BunjaStore,
|
|
5
5
|
createBunjaStore,
|
|
6
6
|
createScope,
|
|
7
|
+
type HashFn,
|
|
7
8
|
type ReadScope,
|
|
8
9
|
type Scope,
|
|
9
10
|
} from "./bunja.ts";
|
|
10
11
|
|
|
11
|
-
export const BunjaStoreContext: Context<BunjaStore> =
|
|
12
|
-
|
|
12
|
+
export const BunjaStoreContext: Context<BunjaStore> = createContext(
|
|
13
|
+
createBunjaStore(),
|
|
14
|
+
);
|
|
13
15
|
|
|
14
16
|
export const scopeContextMap: Map<Scope<any>, Context<any>> = new Map();
|
|
15
17
|
export function bindScope(scope: Scope<any>, context: Context<any>): void {
|
|
16
18
|
scopeContextMap.set(scope, context);
|
|
17
19
|
}
|
|
18
20
|
|
|
19
|
-
export function createScopeFromContext<T>(
|
|
20
|
-
|
|
21
|
+
export function createScopeFromContext<T>(
|
|
22
|
+
context: Context<T>,
|
|
23
|
+
hash?: HashFn<T>,
|
|
24
|
+
): Scope<T> {
|
|
25
|
+
const scope = createScope(hash);
|
|
21
26
|
bindScope(scope, context);
|
|
22
27
|
return scope;
|
|
23
28
|
}
|
package/test.ts
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import { assertEquals } from "jsr:@std/assert";
|
|
2
|
+
import { assertSpyCalls, spy } from "jsr:@std/testing/mock";
|
|
3
|
+
import { FakeTime } from "jsr:@std/testing/time";
|
|
4
|
+
|
|
5
|
+
import { createBunjaStore, createScope } from "./bunja.ts";
|
|
6
|
+
import { bunja } from "./bunja.ts";
|
|
7
|
+
|
|
8
|
+
const readNull = () => (null as any);
|
|
9
|
+
|
|
10
|
+
Deno.test({
|
|
11
|
+
name: "basic",
|
|
12
|
+
fn() {
|
|
13
|
+
using time = new FakeTime();
|
|
14
|
+
const store = createBunjaStore();
|
|
15
|
+
const myBunjaInstance = {};
|
|
16
|
+
const myBunja = bunja([], () => myBunjaInstance);
|
|
17
|
+
const { value, mount } = store.get(myBunja, readNull);
|
|
18
|
+
const cleanup = mount();
|
|
19
|
+
cleanup();
|
|
20
|
+
assertEquals(value, myBunjaInstance);
|
|
21
|
+
time.tick();
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
Deno.test({
|
|
26
|
+
name: "basic effect",
|
|
27
|
+
fn() {
|
|
28
|
+
using time = new FakeTime();
|
|
29
|
+
const store = createBunjaStore();
|
|
30
|
+
const mountSpy = spy();
|
|
31
|
+
const unmountSpy = spy();
|
|
32
|
+
const myBunja = bunja([], () => ({
|
|
33
|
+
[bunja.effect]() {
|
|
34
|
+
mountSpy();
|
|
35
|
+
return unmountSpy;
|
|
36
|
+
},
|
|
37
|
+
}));
|
|
38
|
+
assertSpyCalls(mountSpy, 0);
|
|
39
|
+
const { mount } = store.get(myBunja, readNull);
|
|
40
|
+
assertSpyCalls(mountSpy, 0);
|
|
41
|
+
const cleanup = mount();
|
|
42
|
+
assertSpyCalls(mountSpy, 1);
|
|
43
|
+
assertSpyCalls(unmountSpy, 0);
|
|
44
|
+
cleanup();
|
|
45
|
+
assertSpyCalls(unmountSpy, 0);
|
|
46
|
+
time.tick();
|
|
47
|
+
assertSpyCalls(unmountSpy, 1);
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
Deno.test({
|
|
52
|
+
name: "bunja that depend on other bunja",
|
|
53
|
+
fn() {
|
|
54
|
+
using time = new FakeTime();
|
|
55
|
+
const store = createBunjaStore();
|
|
56
|
+
const [aMountSpy, aUnmountSpy] = [spy(), spy()];
|
|
57
|
+
const [bMountSpy, bUnmountSpy] = [spy(), spy()];
|
|
58
|
+
const aBunjaInstance = {
|
|
59
|
+
[bunja.effect]() {
|
|
60
|
+
aMountSpy();
|
|
61
|
+
return aUnmountSpy;
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
const aBunja = bunja([], () => aBunjaInstance);
|
|
65
|
+
const bBunja = bunja([aBunja], (a) => ({
|
|
66
|
+
a,
|
|
67
|
+
[bunja.effect]() {
|
|
68
|
+
bMountSpy();
|
|
69
|
+
return bUnmountSpy;
|
|
70
|
+
},
|
|
71
|
+
}));
|
|
72
|
+
assertSpyCalls(aMountSpy, 0);
|
|
73
|
+
assertSpyCalls(bMountSpy, 0);
|
|
74
|
+
const { value, mount } = store.get(bBunja, readNull);
|
|
75
|
+
assertEquals(value.a, aBunjaInstance);
|
|
76
|
+
assertSpyCalls(aMountSpy, 0);
|
|
77
|
+
assertSpyCalls(bMountSpy, 0);
|
|
78
|
+
const cleanup = mount();
|
|
79
|
+
assertSpyCalls(aMountSpy, 1);
|
|
80
|
+
assertSpyCalls(bMountSpy, 1);
|
|
81
|
+
assertSpyCalls(aUnmountSpy, 0);
|
|
82
|
+
assertSpyCalls(bUnmountSpy, 0);
|
|
83
|
+
cleanup();
|
|
84
|
+
assertSpyCalls(aUnmountSpy, 0);
|
|
85
|
+
assertSpyCalls(bUnmountSpy, 0);
|
|
86
|
+
time.tick();
|
|
87
|
+
assertSpyCalls(aUnmountSpy, 1);
|
|
88
|
+
assertSpyCalls(bUnmountSpy, 1);
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
Deno.test({
|
|
93
|
+
name: "A mount first, B mount later & A unmount first, B unmount later",
|
|
94
|
+
fn() {
|
|
95
|
+
using time = new FakeTime();
|
|
96
|
+
const store = createBunjaStore();
|
|
97
|
+
const [aMountSpy, aUnmountSpy] = [spy(), spy()];
|
|
98
|
+
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
|
+
}));
|
|
105
|
+
const { mount: m1 } = store.get(aBunja, readNull);
|
|
106
|
+
const c1 = m1();
|
|
107
|
+
assertSpyCalls(aMountSpy, 1);
|
|
108
|
+
assertSpyCalls(bMountSpy, 0);
|
|
109
|
+
const { mount: m2 } = store.get(bBunja, readNull);
|
|
110
|
+
const c2 = m2();
|
|
111
|
+
assertSpyCalls(aMountSpy, 1);
|
|
112
|
+
assertSpyCalls(bMountSpy, 1);
|
|
113
|
+
assertSpyCalls(aUnmountSpy, 0);
|
|
114
|
+
assertSpyCalls(bUnmountSpy, 0);
|
|
115
|
+
c1();
|
|
116
|
+
time.tick();
|
|
117
|
+
assertSpyCalls(aUnmountSpy, 0);
|
|
118
|
+
assertSpyCalls(bUnmountSpy, 0);
|
|
119
|
+
c2();
|
|
120
|
+
time.tick();
|
|
121
|
+
assertSpyCalls(aUnmountSpy, 1);
|
|
122
|
+
assertSpyCalls(bUnmountSpy, 1);
|
|
123
|
+
},
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
Deno.test({
|
|
127
|
+
name: "B mount first, A mount later & B unmount first, A unmount later",
|
|
128
|
+
fn() {
|
|
129
|
+
using time = new FakeTime();
|
|
130
|
+
const store = createBunjaStore();
|
|
131
|
+
const [aMountSpy, aUnmountSpy] = [spy(), spy()];
|
|
132
|
+
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
|
+
}));
|
|
139
|
+
const { mount: m1 } = store.get(bBunja, readNull);
|
|
140
|
+
const c1 = m1();
|
|
141
|
+
assertSpyCalls(aMountSpy, 1);
|
|
142
|
+
assertSpyCalls(bMountSpy, 1);
|
|
143
|
+
const { mount: m2 } = store.get(aBunja, readNull);
|
|
144
|
+
const c2 = m2();
|
|
145
|
+
assertSpyCalls(aMountSpy, 1);
|
|
146
|
+
assertSpyCalls(bMountSpy, 1);
|
|
147
|
+
assertSpyCalls(aUnmountSpy, 0);
|
|
148
|
+
assertSpyCalls(bUnmountSpy, 0);
|
|
149
|
+
c1();
|
|
150
|
+
time.tick();
|
|
151
|
+
assertSpyCalls(aUnmountSpy, 0);
|
|
152
|
+
assertSpyCalls(bUnmountSpy, 1);
|
|
153
|
+
c2();
|
|
154
|
+
time.tick();
|
|
155
|
+
assertSpyCalls(aUnmountSpy, 1);
|
|
156
|
+
assertSpyCalls(bUnmountSpy, 1);
|
|
157
|
+
},
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
Deno.test({
|
|
161
|
+
name: "injecting values into a scope when calling store.get",
|
|
162
|
+
fn() {
|
|
163
|
+
using time = new FakeTime();
|
|
164
|
+
const store = createBunjaStore();
|
|
165
|
+
const myScope = createScope<string>();
|
|
166
|
+
const myBunja = bunja([myScope], (scopeValue) => ({ scopeValue }));
|
|
167
|
+
const readScope = <T>(): T => "injected value" as T;
|
|
168
|
+
const { value: { scopeValue }, mount } = store.get(myBunja, readScope);
|
|
169
|
+
const cleanup = mount();
|
|
170
|
+
cleanup();
|
|
171
|
+
assertEquals(scopeValue, "injected value");
|
|
172
|
+
time.tick();
|
|
173
|
+
},
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
Deno.test({
|
|
177
|
+
name: "scope value deduplication using hash function",
|
|
178
|
+
fn() {
|
|
179
|
+
using time = new FakeTime();
|
|
180
|
+
const store = createBunjaStore();
|
|
181
|
+
const myScope = createScope<string>(({ length }) => length);
|
|
182
|
+
const myBunja = bunja([myScope], (scopeValue) => ({ scopeValue }));
|
|
183
|
+
const { value: { scopeValue: scopeValue1 }, mount: mount1 } = store.get(
|
|
184
|
+
myBunja,
|
|
185
|
+
<T>() => "foo" as T,
|
|
186
|
+
);
|
|
187
|
+
const cleanup1 = mount1();
|
|
188
|
+
const { value: { scopeValue: scopeValue2 }, mount: mount2 } = store.get(
|
|
189
|
+
myBunja,
|
|
190
|
+
<T>() => "bar" as T,
|
|
191
|
+
);
|
|
192
|
+
const cleanup2 = mount2();
|
|
193
|
+
const { value: { scopeValue: scopeValue3 }, mount: mount3 } = store.get(
|
|
194
|
+
myBunja,
|
|
195
|
+
<T>() => "baaz" as T,
|
|
196
|
+
);
|
|
197
|
+
const cleanup3 = mount3();
|
|
198
|
+
assertEquals(scopeValue1, "foo");
|
|
199
|
+
assertEquals(scopeValue2, "foo");
|
|
200
|
+
assertEquals(scopeValue3, "baaz");
|
|
201
|
+
cleanup1();
|
|
202
|
+
cleanup2();
|
|
203
|
+
cleanup3();
|
|
204
|
+
time.tick();
|
|
205
|
+
},
|
|
206
|
+
});
|