@fynixorg/ui 1.0.12 → 1.0.14

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.
@@ -1,4 +1,8 @@
1
- export declare function nixAsync<T>(promiseFactory: (signal: AbortSignal) => Promise<T>): {
1
+ export declare function nixAsync<T>(promiseFactory: (signal: AbortSignal) => Promise<T>, options?: {
2
+ timeout?: number;
3
+ retries?: number;
4
+ autoRun?: boolean;
5
+ }): {
2
6
  data: {
3
7
  value: T | null;
4
8
  };
@@ -10,5 +14,7 @@ export declare function nixAsync<T>(promiseFactory: (signal: AbortSignal) => Pro
10
14
  };
11
15
  run: () => Promise<void>;
12
16
  cancel: () => void;
17
+ cleanup: () => void;
18
+ getCallId: () => number;
13
19
  };
14
20
  //# sourceMappingURL=nixAsync.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"nixAsync.d.ts","sourceRoot":"","sources":["../../hooks/nixAsync.ts"],"names":[],"mappings":"AA+BA,wBAAgB,QAAQ,CAAC,CAAC,EACxB,cAAc,EAAE,CAAC,MAAM,EAAE,WAAW,KAAK,OAAO,CAAC,CAAC,CAAC,GAClD;IACD,IAAI,EAAE;QAAE,KAAK,EAAE,CAAC,GAAG,IAAI,CAAA;KAAE,CAAC;IAC1B,KAAK,EAAE;QAAE,KAAK,EAAE,KAAK,GAAG,IAAI,CAAA;KAAE,CAAC;IAC/B,OAAO,EAAE;QAAE,KAAK,EAAE,OAAO,CAAA;KAAE,CAAC;IAC5B,GAAG,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACzB,MAAM,EAAE,MAAM,IAAI,CAAC;CACpB,CAwCA"}
1
+ {"version":3,"file":"nixAsync.d.ts","sourceRoot":"","sources":["../../hooks/nixAsync.ts"],"names":[],"mappings":"AAmCA,wBAAgB,QAAQ,CAAC,CAAC,EACxB,cAAc,EAAE,CAAC,MAAM,EAAE,WAAW,KAAK,OAAO,CAAC,CAAC,CAAC,EACnD,OAAO,GAAE;IACP,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,CAAC;CACd,GACL;IACD,IAAI,EAAE;QAAE,KAAK,EAAE,CAAC,GAAG,IAAI,CAAA;KAAE,CAAC;IAC1B,KAAK,EAAE;QAAE,KAAK,EAAE,KAAK,GAAG,IAAI,CAAA;KAAE,CAAC;IAC/B,OAAO,EAAE;QAAE,KAAK,EAAE,OAAO,CAAA;KAAE,CAAC;IAC5B,GAAG,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACzB,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,SAAS,EAAE,MAAM,MAAM,CAAC;CACzB,CA8IA"}
@@ -1,40 +1,114 @@
1
1
  import { nixState } from "./nixState";
2
- export function nixAsync(promiseFactory) {
2
+ export function nixAsync(promiseFactory, options = {}) {
3
+ const { timeout = 30000, retries = 0, autoRun = false } = options;
4
+ if (typeof promiseFactory !== "function") {
5
+ throw new TypeError("[nixAsync] promiseFactory must be a function");
6
+ }
3
7
  const data = nixState(null);
4
8
  const error = nixState(null);
5
9
  const loading = nixState(false);
6
10
  let active = true;
7
11
  let controller = null;
8
12
  let callId = 0;
13
+ let timeoutId = null;
14
+ let retryCount = 0;
15
+ const cleanup = () => {
16
+ active = false;
17
+ if (controller) {
18
+ controller.abort();
19
+ controller = null;
20
+ }
21
+ if (timeoutId) {
22
+ clearTimeout(timeoutId);
23
+ timeoutId = null;
24
+ }
25
+ callId = 0;
26
+ retryCount = 0;
27
+ };
9
28
  const run = async () => {
10
- if (controller)
29
+ if (!active) {
30
+ console.warn("[nixAsync] Attempted to run on destroyed async hook");
31
+ return;
32
+ }
33
+ if (controller) {
11
34
  controller.abort();
35
+ }
36
+ if (timeoutId) {
37
+ clearTimeout(timeoutId);
38
+ }
12
39
  controller = new AbortController();
13
40
  const signal = controller.signal;
14
- const id = ++callId;
41
+ const currentCallId = ++callId;
42
+ retryCount = 0;
15
43
  loading.value = true;
16
44
  error.value = null;
45
+ if (timeout > 0) {
46
+ timeoutId = setTimeout(() => {
47
+ if (controller && currentCallId === callId) {
48
+ controller.abort();
49
+ if (active && currentCallId === callId) {
50
+ error.value = new Error(`Request timeout after ${timeout}ms`);
51
+ loading.value = false;
52
+ }
53
+ }
54
+ }, timeout);
55
+ }
17
56
  try {
18
57
  const result = await promiseFactory(signal);
19
- if (!active || id !== callId || signal.aborted)
58
+ if (!active || currentCallId !== callId || signal.aborted) {
20
59
  return;
60
+ }
21
61
  data.value = result;
62
+ retryCount = 0;
22
63
  }
23
64
  catch (e) {
24
- if (!active || id !== callId || signal.aborted)
65
+ if (!active || currentCallId !== callId) {
25
66
  return;
26
- error.value = e instanceof Error ? e : new Error(String(e));
67
+ }
68
+ if (signal.aborted) {
69
+ return;
70
+ }
71
+ const errorInstance = e instanceof Error ? e : new Error(String(e));
72
+ if (retryCount < retries && active && currentCallId === callId) {
73
+ retryCount++;
74
+ console.warn(`[nixAsync] Retrying (${retryCount}/${retries}):`, errorInstance.message);
75
+ const retryDelay = Math.min(1000 * Math.pow(2, retryCount - 1), 10000);
76
+ setTimeout(() => {
77
+ if (active && currentCallId === callId) {
78
+ run();
79
+ }
80
+ }, retryDelay);
81
+ return;
82
+ }
83
+ error.value = errorInstance;
27
84
  }
28
85
  finally {
29
- if (active && id === callId && !signal.aborted) {
86
+ if (timeoutId) {
87
+ clearTimeout(timeoutId);
88
+ timeoutId = null;
89
+ }
90
+ if (active && currentCallId === callId && !signal.aborted) {
30
91
  loading.value = false;
31
92
  }
32
93
  }
33
94
  };
34
95
  const cancel = () => {
35
- active = false;
36
- if (controller)
96
+ if (controller) {
37
97
  controller.abort();
98
+ }
99
+ if (timeoutId) {
100
+ clearTimeout(timeoutId);
101
+ timeoutId = null;
102
+ }
103
+ loading.value = false;
104
+ retryCount = 0;
38
105
  };
39
- return { data, error, loading, run, cancel };
106
+ const getCallId = () => callId;
107
+ if (autoRun) {
108
+ setTimeout(() => {
109
+ if (active)
110
+ run();
111
+ }, 0);
112
+ }
113
+ return { data, error, loading, run, cancel, cleanup, getCallId };
40
114
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../hooks/nixAsync.ts"],
4
- "sourcesContent": ["/* MIT License\r\n\r\n* Copyright (c) 2026 Resty Gonzales\r\n\r\nPermission is hereby granted, free of charge, to any person obtaining a copy\r\nof this software and associated documentation files (the \"Software\"), to deal\r\nin the Software without restriction, including without limitation the rights\r\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\ncopies of the Software, and to permit persons to whom the Software is\r\nfurnished to do so, subject to the following conditions:\r\n\r\nThe above copyright notice and this permission notice shall be included in all\r\ncopies or substantial portions of the Software.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\r\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\r\n* SOFTWARE.\r\n */\r\n\r\nimport { nixState } from \"./nixState\";\r\n\r\n/**\r\n * Async state helper with AbortController support.\r\n *\r\n * @template T\r\n * @param {(signal: AbortSignal) => Promise<T>} promiseFactory\r\n */\r\nexport function nixAsync<T>(\r\n promiseFactory: (signal: AbortSignal) => Promise<T>\r\n): {\r\n data: { value: T | null };\r\n error: { value: Error | null };\r\n loading: { value: boolean };\r\n run: () => Promise<void>;\r\n cancel: () => void;\r\n} {\r\n const data = nixState(null) as { value: T | null };\r\n const error = nixState(null) as { value: Error | null };\r\n const loading = nixState(false) as { value: boolean };\r\n\r\n let active: boolean = true;\r\n let controller: AbortController | null = null;\r\n let callId: number = 0;\r\n\r\n const run = async (): Promise<void> => {\r\n // Cancel previous request\r\n if (controller) controller.abort();\r\n\r\n controller = new AbortController();\r\n const signal = controller.signal;\r\n const id = ++callId;\r\n\r\n loading.value = true;\r\n error.value = null;\r\n\r\n try {\r\n const result = await promiseFactory(signal);\r\n if (!active || id !== callId || signal.aborted) return;\r\n data.value = result;\r\n } catch (e: unknown) {\r\n if (!active || id !== callId || signal.aborted) return;\r\n error.value = e instanceof Error ? e : new Error(String(e));\r\n } finally {\r\n if (active && id === callId && !signal.aborted) {\r\n loading.value = false;\r\n }\r\n }\r\n };\r\n\r\n const cancel = (): void => {\r\n active = false;\r\n if (controller) controller.abort();\r\n };\r\n\r\n return { data, error, loading, run, cancel };\r\n}\r\n"],
5
- "mappings": ";;AAuBA,SAAS,gBAAgB;AAQlB,SAAS,SACd,gBAOA;AACA,QAAM,OAAO,SAAS,IAAI;AAC1B,QAAM,QAAQ,SAAS,IAAI;AAC3B,QAAM,UAAU,SAAS,KAAK;AAE9B,MAAI,SAAkB;AACtB,MAAI,aAAqC;AACzC,MAAI,SAAiB;AAErB,QAAM,MAAM,mCAA2B;AAErC,QAAI,WAAY,YAAW,MAAM;AAEjC,iBAAa,IAAI,gBAAgB;AACjC,UAAM,SAAS,WAAW;AAC1B,UAAM,KAAK,EAAE;AAEb,YAAQ,QAAQ;AAChB,UAAM,QAAQ;AAEd,QAAI;AACF,YAAM,SAAS,MAAM,eAAe,MAAM;AAC1C,UAAI,CAAC,UAAU,OAAO,UAAU,OAAO,QAAS;AAChD,WAAK,QAAQ;AAAA,IACf,SAAS,GAAY;AACnB,UAAI,CAAC,UAAU,OAAO,UAAU,OAAO,QAAS;AAChD,YAAM,QAAQ,aAAa,QAAQ,IAAI,IAAI,MAAM,OAAO,CAAC,CAAC;AAAA,IAC5D,UAAE;AACA,UAAI,UAAU,OAAO,UAAU,CAAC,OAAO,SAAS;AAC9C,gBAAQ,QAAQ;AAAA,MAClB;AAAA,IACF;AAAA,EACF,GAvBY;AAyBZ,QAAM,SAAS,6BAAY;AACzB,aAAS;AACT,QAAI,WAAY,YAAW,MAAM;AAAA,EACnC,GAHe;AAKf,SAAO,EAAE,MAAM,OAAO,SAAS,KAAK,OAAO;AAC7C;AAhDgB;",
4
+ "sourcesContent": ["/* MIT License\r\n\r\n* Copyright (c) 2026 Resty Gonzales\r\n\r\nPermission is hereby granted, free of charge, to any person obtaining a copy\r\nof this software and associated documentation files (the \"Software\"), to deal\r\nin the Software without restriction, including without limitation the rights\r\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\ncopies of the Software, and to permit persons to whom the Software is\r\nfurnished to do so, subject to the following conditions:\r\n\r\nThe above copyright notice and this permission notice shall be included in all\r\ncopies or substantial portions of the Software.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\r\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\r\n* SOFTWARE.\r\n */\r\n\r\nimport { nixState } from \"./nixState\";\r\n\r\n/**\r\n * Async state helper with AbortController support and race condition protection.\r\n *\r\n * @template T\r\n * @param {(signal: AbortSignal) => Promise<T>} promiseFactory\r\n * @param {Object} options - Configuration options\r\n * @param {number} options.timeout - Request timeout in milliseconds (default: 30000)\r\n * @param {number} options.retries - Number of retry attempts (default: 0)\r\n * @param {boolean} options.autoRun - Whether to run immediately (default: false)\r\n */\r\nexport function nixAsync<T>(\r\n promiseFactory: (signal: AbortSignal) => Promise<T>,\r\n options: {\r\n timeout?: number;\r\n retries?: number;\r\n autoRun?: boolean;\r\n } = {}\r\n): {\r\n data: { value: T | null };\r\n error: { value: Error | null };\r\n loading: { value: boolean };\r\n run: () => Promise<void>;\r\n cancel: () => void;\r\n cleanup: () => void;\r\n getCallId: () => number;\r\n} {\r\n const { timeout = 30000, retries = 0, autoRun = false } = options;\r\n\r\n if (typeof promiseFactory !== \"function\") {\r\n throw new TypeError(\"[nixAsync] promiseFactory must be a function\");\r\n }\r\n\r\n const data = nixState<T | null>(null);\r\n const error = nixState<Error | null>(null);\r\n const loading = nixState<boolean>(false);\r\n\r\n let active = true;\r\n let controller: AbortController | null = null;\r\n let callId = 0;\r\n let timeoutId: NodeJS.Timeout | null = null;\r\n let retryCount = 0;\r\n\r\n const cleanup = (): void => {\r\n active = false;\r\n if (controller) {\r\n controller.abort();\r\n controller = null;\r\n }\r\n if (timeoutId) {\r\n clearTimeout(timeoutId);\r\n timeoutId = null;\r\n }\r\n callId = 0;\r\n retryCount = 0;\r\n };\r\n\r\n const run = async (): Promise<void> => {\r\n if (!active) {\r\n console.warn(\"[nixAsync] Attempted to run on destroyed async hook\");\r\n return;\r\n }\r\n\r\n // Cancel previous request\r\n if (controller) {\r\n controller.abort();\r\n }\r\n if (timeoutId) {\r\n clearTimeout(timeoutId);\r\n }\r\n\r\n controller = new AbortController();\r\n const signal = controller.signal;\r\n const currentCallId = ++callId;\r\n retryCount = 0;\r\n\r\n loading.value = true;\r\n error.value = null;\r\n\r\n // Set timeout\r\n if (timeout > 0) {\r\n timeoutId = setTimeout(() => {\r\n if (controller && currentCallId === callId) {\r\n controller.abort();\r\n if (active && currentCallId === callId) {\r\n error.value = new Error(`Request timeout after ${timeout}ms`);\r\n loading.value = false;\r\n }\r\n }\r\n }, timeout);\r\n }\r\n\r\n try {\r\n const result = await promiseFactory(signal);\r\n\r\n // Check if this is still the current call and component is active\r\n if (!active || currentCallId !== callId || signal.aborted) {\r\n return;\r\n }\r\n\r\n data.value = result;\r\n retryCount = 0;\r\n } catch (e: unknown) {\r\n // Check if this is still the current call and component is active\r\n if (!active || currentCallId !== callId) {\r\n return;\r\n }\r\n\r\n // Don't set error if aborted (user cancelled)\r\n if (signal.aborted) {\r\n return;\r\n }\r\n\r\n const errorInstance = e instanceof Error ? e : new Error(String(e));\r\n\r\n // Retry logic\r\n if (retryCount < retries && active && currentCallId === callId) {\r\n retryCount++;\r\n console.warn(\r\n `[nixAsync] Retrying (${retryCount}/${retries}):`,\r\n errorInstance.message\r\n );\r\n // Exponential backoff: 1s, 2s, 4s, etc.\r\n const retryDelay = Math.min(1000 * Math.pow(2, retryCount - 1), 10000);\r\n setTimeout(() => {\r\n if (active && currentCallId === callId) {\r\n run();\r\n }\r\n }, retryDelay);\r\n return;\r\n }\r\n\r\n error.value = errorInstance;\r\n } finally {\r\n if (timeoutId) {\r\n clearTimeout(timeoutId);\r\n timeoutId = null;\r\n }\r\n\r\n if (active && currentCallId === callId && !signal.aborted) {\r\n loading.value = false;\r\n }\r\n }\r\n };\r\n\r\n const cancel = (): void => {\r\n if (controller) {\r\n controller.abort();\r\n }\r\n if (timeoutId) {\r\n clearTimeout(timeoutId);\r\n timeoutId = null;\r\n }\r\n loading.value = false;\r\n retryCount = 0;\r\n };\r\n\r\n const getCallId = (): number => callId;\r\n\r\n // Auto-run if requested\r\n if (autoRun) {\r\n // Use setTimeout to avoid running during component initialization\r\n setTimeout(() => {\r\n if (active) run();\r\n }, 0);\r\n }\r\n\r\n return { data, error, loading, run, cancel, cleanup, getCallId };\r\n}\r\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,4 +1,8 @@
1
- export declare function nixAsyncCached(key: any, promiseFactory: () => Promise<any>): {
1
+ export declare function nixAsyncCached(key: any, promiseFactory: () => Promise<any>, options?: {
2
+ ttl?: number;
3
+ maxCacheSize?: number;
4
+ validateKey?: (key: any) => boolean;
5
+ }): {
2
6
  data: {
3
7
  value: any;
4
8
  };
@@ -10,5 +14,6 @@ export declare function nixAsyncCached(key: any, promiseFactory: () => Promise<a
10
14
  };
11
15
  run: () => Promise<void>;
12
16
  cancel: () => void;
17
+ clearCache: () => void;
13
18
  };
14
19
  //# sourceMappingURL=nixAsyncCache.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"nixAsyncCache.d.ts","sourceRoot":"","sources":["../../hooks/nixAsyncCache.ts"],"names":[],"mappings":"AA0BA,wBAAgB,cAAc,CAC5B,GAAG,EAAE,GAAG,EACR,cAAc,EAAE,MAAM,OAAO,CAAC,GAAG,CAAC,GACjC;IACD,IAAI,EAAE;QAAE,KAAK,EAAE,GAAG,CAAA;KAAE,CAAC;IACrB,KAAK,EAAE;QAAE,KAAK,EAAE,GAAG,CAAA;KAAE,CAAC;IACtB,OAAO,EAAE;QAAE,KAAK,EAAE,OAAO,CAAA;KAAE,CAAC;IAC5B,GAAG,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACzB,MAAM,EAAE,MAAM,IAAI,CAAC;CACpB,CA2DA"}
1
+ {"version":3,"file":"nixAsyncCache.d.ts","sourceRoot":"","sources":["../../hooks/nixAsyncCache.ts"],"names":[],"mappings":"AAoEA,wBAAgB,cAAc,CAC5B,GAAG,EAAE,GAAG,EACR,cAAc,EAAE,MAAM,OAAO,CAAC,GAAG,CAAC,EAClC,OAAO,GAAE;IACP,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,OAAO,CAAC;CAChC,GACL;IACD,IAAI,EAAE;QAAE,KAAK,EAAE,GAAG,CAAA;KAAE,CAAC;IACrB,KAAK,EAAE;QAAE,KAAK,EAAE,GAAG,CAAA;KAAE,CAAC;IACtB,OAAO,EAAE;QAAE,KAAK,EAAE,OAAO,CAAA;KAAE,CAAC;IAC5B,GAAG,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACzB,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB,UAAU,EAAE,MAAM,IAAI,CAAC;CACxB,CA6IA"}
@@ -1,59 +1,137 @@
1
1
  import { nixState } from "./nixState";
2
2
  const asyncCache = new Map();
3
- export function nixAsyncCached(key, promiseFactory) {
3
+ const CACHE_CLEANUP_INTERVAL = 60000;
4
+ let cacheCleanupTimer = null;
5
+ const startCacheCleanup = () => {
6
+ if (cacheCleanupTimer)
7
+ return;
8
+ cacheCleanupTimer = setInterval(() => {
9
+ const now = Date.now();
10
+ const entries = Array.from(asyncCache.entries());
11
+ for (const [key, entry] of entries) {
12
+ if (entry.timestamp && entry.ttl && now - entry.timestamp > entry.ttl) {
13
+ asyncCache.delete(key);
14
+ }
15
+ }
16
+ if (asyncCache.size === 0 && cacheCleanupTimer) {
17
+ clearInterval(cacheCleanupTimer);
18
+ cacheCleanupTimer = null;
19
+ }
20
+ }, CACHE_CLEANUP_INTERVAL);
21
+ };
22
+ export function nixAsyncCached(key, promiseFactory, options = {}) {
23
+ if (!promiseFactory || typeof promiseFactory !== "function") {
24
+ throw new Error("[nixAsyncCache] promiseFactory must be a function");
25
+ }
26
+ if (key == null) {
27
+ throw new Error("[nixAsyncCache] Key cannot be null or undefined");
28
+ }
29
+ if (options.validateKey && !options.validateKey(key)) {
30
+ throw new Error("[nixAsyncCache] Invalid cache key");
31
+ }
32
+ const { ttl = 300000, maxCacheSize = 100 } = options;
33
+ if (asyncCache.size >= maxCacheSize) {
34
+ const entries = Array.from(asyncCache.entries());
35
+ const entriesToRemove = Math.max(1, Math.floor(maxCacheSize * 0.1));
36
+ for (let i = 0; i < entriesToRemove; i++) {
37
+ const entry = entries[i];
38
+ if (entry) {
39
+ asyncCache.delete(entry[0]);
40
+ }
41
+ }
42
+ }
4
43
  const data = nixState(null);
5
44
  const error = nixState(null);
6
45
  const loading = nixState(false);
7
46
  let active = true;
47
+ let abortController = null;
48
+ startCacheCleanup();
8
49
  const run = async () => {
50
+ if (!active)
51
+ return;
52
+ if (abortController) {
53
+ abortController.abort();
54
+ }
55
+ abortController = new AbortController();
9
56
  loading.value = true;
10
57
  error.value = null;
11
- if (asyncCache.has(key)) {
12
- const cached = asyncCache.get(key);
13
- if (cached) {
14
- if (cached.data) {
58
+ try {
59
+ if (asyncCache.has(key)) {
60
+ const cached = asyncCache.get(key);
61
+ const now = Date.now();
62
+ if (cached.timestamp && now - cached.timestamp > ttl) {
63
+ asyncCache.delete(key);
64
+ }
65
+ else if (cached.data !== undefined) {
15
66
  data.value = cached.data;
16
67
  loading.value = false;
17
68
  return;
18
69
  }
19
- try {
20
- const result = await cached.promise;
21
- if (!active)
70
+ if (cached.promise) {
71
+ try {
72
+ const result = await cached.promise;
73
+ if (!active || abortController?.signal.aborted)
74
+ return;
75
+ data.value = result;
76
+ loading.value = false;
22
77
  return;
23
- data.value = result;
24
- loading.value = false;
25
- }
26
- catch (e) {
27
- if (!active)
78
+ }
79
+ catch (e) {
80
+ if (!active || abortController?.signal.aborted)
81
+ return;
82
+ error.value = e;
83
+ loading.value = false;
28
84
  return;
29
- error.value = e;
30
- loading.value = false;
85
+ }
31
86
  }
32
- return;
33
87
  }
34
- }
35
- const promise = Promise.resolve().then(promiseFactory);
36
- asyncCache.set(key, { promise });
37
- try {
88
+ const promise = Promise.resolve().then(() => {
89
+ if (abortController?.signal.aborted) {
90
+ throw new Error("Request was aborted");
91
+ }
92
+ return promiseFactory();
93
+ });
94
+ asyncCache.set(key, {
95
+ promise,
96
+ timestamp: Date.now(),
97
+ ttl,
98
+ });
38
99
  const result = await promise;
39
- asyncCache.set(key, { data: result });
40
- if (!active)
100
+ if (!active || abortController?.signal.aborted)
41
101
  return;
102
+ asyncCache.set(key, {
103
+ data: result,
104
+ timestamp: Date.now(),
105
+ ttl,
106
+ });
42
107
  data.value = result;
43
108
  }
44
109
  catch (e) {
45
- asyncCache.delete(key);
46
- if (!active)
110
+ if (asyncCache.has(key)) {
111
+ const cached = asyncCache.get(key);
112
+ if (cached.promise && !cached.data) {
113
+ asyncCache.delete(key);
114
+ }
115
+ }
116
+ if (!active || abortController?.signal.aborted)
47
117
  return;
48
118
  error.value = e;
49
119
  }
50
120
  finally {
51
- if (active)
121
+ if (active && !abortController?.signal.aborted) {
52
122
  loading.value = false;
123
+ }
53
124
  }
54
125
  };
55
126
  const cancel = () => {
56
127
  active = false;
128
+ if (abortController) {
129
+ abortController.abort();
130
+ abortController = null;
131
+ }
132
+ };
133
+ const clearCache = () => {
134
+ asyncCache.delete(key);
57
135
  };
58
- return { data, error, loading, run, cancel };
136
+ return { data, error, loading, run, cancel, clearCache };
59
137
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../hooks/nixAsyncCache.ts"],
4
- "sourcesContent": ["/* MIT License\r\n\r\n* Copyright (c) 2026 Resty Gonzales\r\n\r\nPermission is hereby granted, free of charge, to any person obtaining a copy\r\nof this software and associated documentation files (the \"Software\"), to deal\r\nin the Software without restriction, including without limitation the rights\r\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\ncopies of the Software, and to permit persons to whom the Software is\r\nfurnished to do so, subject to the following conditions:\r\n\r\nThe above copyright notice and this permission notice shall be included in all\r\ncopies or substantial portions of the Software.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\r\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\r\n* SOFTWARE.\r\n */\r\nimport { nixState } from \"./nixState\";\r\n\r\nconst asyncCache: Map<any, { data?: any; promise?: Promise<any> }> = new Map();\r\n\r\nexport function nixAsyncCached(\r\n key: any,\r\n promiseFactory: () => Promise<any>\r\n): {\r\n data: { value: any };\r\n error: { value: any };\r\n loading: { value: boolean };\r\n run: () => Promise<void>;\r\n cancel: () => void;\r\n} {\r\n const data = nixState(null) as { value: any };\r\n const error = nixState(null) as { value: any };\r\n const loading = nixState(false) as { value: boolean };\r\n\r\n let active: boolean = true;\r\n\r\n const run = async (): Promise<void> => {\r\n loading.value = true;\r\n error.value = null;\r\n\r\n // Cache hit with resolved data\r\n if (asyncCache.has(key)) {\r\n const cached = asyncCache.get(key);\r\n if (cached) {\r\n if (cached.data) {\r\n data.value = cached.data;\r\n loading.value = false;\r\n return;\r\n }\r\n\r\n // Deduping: reuse in-flight promise\r\n try {\r\n const result = await cached.promise;\r\n if (!active) return;\r\n data.value = result;\r\n loading.value = false;\r\n } catch (e) {\r\n if (!active) return;\r\n error.value = e;\r\n loading.value = false;\r\n }\r\n return;\r\n }\r\n }\r\n\r\n // Cache miss\r\n const promise = Promise.resolve().then(promiseFactory);\r\n asyncCache.set(key, { promise });\r\n\r\n try {\r\n const result = await promise;\r\n asyncCache.set(key, { data: result });\r\n if (!active) return;\r\n data.value = result;\r\n } catch (e) {\r\n asyncCache.delete(key);\r\n if (!active) return;\r\n error.value = e;\r\n } finally {\r\n if (active) loading.value = false;\r\n }\r\n };\r\n\r\n const cancel = () => {\r\n active = false;\r\n };\r\n\r\n return { data, error, loading, run, cancel };\r\n}\r\n"],
5
- "mappings": ";;AAsBA,SAAS,gBAAgB;AAEzB,MAAM,aAA+D,oBAAI,IAAI;AAEtE,SAAS,eACd,KACA,gBAOA;AACA,QAAM,OAAO,SAAS,IAAI;AAC1B,QAAM,QAAQ,SAAS,IAAI;AAC3B,QAAM,UAAU,SAAS,KAAK;AAE9B,MAAI,SAAkB;AAEtB,QAAM,MAAM,mCAA2B;AACrC,YAAQ,QAAQ;AAChB,UAAM,QAAQ;AAGd,QAAI,WAAW,IAAI,GAAG,GAAG;AACvB,YAAM,SAAS,WAAW,IAAI,GAAG;AACjC,UAAI,QAAQ;AACV,YAAI,OAAO,MAAM;AACf,eAAK,QAAQ,OAAO;AACpB,kBAAQ,QAAQ;AAChB;AAAA,QACF;AAGA,YAAI;AACF,gBAAM,SAAS,MAAM,OAAO;AAC5B,cAAI,CAAC,OAAQ;AACb,eAAK,QAAQ;AACb,kBAAQ,QAAQ;AAAA,QAClB,SAAS,GAAG;AACV,cAAI,CAAC,OAAQ;AACb,gBAAM,QAAQ;AACd,kBAAQ,QAAQ;AAAA,QAClB;AACA;AAAA,MACF;AAAA,IACF;AAGA,UAAM,UAAU,QAAQ,QAAQ,EAAE,KAAK,cAAc;AACrD,eAAW,IAAI,KAAK,EAAE,QAAQ,CAAC;AAE/B,QAAI;AACF,YAAM,SAAS,MAAM;AACrB,iBAAW,IAAI,KAAK,EAAE,MAAM,OAAO,CAAC;AACpC,UAAI,CAAC,OAAQ;AACb,WAAK,QAAQ;AAAA,IACf,SAAS,GAAG;AACV,iBAAW,OAAO,GAAG;AACrB,UAAI,CAAC,OAAQ;AACb,YAAM,QAAQ;AAAA,IAChB,UAAE;AACA,UAAI,OAAQ,SAAQ,QAAQ;AAAA,IAC9B;AAAA,EACF,GA7CY;AA+CZ,QAAM,SAAS,6BAAM;AACnB,aAAS;AAAA,EACX,GAFe;AAIf,SAAO,EAAE,MAAM,OAAO,SAAS,KAAK,OAAO;AAC7C;AApEgB;",
6
- "names": []
4
+ "sourcesContent": ["/* MIT License\r\n\r\n* Copyright (c) 2026 Resty Gonzales\r\n\r\nPermission is hereby granted, free of charge, to any person obtaining a copy\r\nof this software and associated documentation files (the \"Software\"), to deal\r\nin the Software without restriction, including without limitation the rights\r\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\ncopies of the Software, and to permit persons to whom the Software is\r\nfurnished to do so, subject to the following conditions:\r\n\r\nThe above copyright notice and this permission notice shall be included in all\r\ncopies or substantial portions of the Software.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\r\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\r\n* SOFTWARE.\r\n */\r\nimport { nixState } from \"./nixState\";\r\n\r\nconst asyncCache: Map<\r\n any,\r\n {\r\n data?: any;\r\n promise?: Promise<any>;\r\n timestamp?: number;\r\n ttl?: number;\r\n }\r\n> = new Map();\r\n\r\n// Cache cleanup to prevent memory leaks\r\nconst CACHE_CLEANUP_INTERVAL = 60000; // 1 minute\r\nlet cacheCleanupTimer: NodeJS.Timeout | null = null;\r\n\r\nconst startCacheCleanup = () => {\r\n if (cacheCleanupTimer) return;\r\n\r\n cacheCleanupTimer = setInterval(() => {\r\n const now = Date.now();\r\n const entries = Array.from(asyncCache.entries());\r\n\r\n for (const [key, entry] of entries) {\r\n if (entry.timestamp && entry.ttl && now - entry.timestamp > entry.ttl) {\r\n asyncCache.delete(key);\r\n }\r\n }\r\n\r\n // If cache is empty, stop cleanup timer\r\n if (asyncCache.size === 0 && cacheCleanupTimer) {\r\n clearInterval(cacheCleanupTimer);\r\n cacheCleanupTimer = null;\r\n }\r\n }, CACHE_CLEANUP_INTERVAL);\r\n};\r\n\r\n/**\r\n * Cached async hook with enhanced security and memory management.\r\n *\r\n * @param {any} key - Cache key (must be serializable)\r\n * @param {() => Promise<any>} promiseFactory - Function that returns a promise\r\n * @param {object} options - Configuration options\r\n * @returns {object} Reactive state object with data, error, loading, and control methods\r\n */\r\n\r\nexport function nixAsyncCached(\r\n key: any,\r\n promiseFactory: () => Promise<any>,\r\n options: {\r\n ttl?: number; // Time to live in ms\r\n maxCacheSize?: number;\r\n validateKey?: (key: any) => boolean;\r\n } = {}\r\n): {\r\n data: { value: any };\r\n error: { value: any };\r\n loading: { value: boolean };\r\n run: () => Promise<void>;\r\n cancel: () => void;\r\n clearCache: () => void;\r\n} {\r\n // Input validation\r\n if (!promiseFactory || typeof promiseFactory !== \"function\") {\r\n throw new Error(\"[nixAsyncCache] promiseFactory must be a function\");\r\n }\r\n\r\n // Validate cache key\r\n if (key == null) {\r\n throw new Error(\"[nixAsyncCache] Key cannot be null or undefined\");\r\n }\r\n\r\n if (options.validateKey && !options.validateKey(key)) {\r\n throw new Error(\"[nixAsyncCache] Invalid cache key\");\r\n }\r\n\r\n const { ttl = 300000, maxCacheSize = 100 } = options; // 5 min default TTL\r\n\r\n // Enforce cache size limits to prevent memory attacks\r\n if (asyncCache.size >= maxCacheSize) {\r\n // Remove oldest entries\r\n const entries = Array.from(asyncCache.entries());\r\n const entriesToRemove = Math.max(1, Math.floor(maxCacheSize * 0.1)); // Remove 10%\r\n\r\n for (let i = 0; i < entriesToRemove; i++) {\r\n const entry = entries[i];\r\n if (entry) {\r\n asyncCache.delete(entry[0]);\r\n }\r\n }\r\n }\r\n\r\n const data = nixState(null) as { value: any };\r\n const error = nixState(null) as { value: any };\r\n const loading = nixState(false) as { value: boolean };\r\n\r\n let active: boolean = true;\r\n let abortController: AbortController | null = null;\r\n\r\n startCacheCleanup();\r\n\r\n const run = async (): Promise<void> => {\r\n if (!active) return;\r\n\r\n // Cancel previous request if still running\r\n if (abortController) {\r\n abortController.abort();\r\n }\r\n\r\n abortController = new AbortController();\r\n loading.value = true;\r\n error.value = null;\r\n\r\n try {\r\n // Cache hit with resolved data\r\n if (asyncCache.has(key)) {\r\n const cached = asyncCache.get(key)!;\r\n const now = Date.now();\r\n\r\n // Check TTL\r\n if (cached.timestamp && now - cached.timestamp > ttl) {\r\n asyncCache.delete(key);\r\n } else if (cached.data !== undefined) {\r\n data.value = cached.data;\r\n loading.value = false;\r\n return;\r\n }\r\n\r\n // Deduping: reuse in-flight promise if still valid\r\n if (cached.promise) {\r\n try {\r\n const result = await cached.promise;\r\n if (!active || abortController?.signal.aborted) return;\r\n data.value = result;\r\n loading.value = false;\r\n return;\r\n } catch (e) {\r\n if (!active || abortController?.signal.aborted) return;\r\n error.value = e;\r\n loading.value = false;\r\n return;\r\n }\r\n }\r\n }\r\n\r\n // Cache miss - create new promise\r\n const promise = Promise.resolve().then(() => {\r\n if (abortController?.signal.aborted) {\r\n throw new Error(\"Request was aborted\");\r\n }\r\n return promiseFactory();\r\n });\r\n\r\n asyncCache.set(key, {\r\n promise,\r\n timestamp: Date.now(),\r\n ttl,\r\n });\r\n\r\n const result = await promise;\r\n\r\n if (!active || abortController?.signal.aborted) return;\r\n\r\n // Cache successful result\r\n asyncCache.set(key, {\r\n data: result,\r\n timestamp: Date.now(),\r\n ttl,\r\n });\r\n\r\n data.value = result;\r\n } catch (e) {\r\n // Remove failed promise from cache\r\n if (asyncCache.has(key)) {\r\n const cached = asyncCache.get(key)!;\r\n if (cached.promise && !cached.data) {\r\n asyncCache.delete(key);\r\n }\r\n }\r\n\r\n if (!active || abortController?.signal.aborted) return;\r\n error.value = e;\r\n } finally {\r\n if (active && !abortController?.signal.aborted) {\r\n loading.value = false;\r\n }\r\n }\r\n };\r\n\r\n const cancel = (): void => {\r\n active = false;\r\n if (abortController) {\r\n abortController.abort();\r\n abortController = null;\r\n }\r\n };\r\n\r\n const clearCache = (): void => {\r\n asyncCache.delete(key);\r\n };\r\n\r\n return { data, error, loading, run, cancel, clearCache };\r\n}\r\n"],
5
+ "mappings": "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",
6
+ "names": ["result"]
7
7
  }
@@ -1 +1 @@
1
- {"version":3,"file":"nixComputed.d.ts","sourceRoot":"","sources":["../../hooks/nixComputed.ts"],"names":[],"mappings":"AAuFA,wBAAgB,WAAW,CAAC,CAAC,EAAE,SAAS,EAAE,MAAM,CAAC,GAAG;IAClD,KAAK,EAAE,CAAC,CAAC;IACT,SAAS,EAAE,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,KAAK,MAAM,IAAI,CAAC;IAClD,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,kBAAkB,EAAE,MAAM,MAAM,CAAC;IACjC,kBAAkB,EAAE,MAAM,MAAM,CAAC;IACjC,WAAW,EAAE,MAAM,OAAO,CAAC;IAC3B,iBAAiB,EAAE,MAAM,KAAK,CAAC;QAC7B,KAAK,EAAE,GAAG,CAAC;QACX,UAAU,EAAE,OAAO,CAAC;QACpB,UAAU,EAAE,OAAO,CAAC;KACrB,CAAC,CAAC;IACH,WAAW,EAAE,IAAI,CAAC;IAClB,WAAW,EAAE,IAAI,CAAC;CACnB,CAwLA"}
1
+ {"version":3,"file":"nixComputed.d.ts","sourceRoot":"","sources":["../../hooks/nixComputed.ts"],"names":[],"mappings":"AAuFA,wBAAgB,WAAW,CAAC,CAAC,EAAE,SAAS,EAAE,MAAM,CAAC,GAAG;IAClD,KAAK,EAAE,CAAC,CAAC;IACT,SAAS,EAAE,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,KAAK,MAAM,IAAI,CAAC;IAClD,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,kBAAkB,EAAE,MAAM,MAAM,CAAC;IACjC,kBAAkB,EAAE,MAAM,MAAM,CAAC;IACjC,WAAW,EAAE,MAAM,OAAO,CAAC;IAC3B,iBAAiB,EAAE,MAAM,KAAK,CAAC;QAC7B,KAAK,EAAE,GAAG,CAAC;QACX,UAAU,EAAE,OAAO,CAAC;QACpB,UAAU,EAAE,OAAO,CAAC;KACrB,CAAC,CAAC;IACH,WAAW,EAAE,IAAI,CAAC;IAClB,WAAW,EAAE,IAAI,CAAC;CACnB,CA8LA"}
@@ -64,11 +64,14 @@ export function nixComputed(computeFn) {
64
64
  trackingContext._accessedStates.forEach((state) => {
65
65
  if (!dependencies.has(state)) {
66
66
  const unsub = state.subscribe(() => {
67
+ if (isDestroyed)
68
+ return;
67
69
  isStale = true;
70
+ const newValue = s.value;
68
71
  const subsArray = Array.from(subscribers);
69
72
  subsArray.forEach((fn) => {
70
73
  try {
71
- fn(s.value);
74
+ fn(newValue);
72
75
  }
73
76
  catch (e) {
74
77
  console.error("[nixComputed] Subscriber error:", e);
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../hooks/nixComputed.ts"],
4
- "sourcesContent": ["/* MIT License\r\n\r\n* Copyright (c) 2026 Resty Gonzales\r\n\r\nPermission is hereby granted, free of charge, to any person obtaining a copy\r\nof this software and associated documentation files (the \"Software\"), to deal\r\nin the Software without restriction, including without limitation the rights\r\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\ncopies of the Software, and to permit persons to whom the Software is\r\nfurnished to do so, subject to the following conditions:\r\n\r\nThe above copyright notice and this permission notice shall be included in all\r\ncopies or substantial portions of the Software.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\r\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\r\n* SOFTWARE.\r\n */\r\n/* ----------------------\r\n nixComputed - Computed/Derived State\r\n Memory Leaks & Security Issues Resolved\r\n---------------------- */\r\nimport { activeContext, setActiveContext } from \"../context/context\";\r\n\r\n/**\r\n * @template T\r\n * @typedef {Object} ComputedState\r\n * @property {T} value - Get the computed value (read-only)\r\n * @property {(fn: (value: T) => void) => (() => void)} subscribe - Subscribe to computed value changes\r\n * @property {() => void} cleanup - Cleanup all subscriptions and dependencies\r\n * @property {() => number} getSubscriberCount - Get number of active subscribers (debugging)\r\n * @property {() => number} getDependencyCount - Get number of tracked dependencies (debugging)\r\n * @property {() => boolean} isDestroyed - Check if computed state has been destroyed\r\n * @property {boolean} _isNixState - Internal flag (computed states behave like states)\r\n * @property {boolean} _isComputed - Internal flag to identify computed states\r\n */\r\n\r\n/**\r\n * Create a derived/computed state from other states.\r\n * Automatically tracks dependencies and updates when any dependency changes.\r\n *\r\n * @template T\r\n * @param {() => T} computeFn - Function that computes the derived value\r\n * @returns {ComputedState<T>} A reactive state object with the computed value\r\n *\r\n * @example\r\n * const count = nixState(5);\r\n * const doubled = nixComputed(() => count.value * 2);\r\n * console.log(doubled.value); // 10\r\n * count.value = 10;\r\n * console.log(doubled.value); // 20\r\n *\r\n * @example\r\n * // Multiple dependencies\r\n * const a = nixState(5);\r\n * const b = nixState(10);\r\n * const sum = nixComputed(() => a.value + b.value);\r\n * console.log(sum.value); // 15\r\n *\r\n * @example\r\n * // Conditional dependencies\r\n * const flag = nixState(true);\r\n * const x = nixState(1);\r\n * const y = nixState(2);\r\n * const result = nixComputed(() => flag.value ? x.value : y.value);\r\n *\r\n * @example\r\n * // With cleanup\r\n * const MyComponent = () => {\r\n * const count = nixState(0);\r\n * const doubled = nixComputed(() => count.value * 2);\r\n *\r\n * nixEffect(() => {\r\n * return () => {\r\n * doubled.cleanup();\r\n * count.cleanup();\r\n * };\r\n * }, []);\r\n * };\r\n *\r\n * @throws {Error} If called outside a component context\r\n * @throws {TypeError} If computeFn is not a function\r\n */\r\nexport function nixComputed<T>(computeFn: () => T): {\r\n value: T;\r\n subscribe: (fn: (value: T) => void) => () => void;\r\n cleanup: () => void;\r\n getSubscriberCount: () => number;\r\n getDependencyCount: () => number;\r\n isDestroyed: () => boolean;\r\n getDependencyInfo: () => Array<{\r\n state: any;\r\n hasCleanup: boolean;\r\n isComputed: boolean;\r\n }>;\r\n _isNixState: true;\r\n _isComputed: true;\r\n} {\r\n const ctx = activeContext as\r\n | (typeof activeContext & {\r\n hookIndex: number;\r\n hooks: Array<any>;\r\n stateCleanups?: Array<() => void>;\r\n })\r\n | undefined;\r\n if (!ctx) throw new Error(\"nixComputed() called outside component\");\r\n\r\n if (typeof computeFn !== \"function\") {\r\n throw new TypeError(\"[nixComputed] First argument must be a function\");\r\n }\r\n\r\n const idx = ctx.hookIndex++;\r\n if (!ctx.hooks[idx]) {\r\n const subscribers: Set<(value: T) => void> = new Set();\r\n const dependencies: Set<any> = new Set();\r\n const unsubscribers: Map<any, () => void> = new Map();\r\n let cachedValue: T;\r\n let isStale = true;\r\n let isDestroyed = false;\r\n let isComputing = false;\r\n\r\n function compute(): T {\r\n if (isDestroyed) return cachedValue;\r\n if (isComputing) {\r\n console.error(\"[nixComputed] Circular dependency detected\");\r\n return cachedValue;\r\n }\r\n isComputing = true;\r\n // Provide all required properties for ComponentContext type\r\n // Use type assertion for safety and future-proofing\r\n const trackingContext = {\r\n _accessedStates: new Set(),\r\n hookIndex: 0,\r\n hooks: [],\r\n _subscriptions: new Set(),\r\n _subscriptionCleanups: [],\r\n effects: [],\r\n cleanups: [],\r\n _vnode: null,\r\n version: 0,\r\n props: {},\r\n stateCleanups: [],\r\n parent: null,\r\n context: {},\r\n rerender: () => {},\r\n Component: null,\r\n _isMounted: false,\r\n _isRerendering: false,\r\n } as any; // Use 'as any' to satisfy ComponentContext type\r\n const prevContext = activeContext;\r\n try {\r\n setActiveContext(trackingContext);\r\n cachedValue = computeFn();\r\n const oldDeps = Array.from(dependencies);\r\n oldDeps.forEach((dep) => {\r\n if (!trackingContext._accessedStates.has(dep)) {\r\n if (unsubscribers.has(dep)) {\r\n try {\r\n unsubscribers.get(dep)!();\r\n } catch (e) {\r\n console.error(\r\n \"[nixComputed] Error unsubscribing from old dependency:\",\r\n e\r\n );\r\n }\r\n unsubscribers.delete(dep);\r\n }\r\n dependencies.delete(dep);\r\n }\r\n });\r\n trackingContext._accessedStates.forEach((state: any) => {\r\n if (!dependencies.has(state)) {\r\n const unsub = state.subscribe(() => {\r\n isStale = true;\r\n const subsArray = Array.from(subscribers);\r\n subsArray.forEach((fn) => {\r\n try {\r\n fn(s.value);\r\n } catch (e) {\r\n console.error(\"[nixComputed] Subscriber error:\", e);\r\n subscribers.delete(fn);\r\n }\r\n });\r\n });\r\n unsubscribers.set(state, unsub);\r\n dependencies.add(state);\r\n }\r\n });\r\n isStale = false;\r\n } catch (err) {\r\n console.error(\"[nixComputed] Compute error:\", err);\r\n isStale = false;\r\n } finally {\r\n setActiveContext(prevContext);\r\n isComputing = false;\r\n }\r\n return cachedValue;\r\n }\r\n\r\n const s = {\r\n get value(): T {\r\n if (isDestroyed) {\r\n console.warn(\"[nixComputed] Accessing destroyed computed state\");\r\n return cachedValue;\r\n }\r\n if (isStale) {\r\n compute();\r\n }\r\n if (activeContext && activeContext._accessedStates) {\r\n activeContext._accessedStates.add(s);\r\n }\r\n return cachedValue;\r\n },\r\n subscribe(fn: (value: T) => void): () => void {\r\n if (typeof fn !== \"function\") {\r\n console.error(\"[nixComputed] subscribe() requires a function\");\r\n return () => {};\r\n }\r\n if (isDestroyed) {\r\n console.warn(\r\n \"[nixComputed] Cannot subscribe to destroyed computed state\"\r\n );\r\n return () => {};\r\n }\r\n const MAX_SUBSCRIBERS = 1000;\r\n if (subscribers.size >= MAX_SUBSCRIBERS) {\r\n console.error(\"[nixComputed] Maximum subscriber limit reached\");\r\n return () => {};\r\n }\r\n subscribers.add(fn);\r\n return () => {\r\n subscribers.delete(fn);\r\n };\r\n },\r\n cleanup(): void {\r\n if (isDestroyed) return;\r\n isDestroyed = true;\r\n unsubscribers.forEach((unsub) => {\r\n try {\r\n unsub();\r\n } catch (e) {\r\n console.error(\"[nixComputed] Cleanup error:\", e);\r\n }\r\n });\r\n unsubscribers.clear();\r\n dependencies.clear();\r\n subscribers.clear();\r\n cachedValue = null as any as T;\r\n console.log(\"[nixComputed] Computed state cleaned up\");\r\n },\r\n getSubscriberCount(): number {\r\n return subscribers.size;\r\n },\r\n getDependencyCount(): number {\r\n return dependencies.size;\r\n },\r\n isDestroyed(): boolean {\r\n return isDestroyed;\r\n },\r\n getDependencyInfo(): Array<{\r\n state: any;\r\n hasCleanup: boolean;\r\n isComputed: boolean;\r\n }> {\r\n return Array.from(dependencies).map((state) => ({\r\n state,\r\n hasCleanup: unsubscribers.has(state),\r\n isComputed: !!state._isComputed,\r\n }));\r\n },\r\n _isNixState: true as const,\r\n _isComputed: true as const,\r\n };\r\n\r\n compute();\r\n ctx.hooks[idx] = s;\r\n if (ctx.stateCleanups) {\r\n ctx.stateCleanups.push(() => s.cleanup());\r\n }\r\n }\r\n return ctx.hooks[idx] as ReturnType<typeof nixComputed<T>>;\r\n}\r\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/* MIT License\r\n\r\n* Copyright (c) 2026 Resty Gonzales\r\n\r\nPermission is hereby granted, free of charge, to any person obtaining a copy\r\nof this software and associated documentation files (the \"Software\"), to deal\r\nin the Software without restriction, including without limitation the rights\r\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\ncopies of the Software, and to permit persons to whom the Software is\r\nfurnished to do so, subject to the following conditions:\r\n\r\nThe above copyright notice and this permission notice shall be included in all\r\ncopies or substantial portions of the Software.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\r\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\r\n* SOFTWARE.\r\n */\r\n/* ----------------------\r\n nixComputed - Computed/Derived State\r\n Memory Leaks & Security Issues Resolved\r\n---------------------- */\r\nimport { activeContext, setActiveContext } from \"../context/context\";\r\n\r\n/**\r\n * @template T\r\n * @typedef {Object} ComputedState\r\n * @property {T} value - Get the computed value (read-only)\r\n * @property {(fn: (value: T) => void) => (() => void)} subscribe - Subscribe to computed value changes\r\n * @property {() => void} cleanup - Cleanup all subscriptions and dependencies\r\n * @property {() => number} getSubscriberCount - Get number of active subscribers (debugging)\r\n * @property {() => number} getDependencyCount - Get number of tracked dependencies (debugging)\r\n * @property {() => boolean} isDestroyed - Check if computed state has been destroyed\r\n * @property {boolean} _isNixState - Internal flag (computed states behave like states)\r\n * @property {boolean} _isComputed - Internal flag to identify computed states\r\n */\r\n\r\n/**\r\n * Create a derived/computed state from other states.\r\n * Automatically tracks dependencies and updates when any dependency changes.\r\n *\r\n * @template T\r\n * @param {() => T} computeFn - Function that computes the derived value\r\n * @returns {ComputedState<T>} A reactive state object with the computed value\r\n *\r\n * @example\r\n * const count = nixState(5);\r\n * const doubled = nixComputed(() => count.value * 2);\r\n * console.log(doubled.value); // 10\r\n * count.value = 10;\r\n * console.log(doubled.value); // 20\r\n *\r\n * @example\r\n * // Multiple dependencies\r\n * const a = nixState(5);\r\n * const b = nixState(10);\r\n * const sum = nixComputed(() => a.value + b.value);\r\n * console.log(sum.value); // 15\r\n *\r\n * @example\r\n * // Conditional dependencies\r\n * const flag = nixState(true);\r\n * const x = nixState(1);\r\n * const y = nixState(2);\r\n * const result = nixComputed(() => flag.value ? x.value : y.value);\r\n *\r\n * @example\r\n * // With cleanup\r\n * const MyComponent = () => {\r\n * const count = nixState(0);\r\n * const doubled = nixComputed(() => count.value * 2);\r\n *\r\n * nixEffect(() => {\r\n * return () => {\r\n * doubled.cleanup();\r\n * count.cleanup();\r\n * };\r\n * }, []);\r\n * };\r\n *\r\n * @throws {Error} If called outside a component context\r\n * @throws {TypeError} If computeFn is not a function\r\n */\r\nexport function nixComputed<T>(computeFn: () => T): {\r\n value: T;\r\n subscribe: (fn: (value: T) => void) => () => void;\r\n cleanup: () => void;\r\n getSubscriberCount: () => number;\r\n getDependencyCount: () => number;\r\n isDestroyed: () => boolean;\r\n getDependencyInfo: () => Array<{\r\n state: any;\r\n hasCleanup: boolean;\r\n isComputed: boolean;\r\n }>;\r\n _isNixState: true;\r\n _isComputed: true;\r\n} {\r\n const ctx = activeContext as\r\n | (typeof activeContext & {\r\n hookIndex: number;\r\n hooks: Array<any>;\r\n stateCleanups?: Array<() => void>;\r\n })\r\n | undefined;\r\n if (!ctx) throw new Error(\"nixComputed() called outside component\");\r\n\r\n if (typeof computeFn !== \"function\") {\r\n throw new TypeError(\"[nixComputed] First argument must be a function\");\r\n }\r\n\r\n const idx = ctx.hookIndex++;\r\n if (!ctx.hooks[idx]) {\r\n const subscribers: Set<(value: T) => void> = new Set();\r\n const dependencies: Set<any> = new Set();\r\n const unsubscribers: Map<any, () => void> = new Map();\r\n let cachedValue: T;\r\n let isStale = true;\r\n let isDestroyed = false;\r\n let isComputing = false;\r\n\r\n function compute(): T {\r\n if (isDestroyed) return cachedValue;\r\n if (isComputing) {\r\n console.error(\"[nixComputed] Circular dependency detected\");\r\n return cachedValue;\r\n }\r\n isComputing = true;\r\n // Provide all required properties for ComponentContext type\r\n // Use type assertion for safety and future-proofing\r\n const trackingContext = {\r\n _accessedStates: new Set(),\r\n hookIndex: 0,\r\n hooks: [],\r\n _subscriptions: new Set(),\r\n _subscriptionCleanups: [],\r\n effects: [],\r\n cleanups: [],\r\n _vnode: null,\r\n version: 0,\r\n props: {},\r\n stateCleanups: [],\r\n parent: null,\r\n context: {},\r\n rerender: () => {},\r\n Component: null,\r\n _isMounted: false,\r\n _isRerendering: false,\r\n } as any; // Use 'as any' to satisfy ComponentContext type\r\n const prevContext = activeContext;\r\n try {\r\n setActiveContext(trackingContext);\r\n cachedValue = computeFn();\r\n const oldDeps = Array.from(dependencies);\r\n oldDeps.forEach((dep) => {\r\n if (!trackingContext._accessedStates.has(dep)) {\r\n if (unsubscribers.has(dep)) {\r\n try {\r\n unsubscribers.get(dep)!();\r\n } catch (e) {\r\n console.error(\r\n \"[nixComputed] Error unsubscribing from old dependency:\",\r\n e\r\n );\r\n }\r\n unsubscribers.delete(dep);\r\n }\r\n dependencies.delete(dep);\r\n }\r\n });\r\n trackingContext._accessedStates.forEach((state: any) => {\r\n if (!dependencies.has(state)) {\r\n const unsub = state.subscribe(() => {\r\n if (isDestroyed) return;\r\n\r\n isStale = true;\r\n // Get fresh value when dependency changes\r\n const newValue = s.value;\r\n const subsArray = Array.from(subscribers);\r\n subsArray.forEach((fn) => {\r\n try {\r\n fn(newValue);\r\n } catch (e) {\r\n console.error(\"[nixComputed] Subscriber error:\", e);\r\n subscribers.delete(fn);\r\n }\r\n });\r\n });\r\n unsubscribers.set(state, unsub);\r\n dependencies.add(state);\r\n }\r\n });\r\n isStale = false;\r\n } catch (err) {\r\n console.error(\"[nixComputed] Compute error:\", err);\r\n isStale = false;\r\n } finally {\r\n setActiveContext(prevContext);\r\n isComputing = false;\r\n }\r\n return cachedValue;\r\n }\r\n\r\n // All cleanup functionality is now part of the s object below\r\n\r\n const s = {\r\n get value(): T {\r\n if (isDestroyed) {\r\n console.warn(\"[nixComputed] Accessing destroyed computed state\");\r\n return cachedValue;\r\n }\r\n if (isStale) {\r\n compute();\r\n }\r\n if (activeContext && (activeContext as any)._accessedStates) {\r\n (activeContext as any)._accessedStates.add(s);\r\n }\r\n return cachedValue;\r\n },\r\n subscribe(fn: (value: T) => void): () => void {\r\n if (typeof fn !== \"function\") {\r\n console.error(\"[nixComputed] subscribe() requires a function\");\r\n return () => {};\r\n }\r\n if (isDestroyed) {\r\n console.warn(\r\n \"[nixComputed] Cannot subscribe to destroyed computed state\"\r\n );\r\n return () => {};\r\n }\r\n const MAX_SUBSCRIBERS = 1000;\r\n if (subscribers.size >= MAX_SUBSCRIBERS) {\r\n console.error(\"[nixComputed] Maximum subscriber limit reached\");\r\n return () => {};\r\n }\r\n subscribers.add(fn);\r\n return () => {\r\n subscribers.delete(fn);\r\n };\r\n },\r\n cleanup(): void {\r\n if (isDestroyed) return;\r\n isDestroyed = true;\r\n unsubscribers.forEach((unsub) => {\r\n try {\r\n unsub();\r\n } catch (e) {\r\n console.error(\"[nixComputed] Cleanup error:\", e);\r\n }\r\n });\r\n unsubscribers.clear();\r\n dependencies.clear();\r\n subscribers.clear();\r\n cachedValue = null as any as T;\r\n console.log(\"[nixComputed] Computed state cleaned up\");\r\n },\r\n getSubscriberCount(): number {\r\n return subscribers.size;\r\n },\r\n getDependencyCount(): number {\r\n return dependencies.size;\r\n },\r\n isDestroyed(): boolean {\r\n return isDestroyed;\r\n },\r\n getDependencyInfo(): Array<{\r\n state: any;\r\n hasCleanup: boolean;\r\n isComputed: boolean;\r\n }> {\r\n return Array.from(dependencies).map((state) => ({\r\n state,\r\n hasCleanup: unsubscribers.has(state),\r\n isComputed: !!state._isComputed,\r\n }));\r\n },\r\n _isNixState: true as const,\r\n _isComputed: true as const,\r\n };\r\n\r\n compute();\r\n ctx.hooks[idx] = s;\r\n if (ctx.stateCleanups) {\r\n ctx.stateCleanups.push(() => s.cleanup());\r\n }\r\n }\r\n return ctx.hooks[idx] as ReturnType<typeof nixComputed<T>>;\r\n}\r\n"],
5
+ "mappings": "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",
6
6
  "names": ["compute"]
7
7
  }
@@ -1 +1 @@
1
- {"version":3,"file":"nixEffect.d.ts","sourceRoot":"","sources":["../../hooks/nixEffect.ts"],"names":[],"mappings":"AAkEA,wBAAgB,SAAS,CACvB,MAAM,EAAE,MAAM,IAAI,GAAG,CAAC,MAAM,IAAI,CAAC,EACjC,IAAI,GAAE,GAAG,EAAO,GACf,IAAI,CAyDN;AAgCD,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,IAAI,GAAG,CAAC,MAAM,IAAI,CAAC,GAAG,IAAI,CAErE;AAaD,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,IAAI,GAAG,CAAC,MAAM,IAAI,CAAC,GAAG,IAAI,CAUvE"}
1
+ {"version":3,"file":"nixEffect.d.ts","sourceRoot":"","sources":["../../hooks/nixEffect.ts"],"names":[],"mappings":"AAmEA,wBAAgB,SAAS,CACvB,MAAM,EAAE,MAAM,IAAI,GAAG,CAAC,MAAM,IAAI,CAAC,EACjC,IAAI,GAAE,GAAG,EAAO,GACf,IAAI,CAyDN;AAgCD,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,IAAI,GAAG,CAAC,MAAM,IAAI,CAAC,GAAG,IAAI,CAErE;AAaD,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,IAAI,GAAG,CAAC,MAAM,IAAI,CAAC,GAAG,IAAI,CAUvE"}
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../hooks/nixEffect.ts"],
4
- "sourcesContent": ["/* MIT License\r\n\r\n* Copyright (c) 2026 Resty Gonzales\r\n\r\nPermission is hereby granted, free of charge, to any person obtaining a copy\r\nof this software and associated documentation files (the \"Software\"), to deal\r\nin the Software without restriction, including without limitation the rights\r\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\ncopies of the Software, and to permit persons to whom the Software is\r\nfurnished to do so, subject to the following conditions:\r\n\r\nThe above copyright notice and this permission notice shall be included in all\r\ncopies or substantial portions of the Software.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\r\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\r\n* SOFTWARE.\r\n */\r\n/* ----------------------\r\n nixEffect - Side Effects Hook\r\n Memory Leaks & Security Issues Resolved\r\n---------------------- */\r\nimport { activeContext } from \"../context/context\";\r\n\r\n/**\r\n * Execute side effects in a component with automatic cleanup.\r\n * Similar to React's useEffect.\r\n *\r\n * @param {() => (void | (() => void))} effect - Effect function, optionally returns cleanup function\r\n * @param {Array<any>} [deps=[]] - Dependency array. Effect re-runs when dependencies change.\r\n *\r\n * @example\r\n * // Run once on mount\r\n * nixEffect(() => {\r\n * console.log('Component mounted');\r\n * return () => console.log('Component unmounted');\r\n * }, []);\r\n *\r\n * @example\r\n * // Run when count changes\r\n * const count = nixState(0);\r\n * nixEffect(() => {\r\n * console.log('Count is:', count.value);\r\n * }, [count.value]);\r\n *\r\n * @example\r\n * // Timer with cleanup\r\n * nixEffect(() => {\r\n * const timer = setInterval(() => console.log('tick'), 1000);\r\n * return () => clearInterval(timer);\r\n * }, []);\r\n *\r\n * @example\r\n * // Event listener with cleanup\r\n * nixEffect(() => {\r\n * const handler = (e) => console.log('clicked', e);\r\n * document.addEventListener('click', handler);\r\n * return () => document.removeEventListener('click', handler);\r\n * }, []);\r\n *\r\n * @throws {Error} If called outside a component context\r\n */\r\nexport function nixEffect(\r\n effect: () => void | (() => void),\r\n deps: any[] = []\r\n): void {\r\n const ctx = activeContext as\r\n | (typeof activeContext & {\r\n hookIndex: number;\r\n hooks: Array<any>;\r\n cleanups?: Array<() => void>;\r\n })\r\n | undefined;\r\n if (!ctx) throw new Error(\"nixEffect() called outside component\");\r\n\r\n if (typeof effect !== \"function\") {\r\n console.error(\"[nixEffect] First argument must be a function\");\r\n return;\r\n }\r\n if (!Array.isArray(deps)) {\r\n console.error(\"[nixEffect] Second argument must be an array\");\r\n deps = [];\r\n }\r\n const MAX_DEPS = 100;\r\n if (deps.length > MAX_DEPS) {\r\n console.warn(\r\n `[nixEffect] Dependency array too large (${deps.length}). Limited to ${MAX_DEPS}.`\r\n );\r\n deps = deps.slice(0, MAX_DEPS);\r\n }\r\n const idx = ctx.hookIndex++;\r\n const prev = ctx.hooks[idx];\r\n const hasChanged = !prev || !shallowArrayEqual(prev.deps, deps);\r\n if (hasChanged) {\r\n if (prev?.cleanup) {\r\n try {\r\n if (typeof prev.cleanup === \"function\") {\r\n prev.cleanup();\r\n }\r\n } catch (err) {\r\n console.error(\"[nixEffect] Cleanup error:\", err);\r\n }\r\n }\r\n let cleanup: void | (() => void);\r\n try {\r\n cleanup = effect();\r\n if (cleanup !== undefined && typeof cleanup !== \"function\") {\r\n console.warn(\r\n \"[nixEffect] Effect should return undefined or a cleanup function\"\r\n );\r\n cleanup = undefined;\r\n }\r\n } catch (err) {\r\n console.error(\"[nixEffect] Effect error:\", err);\r\n cleanup = undefined;\r\n }\r\n ctx.hooks[idx] = { deps, cleanup };\r\n if (cleanup && typeof cleanup === \"function\") {\r\n if (!ctx.cleanups) ctx.cleanups = [];\r\n ctx.cleanups.push(cleanup);\r\n }\r\n }\r\n}\r\n\r\n/**\r\n * Shallow comparison of two arrays for dependency checking.\r\n * More reliable and secure than JSON.stringify.\r\n *\r\n * @param {Array<any>} arr1 - First array\r\n * @param {Array<any>} arr2 - Second array\r\n * @returns {boolean} True if arrays are shallowly equal\r\n */\r\nfunction shallowArrayEqual(arr1: any[], arr2: any[]): boolean {\r\n if (arr1.length !== arr2.length) return false;\r\n for (let i = 0; i < arr1.length; i++) {\r\n if (!Object.is(arr1[i], arr2[i])) {\r\n return false;\r\n }\r\n }\r\n return true;\r\n}\r\n\r\n/**\r\n * Run an effect only once on component mount.\r\n * Convenience wrapper around nixEffect.\r\n *\r\n * @param {() => (void | (() => void))} effect - Effect function\r\n *\r\n * @example\r\n * nixEffectOnce(() => {\r\n * console.log('Mounted');\r\n * return () => console.log('Unmounted');\r\n * });\r\n */\r\nexport function nixEffectOnce(effect: () => void | (() => void)): void {\r\n return nixEffect(effect, []);\r\n}\r\n\r\n/**\r\n * Run an effect every time the component renders.\r\n * Use with caution - can cause performance issues.\r\n *\r\n * @param {() => (void | (() => void))} effect - Effect function\r\n *\r\n * @example\r\n * nixEffectAlways(() => {\r\n * console.log('Component rendered');\r\n * });\r\n */\r\nexport function nixEffectAlways(effect: () => void | (() => void)): void {\r\n const ctx = activeContext as\r\n | (typeof activeContext & { version: any })\r\n | undefined;\r\n if (!ctx) throw new Error(\"nixEffectAlways() called outside component\");\r\n if (typeof effect !== \"function\") {\r\n console.error(\"[nixEffectAlways] Argument must be a function\");\r\n return;\r\n }\r\n return nixEffect(effect, [ctx.version]);\r\n}\r\n"],
5
- "mappings": ";;AA0BA,SAAS,qBAAqB;AAwCvB,SAAS,UACd,QACA,OAAc,CAAC,GACT;AACN,QAAM,MAAM;AAOZ,MAAI,CAAC,IAAK,OAAM,IAAI,MAAM,sCAAsC;AAEhE,MAAI,OAAO,WAAW,YAAY;AAChC,YAAQ,MAAM,+CAA+C;AAC7D;AAAA,EACF;AACA,MAAI,CAAC,MAAM,QAAQ,IAAI,GAAG;AACxB,YAAQ,MAAM,8CAA8C;AAC5D,WAAO,CAAC;AAAA,EACV;AACA,QAAM,WAAW;AACjB,MAAI,KAAK,SAAS,UAAU;AAC1B,YAAQ;AAAA,MACN,2CAA2C,KAAK,MAAM,iBAAiB,QAAQ;AAAA,IACjF;AACA,WAAO,KAAK,MAAM,GAAG,QAAQ;AAAA,EAC/B;AACA,QAAM,MAAM,IAAI;AAChB,QAAM,OAAO,IAAI,MAAM,GAAG;AAC1B,QAAM,aAAa,CAAC,QAAQ,CAAC,kBAAkB,KAAK,MAAM,IAAI;AAC9D,MAAI,YAAY;AACd,QAAI,MAAM,SAAS;AACjB,UAAI;AACF,YAAI,OAAO,KAAK,YAAY,YAAY;AACtC,eAAK,QAAQ;AAAA,QACf;AAAA,MACF,SAAS,KAAK;AACZ,gBAAQ,MAAM,8BAA8B,GAAG;AAAA,MACjD;AAAA,IACF;AACA,QAAI;AACJ,QAAI;AACF,gBAAU,OAAO;AACjB,UAAI,YAAY,UAAa,OAAO,YAAY,YAAY;AAC1D,gBAAQ;AAAA,UACN;AAAA,QACF;AACA,kBAAU;AAAA,MACZ;AAAA,IACF,SAAS,KAAK;AACZ,cAAQ,MAAM,6BAA6B,GAAG;AAC9C,gBAAU;AAAA,IACZ;AACA,QAAI,MAAM,GAAG,IAAI,EAAE,MAAM,QAAQ;AACjC,QAAI,WAAW,OAAO,YAAY,YAAY;AAC5C,UAAI,CAAC,IAAI,SAAU,KAAI,WAAW,CAAC;AACnC,UAAI,SAAS,KAAK,OAAO;AAAA,IAC3B;AAAA,EACF;AACF;AA5DgB;AAsEhB,SAAS,kBAAkB,MAAa,MAAsB;AAC5D,MAAI,KAAK,WAAW,KAAK,OAAQ,QAAO;AACxC,WAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;AACpC,QAAI,CAAC,OAAO,GAAG,KAAK,CAAC,GAAG,KAAK,CAAC,CAAC,GAAG;AAChC,aAAO;AAAA,IACT;AAAA,EACF;AACA,SAAO;AACT;AARS;AAsBF,SAAS,cAAc,QAAyC;AACrE,SAAO,UAAU,QAAQ,CAAC,CAAC;AAC7B;AAFgB;AAeT,SAAS,gBAAgB,QAAyC;AACvE,QAAM,MAAM;AAGZ,MAAI,CAAC,IAAK,OAAM,IAAI,MAAM,4CAA4C;AACtE,MAAI,OAAO,WAAW,YAAY;AAChC,YAAQ,MAAM,+CAA+C;AAC7D;AAAA,EACF;AACA,SAAO,UAAU,QAAQ,CAAC,IAAI,OAAO,CAAC;AACxC;AAVgB;",
4
+ "sourcesContent": ["/* MIT License\r\n\r\n* Copyright (c) 2026 Resty Gonzales\r\n\r\nPermission is hereby granted, free of charge, to any person obtaining a copy\r\nof this software and associated documentation files (the \"Software\"), to deal\r\nin the Software without restriction, including without limitation the rights\r\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\ncopies of the Software, and to permit persons to whom the Software is\r\nfurnished to do so, subject to the following conditions:\r\n\r\nThe above copyright notice and this permission notice shall be included in all\r\ncopies or substantial portions of the Software.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\r\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\r\n* SOFTWARE.\r\n */\r\n/* ----------------------\r\n nixEffect - Side Effects Hook\r\n Memory Leaks & Security Issues Resolved\r\n---------------------- */\r\nimport { activeContext } from \"../context/context\";\r\n\r\n/**\r\n * Execute side effects in a component with automatic cleanup and security enhancements.\r\n * Similar to React's useEffect but with enhanced security and memory leak prevention.\r\n *\r\n * @param {() => (void | (() => void))} effect - Effect function, optionally returns cleanup function\r\n * @param {Array<any>} [deps=[]] - Dependency array. Effect re-runs when dependencies change.\r\n * @param {object} [options={}] - Configuration options\r\n *\r\n * @example\r\n * // Run once on mount\r\n * nixEffect(() => {\r\n * console.log('Component mounted');\r\n * return () => console.log('Component unmounted');\r\n * }, []);\r\n *\r\n * @example\r\n * // Run when count changes with timeout\r\n * const count = nixState(0);\r\n * nixEffect(() => {\r\n * console.log('Count is:', count.value);\r\n * }, [count.value], { timeout: 5000 });\r\n *\r\n * @example\r\n * // Timer with cleanup\r\n * nixEffect(() => {\r\n * const timer = setInterval(() => console.log('tick'), 1000);\r\n * return () => clearInterval(timer);\r\n * }, []);\r\n *\r\n * @example\r\n * // Event listener with cleanup\r\n * nixEffect(() => {\r\n * const handler = (e) => console.log('clicked', e);\r\n * document.addEventListener('click', handler);\r\n * return () => document.removeEventListener('click', handler);\r\n * }, []);\r\n *\r\n * @throws {Error} If called outside a component context\r\n */\r\nexport function nixEffect(\r\n effect: () => void | (() => void),\r\n deps: any[] = []\r\n): void {\r\n const ctx = activeContext as\r\n | (typeof activeContext & {\r\n hookIndex: number;\r\n hooks: Array<any>;\r\n cleanups?: Array<() => void>;\r\n })\r\n | undefined;\r\n if (!ctx) throw new Error(\"nixEffect() called outside component\");\r\n\r\n if (typeof effect !== \"function\") {\r\n console.error(\"[nixEffect] First argument must be a function\");\r\n return;\r\n }\r\n if (!Array.isArray(deps)) {\r\n console.error(\"[nixEffect] Second argument must be an array\");\r\n deps = [];\r\n }\r\n const MAX_DEPS = 100;\r\n if (deps.length > MAX_DEPS) {\r\n console.warn(\r\n `[nixEffect] Dependency array too large (${deps.length}). Limited to ${MAX_DEPS}.`\r\n );\r\n deps = deps.slice(0, MAX_DEPS);\r\n }\r\n const idx = ctx.hookIndex++;\r\n const prev = ctx.hooks[idx];\r\n const hasChanged = !prev || !shallowArrayEqual(prev.deps, deps);\r\n if (hasChanged) {\r\n if (prev?.cleanup) {\r\n try {\r\n if (typeof prev.cleanup === \"function\") {\r\n prev.cleanup();\r\n }\r\n } catch (err) {\r\n console.error(\"[nixEffect] Cleanup error:\", err);\r\n }\r\n }\r\n let cleanup: void | (() => void);\r\n try {\r\n cleanup = effect();\r\n if (cleanup !== undefined && typeof cleanup !== \"function\") {\r\n console.warn(\r\n \"[nixEffect] Effect should return undefined or a cleanup function\"\r\n );\r\n cleanup = undefined;\r\n }\r\n } catch (err) {\r\n console.error(\"[nixEffect] Effect error:\", err);\r\n cleanup = undefined;\r\n }\r\n ctx.hooks[idx] = { deps, cleanup };\r\n if (cleanup && typeof cleanup === \"function\") {\r\n if (!ctx.cleanups) ctx.cleanups = [];\r\n ctx.cleanups.push(cleanup);\r\n }\r\n }\r\n}\r\n\r\n/**\r\n * Shallow comparison of two arrays for dependency checking.\r\n * More reliable and secure than JSON.stringify.\r\n *\r\n * @param {Array<any>} arr1 - First array\r\n * @param {Array<any>} arr2 - Second array\r\n * @returns {boolean} True if arrays are shallowly equal\r\n */\r\nfunction shallowArrayEqual(arr1: any[], arr2: any[]): boolean {\r\n if (arr1.length !== arr2.length) return false;\r\n for (let i = 0; i < arr1.length; i++) {\r\n if (!Object.is(arr1[i], arr2[i])) {\r\n return false;\r\n }\r\n }\r\n return true;\r\n}\r\n\r\n/**\r\n * Run an effect only once on component mount.\r\n * Convenience wrapper around nixEffect.\r\n *\r\n * @param {() => (void | (() => void))} effect - Effect function\r\n *\r\n * @example\r\n * nixEffectOnce(() => {\r\n * console.log('Mounted');\r\n * return () => console.log('Unmounted');\r\n * });\r\n */\r\nexport function nixEffectOnce(effect: () => void | (() => void)): void {\r\n return nixEffect(effect, []);\r\n}\r\n\r\n/**\r\n * Run an effect every time the component renders.\r\n * Use with caution - can cause performance issues.\r\n *\r\n * @param {() => (void | (() => void))} effect - Effect function\r\n *\r\n * @example\r\n * nixEffectAlways(() => {\r\n * console.log('Component rendered');\r\n * });\r\n */\r\nexport function nixEffectAlways(effect: () => void | (() => void)): void {\r\n const ctx = activeContext as\r\n | (typeof activeContext & { version: any })\r\n | undefined;\r\n if (!ctx) throw new Error(\"nixEffectAlways() called outside component\");\r\n if (typeof effect !== \"function\") {\r\n console.error(\"[nixEffectAlways] Argument must be a function\");\r\n return;\r\n }\r\n return nixEffect(effect, [ctx.version]);\r\n}\r\n"],
5
+ "mappings": ";;AA0BA,SAAS,qBAAqB;AAyCvB,SAAS,UACd,QACA,OAAc,CAAC,GACT;AACN,QAAM,MAAM;AAOZ,MAAI,CAAC,IAAK,OAAM,IAAI,MAAM,sCAAsC;AAEhE,MAAI,OAAO,WAAW,YAAY;AAChC,YAAQ,MAAM,+CAA+C;AAC7D;AAAA,EACF;AACA,MAAI,CAAC,MAAM,QAAQ,IAAI,GAAG;AACxB,YAAQ,MAAM,8CAA8C;AAC5D,WAAO,CAAC;AAAA,EACV;AACA,QAAM,WAAW;AACjB,MAAI,KAAK,SAAS,UAAU;AAC1B,YAAQ;AAAA,MACN,2CAA2C,KAAK,MAAM,iBAAiB,QAAQ;AAAA,IACjF;AACA,WAAO,KAAK,MAAM,GAAG,QAAQ;AAAA,EAC/B;AACA,QAAM,MAAM,IAAI;AAChB,QAAM,OAAO,IAAI,MAAM,GAAG;AAC1B,QAAM,aAAa,CAAC,QAAQ,CAAC,kBAAkB,KAAK,MAAM,IAAI;AAC9D,MAAI,YAAY;AACd,QAAI,MAAM,SAAS;AACjB,UAAI;AACF,YAAI,OAAO,KAAK,YAAY,YAAY;AACtC,eAAK,QAAQ;AAAA,QACf;AAAA,MACF,SAAS,KAAK;AACZ,gBAAQ,MAAM,8BAA8B,GAAG;AAAA,MACjD;AAAA,IACF;AACA,QAAI;AACJ,QAAI;AACF,gBAAU,OAAO;AACjB,UAAI,YAAY,UAAa,OAAO,YAAY,YAAY;AAC1D,gBAAQ;AAAA,UACN;AAAA,QACF;AACA,kBAAU;AAAA,MACZ;AAAA,IACF,SAAS,KAAK;AACZ,cAAQ,MAAM,6BAA6B,GAAG;AAC9C,gBAAU;AAAA,IACZ;AACA,QAAI,MAAM,GAAG,IAAI,EAAE,MAAM,QAAQ;AACjC,QAAI,WAAW,OAAO,YAAY,YAAY;AAC5C,UAAI,CAAC,IAAI,SAAU,KAAI,WAAW,CAAC;AACnC,UAAI,SAAS,KAAK,OAAO;AAAA,IAC3B;AAAA,EACF;AACF;AA5DgB;AAsEhB,SAAS,kBAAkB,MAAa,MAAsB;AAC5D,MAAI,KAAK,WAAW,KAAK,OAAQ,QAAO;AACxC,WAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;AACpC,QAAI,CAAC,OAAO,GAAG,KAAK,CAAC,GAAG,KAAK,CAAC,CAAC,GAAG;AAChC,aAAO;AAAA,IACT;AAAA,EACF;AACA,SAAO;AACT;AARS;AAsBF,SAAS,cAAc,QAAyC;AACrE,SAAO,UAAU,QAAQ,CAAC,CAAC;AAC7B;AAFgB;AAeT,SAAS,gBAAgB,QAAyC;AACvE,QAAM,MAAM;AAGZ,MAAI,CAAC,IAAK,OAAM,IAAI,MAAM,4CAA4C;AACtE,MAAI,OAAO,WAAW,YAAY;AAChC,YAAQ,MAAM,+CAA+C;AAC7D;AAAA,EACF;AACA,SAAO,UAAU,QAAQ,CAAC,IAAI,OAAO,CAAC;AACxC;AAVgB;",
6
6
  "names": []
7
7
  }
@@ -1,5 +1,8 @@
1
1
  export declare function nixLocalStorage<T>(key: string, initial: T): {
2
2
  value: T;
3
- set: (v: T) => void;
3
+ set: (v: T) => boolean;
4
+ clear: () => void;
5
+ getSize: () => number;
6
+ isValid: () => boolean;
4
7
  };
5
8
  //# sourceMappingURL=nixLocalStorage.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"nixLocalStorage.d.ts","sourceRoot":"","sources":["../../hooks/nixLocalStorage.ts"],"names":[],"mappings":"AA+CA,wBAAgB,eAAe,CAAC,CAAC,EAC/B,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,CAAC,GACT;IAAE,KAAK,EAAE,CAAC,CAAC;IAAC,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,IAAI,CAAA;CAAE,CAsBnC"}
1
+ {"version":3,"file":"nixLocalStorage.d.ts","sourceRoot":"","sources":["../../hooks/nixLocalStorage.ts"],"names":[],"mappings":"AAiIA,wBAAgB,eAAe,CAAC,CAAC,EAC/B,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,CAAC,GACT;IACD,KAAK,EAAE,CAAC,CAAC;IACT,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,OAAO,CAAC;IACvB,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,OAAO,EAAE,MAAM,MAAM,CAAC;IACtB,OAAO,EAAE,MAAM,OAAO,CAAC;CACxB,CA8FA"}