Socket
Book a DemoInstallSign in
Socket

bunja

Package Overview
Dependencies
Maintainers
1
Versions
24
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

bunja - npm Package Compare versions

Comparing version

to
2.0.0-alpha.1

compat/bunja-v1.ts

476

bunja.ts

@@ -0,56 +1,91 @@

export interface BunjaFn {
<T>(init: () => T): Bunja<T>;
use: BunjaUseFn;
effect: BunjaEffectFn;
}
export const bunja: BunjaFn = bunjaFn;
function bunjaFn<T>(init: () => T): Bunja<T> {
return new Bunja(init);
}
bunjaFn.use = invalidUse as BunjaUseFn;
bunjaFn.effect = invalidEffect as BunjaEffectFn;
export type BunjaUseFn = <T>(dep: Dep<T>) => T;
export type BunjaEffectFn = (callback: BunjaEffectCallback) => void;
export type BunjaEffectCallback = () => (() => void) | void;
export function createScope<T>(hash?: HashFn<T>): Scope<T> {
return new Scope(hash);
}
export function createBunjaStore(): BunjaStore {
return new BunjaStore();
}
export type Dep<T> = Bunja<T> | Scope<T>;
const bunjaEffectSymbol: unique symbol = Symbol("Bunja.effect");
type BunjaEffectSymbol = typeof bunjaEffectSymbol;
function invalidUse() {
throw new Error(
"`bunja.use` can only be used inside a bunja init function.",
);
}
function invalidEffect() {
throw new Error(
"`bunja.effect` can only be used inside a bunja init function.",
);
}
export class Bunja<T> {
public static readonly bunjas: Bunja<any>[] = [];
public readonly id: number;
public debugLabel: string = "";
constructor(
public deps: Dep<any>[], // one depth dependencies
public parents: Bunja<any>[], // one depth parents
public relatedBunjas: Bunja<any>[], // toposorted parents without self
public relatedScopes: Scope<any>[], // deduped
public init: (...args: any[]) => T & BunjaValue,
) {
this.id = Bunja.bunjas.length;
Bunja.bunjas.push(this);
}
static readonly effect: BunjaEffectSymbol = bunjaEffectSymbol;
toString(): string {
const { id, debugLabel } = this;
return `[Bunja:${id}${debugLabel && ` - ${debugLabel}`}]`;
}
interface BunjaStoreGetContext {
bunjaInstance: BunjaInstance;
bunjaInstanceMap: BunjaInstanceMap;
scopeInstanceMap: ScopeInstanceMap;
}
export type HashFn<T = any, U = any> = (value: T) => U;
type BunjaInstanceMap = Map<Bunja<unknown>, BunjaInstance>;
type ScopeInstanceMap = Map<Scope<unknown>, ScopeInstance>;
export class Scope<T> {
public static readonly scopes: Scope<any>[] = [];
public readonly id: number;
public debugLabel: string = "";
constructor(public readonly hash: HashFn = id) {
this.id = Scope.scopes.length;
Scope.scopes.push(this);
}
toString(): string {
const { id, debugLabel } = this;
return `[Scope:${id}${debugLabel && ` - ${debugLabel}`}]`;
}
interface BunjaBakingContext {
currentBunja: Bunja<unknown>;
}
export type ReadScope = <T>(scope: Scope<T>) => T;
export class BunjaStore {
#bunjas: Record<string, BunjaInstance> = {};
#scopes: Map<Scope<any>, Map<any, ScopeInstance>> = new Map();
get<T>(
bunja: Bunja<T>,
readScope: ReadScope,
): {
value: T;
mount: () => () => void;
deps: any[];
} {
#scopes: Map<Scope<unknown>, Map<unknown, ScopeInstance>> = new Map();
#bakingContext: BunjaBakingContext | undefined;
dispose(): void {
for (const instance of Object.values(this.#bunjas)) instance.dispose();
for (const instanceMap of Object.values(this.#scopes)) {
for (const instance of instanceMap.values()) instance.dispose();
}
this.#bunjas = {};
this.#scopes = new Map();
}
get<T>(bunja: Bunja<T>, readScope: ReadScope): BunjaStoreGetResult<T> {
const originalUse = bunjaFn.use;
try {
const { bunjaInstance, bunjaInstanceMap, scopeInstanceMap } = bunja.baked
? this.#getBaked(bunja, readScope)
: this.#getUnbaked(bunja, readScope);
return {
value: bunjaInstance.value as T,
mount: () => {
bunjaInstanceMap.forEach((instance) => instance.add());
bunjaInstance.add();
scopeInstanceMap.forEach((instance) => instance.add());
const unmount = () => {
setTimeout(() => {
bunjaInstanceMap.forEach((instance) => instance.sub());
bunjaInstance.sub();
scopeInstanceMap.forEach((instance) => instance.sub());
});
};
return unmount;
},
deps: Array.from(scopeInstanceMap.values()).map(({ value }) => value),
};
} finally {
bunjaFn.use = originalUse;
}
}
#getBaked<T>(bunja: Bunja<T>, readScope: ReadScope): BunjaStoreGetContext {
const scopeInstanceMap = new Map(

@@ -62,33 +97,3 @@ bunja.relatedScopes.map((scope) => [

);
const bunjaInstance = this.#getBunjaInstance(bunja, scopeInstanceMap);
const { relatedBunjaInstanceMap } = bunjaInstance; // toposorted
return {
value: bunjaInstance.value as T,
mount() {
relatedBunjaInstanceMap.forEach((related) => related.add());
bunjaInstance.add();
scopeInstanceMap.forEach((scope) => scope.add());
return function unmount(): void {
// concern: reverse order?
relatedBunjaInstanceMap.forEach((related) => related.sub());
bunjaInstance.sub();
scopeInstanceMap.forEach((scope) => scope.sub());
};
},
deps: Array.from(scopeInstanceMap.values()).map(({ value }) => value),
};
}
#getBunjaInstance(
bunja: Bunja<any>,
scopeInstanceMap: Map<Scope<any>, ScopeInstance>,
): BunjaInstance {
const localScopeInstanceMap = new Map(
bunja.relatedScopes.map((scope) => [scope, scopeInstanceMap.get(scope)!]),
);
const scopeInstanceIds = Array.from(localScopeInstanceMap.values())
.map(({ instanceId }) => instanceId)
.sort((a, b) => a - b);
const bunjaInstanceId = `${bunja.id}:${scopeInstanceIds.join(",")}`;
if (this.#bunjas[bunjaInstanceId]) return this.#bunjas[bunjaInstanceId];
const relatedBunjaInstanceMap = new Map(
const bunjaInstanceMap = new Map(
bunja.relatedBunjas.map((relatedBunja) => [

@@ -99,137 +104,250 @@ relatedBunja,

);
const args = bunja.deps.map((dep) => {
if (dep instanceof Bunja) return relatedBunjaInstanceMap.get(dep)!.value;
if (dep instanceof Scope) return localScopeInstanceMap.get(dep)!.value;
throw new Error("Invalid dependency");
});
const bunjaInstance = new BunjaInstance(
() => delete this.#bunjas[bunjaInstanceId],
bunjaInstanceId,
relatedBunjaInstanceMap,
bunja.init.apply(bunja, args),
bunjaFn.use = <T>(dep: Dep<T>) => {
if (dep instanceof Bunja) {
return bunjaInstanceMap.get(dep as Bunja<unknown>)!.value as T;
}
if (dep instanceof Scope) {
return scopeInstanceMap.get(dep as Scope<unknown>)!.value as T;
}
throw new Error("`bunja.use` can only be used with Bunja or Scope.");
};
const bunjaInstance = this.#getBunjaInstance(bunja, scopeInstanceMap);
return { bunjaInstance, bunjaInstanceMap, scopeInstanceMap };
}
#getUnbaked<T>(bunja: Bunja<T>, readScope: ReadScope): BunjaStoreGetContext {
const bunjaInstanceMap: BunjaInstanceMap = new Map();
const scopeInstanceMap: ScopeInstanceMap = new Map();
function getUse<D extends Dep<unknown>, I extends { value: unknown }>(
map: Map<D, I>,
addDep: (D: D) => void,
getInstance: (dep: D) => I,
) {
return ((dep) => {
const d = dep as D;
addDep(d);
if (map.has(d)) return map.get(d)!.value as T;
const instance = getInstance(d);
map.set(d, instance);
return instance.value as T;
}) as <T>(dep: Dep<T>) => T;
}
const useScope = getUse(
scopeInstanceMap,
(dep) => this.#bakingContext!.currentBunja.addScope(dep),
(dep) => this.#getScopeInstance(dep, readScope(dep)),
);
this.#bunjas[bunjaInstanceId] = bunjaInstance;
return bunjaInstance;
const useBunja = getUse(
bunjaInstanceMap,
(dep) => this.#bakingContext!.currentBunja.addParent(dep),
(dep) => {
if (dep.baked) {
for (const scope of dep.relatedScopes) useScope(scope);
}
return this.#getBunjaInstance(dep, scopeInstanceMap);
},
);
bunjaFn.use = <T>(dep: Dep<T>) => {
if (dep instanceof Bunja) return useBunja(dep) as T;
if (dep instanceof Scope) return useScope(dep) as T;
throw new Error("`bunja.use` can only be used with Bunja or Scope.");
};
try {
this.#bakingContext = { currentBunja: bunja };
const bunjaInstance = this.#getBunjaInstance(bunja, scopeInstanceMap);
return { bunjaInstance, bunjaInstanceMap, scopeInstanceMap };
} finally {
this.#bakingContext = undefined;
}
}
#getScopeInstance(scope: Scope<any>, value: any): ScopeInstance {
#getBunjaInstance<T>(
bunja: Bunja<T>,
scopeInstanceMap: ScopeInstanceMap,
): BunjaInstance {
const originalEffect = bunjaFn.effect;
const prevBunja = this.#bakingContext?.currentBunja;
try {
const effects: BunjaEffectCallback[] = [];
bunjaFn.effect = (callback: BunjaEffectCallback) => {
effects.push(callback);
};
if (this.#bakingContext) this.#bakingContext.currentBunja = bunja;
if (bunja.baked) {
const id = bunja.calcInstanceId(scopeInstanceMap);
if (id in this.#bunjas) return this.#bunjas[id];
const bunjaInstanceValue = bunja.init();
return this.#createBunjaInstance(id, bunjaInstanceValue, effects);
} else {
const bunjaInstanceValue = bunja.init();
bunja.bake();
const id = bunja.calcInstanceId(scopeInstanceMap);
return this.#createBunjaInstance(id, bunjaInstanceValue, effects);
}
} finally {
bunjaFn.effect = originalEffect;
if (this.#bakingContext) this.#bakingContext.currentBunja = prevBunja!;
}
}
#getScopeInstance(scope: Scope<unknown>, value: unknown): ScopeInstance {
const key = scope.hash(value);
const scopeInstanceMap = this.#scopes.get(scope) ??
const instanceMap = this.#scopes.get(scope) ??
this.#scopes.set(scope, new Map()).get(scope)!;
const init = () =>
new ScopeInstance(
() => scopeInstanceMap.delete(key),
ScopeInstance.counter++,
scope,
value,
);
return (
scopeInstanceMap.get(key) ??
scopeInstanceMap.set(key, init()).get(key)!
);
return instanceMap.get(key) ??
instanceMap.set(
key,
new ScopeInstance(value, () => instanceMap.delete(key)),
).get(key)!;
}
#createBunjaInstance(
id: string,
value: unknown,
effects: BunjaEffectCallback[],
): BunjaInstance {
const effect = () => {
const cleanups = effects
.map((effect) => effect())
.filter(Boolean) as (() => void)[];
return () => cleanups.forEach((cleanup) => cleanup());
};
const dispose = () => delete this.#bunjas[id];
const bunjaInstance = new BunjaInstance(id, value, effect, dispose);
this.#bunjas[id] = bunjaInstance;
return bunjaInstance;
}
}
export const createBunjaStore = (): BunjaStore => new BunjaStore();
export type ReadScope = <T>(scope: Scope<T>) => T;
export type BunjaEffectFn = () => () => void;
export interface BunjaValue {
[Bunja.effect]?: BunjaEffectFn;
export interface BunjaStoreGetResult<T> {
value: T;
mount: () => () => void;
deps: unknown[];
}
function bunjaImpl<T, const U extends any[]>(
deps: { [K in keyof U]: Dep<U[K]> },
init: (...args: U) => T & BunjaValue,
): Bunja<T> {
const parents = deps.filter((dep) => dep instanceof Bunja) as Bunja<any>[];
const scopes = deps.filter((dep) => dep instanceof Scope) as Scope<any>[];
const relatedBunjas = toposort(parents);
const relatedScopes = Array.from(
new Set([...scopes, ...parents.flatMap((parent) => parent.relatedScopes)]),
);
return new Bunja(deps, parents, relatedBunjas, relatedScopes, init as any);
export class Bunja<T> {
private static counter: number = 0;
readonly id: string = String(Bunja.counter++);
debugLabel: string = "";
#phase: BunjaPhase = { baked: false, parents: new Set(), scopes: new Set() };
constructor(public init: () => T) {}
get baked(): boolean {
return this.#phase.baked;
}
get parents(): Bunja<unknown>[] {
if (this.#phase.baked) return this.#phase.parents;
return Array.from(this.#phase.parents);
}
get relatedBunjas(): Bunja<unknown>[] {
if (!this.#phase.baked) throw new Error("Bunja is not baked yet.");
return this.#phase.relatedBunjas;
}
get relatedScopes(): Scope<unknown>[] {
if (!this.#phase.baked) throw new Error("Bunja is not baked yet.");
return this.#phase.relatedScopes;
}
addParent(bunja: Bunja<unknown>): void {
if (this.#phase.baked) return;
this.#phase.parents.add(bunja);
}
addScope(scope: Scope<unknown>): void {
if (this.#phase.baked) return;
this.#phase.scopes.add(scope);
}
bake(): void {
if (this.#phase.baked) throw new Error("Bunja is already baked.");
const scopes = this.#phase.scopes;
const parents = this.parents;
const relatedBunjas = toposort(parents);
const relatedScopes = Array.from(
new Set([
...relatedBunjas.flatMap((bunja) => bunja.relatedScopes),
...scopes,
]),
);
this.#phase = { baked: true, parents, relatedBunjas, relatedScopes };
}
calcInstanceId(scopeInstanceMap: Map<Scope<unknown>, ScopeInstance>): string {
const scopeInstanceIds = this.relatedScopes.map(
(scope) => scopeInstanceMap.get(scope)!.id,
);
return `${this.id}:${scopeInstanceIds.join(",")}`;
}
toString(): string {
const { id, debugLabel } = this;
return `[Bunja:${id}${debugLabel && ` - ${debugLabel}`}]`;
}
}
bunjaImpl.effect = Bunja.effect;
export const bunja: {
<T>(deps: [], init: () => T & BunjaValue): Bunja<T>;
<T, U>(deps: [Dep<U>], init: (u: U) => T & BunjaValue): Bunja<T>;
<T, U, V>(
deps: [Dep<U>, Dep<V>],
init: (u: U, v: V) => T & BunjaValue,
): Bunja<T>;
<T, U, V, W>(
deps: [Dep<U>, Dep<V>, Dep<W>],
init: (u: U, v: V, w: W) => T & BunjaValue,
): Bunja<T>;
<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>;
<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>;
<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>;
readonly effect: BunjaEffectSymbol;
} = bunjaImpl;
type BunjaPhase = BunjaPhaseUnbaked | BunjaPhaseBaked;
export function createScope<T>(hash?: HashFn): Scope<T> {
return new Scope(hash);
interface BunjaPhaseUnbaked {
readonly baked: false;
readonly parents: Set<Bunja<unknown>>;
readonly scopes: Set<Scope<unknown>>;
}
interface BunjaPhaseBaked {
readonly baked: true;
readonly parents: Bunja<unknown>[];
readonly relatedBunjas: Bunja<unknown>[];
readonly relatedScopes: Scope<unknown>[];
}
export class Scope<T> {
private static counter: number = 0;
readonly id: string = String(Scope.counter++);
debugLabel: string = "";
constructor(public readonly hash: HashFn<T> = Scope.identity) {}
private static identity<T>(x: T): T {
return x;
}
toString(): string {
const { id, debugLabel } = this;
return `[Scope:${id}${debugLabel && ` - ${debugLabel}`}]`;
}
}
export type HashFn<T> = (value: T) => unknown;
const noop = () => {};
abstract class RefCounter {
#disposed = false;
#count = 0;
add() {
this.#count++;
#count: number = 0;
abstract dispose(): void;
add(): void {
++this.#count;
}
sub() {
this.#count--;
setTimeout(() => {
if (this.#disposed) return;
if (this.#count < 1) {
this.#disposed = true;
this.dispose();
}
});
sub(): void {
--this.#count;
if (this.#count < 1) {
this.dispose();
this.dispose = noop;
}
}
abstract dispose(): void;
}
const id = <T>(x: T): T => x;
const noop = () => {};
class BunjaInstance extends RefCounter {
#cleanup: (() => void) | undefined;
#dispose: () => void;
constructor(
dispose: () => void,
public instanceId: string,
public relatedBunjaInstanceMap: Map<Bunja<any>, BunjaInstance>,
public value: BunjaValue,
public readonly id: string,
public readonly value: unknown,
public readonly effect: BunjaEffectCallback,
private readonly _dispose: () => void,
) {
super();
this.#dispose = () => {
this.#cleanup?.();
dispose();
};
}
override add() {
this.#cleanup ??= this.value[Bunja.effect]?.() ?? noop;
override dispose(): void {
this.#cleanup?.();
this._dispose();
}
override add(): void {
this.#cleanup ??= this.effect() ?? noop;
super.add();
}
dispose() {
this.#dispose();
}
}
class ScopeInstance extends RefCounter {
public static counter = 0;
private static counter: number = 0;
readonly id: string = String(ScopeInstance.counter++);
constructor(
public dispose: () => void,
public instanceId: number,
public scope: Scope<any>,
public value: any,
public readonly value: unknown,
public readonly dispose: () => void,
) {

@@ -236,0 +354,0 @@ super();

{
"name": "@disjukr/bunja",
"version": "1.0.0",
"version": "2.0.0-alpha.1",
"license": "Zlib",

@@ -5,0 +5,0 @@ "exports": {

@@ -0,55 +1,64 @@

export interface BunjaFn {
<T>(init: () => T): Bunja<T>;
use: BunjaUseFn;
effect: BunjaEffectFn;
}
export declare const bunja: BunjaFn;
export type BunjaUseFn = <T>(dep: Dep<T>) => T;
export type BunjaEffectFn = (callback: BunjaEffectCallback) => void;
export type BunjaEffectCallback = () => (() => void) | void;
export declare function createScope<T>(hash?: HashFn<T>): Scope<T>;
export declare function createBunjaStore(): BunjaStore;
export type Dep<T> = Bunja<T> | Scope<T>;
declare const bunjaEffectSymbol: unique symbol;
type BunjaEffectSymbol = typeof bunjaEffectSymbol;
export declare class BunjaStore {
#private;
dispose(): void;
get<T>(bunja: Bunja<T>, readScope: ReadScope): BunjaStoreGetResult<T>;
}
export type ReadScope = <T>(scope: Scope<T>) => T;
export interface BunjaStoreGetResult<T> {
value: T;
mount: () => () => void;
deps: unknown[];
}
export declare class Bunja<T> {
deps: Dep<any>[];
parents: Bunja<any>[];
relatedBunjas: Bunja<any>[];
relatedScopes: Scope<any>[];
init: (...args: any[]) => T & BunjaValue;
static readonly bunjas: Bunja<any>[];
readonly id: number;
#private;
init: () => T;
private static counter;
readonly id: string;
debugLabel: string;
constructor(deps: Dep<any>[], // one depth dependencies
parents: Bunja<any>[], // one depth parents
relatedBunjas: Bunja<any>[], // toposorted parents without self
relatedScopes: Scope<any>[], // deduped
init: (...args: any[]) => T & BunjaValue);
static readonly effect: BunjaEffectSymbol;
constructor(init: () => T);
get baked(): boolean;
get parents(): Bunja<unknown>[];
get relatedBunjas(): Bunja<unknown>[];
get relatedScopes(): Scope<unknown>[];
addParent(bunja: Bunja<unknown>): void;
addScope(scope: Scope<unknown>): void;
bake(): void;
calcInstanceId(scopeInstanceMap: Map<Scope<unknown>, ScopeInstance>): string;
toString(): string;
}
export type HashFn<T = any, U = any> = (value: T) => U;
export declare class Scope<T> {
readonly hash: HashFn;
static readonly scopes: Scope<any>[];
readonly id: number;
readonly hash: HashFn<T>;
private static counter;
readonly id: string;
debugLabel: string;
constructor(hash?: HashFn);
constructor(hash?: HashFn<T>);
private static identity;
toString(): string;
}
export type ReadScope = <T>(scope: Scope<T>) => T;
export declare class BunjaStore {
export type HashFn<T> = (value: T) => unknown;
declare abstract class RefCounter {
#private;
get<T>(bunja: Bunja<T>, readScope: ReadScope): {
value: T;
mount: () => () => void;
deps: any[];
};
abstract dispose(): void;
add(): void;
sub(): void;
}
export declare const createBunjaStore: () => BunjaStore;
export type BunjaEffectFn = () => () => void;
export interface BunjaValue {
[Bunja.effect]?: BunjaEffectFn;
declare class ScopeInstance extends RefCounter {
readonly value: unknown;
readonly dispose: () => void;
private static counter;
readonly id: string;
constructor(value: unknown, dispose: () => void);
}
export declare const bunja: {
<T>(deps: [], init: () => T & BunjaValue): Bunja<T>;
<T, U>(deps: [Dep<U>], init: (u: U) => T & BunjaValue): Bunja<T>;
<T, U, V>(deps: [Dep<U>, Dep<V>], init: (u: U, v: V) => T & BunjaValue): Bunja<T>;
<T, U, V, W>(deps: [Dep<U>, Dep<V>, Dep<W>], init: (u: U, v: V, w: W) => T & BunjaValue): Bunja<T>;
<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>;
<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>;
<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>;
readonly effect: BunjaEffectSymbol;
};
export declare function createScope<T>(hash?: HashFn): Scope<T>;
export {};

@@ -1,3 +0,3 @@

import { Bunja, BunjaStore, Scope, bunja, createBunjaStore, createScope } from "./bunja-fHIhQAuL.js";
import { Bunja, BunjaStore, Scope, bunja, createBunjaStore, createScope } from "./bunja-wcx846sL.js";
export { Bunja, BunjaStore, Scope, bunja, createBunjaStore, createScope };
import { type Context } from "react";
import { type Bunja, type BunjaStore, type HashFn, type ReadScope, type Scope } from "./bunja.ts";
export declare const BunjaStoreContext: Context<BunjaStore>;
export declare const scopeContextMap: Map<Scope<any>, Context<any>>;
export declare function bindScope(scope: Scope<any>, context: Context<any>): void;
export declare const scopeContextMap: Map<Scope<unknown>, Context<unknown>>;
export declare function bindScope<T>(scope: Scope<T>, context: Context<T>): void;
export declare function createScopeFromContext<T>(context: Context<T>, hash?: HashFn<T>): Scope<T>;
export declare function useBunja<T>(bunja: Bunja<T>, readScope?: ReadScope): T;
export type ScopePair<T> = [Scope<T>, T];
export declare function inject<const T extends ScopePair<any>[]>(overrideTable: T): ReadScope;
export declare function inject<const T extends ScopePair<unknown>[]>(overrideTable: T): ReadScope;

@@ -1,3 +0,3 @@

import { createBunjaStore, createScope } from "./bunja-fHIhQAuL.js";
import { createContext, useContext, useEffect } from "react";
import { createBunjaStore, createScope } from "./bunja-wcx846sL.js";
import { createContext, use, useEffect } from "react";

@@ -17,6 +17,6 @@ //#region react.ts

const context = scopeContextMap.get(scope);
return useContext(context);
return use(context);
};
function useBunja(bunja, readScope = defaultReadScope) {
const store = useContext(BunjaStoreContext);
const store = use(BunjaStoreContext);
const { value, mount, deps } = store.get(bunja, readScope);

@@ -32,3 +32,3 @@ useEffect(mount, deps);

if (!context) throw new Error("Unable to read the scope. Please inject the value explicitly or bind scope to the React context.");
return useContext(context);
return use(context);
};

@@ -35,0 +35,0 @@ }

{
"name": "bunja",
"type": "module",
"version": "1.0.0",
"version": "2.0.0-alpha.1",
"description": "State Lifetime Manager",

@@ -52,4 +52,4 @@ "main": "dist/bunja.cjs",

"devDependencies": {
"@types/react": "^18",
"react": "^18",
"@types/react": "^19",
"react": "^19",
"tsdown": "^0.2.17",

@@ -60,3 +60,3 @@ "typescript": "^5.6.3"

"@types/react": "*",
"react": ">=17"
"react": ">=19"
},

@@ -63,0 +63,0 @@ "peerDependenciesMeta": {

@@ -1,2 +0,2 @@

import { type Context, createContext, useContext, useEffect } from "react";
import { type Context, createContext, use, useEffect } from "react";
import {

@@ -16,5 +16,5 @@ type Bunja,

export const scopeContextMap: Map<Scope<any>, Context<any>> = new Map();
export function bindScope(scope: Scope<any>, context: Context<any>): void {
scopeContextMap.set(scope, context);
export const scopeContextMap: Map<Scope<unknown>, Context<unknown>> = new Map();
export function bindScope<T>(scope: Scope<T>, context: Context<T>): void {
scopeContextMap.set(scope as Scope<unknown>, context as Context<unknown>);
}

@@ -26,3 +26,3 @@

): Scope<T> {
const scope = createScope(hash);
const scope = createScope<T>(hash);
bindScope(scope, context);

@@ -32,5 +32,5 @@ return scope;

const defaultReadScope: ReadScope = (scope) => {
const context = scopeContextMap.get(scope)!;
return useContext(context);
const defaultReadScope: ReadScope = <T>(scope: Scope<T>) => {
const context = scopeContextMap.get(scope as Scope<unknown>)!;
return use(context) as T;
};

@@ -42,3 +42,3 @@

): T {
const store = useContext(BunjaStoreContext);
const store = use(BunjaStoreContext);
const { value, mount, deps } = store.get(bunja, readScope);

@@ -51,9 +51,11 @@ useEffect(mount, deps);

export function inject<const T extends ScopePair<any>[]>(
export function inject<const T extends ScopePair<unknown>[]>(
overrideTable: T,
): ReadScope {
const map = new Map(overrideTable);
return (scope) => {
if (map.has(scope)) return map.get(scope);
const context = scopeContextMap.get(scope);
return <T>(scope: Scope<T>) => {
if (map.has(scope as Scope<unknown>)) {
return map.get(scope as Scope<unknown>) as T;
}
const context = scopeContextMap.get(scope as Scope<unknown>);
if (!context) {

@@ -64,4 +66,4 @@ throw new Error(

}
return useContext(context);
return use(context) as T;
};
}

@@ -0,1 +1,8 @@

> [!WARNING]
> You are viewing the `v2` branch.
>
> The current stable version is `1.x.x`.\
> If you want to view the code for that version,
> please [switch to the `v1` branch](https://github.com/disjukr/bunja/tree/v1).
# Bunja

@@ -36,3 +43,3 @@

If you want to trigger effects when the lifetime of a bunja starts and ends, you can use the `bunja.effect` field.
If you want to trigger effects when the lifetime of a bunja starts and ends, you can use the `bunja.effect` function.

@@ -43,11 +50,11 @@ ```ts

const countBunja = bunja([], () => {
const countBunja = bunja(() => {
const countAtom = atom(0);
return {
countAtom,
[bunja.effect]() {
console.log("mounted");
return () => console.log("unmounted");
},
};
bunja.effect(() => {
console.log("mounted");
return () => console.log("unmounted");
});
return { countAtom };
});

@@ -72,3 +79,3 @@

// To simplify the example, code for buffering and reconnection has been omitted.
const websocketBunja = bunja([], () => {
const websocketBunja = bunja(() => {
let socket;

@@ -83,31 +90,31 @@ const send = (message) => socket.send(JSON.stringify(message));

return {
send,
on,
[bunja.effect]() {
socket = new WebSocket("...");
socket.onmessage = (e) => emitter.emit("message", JSON.parse(e.data));
return () => socket.close();
},
};
bunja.effect(() => {
socket = new WebSocket("...");
socket.onmessage = (e) => emitter.emit("message", JSON.parse(e.data));
return () => socket.close();
});
return { send, on };
});
const resourceFooBunja = bunja([websocketBunja], ({ send, on }) => {
const resourceFooBunja = bunja(() => {
const { send, on } = bunja.use(websocketBunja);
const resourceFooAtom = atom();
return {
resourceFooAtom,
[bunja.effect]() {
const off = on((message) => {
if (message.type === "foo") store.set(resourceAtom, message.value);
});
send("subscribe-foo");
return () => {
send("unsubscribe-foo");
off();
};
},
};
bunja.effect(() => {
const off = on((message) => {
if (message.type === "foo") store.set(resourceAtom, message.value);
});
send("subscribe-foo");
return () => {
send("unsubscribe-foo");
off();
};
});
return { resourceFooAtom };
});
const resourceBarBunja = bunja([websocketBunja], ({ send, on }) => {
const resourceBarBunja = bunja(() => {
const { send, on } = bunja.use(websocketBunja);
const resourceBarAtom = atom();

@@ -151,3 +158,5 @@ // ...

const fetchBunja = bunja([UrlScope], (url) => {
const fetchBunja = bunja(() => {
const url = bunja.use(UrlScope);
const queryAtom = atomWithQuery((get) => ({

@@ -157,2 +166,3 @@ queryKey: [url],

}));
return { queryAtom };

@@ -177,3 +187,5 @@ });

const fetchBunja = bunja([UrlScope], (url) => {
const fetchBunja = bunja(() => {
const url = bunja.use(UrlScope);
const queryAtom = atomWithQuery((get) => ({

@@ -183,2 +195,3 @@ queryKey: [url],

}));
return { queryAtom };

@@ -190,8 +203,8 @@ });

<>
<UrlContext.Provider value="https://example.com/foo">
<UrlContext value="https://example.com/foo">
<ChildComponent />
</UrlContext.Provider>
<UrlContext.Provider value="https://example.com/bar">
</UrlContext>
<UrlContext value="https://example.com/bar">
<ChildComponent />
</UrlContext.Provider>
</UrlContext>
</>

@@ -198,0 +211,0 @@ );

@@ -5,6 +5,5 @@ import { assertEquals } from "jsr:@std/assert";

import { createBunjaStore, createScope } from "./bunja.ts";
import { bunja } from "./bunja.ts";
import { bunja, createBunjaStore, createScope } from "./bunja.ts";
const readNull = () => (null as any);
const readNull = <T>() => (null as T);

@@ -17,3 +16,3 @@ Deno.test({

const myBunjaInstance = {};
const myBunja = bunja([], () => myBunjaInstance);
const myBunja = bunja(() => myBunjaInstance);
const { value, mount } = store.get(myBunja, readNull);

@@ -34,8 +33,8 @@ const cleanup = mount();

const unmountSpy = spy();
const myBunja = bunja([], () => ({
[bunja.effect]() {
const myBunja = bunja(() => {
bunja.effect(() => {
mountSpy();
return unmountSpy;
},
}));
});
});
assertSpyCalls(mountSpy, 0);

@@ -61,16 +60,18 @@ const { mount } = store.get(myBunja, readNull);

const [bMountSpy, bUnmountSpy] = [spy(), spy()];
const aBunjaInstance = {
[bunja.effect]() {
const aBunjaInstance = {};
const aBunja = bunja(() => {
bunja.effect(() => {
aMountSpy();
return aUnmountSpy;
},
};
const aBunja = bunja([], () => aBunjaInstance);
const bBunja = bunja([aBunja], (a) => ({
a,
[bunja.effect]() {
});
return aBunjaInstance;
});
const bBunja = bunja(() => {
const a = bunja.use(aBunja);
bunja.effect(() => {
bMountSpy();
return bUnmountSpy;
},
}));
});
return { a };
});
assertSpyCalls(aMountSpy, 0);

@@ -103,8 +104,15 @@ assertSpyCalls(bMountSpy, 0);

const [bMountSpy, bUnmountSpy] = [spy(), spy()];
const aBunja = bunja([], () => ({
[bunja.effect]: () => (aMountSpy(), aUnmountSpy),
}));
const bBunja = bunja([aBunja], () => ({
[bunja.effect]: () => (bMountSpy(), bUnmountSpy),
}));
const aBunja = bunja(() => {
bunja.effect(() => {
aMountSpy();
return aUnmountSpy;
});
});
const bBunja = bunja(() => {
bunja.use(aBunja);
bunja.effect(() => {
bMountSpy();
return bUnmountSpy;
});
});
const { mount: m1 } = store.get(aBunja, readNull);

@@ -138,8 +146,15 @@ const c1 = m1();

const [bMountSpy, bUnmountSpy] = [spy(), spy()];
const aBunja = bunja([], () => ({
[bunja.effect]: () => (aMountSpy(), aUnmountSpy),
}));
const bBunja = bunja([aBunja], () => ({
[bunja.effect]: () => (bMountSpy(), bUnmountSpy),
}));
const aBunja = bunja(() => {
bunja.effect(() => {
aMountSpy();
return aUnmountSpy;
});
});
const bBunja = bunja(() => {
bunja.use(aBunja);
bunja.effect(() => {
bMountSpy();
return bUnmountSpy;
});
});
const { mount: m1 } = store.get(bBunja, readNull);

@@ -172,3 +187,6 @@ const c1 = m1();

const myScope = createScope<string>();
const myBunja = bunja([myScope], (scopeValue) => ({ scopeValue }));
const myBunja = bunja(() => {
const scopeValue = bunja.use(myScope);
return { scopeValue };
});
const readScope = <T>(): T => "injected value" as T;

@@ -189,3 +207,6 @@ const { value: { scopeValue }, mount } = store.get(myBunja, readScope);

const myScope = createScope<string>(({ length }) => length);
const myBunja = bunja([myScope], (scopeValue) => ({ scopeValue }));
const myBunja = bunja(() => {
const scopeValue = bunja.use(myScope);
return { scopeValue };
});
const { value: { scopeValue: scopeValue1 }, mount: mount1 } = store.get(

@@ -192,0 +213,0 @@ myBunja,

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet