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/README.md +53 -40
- package/bunja.ts +300 -182
- package/codemod/bunja-v1-to-v2.js +118 -0
- package/compat/bunja-v1.ts +48 -0
- package/deno.json +1 -1
- package/deno.lock +174 -83
- package/dist/bunja-bUA1rGXy.cjs +305 -0
- package/dist/bunja-wcx846sL.js +274 -0
- package/dist/bunja.cjs +1 -1
- package/dist/bunja.d.cts +51 -42
- package/dist/bunja.d.ts +51 -42
- package/dist/bunja.js +1 -1
- package/dist/react.cjs +14 -5
- package/dist/react.d.cts +5 -4
- package/dist/react.d.ts +5 -4
- package/dist/react.js +14 -6
- package/package.json +4 -4
- package/react.ts +32 -14
- package/test.ts +53 -32
- package/dist/bunja-Q0ZusYIM.cjs +0 -189
- package/dist/bunja-fHIhQAuL.js +0 -158
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
//#region bunja.ts
|
|
4
|
+
const bunja = bunjaFn;
|
|
5
|
+
function bunjaFn(init) {
|
|
6
|
+
return new Bunja(init);
|
|
7
|
+
}
|
|
8
|
+
bunjaFn.use = invalidUse;
|
|
9
|
+
bunjaFn.effect = invalidEffect;
|
|
10
|
+
function createScope(hash) {
|
|
11
|
+
return new Scope(hash);
|
|
12
|
+
}
|
|
13
|
+
function createBunjaStore() {
|
|
14
|
+
return new BunjaStore();
|
|
15
|
+
}
|
|
16
|
+
function invalidUse() {
|
|
17
|
+
throw new Error("`bunja.use` can only be used inside a bunja init function.");
|
|
18
|
+
}
|
|
19
|
+
function invalidEffect() {
|
|
20
|
+
throw new Error("`bunja.effect` can only be used inside a bunja init function.");
|
|
21
|
+
}
|
|
22
|
+
var BunjaStore = class {
|
|
23
|
+
#bunjas = {};
|
|
24
|
+
#scopes = new Map();
|
|
25
|
+
#bakingContext;
|
|
26
|
+
dispose() {
|
|
27
|
+
for (const instance of Object.values(this.#bunjas)) instance.dispose();
|
|
28
|
+
for (const instanceMap of Object.values(this.#scopes)) for (const instance of instanceMap.values()) instance.dispose();
|
|
29
|
+
this.#bunjas = {};
|
|
30
|
+
this.#scopes = new Map();
|
|
31
|
+
}
|
|
32
|
+
get(bunja$1, readScope) {
|
|
33
|
+
const originalUse = bunjaFn.use;
|
|
34
|
+
try {
|
|
35
|
+
const { bunjaInstance, bunjaInstanceMap, scopeInstanceMap } = bunja$1.baked ? this.#getBaked(bunja$1, readScope) : this.#getUnbaked(bunja$1, readScope);
|
|
36
|
+
return {
|
|
37
|
+
value: bunjaInstance.value,
|
|
38
|
+
mount: () => {
|
|
39
|
+
bunjaInstanceMap.forEach((instance) => instance.add());
|
|
40
|
+
bunjaInstance.add();
|
|
41
|
+
scopeInstanceMap.forEach((instance) => instance.add());
|
|
42
|
+
const unmount = () => {
|
|
43
|
+
setTimeout(() => {
|
|
44
|
+
bunjaInstanceMap.forEach((instance) => instance.sub());
|
|
45
|
+
bunjaInstance.sub();
|
|
46
|
+
scopeInstanceMap.forEach((instance) => instance.sub());
|
|
47
|
+
});
|
|
48
|
+
};
|
|
49
|
+
return unmount;
|
|
50
|
+
},
|
|
51
|
+
deps: Array.from(scopeInstanceMap.values()).map(({ value }) => value)
|
|
52
|
+
};
|
|
53
|
+
} finally {
|
|
54
|
+
bunjaFn.use = originalUse;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
#getBaked(bunja$1, readScope) {
|
|
58
|
+
const scopeInstanceMap = new Map(bunja$1.relatedScopes.map((scope) => [scope, this.#getScopeInstance(scope, readScope(scope))]));
|
|
59
|
+
const bunjaInstanceMap = new Map(bunja$1.relatedBunjas.map((relatedBunja) => [relatedBunja, this.#getBunjaInstance(relatedBunja, scopeInstanceMap)]));
|
|
60
|
+
bunjaFn.use = (dep) => {
|
|
61
|
+
if (dep instanceof Bunja) return bunjaInstanceMap.get(dep).value;
|
|
62
|
+
if (dep instanceof Scope) return scopeInstanceMap.get(dep).value;
|
|
63
|
+
throw new Error("`bunja.use` can only be used with Bunja or Scope.");
|
|
64
|
+
};
|
|
65
|
+
const bunjaInstance = this.#getBunjaInstance(bunja$1, scopeInstanceMap);
|
|
66
|
+
return {
|
|
67
|
+
bunjaInstance,
|
|
68
|
+
bunjaInstanceMap,
|
|
69
|
+
scopeInstanceMap
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
#getUnbaked(bunja$1, readScope) {
|
|
73
|
+
const bunjaInstanceMap = new Map();
|
|
74
|
+
const scopeInstanceMap = new Map();
|
|
75
|
+
function getUse(map, addDep, getInstance) {
|
|
76
|
+
return (dep) => {
|
|
77
|
+
const d = dep;
|
|
78
|
+
addDep(d);
|
|
79
|
+
if (map.has(d)) return map.get(d).value;
|
|
80
|
+
const instance = getInstance(d);
|
|
81
|
+
map.set(d, instance);
|
|
82
|
+
return instance.value;
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
const useScope = getUse(scopeInstanceMap, (dep) => this.#bakingContext.currentBunja.addScope(dep), (dep) => this.#getScopeInstance(dep, readScope(dep)));
|
|
86
|
+
const useBunja = getUse(bunjaInstanceMap, (dep) => this.#bakingContext.currentBunja.addParent(dep), (dep) => {
|
|
87
|
+
if (dep.baked) for (const scope of dep.relatedScopes) useScope(scope);
|
|
88
|
+
return this.#getBunjaInstance(dep, scopeInstanceMap);
|
|
89
|
+
});
|
|
90
|
+
bunjaFn.use = (dep) => {
|
|
91
|
+
if (dep instanceof Bunja) return useBunja(dep);
|
|
92
|
+
if (dep instanceof Scope) return useScope(dep);
|
|
93
|
+
throw new Error("`bunja.use` can only be used with Bunja or Scope.");
|
|
94
|
+
};
|
|
95
|
+
try {
|
|
96
|
+
this.#bakingContext = { currentBunja: bunja$1 };
|
|
97
|
+
const bunjaInstance = this.#getBunjaInstance(bunja$1, scopeInstanceMap);
|
|
98
|
+
return {
|
|
99
|
+
bunjaInstance,
|
|
100
|
+
bunjaInstanceMap,
|
|
101
|
+
scopeInstanceMap
|
|
102
|
+
};
|
|
103
|
+
} finally {
|
|
104
|
+
this.#bakingContext = undefined;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
#getBunjaInstance(bunja$1, scopeInstanceMap) {
|
|
108
|
+
const originalEffect = bunjaFn.effect;
|
|
109
|
+
const prevBunja = this.#bakingContext?.currentBunja;
|
|
110
|
+
try {
|
|
111
|
+
const effects = [];
|
|
112
|
+
bunjaFn.effect = (callback) => {
|
|
113
|
+
effects.push(callback);
|
|
114
|
+
};
|
|
115
|
+
if (this.#bakingContext) this.#bakingContext.currentBunja = bunja$1;
|
|
116
|
+
if (bunja$1.baked) {
|
|
117
|
+
const id = bunja$1.calcInstanceId(scopeInstanceMap);
|
|
118
|
+
if (id in this.#bunjas) return this.#bunjas[id];
|
|
119
|
+
const bunjaInstanceValue = bunja$1.init();
|
|
120
|
+
return this.#createBunjaInstance(id, bunjaInstanceValue, effects);
|
|
121
|
+
} else {
|
|
122
|
+
const bunjaInstanceValue = bunja$1.init();
|
|
123
|
+
bunja$1.bake();
|
|
124
|
+
const id = bunja$1.calcInstanceId(scopeInstanceMap);
|
|
125
|
+
return this.#createBunjaInstance(id, bunjaInstanceValue, effects);
|
|
126
|
+
}
|
|
127
|
+
} finally {
|
|
128
|
+
bunjaFn.effect = originalEffect;
|
|
129
|
+
if (this.#bakingContext) this.#bakingContext.currentBunja = prevBunja;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
#getScopeInstance(scope, value) {
|
|
133
|
+
const key = scope.hash(value);
|
|
134
|
+
const instanceMap = this.#scopes.get(scope) ?? this.#scopes.set(scope, new Map()).get(scope);
|
|
135
|
+
return instanceMap.get(key) ?? instanceMap.set(key, new ScopeInstance(value, () => instanceMap.delete(key))).get(key);
|
|
136
|
+
}
|
|
137
|
+
#createBunjaInstance(id, value, effects) {
|
|
138
|
+
const effect = () => {
|
|
139
|
+
const cleanups = effects.map((effect$1) => effect$1()).filter(Boolean);
|
|
140
|
+
return () => cleanups.forEach((cleanup) => cleanup());
|
|
141
|
+
};
|
|
142
|
+
const dispose = () => delete this.#bunjas[id];
|
|
143
|
+
const bunjaInstance = new BunjaInstance(id, value, effect, dispose);
|
|
144
|
+
this.#bunjas[id] = bunjaInstance;
|
|
145
|
+
return bunjaInstance;
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
var Bunja = class Bunja {
|
|
149
|
+
static counter = 0;
|
|
150
|
+
id = String(Bunja.counter++);
|
|
151
|
+
debugLabel = "";
|
|
152
|
+
#phase = {
|
|
153
|
+
baked: false,
|
|
154
|
+
parents: new Set(),
|
|
155
|
+
scopes: new Set()
|
|
156
|
+
};
|
|
157
|
+
constructor(init) {
|
|
158
|
+
this.init = init;
|
|
159
|
+
}
|
|
160
|
+
get baked() {
|
|
161
|
+
return this.#phase.baked;
|
|
162
|
+
}
|
|
163
|
+
get parents() {
|
|
164
|
+
if (this.#phase.baked) return this.#phase.parents;
|
|
165
|
+
return Array.from(this.#phase.parents);
|
|
166
|
+
}
|
|
167
|
+
get relatedBunjas() {
|
|
168
|
+
if (!this.#phase.baked) throw new Error("Bunja is not baked yet.");
|
|
169
|
+
return this.#phase.relatedBunjas;
|
|
170
|
+
}
|
|
171
|
+
get relatedScopes() {
|
|
172
|
+
if (!this.#phase.baked) throw new Error("Bunja is not baked yet.");
|
|
173
|
+
return this.#phase.relatedScopes;
|
|
174
|
+
}
|
|
175
|
+
addParent(bunja$1) {
|
|
176
|
+
if (this.#phase.baked) return;
|
|
177
|
+
this.#phase.parents.add(bunja$1);
|
|
178
|
+
}
|
|
179
|
+
addScope(scope) {
|
|
180
|
+
if (this.#phase.baked) return;
|
|
181
|
+
this.#phase.scopes.add(scope);
|
|
182
|
+
}
|
|
183
|
+
bake() {
|
|
184
|
+
if (this.#phase.baked) throw new Error("Bunja is already baked.");
|
|
185
|
+
const scopes = this.#phase.scopes;
|
|
186
|
+
const parents = this.parents;
|
|
187
|
+
const relatedBunjas = toposort(parents);
|
|
188
|
+
const relatedScopes = Array.from(new Set([...relatedBunjas.flatMap((bunja$1) => bunja$1.relatedScopes), ...scopes]));
|
|
189
|
+
this.#phase = {
|
|
190
|
+
baked: true,
|
|
191
|
+
parents,
|
|
192
|
+
relatedBunjas,
|
|
193
|
+
relatedScopes
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
calcInstanceId(scopeInstanceMap) {
|
|
197
|
+
const scopeInstanceIds = this.relatedScopes.map((scope) => scopeInstanceMap.get(scope).id);
|
|
198
|
+
return `${this.id}:${scopeInstanceIds.join(",")}`;
|
|
199
|
+
}
|
|
200
|
+
toString() {
|
|
201
|
+
const { id, debugLabel } = this;
|
|
202
|
+
return `[Bunja:${id}${debugLabel && ` - ${debugLabel}`}]`;
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
var Scope = class Scope {
|
|
206
|
+
static counter = 0;
|
|
207
|
+
id = String(Scope.counter++);
|
|
208
|
+
debugLabel = "";
|
|
209
|
+
constructor(hash = Scope.identity) {
|
|
210
|
+
this.hash = hash;
|
|
211
|
+
}
|
|
212
|
+
static identity(x) {
|
|
213
|
+
return x;
|
|
214
|
+
}
|
|
215
|
+
toString() {
|
|
216
|
+
const { id, debugLabel } = this;
|
|
217
|
+
return `[Scope:${id}${debugLabel && ` - ${debugLabel}`}]`;
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
const noop = () => {};
|
|
221
|
+
var RefCounter = class {
|
|
222
|
+
#count = 0;
|
|
223
|
+
add() {
|
|
224
|
+
++this.#count;
|
|
225
|
+
}
|
|
226
|
+
sub() {
|
|
227
|
+
--this.#count;
|
|
228
|
+
if (this.#count < 1) {
|
|
229
|
+
this.dispose();
|
|
230
|
+
this.dispose = noop;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
};
|
|
234
|
+
var BunjaInstance = class extends RefCounter {
|
|
235
|
+
#cleanup;
|
|
236
|
+
constructor(id, value, effect, _dispose) {
|
|
237
|
+
super();
|
|
238
|
+
this.id = id;
|
|
239
|
+
this.value = value;
|
|
240
|
+
this.effect = effect;
|
|
241
|
+
this._dispose = _dispose;
|
|
242
|
+
}
|
|
243
|
+
dispose() {
|
|
244
|
+
this.#cleanup?.();
|
|
245
|
+
this._dispose();
|
|
246
|
+
}
|
|
247
|
+
add() {
|
|
248
|
+
this.#cleanup ??= this.effect() ?? noop;
|
|
249
|
+
super.add();
|
|
250
|
+
}
|
|
251
|
+
};
|
|
252
|
+
var ScopeInstance = class ScopeInstance extends RefCounter {
|
|
253
|
+
static counter = 0;
|
|
254
|
+
id = String(ScopeInstance.counter++);
|
|
255
|
+
constructor(value, dispose) {
|
|
256
|
+
super();
|
|
257
|
+
this.value = value;
|
|
258
|
+
this.dispose = dispose;
|
|
259
|
+
}
|
|
260
|
+
};
|
|
261
|
+
function toposort(nodes) {
|
|
262
|
+
const visited = new Set();
|
|
263
|
+
const result = [];
|
|
264
|
+
function visit(current) {
|
|
265
|
+
if (visited.has(current)) return;
|
|
266
|
+
visited.add(current);
|
|
267
|
+
for (const parent of current.parents) visit(parent);
|
|
268
|
+
result.push(current);
|
|
269
|
+
}
|
|
270
|
+
for (const node of nodes) visit(node);
|
|
271
|
+
return result;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
//#endregion
|
|
275
|
+
Object.defineProperty(exports, 'Bunja', {
|
|
276
|
+
enumerable: true,
|
|
277
|
+
get: function () {
|
|
278
|
+
return Bunja;
|
|
279
|
+
}
|
|
280
|
+
});Object.defineProperty(exports, 'BunjaStore', {
|
|
281
|
+
enumerable: true,
|
|
282
|
+
get: function () {
|
|
283
|
+
return BunjaStore;
|
|
284
|
+
}
|
|
285
|
+
});Object.defineProperty(exports, 'Scope', {
|
|
286
|
+
enumerable: true,
|
|
287
|
+
get: function () {
|
|
288
|
+
return Scope;
|
|
289
|
+
}
|
|
290
|
+
});Object.defineProperty(exports, 'bunja', {
|
|
291
|
+
enumerable: true,
|
|
292
|
+
get: function () {
|
|
293
|
+
return bunja;
|
|
294
|
+
}
|
|
295
|
+
});Object.defineProperty(exports, 'createBunjaStore', {
|
|
296
|
+
enumerable: true,
|
|
297
|
+
get: function () {
|
|
298
|
+
return createBunjaStore;
|
|
299
|
+
}
|
|
300
|
+
});Object.defineProperty(exports, 'createScope', {
|
|
301
|
+
enumerable: true,
|
|
302
|
+
get: function () {
|
|
303
|
+
return createScope;
|
|
304
|
+
}
|
|
305
|
+
});
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
|
|
2
|
+
//#region bunja.ts
|
|
3
|
+
const bunja = bunjaFn;
|
|
4
|
+
function bunjaFn(init) {
|
|
5
|
+
return new Bunja(init);
|
|
6
|
+
}
|
|
7
|
+
bunjaFn.use = invalidUse;
|
|
8
|
+
bunjaFn.effect = invalidEffect;
|
|
9
|
+
function createScope(hash) {
|
|
10
|
+
return new Scope(hash);
|
|
11
|
+
}
|
|
12
|
+
function createBunjaStore() {
|
|
13
|
+
return new BunjaStore();
|
|
14
|
+
}
|
|
15
|
+
function invalidUse() {
|
|
16
|
+
throw new Error("`bunja.use` can only be used inside a bunja init function.");
|
|
17
|
+
}
|
|
18
|
+
function invalidEffect() {
|
|
19
|
+
throw new Error("`bunja.effect` can only be used inside a bunja init function.");
|
|
20
|
+
}
|
|
21
|
+
var BunjaStore = class {
|
|
22
|
+
#bunjas = {};
|
|
23
|
+
#scopes = new Map();
|
|
24
|
+
#bakingContext;
|
|
25
|
+
dispose() {
|
|
26
|
+
for (const instance of Object.values(this.#bunjas)) instance.dispose();
|
|
27
|
+
for (const instanceMap of Object.values(this.#scopes)) for (const instance of instanceMap.values()) instance.dispose();
|
|
28
|
+
this.#bunjas = {};
|
|
29
|
+
this.#scopes = new Map();
|
|
30
|
+
}
|
|
31
|
+
get(bunja$1, readScope) {
|
|
32
|
+
const originalUse = bunjaFn.use;
|
|
33
|
+
try {
|
|
34
|
+
const { bunjaInstance, bunjaInstanceMap, scopeInstanceMap } = bunja$1.baked ? this.#getBaked(bunja$1, readScope) : this.#getUnbaked(bunja$1, readScope);
|
|
35
|
+
return {
|
|
36
|
+
value: bunjaInstance.value,
|
|
37
|
+
mount: () => {
|
|
38
|
+
bunjaInstanceMap.forEach((instance) => instance.add());
|
|
39
|
+
bunjaInstance.add();
|
|
40
|
+
scopeInstanceMap.forEach((instance) => instance.add());
|
|
41
|
+
const unmount = () => {
|
|
42
|
+
setTimeout(() => {
|
|
43
|
+
bunjaInstanceMap.forEach((instance) => instance.sub());
|
|
44
|
+
bunjaInstance.sub();
|
|
45
|
+
scopeInstanceMap.forEach((instance) => instance.sub());
|
|
46
|
+
});
|
|
47
|
+
};
|
|
48
|
+
return unmount;
|
|
49
|
+
},
|
|
50
|
+
deps: Array.from(scopeInstanceMap.values()).map(({ value }) => value)
|
|
51
|
+
};
|
|
52
|
+
} finally {
|
|
53
|
+
bunjaFn.use = originalUse;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
#getBaked(bunja$1, readScope) {
|
|
57
|
+
const scopeInstanceMap = new Map(bunja$1.relatedScopes.map((scope) => [scope, this.#getScopeInstance(scope, readScope(scope))]));
|
|
58
|
+
const bunjaInstanceMap = new Map(bunja$1.relatedBunjas.map((relatedBunja) => [relatedBunja, this.#getBunjaInstance(relatedBunja, scopeInstanceMap)]));
|
|
59
|
+
bunjaFn.use = (dep) => {
|
|
60
|
+
if (dep instanceof Bunja) return bunjaInstanceMap.get(dep).value;
|
|
61
|
+
if (dep instanceof Scope) return scopeInstanceMap.get(dep).value;
|
|
62
|
+
throw new Error("`bunja.use` can only be used with Bunja or Scope.");
|
|
63
|
+
};
|
|
64
|
+
const bunjaInstance = this.#getBunjaInstance(bunja$1, scopeInstanceMap);
|
|
65
|
+
return {
|
|
66
|
+
bunjaInstance,
|
|
67
|
+
bunjaInstanceMap,
|
|
68
|
+
scopeInstanceMap
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
#getUnbaked(bunja$1, readScope) {
|
|
72
|
+
const bunjaInstanceMap = new Map();
|
|
73
|
+
const scopeInstanceMap = new Map();
|
|
74
|
+
function getUse(map, addDep, getInstance) {
|
|
75
|
+
return (dep) => {
|
|
76
|
+
const d = dep;
|
|
77
|
+
addDep(d);
|
|
78
|
+
if (map.has(d)) return map.get(d).value;
|
|
79
|
+
const instance = getInstance(d);
|
|
80
|
+
map.set(d, instance);
|
|
81
|
+
return instance.value;
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
const useScope = getUse(scopeInstanceMap, (dep) => this.#bakingContext.currentBunja.addScope(dep), (dep) => this.#getScopeInstance(dep, readScope(dep)));
|
|
85
|
+
const useBunja = getUse(bunjaInstanceMap, (dep) => this.#bakingContext.currentBunja.addParent(dep), (dep) => {
|
|
86
|
+
if (dep.baked) for (const scope of dep.relatedScopes) useScope(scope);
|
|
87
|
+
return this.#getBunjaInstance(dep, scopeInstanceMap);
|
|
88
|
+
});
|
|
89
|
+
bunjaFn.use = (dep) => {
|
|
90
|
+
if (dep instanceof Bunja) return useBunja(dep);
|
|
91
|
+
if (dep instanceof Scope) return useScope(dep);
|
|
92
|
+
throw new Error("`bunja.use` can only be used with Bunja or Scope.");
|
|
93
|
+
};
|
|
94
|
+
try {
|
|
95
|
+
this.#bakingContext = { currentBunja: bunja$1 };
|
|
96
|
+
const bunjaInstance = this.#getBunjaInstance(bunja$1, scopeInstanceMap);
|
|
97
|
+
return {
|
|
98
|
+
bunjaInstance,
|
|
99
|
+
bunjaInstanceMap,
|
|
100
|
+
scopeInstanceMap
|
|
101
|
+
};
|
|
102
|
+
} finally {
|
|
103
|
+
this.#bakingContext = undefined;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
#getBunjaInstance(bunja$1, scopeInstanceMap) {
|
|
107
|
+
const originalEffect = bunjaFn.effect;
|
|
108
|
+
const prevBunja = this.#bakingContext?.currentBunja;
|
|
109
|
+
try {
|
|
110
|
+
const effects = [];
|
|
111
|
+
bunjaFn.effect = (callback) => {
|
|
112
|
+
effects.push(callback);
|
|
113
|
+
};
|
|
114
|
+
if (this.#bakingContext) this.#bakingContext.currentBunja = bunja$1;
|
|
115
|
+
if (bunja$1.baked) {
|
|
116
|
+
const id = bunja$1.calcInstanceId(scopeInstanceMap);
|
|
117
|
+
if (id in this.#bunjas) return this.#bunjas[id];
|
|
118
|
+
const bunjaInstanceValue = bunja$1.init();
|
|
119
|
+
return this.#createBunjaInstance(id, bunjaInstanceValue, effects);
|
|
120
|
+
} else {
|
|
121
|
+
const bunjaInstanceValue = bunja$1.init();
|
|
122
|
+
bunja$1.bake();
|
|
123
|
+
const id = bunja$1.calcInstanceId(scopeInstanceMap);
|
|
124
|
+
return this.#createBunjaInstance(id, bunjaInstanceValue, effects);
|
|
125
|
+
}
|
|
126
|
+
} finally {
|
|
127
|
+
bunjaFn.effect = originalEffect;
|
|
128
|
+
if (this.#bakingContext) this.#bakingContext.currentBunja = prevBunja;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
#getScopeInstance(scope, value) {
|
|
132
|
+
const key = scope.hash(value);
|
|
133
|
+
const instanceMap = this.#scopes.get(scope) ?? this.#scopes.set(scope, new Map()).get(scope);
|
|
134
|
+
return instanceMap.get(key) ?? instanceMap.set(key, new ScopeInstance(value, () => instanceMap.delete(key))).get(key);
|
|
135
|
+
}
|
|
136
|
+
#createBunjaInstance(id, value, effects) {
|
|
137
|
+
const effect = () => {
|
|
138
|
+
const cleanups = effects.map((effect$1) => effect$1()).filter(Boolean);
|
|
139
|
+
return () => cleanups.forEach((cleanup) => cleanup());
|
|
140
|
+
};
|
|
141
|
+
const dispose = () => delete this.#bunjas[id];
|
|
142
|
+
const bunjaInstance = new BunjaInstance(id, value, effect, dispose);
|
|
143
|
+
this.#bunjas[id] = bunjaInstance;
|
|
144
|
+
return bunjaInstance;
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
var Bunja = class Bunja {
|
|
148
|
+
static counter = 0;
|
|
149
|
+
id = String(Bunja.counter++);
|
|
150
|
+
debugLabel = "";
|
|
151
|
+
#phase = {
|
|
152
|
+
baked: false,
|
|
153
|
+
parents: new Set(),
|
|
154
|
+
scopes: new Set()
|
|
155
|
+
};
|
|
156
|
+
constructor(init) {
|
|
157
|
+
this.init = init;
|
|
158
|
+
}
|
|
159
|
+
get baked() {
|
|
160
|
+
return this.#phase.baked;
|
|
161
|
+
}
|
|
162
|
+
get parents() {
|
|
163
|
+
if (this.#phase.baked) return this.#phase.parents;
|
|
164
|
+
return Array.from(this.#phase.parents);
|
|
165
|
+
}
|
|
166
|
+
get relatedBunjas() {
|
|
167
|
+
if (!this.#phase.baked) throw new Error("Bunja is not baked yet.");
|
|
168
|
+
return this.#phase.relatedBunjas;
|
|
169
|
+
}
|
|
170
|
+
get relatedScopes() {
|
|
171
|
+
if (!this.#phase.baked) throw new Error("Bunja is not baked yet.");
|
|
172
|
+
return this.#phase.relatedScopes;
|
|
173
|
+
}
|
|
174
|
+
addParent(bunja$1) {
|
|
175
|
+
if (this.#phase.baked) return;
|
|
176
|
+
this.#phase.parents.add(bunja$1);
|
|
177
|
+
}
|
|
178
|
+
addScope(scope) {
|
|
179
|
+
if (this.#phase.baked) return;
|
|
180
|
+
this.#phase.scopes.add(scope);
|
|
181
|
+
}
|
|
182
|
+
bake() {
|
|
183
|
+
if (this.#phase.baked) throw new Error("Bunja is already baked.");
|
|
184
|
+
const scopes = this.#phase.scopes;
|
|
185
|
+
const parents = this.parents;
|
|
186
|
+
const relatedBunjas = toposort(parents);
|
|
187
|
+
const relatedScopes = Array.from(new Set([...relatedBunjas.flatMap((bunja$1) => bunja$1.relatedScopes), ...scopes]));
|
|
188
|
+
this.#phase = {
|
|
189
|
+
baked: true,
|
|
190
|
+
parents,
|
|
191
|
+
relatedBunjas,
|
|
192
|
+
relatedScopes
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
calcInstanceId(scopeInstanceMap) {
|
|
196
|
+
const scopeInstanceIds = this.relatedScopes.map((scope) => scopeInstanceMap.get(scope).id);
|
|
197
|
+
return `${this.id}:${scopeInstanceIds.join(",")}`;
|
|
198
|
+
}
|
|
199
|
+
toString() {
|
|
200
|
+
const { id, debugLabel } = this;
|
|
201
|
+
return `[Bunja:${id}${debugLabel && ` - ${debugLabel}`}]`;
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
var Scope = class Scope {
|
|
205
|
+
static counter = 0;
|
|
206
|
+
id = String(Scope.counter++);
|
|
207
|
+
debugLabel = "";
|
|
208
|
+
constructor(hash = Scope.identity) {
|
|
209
|
+
this.hash = hash;
|
|
210
|
+
}
|
|
211
|
+
static identity(x) {
|
|
212
|
+
return x;
|
|
213
|
+
}
|
|
214
|
+
toString() {
|
|
215
|
+
const { id, debugLabel } = this;
|
|
216
|
+
return `[Scope:${id}${debugLabel && ` - ${debugLabel}`}]`;
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
const noop = () => {};
|
|
220
|
+
var RefCounter = class {
|
|
221
|
+
#count = 0;
|
|
222
|
+
add() {
|
|
223
|
+
++this.#count;
|
|
224
|
+
}
|
|
225
|
+
sub() {
|
|
226
|
+
--this.#count;
|
|
227
|
+
if (this.#count < 1) {
|
|
228
|
+
this.dispose();
|
|
229
|
+
this.dispose = noop;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
var BunjaInstance = class extends RefCounter {
|
|
234
|
+
#cleanup;
|
|
235
|
+
constructor(id, value, effect, _dispose) {
|
|
236
|
+
super();
|
|
237
|
+
this.id = id;
|
|
238
|
+
this.value = value;
|
|
239
|
+
this.effect = effect;
|
|
240
|
+
this._dispose = _dispose;
|
|
241
|
+
}
|
|
242
|
+
dispose() {
|
|
243
|
+
this.#cleanup?.();
|
|
244
|
+
this._dispose();
|
|
245
|
+
}
|
|
246
|
+
add() {
|
|
247
|
+
this.#cleanup ??= this.effect() ?? noop;
|
|
248
|
+
super.add();
|
|
249
|
+
}
|
|
250
|
+
};
|
|
251
|
+
var ScopeInstance = class ScopeInstance extends RefCounter {
|
|
252
|
+
static counter = 0;
|
|
253
|
+
id = String(ScopeInstance.counter++);
|
|
254
|
+
constructor(value, dispose) {
|
|
255
|
+
super();
|
|
256
|
+
this.value = value;
|
|
257
|
+
this.dispose = dispose;
|
|
258
|
+
}
|
|
259
|
+
};
|
|
260
|
+
function toposort(nodes) {
|
|
261
|
+
const visited = new Set();
|
|
262
|
+
const result = [];
|
|
263
|
+
function visit(current) {
|
|
264
|
+
if (visited.has(current)) return;
|
|
265
|
+
visited.add(current);
|
|
266
|
+
for (const parent of current.parents) visit(parent);
|
|
267
|
+
result.push(current);
|
|
268
|
+
}
|
|
269
|
+
for (const node of nodes) visit(node);
|
|
270
|
+
return result;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
//#endregion
|
|
274
|
+
export { Bunja, BunjaStore, Scope, bunja, createBunjaStore, createScope };
|
package/dist/bunja.cjs
CHANGED
package/dist/bunja.d.cts
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
|
|
3
|
-
|
|
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
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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(
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
|
25
|
-
readonly id:
|
|
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
|
|
31
|
-
|
|
50
|
+
export type HashFn<T> = (value: T) => unknown;
|
|
51
|
+
declare abstract class RefCounter {
|
|
32
52
|
#private;
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
deps: any[];
|
|
37
|
-
};
|
|
53
|
+
abstract dispose(): void;
|
|
54
|
+
add(): void;
|
|
55
|
+
sub(): void;
|
|
38
56
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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 {};
|