From f7927b0c6587bed53226fbb44cbe45d9c1ea4b80 Mon Sep 17 00:00:00 2001 From: AnnuKumar Date: Tue, 7 Jul 2026 19:13:52 +0530 Subject: [PATCH 1/2] feat: implement React Hooks library for Live Context State Management (#37) --- src/index.mjs | 3 +- src/react.mjs | 96 +++++++++++++++++++++++++++++++++++++++++++++ test/react.test.mjs | 67 +++++++++++++++++++++++++++++++ 3 files changed, 165 insertions(+), 1 deletion(-) create mode 100644 src/react.mjs create mode 100644 test/react.test.mjs diff --git a/src/index.mjs b/src/index.mjs index 82b94bb..e991aaf 100644 --- a/src/index.mjs +++ b/src/index.mjs @@ -1,3 +1,4 @@ export { createMemactClient } from "./client.mjs" export { MemactSDKError } from "./errors.mjs" -export { validateClientConfig } from "./config-validation.mjs" \ No newline at end of file +export { validateClientConfig } from "./config-validation.mjs" +export { MemactProvider, useContextClaim } from "./react.mjs" \ No newline at end of file diff --git a/src/react.mjs b/src/react.mjs new file mode 100644 index 0000000..406f50e --- /dev/null +++ b/src/react.mjs @@ -0,0 +1,96 @@ +/** + * Global fallback or structural bindings for React primitives. + * This prevents runtime crash failures in non-React vanilla environments. + */ +const ReactInstance = typeof window !== "undefined" && window.React ? window.React : null; + +// Context provider backing fallback constructor block +const DummyContext = { Provider: function({ value, children }) { return children; } }; +export const MemactContext = { Provider: DummyContext.Provider }; + +export function MemactProvider({ client, children }) { + if (!client) { + console.warn("[Memact SDK] MemactProvider was mounted without a valid client instance."); + } + + // Use dynamically injected React instances if present, otherwise handle structure ducks + const R = ReactInstance || (typeof globalThis !== "undefined" && globalThis.React); + if (R && typeof R.createContext === "function") { + return R.createElement(R.createContext(null).Provider, { value: client }, children); + } + + return children; +} + +/** + * Custom React hook subscribing to live context state updates and claims. + * @param {string} category - The domain category filtering scope (e.g., 'developer_work', 'music') + * @param {Object} options - Subscriptions modifiers and query configuration hooks + */ +export function useContextClaim(category, options = {}) { + // Grab standard state primitives gracefully from global contextual environment injections + const R = ReactInstance || (typeof globalThis !== "undefined" && globalThis.React); + + if (!R) { + return { claim: null, loading: false, error: new Error("React instance not found. Ensure this hook is executed inside a running React application environment.") }; + } + + // Use structural hook bindings natively at runtime + const [claim, setClaim] = R.useState(null); + const [loading, setLoading] = R.useState(true); + const [error, setError] = R.useState(null); + + R.useEffect(() => { + // Structural runtime safety constraints checks + if (!category) { + setError(new Error("category parameter is required for state management filtering.")); + setLoading(false); + return; + } + + let isMounted = true; + setLoading(true); + + const fetchCurrentClaim = async () => { + try { + // Dynamic evaluation pass + const contextClient = typeof options.client === "object" ? options.client : null; + if (!contextClient) { + throw new Error("useContextClaim must be used within a MemactProvider element tree."); + } + + const connectionId = options.connection_id || contextClient.connectionId; + const result = await contextClient.getAllowedMemory({ + connection_id: connectionId, + activity_categories: [category], + ...options + }); + + if (isMounted) { + setClaim(result || null); + setError(null); + } + } catch (err) { + if (isMounted) { + setError(err); + } + } finally { + if (isMounted) { + setLoading(false); + } + } + }; + + fetchCurrentClaim(); + + const liveIntervalMs = options.pollInterval || 4000; + const trackerClockId = setInterval(fetchCurrentClaim, liveIntervalMs); + + return () => { + isMounted = false; + clearInterval(trackerClockId); + }; + }, [category, JSON.stringify(options)]); + + return { claim, loading, error }; +} \ No newline at end of file diff --git a/test/react.test.mjs b/test/react.test.mjs new file mode 100644 index 0000000..1a20751 --- /dev/null +++ b/test/react.test.mjs @@ -0,0 +1,67 @@ +import test from "node:test" +import assert from "node:assert/strict" +import { MemactProvider, useContextClaim } from "../src/react.mjs" + +test("useContextClaim returns error if executed outside of running React engine environment context", () => { + // Ensure runtime does not find React primitive structures + const originalGlobalReact = globalThis.React; + delete globalThis.React; + + try { + const result = useContextClaim("developer_work", {}); + assert.ok(result.error); + assert.match(result.error.message, /React instance not found/); + } finally { + globalThis.React = originalGlobalReact; + } +}); + +test("useContextClaim executes queries correctly under mock structural injection passes", async () => { + let queriesPassed = []; + let trackingClocksCleared = false; + + const mockClient = { + connectionId: "conn_react_isolated_suite", + async getAllowedMemory(payload) { + queriesPassed.push(payload); + return { ok: true, claims: ["react-hook-isolated-assertion-token"] }; + } + }; + + // Construct a clean mock loop mimicking state transformations + let hookEffectClosure = null; + const mockReactEngine = { + useState(initial) { + let val = initial; + const setter = (newVal) => { val = newVal; }; + return [val, setter]; + }, + useEffect(effectClosure) { + hookEffectClosure = effectClosure; + } + }; + + // Inject structural duck parameters natively onto global scope path + globalThis.React = mockReactEngine; + + try { + // Invoke hook with custom client option override injection path mapping + const result = useContextClaim("music", { client: mockClient }); + + // Fire structural lifecycle trigger hooks + if (typeof hookEffectClosure === "function") { + const cleanupFn = await hookEffectClosure(); + if (typeof cleanupFn === "function") { + cleanupFn(); + trackingClocksCleared = true; + } + } + + assert.equal(queriesPassed.length, 1); + assert.equal(queriesPassed[0].connection_id, "conn_react_isolated_suite"); + assert.deepEqual(queriesPassed[0].activity_categories, ["music"]); + assert.ok(trackingClocksCleared); + } finally { + delete globalThis.React; + } +}); \ No newline at end of file From 530b485f27188ad9abab27f92269f783129e85d0 Mon Sep 17 00:00:00 2001 From: AnnuKumar Date: Wed, 8 Jul 2026 17:30:41 +0530 Subject: [PATCH 2/2] refactor: optimize hook context instantiations and env checks per review --- src/react.mjs | 67 ++++++++++++++++++++++++++++----------------- test/react.test.mjs | 21 +++++++++----- 2 files changed, 56 insertions(+), 32 deletions(-) diff --git a/src/react.mjs b/src/react.mjs index 406f50e..5c9edc1 100644 --- a/src/react.mjs +++ b/src/react.mjs @@ -1,22 +1,34 @@ /** - * Global fallback or structural bindings for React primitives. - * This prevents runtime crash failures in non-React vanilla environments. + * Safe reference helper to pull the running React instance. + * Checks typeof window first to guarantee absolute safety in Node environment tests. */ -const ReactInstance = typeof window !== "undefined" && window.React ? window.React : null; +const getReactInstance = () => { + if (typeof window !== "undefined" && window.React) return window.React; + if (typeof globalThis !== "undefined" && globalThis.React) return globalThis.React; + return null; +}; -// Context provider backing fallback constructor block -const DummyContext = { Provider: function({ value, children }) { return children; } }; -export const MemactContext = { Provider: DummyContext.Provider }; +// 1. FIXED: Creating the shared stable context instance OUTSIDE of the rendering cycle +let SharedMemactContext = null; + +const getContextInstance = (R) => { + if (!SharedMemactContext && R && typeof R.createContext === "function") { + SharedMemactContext = R.createContext(null); + } + return SharedMemactContext; +}; export function MemactProvider({ client, children }) { if (!client) { console.warn("[Memact SDK] MemactProvider was mounted without a valid client instance."); } - // Use dynamically injected React instances if present, otherwise handle structure ducks - const R = ReactInstance || (typeof globalThis !== "undefined" && globalThis.React); - if (R && typeof R.createContext === "function") { - return R.createElement(R.createContext(null).Provider, { value: client }, children); + const R = getReactInstance(); + const Context = getContextInstance(R); + + // If running in an active React environment, cleanly create the element provider + if (R && Context) { + return R.createElement(Context.Provider, { value: client }, children); } return children; @@ -28,20 +40,31 @@ export function MemactProvider({ client, children }) { * @param {Object} options - Subscriptions modifiers and query configuration hooks */ export function useContextClaim(category, options = {}) { - // Grab standard state primitives gracefully from global contextual environment injections - const R = ReactInstance || (typeof globalThis !== "undefined" && globalThis.React); + const R = getReactInstance(); + const Context = getContextInstance(R); - if (!R) { - return { claim: null, loading: false, error: new Error("React instance not found. Ensure this hook is executed inside a running React application environment.") }; + if (!R || !Context) { + return { + claim: null, + loading: false, + error: new Error("React instance not found. Ensure this hook is executed inside a running React application environment.") + }; } - // Use structural hook bindings natively at runtime + // 3. FIXED: Extract client via React.useContext from our stable, outer context instance + const client = R.useContext(Context); + const [claim, setClaim] = R.useState(null); const [loading, setLoading] = R.useState(true); const [error, setError] = R.useState(null); R.useEffect(() => { - // Structural runtime safety constraints checks + if (!client) { + setError(new Error("useContextClaim must be used within a MemactProvider element tree.")); + setLoading(false); + return; + } + if (!category) { setError(new Error("category parameter is required for state management filtering.")); setLoading(false); @@ -53,14 +76,8 @@ export function useContextClaim(category, options = {}) { const fetchCurrentClaim = async () => { try { - // Dynamic evaluation pass - const contextClient = typeof options.client === "object" ? options.client : null; - if (!contextClient) { - throw new Error("useContextClaim must be used within a MemactProvider element tree."); - } - - const connectionId = options.connection_id || contextClient.connectionId; - const result = await contextClient.getAllowedMemory({ + const connectionId = options.connection_id || client.connectionId; + const result = await client.getAllowedMemory({ connection_id: connectionId, activity_categories: [category], ...options @@ -90,7 +107,7 @@ export function useContextClaim(category, options = {}) { isMounted = false; clearInterval(trackerClockId); }; - }, [category, JSON.stringify(options)]); + }, [client, category, JSON.stringify(options)]); return { claim, loading, error }; } \ No newline at end of file diff --git a/test/react.test.mjs b/test/react.test.mjs index 1a20751..daf8ac4 100644 --- a/test/react.test.mjs +++ b/test/react.test.mjs @@ -3,7 +3,6 @@ import assert from "node:assert/strict" import { MemactProvider, useContextClaim } from "../src/react.mjs" test("useContextClaim returns error if executed outside of running React engine environment context", () => { - // Ensure runtime does not find React primitive structures const originalGlobalReact = globalThis.React; delete globalThis.React; @@ -16,7 +15,7 @@ test("useContextClaim returns error if executed outside of running React engine } }); -test("useContextClaim executes queries correctly under mock structural injection passes", async () => { +test("useContextClaim executes queries correctly under mock structural context injection passes", async () => { let queriesPassed = []; let trackingClocksCleared = false; @@ -28,9 +27,18 @@ test("useContextClaim executes queries correctly under mock structural injection } }; - // Construct a clean mock loop mimicking state transformations let hookEffectClosure = null; const mockReactEngine = { + createContext() { + return { Provider: function({ children }) { return children; } }; + }, + // FIXED: Added mock createElement tracking to return children context safely + createElement(type, props, children) { + return children; + }, + useContext() { + return mockClient; + }, useState(initial) { let val = initial; const setter = (newVal) => { val = newVal; }; @@ -41,14 +49,13 @@ test("useContextClaim executes queries correctly under mock structural injection } }; - // Inject structural duck parameters natively onto global scope path globalThis.React = mockReactEngine; try { - // Invoke hook with custom client option override injection path mapping - const result = useContextClaim("music", { client: mockClient }); + // Mount provider then pull hook state tracking metrics + MemactProvider({ client: mockClient, children: null }); + const result = useContextClaim("music", {}); - // Fire structural lifecycle trigger hooks if (typeof hookEffectClosure === "function") { const cleanupFn = await hookEffectClosure(); if (typeof cleanupFn === "function") {