diff --git a/.changeset/rotten-spies-unite.md b/.changeset/rotten-spies-unite.md new file mode 100644 index 0000000..9948a47 --- /dev/null +++ b/.changeset/rotten-spies-unite.md @@ -0,0 +1,5 @@ +--- +"bunshi": patch +--- + +Improve handling for molecules that throw exceptions. This will no longer break the global `use` functions, or break `getMol` or `getScope`. Fixes #61 diff --git a/examples/old-typescript/package-lock.json b/examples/old-typescript/package-lock.json index 3da31d4..d0e2ec7 100644 --- a/examples/old-typescript/package-lock.json +++ b/examples/old-typescript/package-lock.json @@ -23,12 +23,16 @@ "@testing-library/vue": "^8.0.1", "@types/react": "^18", "@types/use-sync-external-store": "^0.0.6", + "@typescript-eslint/eslint-plugin": "^5.62.0", + "@typescript-eslint/parser": "^5.62.0", "@vitejs/plugin-vue": "^5.0.4", "@vitest/browser": "^1.2.2", "@vitest/coverage-v8": "^1.2.2", "@vitest/ui": "^1.2.2", "@vue/tsconfig": "^0.1.3", "downlevel-dts": "^0.11.0", + "eslint": "^8.57.0", + "eslint-plugin-compat": "^4.2.0", "happy-dom": "^10.0.3", "husky": "^7.0.4", "jotai": "^2.4.2", diff --git a/src/react/ScopeProvider.reactivity.test.tsx b/src/react/ScopeProvider.reactivity.test.tsx index d2145a1..3dac4f5 100644 --- a/src/react/ScopeProvider.reactivity.test.tsx +++ b/src/react/ScopeProvider.reactivity.test.tsx @@ -8,7 +8,7 @@ const TestCope = createScope<{ number?: number }>({}); const lifecycle = createLifecycleUtils(); const TestMol = molecule(() => { const scope = use(TestCope); - console.log(scope.number); //In the first case, there is no change, and in the second case, the value from one timing ago is output. + // console.log(scope.number); //In the first case, there is no change, and in the second case, the value from one timing ago is output. lifecycle.connect(scope.number); return { scope }; }); diff --git a/src/vanilla/injector.ts b/src/vanilla/injector.ts index d8108a8..b56e8ec 100644 --- a/src/vanilla/injector.ts +++ b/src/vanilla/injector.ts @@ -647,29 +647,31 @@ function runMolecule( useImpl.push(use); let running = true; trackingScopeGetter(InternalOnlyGlobalScope); - const value = m[GetterSymbol](trackingGetter, trackingScopeGetter); - running = false; - onMountImpl.pop(); - useImpl.pop(); - - return { - deps: { - molecules: dependentMolecules, - allScopes, - defaultScopes, - mountedCallbacks, - /** - * Returns a copy - * - * Reverses the order so that the deepest dependencies are at the top - * of the list. This will be important for ensuring ordering for how - * mounts are called with transient dependencies. - * - */ - buddies: buddies.slice().reverse(), - }, - value, - }; + try { + const value = m[GetterSymbol](trackingGetter, trackingScopeGetter); + return { + deps: { + molecules: dependentMolecules, + allScopes, + defaultScopes, + mountedCallbacks, + /** + * Returns a copy + * + * Reverses the order so that the deepest dependencies are at the top + * of the list. This will be important for ensuring ordering for how + * mounts are called with transient dependencies. + * + */ + buddies: buddies.slice().reverse(), + }, + value, + }; + } finally { + running = false; + onMountImpl.pop(); + useImpl.pop(); + } } type CreationProps = { diff --git a/src/vanilla/lifecycle.test.ts b/src/vanilla/lifecycle.test.ts index 7e28483..7c5a2da 100644 --- a/src/vanilla/lifecycle.test.ts +++ b/src/vanilla/lifecycle.test.ts @@ -6,6 +6,11 @@ import { AnyScopeTuple } from "./internal/internal-types"; import { onMount, onUnmount, use } from "./lifecycle"; import { molecule } from "./molecule"; import { createScope } from "./scope"; +import { + ConfigMolecule, + ConfigScope, + LibaryMolecule, +} from "./testing/test-molecules"; const defaultFn = vi.fn(); const ExampleScope = createScope(defaultFn); @@ -444,6 +449,24 @@ describe("Conditional dependencies", () => { expect(() => injector.use(TwoForks)).toThrow(); }); }); + + describe("Required scope is a molecule", () => { + test("Use the molecule, expect error", () => { + const injector = createInjector(); + expect(() => injector.use(LibaryMolecule)).toThrow(); + }); + test("Non-conditional path works", () => { + const injector = createInjector(); + const [library, unsub1] = injector.use(LibaryMolecule, [ + ConfigScope, + ConfigMolecule, + ]); + const [config, unsub2] = injector.use(ConfigMolecule); + expect(library.example).toBe(config.example); + unsub1(); + unsub2(); + }); + }); }); describe("unmount lifecycle", () => { diff --git a/src/vanilla/testing/test-molecules.ts b/src/vanilla/testing/test-molecules.ts index 8f8e4a7..efa4ea8 100644 --- a/src/vanilla/testing/test-molecules.ts +++ b/src/vanilla/testing/test-molecules.ts @@ -1,7 +1,8 @@ -import { atom, PrimitiveAtom } from "jotai"; +import { Atom, atom, PrimitiveAtom } from "jotai"; import { molecule } from "../molecule"; import { createScope } from "../scope"; import { ScopeTuple } from "../types"; +import { Molecule, use } from ".."; type BaseAtoms = { nameAtom: PrimitiveAtom; @@ -49,3 +50,29 @@ export const companyMolecule = molecule((_, getScope) => { companyNameAtom, }; }); + +type Config = { + example: Atom; +}; + +export const ConfigMolecule = molecule(() => { + return { + example: atom("example"), + }; +}); + +export const ConfigScope = createScope | undefined>(undefined); + +export const LibaryMolecule = molecule(() => { + const configMol = use(ConfigScope); + if (!configMol) + throw new Error("This molecule requires ConfigScope to function!"); + + const config = use(configMol) as Config; + const derivedAtom = atom((get) => get(config.example).toUpperCase()); + + return { + ...config, + derivedAtom, + }; +});