bunja 2.1.0 → 3.0.0-alpha.3
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 +135 -4
- package/bunja.ts +847 -222
- package/deno.json +1 -1
- package/dist/bunja-BJSmIdkQ.cjs +682 -0
- package/dist/{bunja-BKpQTG04.d.cts → bunja-BxbzuHdH.d.cts} +73 -19
- package/dist/bunja-CtHOS4_Q.js +634 -0
- package/dist/{bunja-B_HNgDan.d.ts → bunja-DEFeIlpt.d.ts} +73 -19
- package/dist/bunja.cjs +1 -1
- package/dist/bunja.d.cts +2 -2
- package/dist/bunja.d.ts +2 -2
- package/dist/bunja.js +1 -1
- package/dist/react.cjs +16 -4
- package/dist/react.d.cts +3 -2
- package/dist/react.d.ts +3 -2
- package/dist/react.js +17 -4
- package/dist/solid.cjs +1 -1
- package/dist/solid.d.cts +2 -2
- package/dist/solid.d.ts +2 -2
- package/dist/solid.js +1 -1
- package/package.json +2 -2
- package/react.ts +34 -8
- package/solid.ts +4 -3
- package/test.ts +434 -4
- package/dist/bunja-BOUkMIz6.js +0 -396
- package/dist/bunja-DFFVW7Gi.cjs +0 -444
|
@@ -0,0 +1,634 @@
|
|
|
1
|
+
//#region bunja.ts
|
|
2
|
+
const __DEV__ = process.env.NODE_ENV !== "production";
|
|
3
|
+
const bunja = bunjaFn;
|
|
4
|
+
function bunjaFn(init) {
|
|
5
|
+
return new Bunja(() => init(), NO_SEED);
|
|
6
|
+
}
|
|
7
|
+
const NO_SEED = Symbol("bunja.noSeed");
|
|
8
|
+
bunjaFn.withSeed = function withSeed(defaultSeed, init) {
|
|
9
|
+
return new Bunja(init, defaultSeed);
|
|
10
|
+
};
|
|
11
|
+
bunjaFn.use = ((dep, scopeValuePairs) => getCurrentFrame("`bunja.use`").use(dep, scopeValuePairs));
|
|
12
|
+
bunjaFn.will = ((dep, scopeValuePairs) => getCurrentFrame("`bunja.will`").will(dep, scopeValuePairs));
|
|
13
|
+
bunjaFn.effect = ((callback) => getCurrentFrame("`bunja.effect`").effect(callback));
|
|
14
|
+
function createScope(hash) {
|
|
15
|
+
return new Scope(hash);
|
|
16
|
+
}
|
|
17
|
+
function createBunjaStore(config) {
|
|
18
|
+
const { wrapInstance = defaultWrapInstanceFn } = config ?? {};
|
|
19
|
+
const store = new BunjaStore();
|
|
20
|
+
store.wrapInstance = wrapInstance;
|
|
21
|
+
return store;
|
|
22
|
+
}
|
|
23
|
+
const frameStack = [];
|
|
24
|
+
function getCurrentFrame(api) {
|
|
25
|
+
const frame = frameStack[frameStack.length - 1];
|
|
26
|
+
if (!frame) throw new Error(`${api} can only be used inside a bunja init function.`);
|
|
27
|
+
return frame;
|
|
28
|
+
}
|
|
29
|
+
function runWithFrame(frame, fn) {
|
|
30
|
+
frameStack.push(frame);
|
|
31
|
+
try {
|
|
32
|
+
return fn();
|
|
33
|
+
} finally {
|
|
34
|
+
frameStack.pop();
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
const defaultWrapInstanceFn = (fn) => fn(noop);
|
|
38
|
+
function normalizeBunjaRuntimeRef(bunjaOrRef, scopeValuePairs = []) {
|
|
39
|
+
if (bunjaOrRef instanceof Bunja) return {
|
|
40
|
+
bunja: bunjaOrRef,
|
|
41
|
+
scopeValuePairs,
|
|
42
|
+
seed: bunjaOrRef.defaultSeed
|
|
43
|
+
};
|
|
44
|
+
const { bunja: bunja$1, with: refScopeValuePairs = [] } = bunjaOrRef;
|
|
45
|
+
return {
|
|
46
|
+
bunja: bunja$1,
|
|
47
|
+
scopeValuePairs: refScopeValuePairs,
|
|
48
|
+
seed: "seed" in bunjaOrRef ? bunjaOrRef.seed : bunja$1.defaultSeed
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
function normalizeBunjaPrebakeRef(bunjaOrRef) {
|
|
52
|
+
if (bunjaOrRef instanceof Bunja) return {
|
|
53
|
+
bunja: bunjaOrRef,
|
|
54
|
+
scopeValuePairs: []
|
|
55
|
+
};
|
|
56
|
+
if ("seed" in bunjaOrRef) throw new Error("A bunja seed cannot be provided to `store.prebake`.");
|
|
57
|
+
const { bunja: bunja$1, with: refScopeValuePairs = [] } = bunjaOrRef;
|
|
58
|
+
return {
|
|
59
|
+
bunja: bunja$1,
|
|
60
|
+
scopeValuePairs: refScopeValuePairs
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
function toBunjaGraphRef(bunjaRef) {
|
|
64
|
+
return {
|
|
65
|
+
bunja: bunjaRef.bunja,
|
|
66
|
+
scopeValuePairs: bunjaRef.scopeValuePairs
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
function isBunjaRef(value) {
|
|
70
|
+
return typeof value === "object" && value !== null && "bunja" in value && value.bunja instanceof Bunja;
|
|
71
|
+
}
|
|
72
|
+
function getBoundScopeSet(scopeValuePairs) {
|
|
73
|
+
return new Set(scopeValuePairs.map(([scope]) => scope));
|
|
74
|
+
}
|
|
75
|
+
function getScopeInstances(scopes, scopeInstanceMap, excludeScopes = /* @__PURE__ */ new Set()) {
|
|
76
|
+
return scopes.filter((scope) => !excludeScopes.has(scope)).map((scope) => scopeInstanceMap.get(scope));
|
|
77
|
+
}
|
|
78
|
+
function dedupeScopeInstances(scopeInstances) {
|
|
79
|
+
const seen = /* @__PURE__ */ new Set();
|
|
80
|
+
const result = [];
|
|
81
|
+
for (const scopeInstance of scopeInstances) {
|
|
82
|
+
if (seen.has(scopeInstance.id)) continue;
|
|
83
|
+
seen.add(scopeInstance.id);
|
|
84
|
+
result.push(scopeInstance);
|
|
85
|
+
}
|
|
86
|
+
return result;
|
|
87
|
+
}
|
|
88
|
+
function dedupeBunjas(bunjas) {
|
|
89
|
+
return Array.from(new Set(bunjas));
|
|
90
|
+
}
|
|
91
|
+
function addUniqueBunjaRef(refs, ref) {
|
|
92
|
+
if (!refs.includes(ref)) refs.push(ref);
|
|
93
|
+
}
|
|
94
|
+
function createBunjaPrebakeContext() {
|
|
95
|
+
return {
|
|
96
|
+
inProgressBunjas: /* @__PURE__ */ new Set(),
|
|
97
|
+
values: /* @__PURE__ */ new Map()
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
var BunjaStore = class BunjaStore {
|
|
101
|
+
static counter = 0;
|
|
102
|
+
id = String(BunjaStore.counter++);
|
|
103
|
+
#bunjas = {};
|
|
104
|
+
#bunjaBuckets = /* @__PURE__ */ new Map();
|
|
105
|
+
#scopes = /* @__PURE__ */ new Map();
|
|
106
|
+
wrapInstance = defaultWrapInstanceFn;
|
|
107
|
+
constructor() {
|
|
108
|
+
if (__DEV__) devtoolsGlobalHook.emit("storeCreated", { storeId: this.id });
|
|
109
|
+
}
|
|
110
|
+
get _internalState() {
|
|
111
|
+
if (__DEV__) return {
|
|
112
|
+
bunjas: this.#bunjas,
|
|
113
|
+
scopes: this.#scopes,
|
|
114
|
+
get instantiating() {
|
|
115
|
+
return frameStack.length > 0;
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
dispose() {
|
|
120
|
+
for (const instance of Object.values(this.#bunjas)) instance.dispose();
|
|
121
|
+
for (const instanceMap of this.#scopes.values()) for (const instance of instanceMap.values()) instance.dispose();
|
|
122
|
+
this.#bunjas = {};
|
|
123
|
+
this.#bunjaBuckets = /* @__PURE__ */ new Map();
|
|
124
|
+
this.#scopes = /* @__PURE__ */ new Map();
|
|
125
|
+
if (__DEV__) devtoolsGlobalHook.emit("storeDisposed", { storeId: this.id });
|
|
126
|
+
}
|
|
127
|
+
get(bunjaOrRef, readScope) {
|
|
128
|
+
const bunjaRef = normalizeBunjaRuntimeRef(bunjaOrRef);
|
|
129
|
+
const resolved = this.#resolveBunjaRef(toBunjaGraphRef(bunjaRef), readScope, /* @__PURE__ */ new Set(), bunjaRef.seed);
|
|
130
|
+
const result = {
|
|
131
|
+
value: resolved.value,
|
|
132
|
+
mount: resolved.mount,
|
|
133
|
+
deps: resolved.deps.map(({ value }) => value)
|
|
134
|
+
};
|
|
135
|
+
if (__DEV__) {
|
|
136
|
+
result.bunjaInstance = resolved.instance;
|
|
137
|
+
devtoolsGlobalHook.emit("getCalled", {
|
|
138
|
+
storeId: this.id,
|
|
139
|
+
bunjaInstanceId: resolved.instance.id
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
return result;
|
|
143
|
+
}
|
|
144
|
+
prebake(bunjaOrRef, readScope) {
|
|
145
|
+
const bunjaRef = normalizeBunjaPrebakeRef(bunjaOrRef);
|
|
146
|
+
this.#prebakeBunjaRef(bunjaRef, readScope, createBunjaPrebakeContext());
|
|
147
|
+
return {
|
|
148
|
+
relatedBunjas: bunjaRef.bunja.relatedBunjas,
|
|
149
|
+
requiredScopes: bunjaRef.bunja.requiredScopes
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
#resolveBunjaRef(bunjaRef, readScope, inProgressBunjas, seed = bunjaRef.bunja.defaultSeed) {
|
|
153
|
+
const { bunja: bunja$1 } = bunjaRef;
|
|
154
|
+
if (inProgressBunjas.has(bunja$1)) throw new Error("Circular bunja dependency detected.");
|
|
155
|
+
const resolvedReadScope = bunjaRef.scopeValuePairs.length > 0 ? createReadScopeFn(bunjaRef.scopeValuePairs, readScope) : readScope;
|
|
156
|
+
inProgressBunjas.add(bunja$1);
|
|
157
|
+
try {
|
|
158
|
+
if (!bunja$1.baked) return this.#createResolvedBunja(bunjaRef, resolvedReadScope, inProgressBunjas, seed);
|
|
159
|
+
const scopeInstanceMap = this.#resolveScopeInstanceMap(bunja$1, resolvedReadScope);
|
|
160
|
+
const boundScopes = getBoundScopeSet(bunjaRef.scopeValuePairs);
|
|
161
|
+
const scopeInstances = getScopeInstances(bunja$1.requiredScopes, scopeInstanceMap);
|
|
162
|
+
const directDeps = getScopeInstances(bunja$1.requiredScopes, scopeInstanceMap, boundScopes);
|
|
163
|
+
const baseId = bunja$1.calcBaseInstanceId(scopeInstanceMap);
|
|
164
|
+
const bucket = this.#bunjaBuckets.get(baseId);
|
|
165
|
+
if (bucket) for (const candidateId of Array.from(bucket)) {
|
|
166
|
+
const candidate = this.#bunjas[candidateId];
|
|
167
|
+
if (!candidate) {
|
|
168
|
+
bucket.delete(candidateId);
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
const activeDeps = this.#resolveActiveDependencyRecipe(candidate.recipe, resolvedReadScope, inProgressBunjas);
|
|
172
|
+
const currentId = bunja$1.calcInstanceId(scopeInstanceMap, activeDeps.activeDependencyIds);
|
|
173
|
+
const instance = currentId === candidate.id ? candidate : this.#bunjas[currentId];
|
|
174
|
+
if (!instance) continue;
|
|
175
|
+
return this.#toResolvedBunja(instance, scopeInstances, directDeps, activeDeps.deps);
|
|
176
|
+
}
|
|
177
|
+
return this.#createResolvedBunja(bunjaRef, resolvedReadScope, inProgressBunjas, seed, scopeInstanceMap);
|
|
178
|
+
} finally {
|
|
179
|
+
inProgressBunjas.delete(bunja$1);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
#createResolvedBunja(bunjaRef, readScope, inProgressBunjas, seed, initialScopeInstanceMap = /* @__PURE__ */ new Map()) {
|
|
183
|
+
const { bunja: bunja$1 } = bunjaRef;
|
|
184
|
+
return this.wrapInstance((dispose) => {
|
|
185
|
+
let instanceCreated = false;
|
|
186
|
+
let disposed = false;
|
|
187
|
+
const disposeOnce = () => {
|
|
188
|
+
if (disposed) return;
|
|
189
|
+
disposed = true;
|
|
190
|
+
dispose();
|
|
191
|
+
};
|
|
192
|
+
try {
|
|
193
|
+
const frame = this.#createInitFrame(bunja$1, readScope, initialScopeInstanceMap, inProgressBunjas);
|
|
194
|
+
const value = runWithFrame(frame, () => bunja$1.init(seed));
|
|
195
|
+
if (!bunja$1.baked) bunja$1.bake();
|
|
196
|
+
this.#ensureRequiredScopeInstances(bunja$1, frame.scopeInstanceMap, readScope);
|
|
197
|
+
const boundScopes = getBoundScopeSet(bunjaRef.scopeValuePairs);
|
|
198
|
+
const scopeInstances = getScopeInstances(bunja$1.requiredScopes, frame.scopeInstanceMap);
|
|
199
|
+
const directDeps = getScopeInstances(bunja$1.requiredScopes, frame.scopeInstanceMap, boundScopes);
|
|
200
|
+
const baseId = bunja$1.calcBaseInstanceId(frame.scopeInstanceMap);
|
|
201
|
+
const id = bunja$1.calcInstanceId(frame.scopeInstanceMap, frame.activeDependencyIds);
|
|
202
|
+
const existing = this.#bunjas[id];
|
|
203
|
+
if (existing) {
|
|
204
|
+
disposeOnce();
|
|
205
|
+
return this.#toResolvedBunja(existing, scopeInstances, directDeps, frame.activeDependencyDeps);
|
|
206
|
+
}
|
|
207
|
+
const instance = this.#createBunjaInstance(id, baseId, value, Array.from(frame.activeDependencyMounts.values()), frame.effects, { activeDependencies: frame.activeDependencyRecipes }, dispose);
|
|
208
|
+
instanceCreated = true;
|
|
209
|
+
return this.#toResolvedBunja(instance, scopeInstances, directDeps, frame.activeDependencyDeps);
|
|
210
|
+
} finally {
|
|
211
|
+
if (!instanceCreated) disposeOnce();
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
#toResolvedBunja(instance, scopeInstances, directDeps, activeDependencyDeps) {
|
|
216
|
+
const deps = dedupeScopeInstances([...directDeps, ...activeDependencyDeps]);
|
|
217
|
+
return {
|
|
218
|
+
value: instance.value,
|
|
219
|
+
instance,
|
|
220
|
+
deps,
|
|
221
|
+
mount: () => {
|
|
222
|
+
for (const scopeInstance of scopeInstances) scopeInstance.add();
|
|
223
|
+
instance.add();
|
|
224
|
+
return () => {
|
|
225
|
+
instance.sub();
|
|
226
|
+
for (const scopeInstance of scopeInstances) scopeInstance.sub();
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
#createInitFrame(currentBunja, readScope, scopeInstanceMap, inProgressBunjas) {
|
|
232
|
+
const frame = {
|
|
233
|
+
currentBunja,
|
|
234
|
+
readScope,
|
|
235
|
+
scopeInstanceMap,
|
|
236
|
+
inProgressBunjas,
|
|
237
|
+
effects: [],
|
|
238
|
+
activeDependencyIds: /* @__PURE__ */ new Set(),
|
|
239
|
+
activeDependencyRecipes: [],
|
|
240
|
+
activeDependencyMounts: /* @__PURE__ */ new Map(),
|
|
241
|
+
activeDependencyDeps: [],
|
|
242
|
+
use: ((dep, scopeValuePairs) => {
|
|
243
|
+
if (dep instanceof Scope) return this.#useScopeInFrame(frame, dep);
|
|
244
|
+
if (dep instanceof Bunja || isBunjaRef(dep)) return this.#useBunjaDependencyInFrame(frame, normalizeBunjaRuntimeRef(dep, scopeValuePairs), "required");
|
|
245
|
+
throw new Error("`bunja.use` can only be used with Bunja or Scope.");
|
|
246
|
+
}),
|
|
247
|
+
will: ((dep, scopeValuePairs) => {
|
|
248
|
+
if (!(dep instanceof Bunja || isBunjaRef(dep))) throw new Error("`bunja.will` can only be used with Bunja.");
|
|
249
|
+
const bunjaRef = normalizeBunjaRuntimeRef(dep, scopeValuePairs);
|
|
250
|
+
currentBunja.addOptionalBunjaRef(toBunjaGraphRef(bunjaRef));
|
|
251
|
+
return () => {
|
|
252
|
+
if (frameStack[frameStack.length - 1] !== frame) throw new Error("A thunk returned by `bunja.will` can only be called inside the same bunja init function.");
|
|
253
|
+
return this.#useBunjaDependencyInFrame(frame, bunjaRef, "optional");
|
|
254
|
+
};
|
|
255
|
+
}),
|
|
256
|
+
effect: ((callback) => {
|
|
257
|
+
frame.effects.push(callback);
|
|
258
|
+
})
|
|
259
|
+
};
|
|
260
|
+
return frame;
|
|
261
|
+
}
|
|
262
|
+
#useScopeInFrame(frame, scope) {
|
|
263
|
+
if (!frame.currentBunja.baked) frame.currentBunja.addScope(scope);
|
|
264
|
+
let scopeInstance = frame.scopeInstanceMap.get(scope);
|
|
265
|
+
if (!scopeInstance) {
|
|
266
|
+
if (frame.currentBunja.baked) throw new Error("`bunja.use(scope)` cannot introduce a new scope after the bunja is baked.");
|
|
267
|
+
scopeInstance = this.#getScopeInstance(scope, frame.readScope(scope));
|
|
268
|
+
frame.scopeInstanceMap.set(scope, scopeInstance);
|
|
269
|
+
}
|
|
270
|
+
return scopeInstance.value;
|
|
271
|
+
}
|
|
272
|
+
#useBunjaDependencyInFrame(frame, bunjaRef, edge) {
|
|
273
|
+
const graphRef = toBunjaGraphRef(bunjaRef);
|
|
274
|
+
if (edge === "optional" || graphRef.scopeValuePairs.length > 0) frame.currentBunja.addOptionalBunjaRef(graphRef);
|
|
275
|
+
else if (edge === "required") frame.currentBunja.addRequiredBunjaRef(graphRef);
|
|
276
|
+
const resolved = this.#resolveBunjaRef(graphRef, frame.readScope, frame.inProgressBunjas, bunjaRef.seed);
|
|
277
|
+
frame.activeDependencyIds.add(resolved.instance.id);
|
|
278
|
+
frame.activeDependencyRecipes.push({
|
|
279
|
+
ref: graphRef,
|
|
280
|
+
seed: bunjaRef.seed
|
|
281
|
+
});
|
|
282
|
+
if (!frame.activeDependencyMounts.has(resolved.instance.id)) frame.activeDependencyMounts.set(resolved.instance.id, resolved.mount);
|
|
283
|
+
frame.activeDependencyDeps.push(...resolved.deps);
|
|
284
|
+
return resolved.value;
|
|
285
|
+
}
|
|
286
|
+
#resolveActiveDependencyRecipe(recipe, readScope, inProgressBunjas) {
|
|
287
|
+
const activeDependencyIds = /* @__PURE__ */ new Set();
|
|
288
|
+
const deps = [];
|
|
289
|
+
for (const { ref, seed } of recipe.activeDependencies) {
|
|
290
|
+
const resolved = this.#resolveBunjaRef(ref, readScope, inProgressBunjas, seed);
|
|
291
|
+
activeDependencyIds.add(resolved.instance.id);
|
|
292
|
+
deps.push(...resolved.deps);
|
|
293
|
+
}
|
|
294
|
+
return {
|
|
295
|
+
activeDependencyIds,
|
|
296
|
+
deps: dedupeScopeInstances(deps)
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
#prebakeBunjaRef(bunjaRef, readScope, prebakeContext) {
|
|
300
|
+
const { bunja: bunja$1 } = bunjaRef;
|
|
301
|
+
if (prebakeContext.values.has(bunja$1)) return prebakeContext.values.get(bunja$1);
|
|
302
|
+
const { inProgressBunjas } = prebakeContext;
|
|
303
|
+
if (inProgressBunjas.has(bunja$1)) throw new Error("Circular bunja dependency detected.");
|
|
304
|
+
const resolvedReadScope = bunjaRef.scopeValuePairs.length > 0 ? createReadScopeFn(bunjaRef.scopeValuePairs, readScope) : readScope;
|
|
305
|
+
inProgressBunjas.add(bunja$1);
|
|
306
|
+
try {
|
|
307
|
+
return this.wrapInstance((dispose) => {
|
|
308
|
+
try {
|
|
309
|
+
const value = runWithFrame(this.#createPrebakeFrame(bunja$1, resolvedReadScope, prebakeContext), () => bunja$1.init(bunja$1.defaultSeed));
|
|
310
|
+
if (!bunja$1.baked) bunja$1.bake();
|
|
311
|
+
for (const ref of [...bunja$1.requiredBunjaRefs, ...bunja$1.optionalBunjaRefs]) this.#prebakeBunjaRef(ref, resolvedReadScope, prebakeContext);
|
|
312
|
+
prebakeContext.values.set(bunja$1, value);
|
|
313
|
+
return value;
|
|
314
|
+
} finally {
|
|
315
|
+
dispose();
|
|
316
|
+
}
|
|
317
|
+
});
|
|
318
|
+
} finally {
|
|
319
|
+
inProgressBunjas.delete(bunja$1);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
#createPrebakeFrame(currentBunja, readScope, prebakeContext) {
|
|
323
|
+
const frame = {
|
|
324
|
+
currentBunja,
|
|
325
|
+
readScope,
|
|
326
|
+
prebakeContext,
|
|
327
|
+
use: ((dep, scopeValuePairs) => {
|
|
328
|
+
if (dep instanceof Scope) {
|
|
329
|
+
if (!currentBunja.baked) currentBunja.addScope(dep);
|
|
330
|
+
return readScope(dep);
|
|
331
|
+
}
|
|
332
|
+
if (dep instanceof Bunja || isBunjaRef(dep)) {
|
|
333
|
+
const bunjaRef = normalizeBunjaRuntimeRef(dep, scopeValuePairs);
|
|
334
|
+
return this.#prebakeBunjaDependencyInFrame(frame, toBunjaGraphRef(bunjaRef), "required");
|
|
335
|
+
}
|
|
336
|
+
throw new Error("`bunja.use` can only be used with Bunja or Scope.");
|
|
337
|
+
}),
|
|
338
|
+
will: ((dep, scopeValuePairs) => {
|
|
339
|
+
if (!(dep instanceof Bunja || isBunjaRef(dep))) throw new Error("`bunja.will` can only be used with Bunja.");
|
|
340
|
+
const bunjaRef = toBunjaGraphRef(normalizeBunjaRuntimeRef(dep, scopeValuePairs));
|
|
341
|
+
currentBunja.addOptionalBunjaRef(bunjaRef);
|
|
342
|
+
const value = this.#prebakeBunjaRef(bunjaRef, readScope, prebakeContext);
|
|
343
|
+
return () => {
|
|
344
|
+
if (frameStack[frameStack.length - 1] !== frame) throw new Error("A thunk returned by `bunja.will` can only be called inside the same bunja init function.");
|
|
345
|
+
return value;
|
|
346
|
+
};
|
|
347
|
+
}),
|
|
348
|
+
effect: noop
|
|
349
|
+
};
|
|
350
|
+
return frame;
|
|
351
|
+
}
|
|
352
|
+
#prebakeBunjaDependencyInFrame(frame, bunjaRef, edge) {
|
|
353
|
+
if (edge === "optional" || bunjaRef.scopeValuePairs.length > 0) frame.currentBunja.addOptionalBunjaRef(bunjaRef);
|
|
354
|
+
else if (edge === "required") frame.currentBunja.addRequiredBunjaRef(bunjaRef);
|
|
355
|
+
return this.#prebakeBunjaRef(bunjaRef, frame.readScope, frame.prebakeContext);
|
|
356
|
+
}
|
|
357
|
+
#resolveScopeInstanceMap(bunja$1, readScope) {
|
|
358
|
+
const scopeInstanceMap = /* @__PURE__ */ new Map();
|
|
359
|
+
this.#ensureRequiredScopeInstances(bunja$1, scopeInstanceMap, readScope);
|
|
360
|
+
return scopeInstanceMap;
|
|
361
|
+
}
|
|
362
|
+
#ensureRequiredScopeInstances(bunja$1, scopeInstanceMap, readScope) {
|
|
363
|
+
for (const scope of bunja$1.requiredScopes) {
|
|
364
|
+
if (scopeInstanceMap.has(scope)) continue;
|
|
365
|
+
scopeInstanceMap.set(scope, this.#getScopeInstance(scope, readScope(scope)));
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
#getScopeInstance(scope, value) {
|
|
369
|
+
const key = scope.hash(value);
|
|
370
|
+
const instanceMap = this.#scopes.get(scope) ?? this.#scopes.set(scope, /* @__PURE__ */ new Map()).get(scope);
|
|
371
|
+
return instanceMap.get(key) ?? instanceMap.set(key, this.#createScopeInstance(scope, key, value, () => {
|
|
372
|
+
instanceMap.delete(key);
|
|
373
|
+
if (__DEV__) devtoolsGlobalHook.emit("scopeInstanceUnmounted", {
|
|
374
|
+
storeId: this.id,
|
|
375
|
+
scope,
|
|
376
|
+
key
|
|
377
|
+
});
|
|
378
|
+
})).get(key);
|
|
379
|
+
}
|
|
380
|
+
#createBunjaInstance(id, baseId, value, dependencyMounts, effects, recipe, dispose) {
|
|
381
|
+
const bunjaInstance = new BunjaInstance(id, baseId, value, dependencyMounts, effects, recipe, () => {
|
|
382
|
+
if (__DEV__) devtoolsGlobalHook.emit("bunjaInstanceUnmounted", {
|
|
383
|
+
storeId: this.id,
|
|
384
|
+
bunjaInstanceId: id
|
|
385
|
+
});
|
|
386
|
+
dispose();
|
|
387
|
+
delete this.#bunjas[id];
|
|
388
|
+
const bucket = this.#bunjaBuckets.get(baseId);
|
|
389
|
+
bucket?.delete(id);
|
|
390
|
+
if (bucket?.size === 0) this.#bunjaBuckets.delete(baseId);
|
|
391
|
+
});
|
|
392
|
+
this.#bunjas[id] = bunjaInstance;
|
|
393
|
+
(this.#bunjaBuckets.get(baseId) ?? this.#bunjaBuckets.set(baseId, /* @__PURE__ */ new Set()).get(baseId)).add(id);
|
|
394
|
+
if (__DEV__) devtoolsGlobalHook.emit("bunjaInstanceMounted", {
|
|
395
|
+
storeId: this.id,
|
|
396
|
+
bunjaInstanceId: id
|
|
397
|
+
});
|
|
398
|
+
return bunjaInstance;
|
|
399
|
+
}
|
|
400
|
+
#createScopeInstance(scope, key, value, dispose) {
|
|
401
|
+
if (__DEV__) devtoolsGlobalHook.emit("scopeInstanceMounted", {
|
|
402
|
+
storeId: this.id,
|
|
403
|
+
scope,
|
|
404
|
+
key
|
|
405
|
+
});
|
|
406
|
+
return new ScopeInstance(value, dispose);
|
|
407
|
+
}
|
|
408
|
+
};
|
|
409
|
+
function createReadScopeFn(scopeValuePairs, readScope) {
|
|
410
|
+
const map = new Map(scopeValuePairs);
|
|
411
|
+
return (scope) => {
|
|
412
|
+
if (map.has(scope)) return map.get(scope);
|
|
413
|
+
return readScope(scope);
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
function delayUnmount(mount, ms = 0) {
|
|
417
|
+
return () => {
|
|
418
|
+
const unmount = mount();
|
|
419
|
+
return () => setTimeout(unmount, ms);
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
var Bunja = class Bunja {
|
|
423
|
+
static counter = 0;
|
|
424
|
+
id = String(Bunja.counter++);
|
|
425
|
+
debugLabel = "";
|
|
426
|
+
#phase = {
|
|
427
|
+
baked: false,
|
|
428
|
+
requiredBunjaRefs: [],
|
|
429
|
+
optionalBunjaRefs: [],
|
|
430
|
+
scopes: /* @__PURE__ */ new Set()
|
|
431
|
+
};
|
|
432
|
+
constructor(init, defaultSeed) {
|
|
433
|
+
this.init = init;
|
|
434
|
+
this.defaultSeed = defaultSeed;
|
|
435
|
+
if (__DEV__) {
|
|
436
|
+
devtoolsGlobalHook.bunjas[this.id] = this;
|
|
437
|
+
devtoolsGlobalHook.emit("bunjaCreated", { bunjaId: this.id });
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
get baked() {
|
|
441
|
+
return this.#phase.baked;
|
|
442
|
+
}
|
|
443
|
+
get requiredBunjas() {
|
|
444
|
+
return dedupeBunjas(this.#phase.requiredBunjaRefs.map(({ bunja: bunja$1 }) => bunja$1));
|
|
445
|
+
}
|
|
446
|
+
get optionalBunjas() {
|
|
447
|
+
return dedupeBunjas(this.#phase.optionalBunjaRefs.map(({ bunja: bunja$1 }) => bunja$1));
|
|
448
|
+
}
|
|
449
|
+
get requiredBunjaRefs() {
|
|
450
|
+
return this.#phase.requiredBunjaRefs;
|
|
451
|
+
}
|
|
452
|
+
get optionalBunjaRefs() {
|
|
453
|
+
return this.#phase.optionalBunjaRefs;
|
|
454
|
+
}
|
|
455
|
+
get expandedRequiredBunjas() {
|
|
456
|
+
if (!this.#phase.baked) throw new Error("Bunja is not baked yet.");
|
|
457
|
+
return this.#phase.expandedRequiredBunjas;
|
|
458
|
+
}
|
|
459
|
+
get relatedBunjas() {
|
|
460
|
+
if (!this.#phase.baked) throw new Error("Bunja is not baked yet.");
|
|
461
|
+
return toposortRelatedBunjas([...this.requiredBunjas, ...this.optionalBunjas]);
|
|
462
|
+
}
|
|
463
|
+
get requiredScopes() {
|
|
464
|
+
if (!this.#phase.baked) throw new Error("Bunja is not baked yet.");
|
|
465
|
+
return this.#phase.requiredScopes;
|
|
466
|
+
}
|
|
467
|
+
addRequiredBunjaRef(ref) {
|
|
468
|
+
if (this.#phase.baked) return;
|
|
469
|
+
addUniqueBunjaRef(this.#phase.requiredBunjaRefs, ref);
|
|
470
|
+
}
|
|
471
|
+
addOptionalBunjaRef(ref) {
|
|
472
|
+
if (this.#phase.baked) return;
|
|
473
|
+
addUniqueBunjaRef(this.#phase.optionalBunjaRefs, ref);
|
|
474
|
+
}
|
|
475
|
+
addScope(scope) {
|
|
476
|
+
if (this.#phase.baked) return;
|
|
477
|
+
this.#phase.scopes.add(scope);
|
|
478
|
+
}
|
|
479
|
+
bake() {
|
|
480
|
+
if (this.#phase.baked) throw new Error("Bunja is already baked.");
|
|
481
|
+
const scopes = this.#phase.scopes;
|
|
482
|
+
const requiredBunjaRefs = this.#phase.requiredBunjaRefs;
|
|
483
|
+
const optionalBunjaRefs = this.#phase.optionalBunjaRefs;
|
|
484
|
+
const requiredBunjas = this.requiredBunjas;
|
|
485
|
+
const expandedRequiredBunjas = toposortRequiredBunjas(requiredBunjas);
|
|
486
|
+
const requiredScopeSet = /* @__PURE__ */ new Set();
|
|
487
|
+
for (const bunja$1 of expandedRequiredBunjas) for (const scope of bunja$1.requiredScopes) requiredScopeSet.add(scope);
|
|
488
|
+
for (const scope of scopes) requiredScopeSet.add(scope);
|
|
489
|
+
this.#phase = {
|
|
490
|
+
baked: true,
|
|
491
|
+
requiredBunjaRefs,
|
|
492
|
+
optionalBunjaRefs,
|
|
493
|
+
expandedRequiredBunjas,
|
|
494
|
+
requiredScopes: Array.from(requiredScopeSet)
|
|
495
|
+
};
|
|
496
|
+
}
|
|
497
|
+
calcBaseInstanceId(scopeInstanceMap) {
|
|
498
|
+
const scopeInstanceIds = this.requiredScopes.map((scope) => scopeInstanceMap.get(scope).id);
|
|
499
|
+
return `${this.id}:${scopeInstanceIds.join(",")}`;
|
|
500
|
+
}
|
|
501
|
+
calcInstanceId(scopeInstanceMap, activeDependencyIds = []) {
|
|
502
|
+
return `${this.calcBaseInstanceId(scopeInstanceMap)}:${Array.from(activeDependencyIds).join(",")}`;
|
|
503
|
+
}
|
|
504
|
+
toString() {
|
|
505
|
+
const { id, debugLabel } = this;
|
|
506
|
+
return `[Bunja:${id}${debugLabel && ` - ${debugLabel}`}]`;
|
|
507
|
+
}
|
|
508
|
+
};
|
|
509
|
+
var Scope = class Scope {
|
|
510
|
+
static counter = 0;
|
|
511
|
+
id = String(Scope.counter++);
|
|
512
|
+
debugLabel = "";
|
|
513
|
+
constructor(hash = Scope.identity) {
|
|
514
|
+
this.hash = hash;
|
|
515
|
+
if (__DEV__) {
|
|
516
|
+
devtoolsGlobalHook.scopes[this.id] = this;
|
|
517
|
+
devtoolsGlobalHook.emit("scopeCreated", { scopeId: this.id });
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
static identity(x) {
|
|
521
|
+
return x;
|
|
522
|
+
}
|
|
523
|
+
bind(value) {
|
|
524
|
+
return [this, value];
|
|
525
|
+
}
|
|
526
|
+
toString() {
|
|
527
|
+
const { id, debugLabel } = this;
|
|
528
|
+
return `[Scope:${id}${debugLabel && ` - ${debugLabel}`}]`;
|
|
529
|
+
}
|
|
530
|
+
};
|
|
531
|
+
var RefCounter = class {
|
|
532
|
+
#count = 0;
|
|
533
|
+
add() {
|
|
534
|
+
++this.#count;
|
|
535
|
+
}
|
|
536
|
+
sub() {
|
|
537
|
+
--this.#count;
|
|
538
|
+
if (this.#count < 1) {
|
|
539
|
+
this.dispose();
|
|
540
|
+
this.dispose = noop;
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
};
|
|
544
|
+
var BunjaInstance = class extends RefCounter {
|
|
545
|
+
#cleanup;
|
|
546
|
+
#disposed = false;
|
|
547
|
+
constructor(id, baseId, value, dependencyMounts, effects, recipe, _dispose) {
|
|
548
|
+
super();
|
|
549
|
+
this.id = id;
|
|
550
|
+
this.baseId = baseId;
|
|
551
|
+
this.value = value;
|
|
552
|
+
this.dependencyMounts = dependencyMounts;
|
|
553
|
+
this.effects = effects;
|
|
554
|
+
this.recipe = recipe;
|
|
555
|
+
this._dispose = _dispose;
|
|
556
|
+
}
|
|
557
|
+
dispose() {
|
|
558
|
+
if (this.#disposed) return;
|
|
559
|
+
this.#disposed = true;
|
|
560
|
+
this.#cleanup?.();
|
|
561
|
+
this.#cleanup = void 0;
|
|
562
|
+
this._dispose();
|
|
563
|
+
}
|
|
564
|
+
add() {
|
|
565
|
+
this.#cleanup ??= this.#mount();
|
|
566
|
+
super.add();
|
|
567
|
+
}
|
|
568
|
+
#mount() {
|
|
569
|
+
const dependencyCleanups = this.dependencyMounts.map((mount) => mount());
|
|
570
|
+
const effectCleanups = this.effects.map((effect) => effect()).filter(Boolean);
|
|
571
|
+
return () => {
|
|
572
|
+
for (const cleanup of dependencyCleanups) cleanup();
|
|
573
|
+
for (const cleanup of effectCleanups) cleanup();
|
|
574
|
+
};
|
|
575
|
+
}
|
|
576
|
+
};
|
|
577
|
+
var ScopeInstance = class ScopeInstance extends RefCounter {
|
|
578
|
+
static counter = 0;
|
|
579
|
+
id = String(ScopeInstance.counter++);
|
|
580
|
+
constructor(value, dispose) {
|
|
581
|
+
super();
|
|
582
|
+
this.value = value;
|
|
583
|
+
this.dispose = dispose;
|
|
584
|
+
}
|
|
585
|
+
};
|
|
586
|
+
function toposort(nodes, getDependencies) {
|
|
587
|
+
const visited = /* @__PURE__ */ new Set();
|
|
588
|
+
const result = [];
|
|
589
|
+
function visit(current) {
|
|
590
|
+
if (visited.has(current)) return;
|
|
591
|
+
visited.add(current);
|
|
592
|
+
for (const dependency of getDependencies(current)) visit(dependency);
|
|
593
|
+
result.push(current);
|
|
594
|
+
}
|
|
595
|
+
for (const node of nodes) visit(node);
|
|
596
|
+
return result;
|
|
597
|
+
}
|
|
598
|
+
function toposortRequiredBunjas(bunjas) {
|
|
599
|
+
return toposort(bunjas, (bunja$1) => bunja$1.requiredBunjas);
|
|
600
|
+
}
|
|
601
|
+
function toposortRelatedBunjas(bunjas) {
|
|
602
|
+
return toposort(bunjas, (bunja$1) => [...bunja$1.requiredBunjas, ...bunja$1.optionalBunjas]);
|
|
603
|
+
}
|
|
604
|
+
const noop = () => {};
|
|
605
|
+
let devtoolsGlobalHook;
|
|
606
|
+
if (__DEV__) if (globalThis.__BUNJA_DEVTOOLS_GLOBAL_HOOK__) devtoolsGlobalHook = globalThis.__BUNJA_DEVTOOLS_GLOBAL_HOOK__;
|
|
607
|
+
else {
|
|
608
|
+
devtoolsGlobalHook = {
|
|
609
|
+
bunjas: {},
|
|
610
|
+
scopes: {},
|
|
611
|
+
listeners: {
|
|
612
|
+
bunjaCreated: /* @__PURE__ */ new Set(),
|
|
613
|
+
scopeCreated: /* @__PURE__ */ new Set(),
|
|
614
|
+
storeCreated: /* @__PURE__ */ new Set(),
|
|
615
|
+
storeDisposed: /* @__PURE__ */ new Set(),
|
|
616
|
+
getCalled: /* @__PURE__ */ new Set(),
|
|
617
|
+
bunjaInstanceMounted: /* @__PURE__ */ new Set(),
|
|
618
|
+
bunjaInstanceUnmounted: /* @__PURE__ */ new Set(),
|
|
619
|
+
scopeInstanceMounted: /* @__PURE__ */ new Set(),
|
|
620
|
+
scopeInstanceUnmounted: /* @__PURE__ */ new Set()
|
|
621
|
+
},
|
|
622
|
+
emit: (type, event) => {
|
|
623
|
+
for (const fn of devtoolsGlobalHook.listeners[type]) fn(event);
|
|
624
|
+
},
|
|
625
|
+
on: (type, listener) => {
|
|
626
|
+
devtoolsGlobalHook.listeners[type].add(listener);
|
|
627
|
+
return () => devtoolsGlobalHook.listeners[type].delete(listener);
|
|
628
|
+
}
|
|
629
|
+
};
|
|
630
|
+
globalThis.__BUNJA_DEVTOOLS_GLOBAL_HOOK__ = devtoolsGlobalHook;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
//#endregion
|
|
634
|
+
export { Bunja, BunjaStore, Scope, bunja, createBunjaStore, createReadScopeFn, createScope, delayUnmount };
|