@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.
- package/dist/hooks/nixAsync.d.ts +7 -1
- package/dist/hooks/nixAsync.d.ts.map +1 -1
- package/dist/hooks/nixAsync.js +84 -10
- package/dist/hooks/nixAsync.js.map +2 -2
- package/dist/hooks/nixAsyncCache.d.ts +6 -1
- package/dist/hooks/nixAsyncCache.d.ts.map +1 -1
- package/dist/hooks/nixAsyncCache.js +104 -26
- package/dist/hooks/nixAsyncCache.js.map +3 -3
- package/dist/hooks/nixComputed.d.ts.map +1 -1
- package/dist/hooks/nixComputed.js +4 -1
- package/dist/hooks/nixComputed.js.map +2 -2
- package/dist/hooks/nixEffect.d.ts.map +1 -1
- package/dist/hooks/nixEffect.js.map +2 -2
- package/dist/hooks/nixLocalStorage.d.ts +4 -1
- package/dist/hooks/nixLocalStorage.d.ts.map +1 -1
- package/dist/hooks/nixLocalStorage.js +118 -8
- package/dist/hooks/nixLocalStorage.js.map +2 -2
- package/dist/hooks/nixStore.d.ts +3 -0
- package/dist/hooks/nixStore.d.ts.map +1 -1
- package/dist/hooks/nixStore.js +57 -4
- package/dist/hooks/nixStore.js.map +2 -2
- package/dist/package.json +1 -1
- package/dist/plugins/vite-plugin-res.d.ts.map +1 -1
- package/dist/plugins/vite-plugin-res.js +239 -47
- package/dist/plugins/vite-plugin-res.js.map +3 -3
- package/dist/router/router.d.ts +13 -0
- package/dist/router/router.d.ts.map +1 -1
- package/dist/router/router.js +329 -21
- package/dist/router/router.js.map +2 -2
- package/dist/runtime.d.ts +62 -0
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +489 -13
- package/dist/runtime.js.map +2 -2
- package/package.json +1 -1
|
@@ -1,26 +1,136 @@
|
|
|
1
1
|
import { nixState } from "./nixState";
|
|
2
|
+
function safeJSONParse(value, fallback) {
|
|
3
|
+
if (value === null || value === undefined) {
|
|
4
|
+
return fallback;
|
|
5
|
+
}
|
|
6
|
+
try {
|
|
7
|
+
if (typeof value !== "string") {
|
|
8
|
+
console.warn("[nixLocalStorage] Non-string value provided to JSON.parse");
|
|
9
|
+
return fallback;
|
|
10
|
+
}
|
|
11
|
+
const suspiciousPatterns = [
|
|
12
|
+
/__proto__/,
|
|
13
|
+
/constructor/,
|
|
14
|
+
/prototype/,
|
|
15
|
+
/function\s*\(/,
|
|
16
|
+
/=>\s*{/,
|
|
17
|
+
/javascript:/,
|
|
18
|
+
/<script/i,
|
|
19
|
+
];
|
|
20
|
+
for (const pattern of suspiciousPatterns) {
|
|
21
|
+
if (pattern.test(value)) {
|
|
22
|
+
console.warn("[nixLocalStorage] Potentially malicious content detected, using fallback");
|
|
23
|
+
return fallback;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
const parsed = JSON.parse(value);
|
|
27
|
+
if (parsed && typeof parsed === "object") {
|
|
28
|
+
if (parsed.constructor !== Object && parsed.constructor !== Array) {
|
|
29
|
+
console.warn("[nixLocalStorage] Unsafe object constructor detected");
|
|
30
|
+
return fallback;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return parsed;
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
console.warn("[nixLocalStorage] JSON parsing failed:", error);
|
|
37
|
+
return fallback;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function safeJSONStringify(value, maxSize = 1024 * 1024) {
|
|
41
|
+
try {
|
|
42
|
+
const stringified = JSON.stringify(value);
|
|
43
|
+
if (stringified.length > maxSize) {
|
|
44
|
+
console.warn("[nixLocalStorage] Value too large to store:", stringified.length, "characters");
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
return stringified;
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
console.warn("[nixLocalStorage] JSON stringification failed:", error);
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
2
54
|
export function nixLocalStorage(key, initial) {
|
|
55
|
+
if (!key || typeof key !== "string") {
|
|
56
|
+
throw new Error("[nixLocalStorage] Key must be a non-empty string");
|
|
57
|
+
}
|
|
58
|
+
if (key.length > 100) {
|
|
59
|
+
throw new Error("[nixLocalStorage] Key too long (max 100 characters)");
|
|
60
|
+
}
|
|
3
61
|
let initialValue;
|
|
62
|
+
let isStorageValid = true;
|
|
4
63
|
try {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
else
|
|
64
|
+
if (typeof localStorage === "undefined") {
|
|
65
|
+
console.warn("[nixLocalStorage] localStorage not available, using in-memory fallback");
|
|
66
|
+
isStorageValid = false;
|
|
9
67
|
initialValue = initial;
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
const stored = localStorage.getItem(key);
|
|
71
|
+
initialValue = safeJSONParse(stored, initial);
|
|
72
|
+
}
|
|
10
73
|
}
|
|
11
74
|
catch (err) {
|
|
12
75
|
console.error(`[nixLocalStorage] Error reading key "${key}":`, err);
|
|
13
76
|
initialValue = initial;
|
|
77
|
+
isStorageValid = false;
|
|
14
78
|
}
|
|
15
|
-
const
|
|
79
|
+
const state = nixState(initialValue);
|
|
16
80
|
const set = (v) => {
|
|
17
|
-
|
|
81
|
+
if (!isStorageValid) {
|
|
82
|
+
state.value = v;
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
18
85
|
try {
|
|
19
|
-
|
|
86
|
+
const stringified = safeJSONStringify(v);
|
|
87
|
+
if (stringified === null) {
|
|
88
|
+
console.error(`[nixLocalStorage] Failed to stringify value for key "${key}"`);
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
localStorage.setItem(key, stringified);
|
|
92
|
+
state.value = v;
|
|
93
|
+
return true;
|
|
20
94
|
}
|
|
21
95
|
catch (err) {
|
|
22
96
|
console.error(`[nixLocalStorage] Error setting key "${key}":`, err);
|
|
97
|
+
state.value = v;
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
const clear = () => {
|
|
102
|
+
try {
|
|
103
|
+
if (isStorageValid) {
|
|
104
|
+
localStorage.removeItem(key);
|
|
105
|
+
}
|
|
106
|
+
state.value = initial;
|
|
23
107
|
}
|
|
108
|
+
catch (err) {
|
|
109
|
+
console.error(`[nixLocalStorage] Error clearing key "${key}":`, err);
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
const getSize = () => {
|
|
113
|
+
try {
|
|
114
|
+
if (!isStorageValid)
|
|
115
|
+
return 0;
|
|
116
|
+
const stored = localStorage.getItem(key);
|
|
117
|
+
return stored ? stored.length : 0;
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
return 0;
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
const isValid = () => isStorageValid;
|
|
124
|
+
return {
|
|
125
|
+
get value() {
|
|
126
|
+
return state.value;
|
|
127
|
+
},
|
|
128
|
+
set value(v) {
|
|
129
|
+
set(v);
|
|
130
|
+
},
|
|
131
|
+
set,
|
|
132
|
+
clear,
|
|
133
|
+
getSize,
|
|
134
|
+
isValid,
|
|
24
135
|
};
|
|
25
|
-
return { value: s.value, set };
|
|
26
136
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../hooks/nixLocalStorage.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 * @fileoverview Reactive localStorage hook for Fynix.\r\n * Automatically syncs state with localStorage using JSON serialization.\r\n */\r\n\r\nimport { nixState } from \"./nixState\";\r\n\r\n/**\r\n * Reactive wrapper around localStorage with safe JSON parsing/stringifying.\r\n * Automatically persists state changes to localStorage.\r\n *\r\n * @template T\r\n * @param {string} key - LocalStorage key\r\n * @param {T} initial - Initial value if key does not exist\r\n * @returns {{ value: T, set: (v: T) => void
|
|
5
|
-
"mappings": ";;AA2BA,SAAS,gBAAgB;
|
|
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 * @fileoverview Reactive localStorage hook for Fynix.\r\n * Automatically syncs state with localStorage using JSON serialization.\r\n */\r\n\r\nimport { nixState } from \"./nixState\";\r\n\r\n/**\r\n * Safe JSON parsing with validation to prevent code injection\r\n * @param value - String to parse\r\n * @param fallback - Fallback value if parsing fails\r\n * @returns Parsed value or fallback\r\n */\r\nfunction safeJSONParse<T>(value: string | null, fallback: T): T {\r\n if (value === null || value === undefined) {\r\n return fallback;\r\n }\r\n\r\n try {\r\n // Basic validation: check for suspicious patterns\r\n if (typeof value !== \"string\") {\r\n console.warn(\"[nixLocalStorage] Non-string value provided to JSON.parse\");\r\n return fallback;\r\n }\r\n\r\n // Check for potentially malicious patterns\r\n const suspiciousPatterns = [\r\n /__proto__/,\r\n /constructor/,\r\n /prototype/,\r\n /function\\s*\\(/,\r\n /=>\\s*{/,\r\n /javascript:/,\r\n /<script/i,\r\n ];\r\n\r\n for (const pattern of suspiciousPatterns) {\r\n if (pattern.test(value)) {\r\n console.warn(\r\n \"[nixLocalStorage] Potentially malicious content detected, using fallback\"\r\n );\r\n return fallback;\r\n }\r\n }\r\n\r\n // Parse and validate the result\r\n const parsed = JSON.parse(value);\r\n\r\n // Additional validation: ensure the parsed object doesn't have dangerous properties\r\n if (parsed && typeof parsed === \"object\") {\r\n if (parsed.constructor !== Object && parsed.constructor !== Array) {\r\n console.warn(\"[nixLocalStorage] Unsafe object constructor detected\");\r\n return fallback;\r\n }\r\n }\r\n\r\n return parsed as T;\r\n } catch (error) {\r\n console.warn(\"[nixLocalStorage] JSON parsing failed:\", error);\r\n return fallback;\r\n }\r\n}\r\n\r\n/**\r\n * Safe JSON stringification with size limits\r\n * @param value - Value to stringify\r\n * @param maxSize - Maximum size in characters (default: 1MB)\r\n * @returns JSON string or null if too large/invalid\r\n */\r\nfunction safeJSONStringify<T>(value: T, maxSize = 1024 * 1024): string | null {\r\n try {\r\n const stringified = JSON.stringify(value);\r\n\r\n if (stringified.length > maxSize) {\r\n console.warn(\r\n \"[nixLocalStorage] Value too large to store:\",\r\n stringified.length,\r\n \"characters\"\r\n );\r\n return null;\r\n }\r\n\r\n return stringified;\r\n } catch (error) {\r\n console.warn(\"[nixLocalStorage] JSON stringification failed:\", error);\r\n return null;\r\n }\r\n}\r\n\r\n/**\r\n * Reactive wrapper around localStorage with safe JSON parsing/stringifying.\r\n * Automatically persists state changes to localStorage.\r\n *\r\n * @template T\r\n * @param {string} key - LocalStorage key\r\n * @param {T} initial - Initial value if key does not exist\r\n * @returns {{ value: T, set: (v: T) => void, clear: () => void, getSize: () => number }} Secure storage object\r\n *\r\n * @example\r\n * const theme = nixLocalStorage('theme', 'light');\r\n * console.log(theme.value); // 'light' or stored value\r\n * theme.set('dark'); // Updates state and localStorage\r\n *\r\n * @example\r\n * const user = nixLocalStorage('user', { name: '', age: 0 });\r\n * user.set({ name: 'John', age: 30 });\r\n */\r\nexport function nixLocalStorage<T>(\r\n key: string,\r\n initial: T\r\n): {\r\n value: T;\r\n set: (v: T) => boolean;\r\n clear: () => void;\r\n getSize: () => number;\r\n isValid: () => boolean;\r\n} {\r\n // Validate inputs\r\n if (!key || typeof key !== \"string\") {\r\n throw new Error(\"[nixLocalStorage] Key must be a non-empty string\");\r\n }\r\n\r\n if (key.length > 100) {\r\n throw new Error(\"[nixLocalStorage] Key too long (max 100 characters)\");\r\n }\r\n\r\n let initialValue: T;\r\n let isStorageValid = true;\r\n\r\n try {\r\n // Check if localStorage is available\r\n if (typeof localStorage === \"undefined\") {\r\n console.warn(\r\n \"[nixLocalStorage] localStorage not available, using in-memory fallback\"\r\n );\r\n isStorageValid = false;\r\n initialValue = initial;\r\n } else {\r\n const stored = localStorage.getItem(key);\r\n initialValue = safeJSONParse(stored, initial);\r\n }\r\n } catch (err) {\r\n console.error(`[nixLocalStorage] Error reading key \"${key}\":`, err);\r\n initialValue = initial;\r\n isStorageValid = false;\r\n }\r\n\r\n const state = nixState<T>(initialValue);\r\n\r\n const set = (v: T): boolean => {\r\n if (!isStorageValid) {\r\n state.value = v;\r\n return false;\r\n }\r\n\r\n try {\r\n const stringified = safeJSONStringify(v);\r\n if (stringified === null) {\r\n console.error(\r\n `[nixLocalStorage] Failed to stringify value for key \"${key}\"`\r\n );\r\n return false;\r\n }\r\n\r\n localStorage.setItem(key, stringified);\r\n state.value = v;\r\n return true;\r\n } catch (err) {\r\n console.error(`[nixLocalStorage] Error setting key \"${key}\":`, err);\r\n // Update state even if localStorage fails (in-memory fallback)\r\n state.value = v;\r\n return false;\r\n }\r\n };\r\n\r\n const clear = (): void => {\r\n try {\r\n if (isStorageValid) {\r\n localStorage.removeItem(key);\r\n }\r\n state.value = initial;\r\n } catch (err) {\r\n console.error(`[nixLocalStorage] Error clearing key \"${key}\":`, err);\r\n }\r\n };\r\n\r\n const getSize = (): number => {\r\n try {\r\n if (!isStorageValid) return 0;\r\n const stored = localStorage.getItem(key);\r\n return stored ? stored.length : 0;\r\n } catch {\r\n return 0;\r\n }\r\n };\r\n\r\n const isValid = (): boolean => isStorageValid;\r\n\r\n return {\r\n get value() {\r\n return state.value;\r\n },\r\n set value(v: T) {\r\n set(v);\r\n },\r\n set,\r\n clear,\r\n getSize,\r\n isValid,\r\n };\r\n}\r\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/hooks/nixStore.d.ts
CHANGED
|
@@ -2,6 +2,9 @@ export declare function nixStore<T = any>(path: string, initial: T): {
|
|
|
2
2
|
value: T;
|
|
3
3
|
subscribe: (fn: () => void) => () => void;
|
|
4
4
|
path: string;
|
|
5
|
+
cleanup: () => void;
|
|
6
|
+
getSubscriberCount: () => number;
|
|
7
|
+
isDestroyed: () => boolean;
|
|
5
8
|
_isNixState: boolean;
|
|
6
9
|
};
|
|
7
10
|
//# sourceMappingURL=nixStore.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"nixStore.d.ts","sourceRoot":"","sources":["../../hooks/nixStore.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"nixStore.d.ts","sourceRoot":"","sources":["../../hooks/nixStore.ts"],"names":[],"mappings":"AAkCA,wBAAgB,QAAQ,CAAC,CAAC,GAAG,GAAG,EAC9B,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,CAAC,GACT;IACD,KAAK,EAAE,CAAC,CAAC;IACT,SAAS,EAAE,CAAC,EAAE,EAAE,MAAM,IAAI,KAAK,MAAM,IAAI,CAAC;IAC1C,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,kBAAkB,EAAE,MAAM,MAAM,CAAC;IACjC,WAAW,EAAE,MAAM,OAAO,CAAC;IAC3B,WAAW,EAAE,OAAO,CAAC;CACtB,CAkJA"}
|
package/dist/hooks/nixStore.js
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
import { activeContext } from "../context/context";
|
|
2
2
|
export function nixStore(path, initial) {
|
|
3
|
+
if (!path || typeof path !== "string") {
|
|
4
|
+
throw new Error("[nixStore] Path must be a non-empty string");
|
|
5
|
+
}
|
|
6
|
+
const dangerousKeys = ["__proto__", "constructor", "prototype"];
|
|
7
|
+
if (dangerousKeys.some((key) => path.includes(key))) {
|
|
8
|
+
throw new Error("[nixStore] Path contains dangerous keywords");
|
|
9
|
+
}
|
|
10
|
+
if (path.length > 200) {
|
|
11
|
+
throw new Error("[nixStore] Path too long (max 200 characters)");
|
|
12
|
+
}
|
|
3
13
|
const ctx = activeContext;
|
|
4
14
|
if (!ctx)
|
|
5
15
|
throw new Error("nixStore() called outside component");
|
|
@@ -7,8 +17,14 @@ export function nixStore(path, initial) {
|
|
|
7
17
|
if (!ctx.hooks[idx]) {
|
|
8
18
|
let value = initial;
|
|
9
19
|
const subscribers = new Set();
|
|
20
|
+
let isDestroyed = false;
|
|
21
|
+
let maxSubscribers = 100;
|
|
10
22
|
const s = {
|
|
11
23
|
get value() {
|
|
24
|
+
if (isDestroyed) {
|
|
25
|
+
console.warn("[nixStore] Accessing destroyed store:", path);
|
|
26
|
+
return value;
|
|
27
|
+
}
|
|
12
28
|
try {
|
|
13
29
|
if (activeContext?._accessedStates) {
|
|
14
30
|
activeContext._accessedStates.add(s);
|
|
@@ -20,30 +36,67 @@ export function nixStore(path, initial) {
|
|
|
20
36
|
return value;
|
|
21
37
|
},
|
|
22
38
|
set value(v) {
|
|
23
|
-
|
|
24
|
-
|
|
39
|
+
if (isDestroyed) {
|
|
40
|
+
console.warn("[nixStore] Attempting to set value on destroyed store:", path);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
if (v && typeof v === "object") {
|
|
44
|
+
const safeValue = Object.create(null);
|
|
45
|
+
for (const key in v) {
|
|
46
|
+
if (Object.prototype.hasOwnProperty.call(v, key) &&
|
|
47
|
+
!dangerousKeys.includes(key)) {
|
|
48
|
+
safeValue[key] = v[key];
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
value = safeValue;
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
value = v;
|
|
55
|
+
}
|
|
56
|
+
const subscriberArray = Array.from(subscribers);
|
|
57
|
+
subscriberArray.forEach((fn) => {
|
|
25
58
|
try {
|
|
26
59
|
fn();
|
|
27
60
|
}
|
|
28
61
|
catch (err) {
|
|
29
62
|
console.error("[nixStore] Subscriber error:", err);
|
|
63
|
+
subscribers.delete(fn);
|
|
30
64
|
}
|
|
31
65
|
});
|
|
32
66
|
},
|
|
33
67
|
subscribe(fn) {
|
|
68
|
+
if (isDestroyed) {
|
|
69
|
+
console.warn("[nixStore] Attempting to subscribe to destroyed store:", path);
|
|
70
|
+
return () => { };
|
|
71
|
+
}
|
|
34
72
|
if (typeof fn !== "function") {
|
|
35
73
|
console.error("[nixStore] Subscriber must be a function");
|
|
36
74
|
return () => { };
|
|
37
75
|
}
|
|
76
|
+
if (subscribers.size >= maxSubscribers) {
|
|
77
|
+
console.warn(`[nixStore] Maximum subscribers (${maxSubscribers}) reached for store:`, path);
|
|
78
|
+
return () => { };
|
|
79
|
+
}
|
|
38
80
|
subscribers.add(fn);
|
|
39
|
-
return () =>
|
|
81
|
+
return () => {
|
|
82
|
+
subscribers.delete(fn);
|
|
83
|
+
};
|
|
84
|
+
},
|
|
85
|
+
cleanup() {
|
|
86
|
+
if (isDestroyed)
|
|
87
|
+
return;
|
|
88
|
+
isDestroyed = true;
|
|
89
|
+
subscribers.clear();
|
|
90
|
+
console.debug(`[nixStore] Cleaned up store: ${path}`);
|
|
40
91
|
},
|
|
92
|
+
getSubscriberCount: () => subscribers.size,
|
|
93
|
+
isDestroyed: () => isDestroyed,
|
|
41
94
|
path,
|
|
42
95
|
_isNixState: true,
|
|
43
96
|
};
|
|
44
97
|
if (!ctx.cleanups)
|
|
45
98
|
ctx.cleanups = [];
|
|
46
|
-
ctx.cleanups.push(() =>
|
|
99
|
+
ctx.cleanups.push(() => s.cleanup());
|
|
47
100
|
ctx.hooks[idx] = s;
|
|
48
101
|
}
|
|
49
102
|
return ctx.hooks[idx];
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../hooks/nixStore.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 { activeContext } from \"../context/context\";\r\n\r\n/**\r\n * Reactive store hook with subscription support.\r\n * Memory-safe: cleans up subscribers on component unmount.\r\n *\r\n * @param {string} path -
|
|
5
|
-
"mappings": ";;AAsBA,SAAS,qBAAqB;
|
|
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 { activeContext } from \"../context/context\";\r\n\r\n/**\r\n * Reactive store hook with subscription support and enhanced security.\r\n * Memory-safe: cleans up subscribers on component unmount.\r\n * Prevents prototype pollution and validates inputs.\r\n *\r\n * @template T\r\n * @param {string} path - Path identifier (required, must be safe)\r\n * @param {T} initial - Initial value\r\n * @returns {Object} Reactive store object with get/set/subscribe\r\n */\r\nexport function nixStore<T = any>(\r\n path: string,\r\n initial: T\r\n): {\r\n value: T;\r\n subscribe: (fn: () => void) => () => void;\r\n path: string;\r\n cleanup: () => void;\r\n getSubscriberCount: () => number;\r\n isDestroyed: () => boolean;\r\n _isNixState: boolean;\r\n} {\r\n // Input validation\r\n if (!path || typeof path !== \"string\") {\r\n throw new Error(\"[nixStore] Path must be a non-empty string\");\r\n }\r\n\r\n // Validate path for security (prevent prototype pollution)\r\n const dangerousKeys = [\"__proto__\", \"constructor\", \"prototype\"];\r\n if (dangerousKeys.some((key) => path.includes(key))) {\r\n throw new Error(\"[nixStore] Path contains dangerous keywords\");\r\n }\r\n\r\n if (path.length > 200) {\r\n throw new Error(\"[nixStore] Path too long (max 200 characters)\");\r\n }\r\n\r\n type StoreHook = {\r\n value: T;\r\n subscribe: (fn: () => void) => () => void;\r\n path: string;\r\n cleanup: () => void;\r\n getSubscriberCount: () => number;\r\n isDestroyed: () => boolean;\r\n _isNixState: boolean;\r\n };\r\n\r\n const ctx = activeContext as\r\n | (typeof activeContext & {\r\n hookIndex: number;\r\n hooks: Array<StoreHook | undefined>;\r\n cleanups?: Array<() => void>;\r\n })\r\n | undefined;\r\n if (!ctx) throw new Error(\"nixStore() called outside component\");\r\n\r\n const idx: number = ctx.hookIndex++;\r\n\r\n if (!ctx.hooks[idx]) {\r\n let value: T = initial;\r\n const subscribers: Set<() => void> = new Set();\r\n let isDestroyed = false;\r\n let maxSubscribers = 100; // Prevent memory leaks\r\n\r\n const s: StoreHook = {\r\n get value() {\r\n if (isDestroyed) {\r\n console.warn(\"[nixStore] Accessing destroyed store:\", path);\r\n return value;\r\n }\r\n\r\n try {\r\n if ((activeContext as any)?._accessedStates) {\r\n (activeContext as any)._accessedStates.add(s);\r\n }\r\n } catch (err) {\r\n console.error(\"[nixStore] Error tracking accessed state:\", err);\r\n }\r\n return value;\r\n },\r\n set value(v: T) {\r\n if (isDestroyed) {\r\n console.warn(\r\n \"[nixStore] Attempting to set value on destroyed store:\",\r\n path\r\n );\r\n return;\r\n }\r\n\r\n // Validate value for security\r\n if (v && typeof v === \"object\") {\r\n // Create safe object without dangerous properties\r\n const safeValue = Object.create(null);\r\n for (const key in v) {\r\n if (\r\n Object.prototype.hasOwnProperty.call(v, key) &&\r\n !dangerousKeys.includes(key)\r\n ) {\r\n safeValue[key] = v[key];\r\n }\r\n }\r\n value = safeValue as T;\r\n } else {\r\n value = v;\r\n }\r\n\r\n // Notify subscribers safely\r\n const subscriberArray = Array.from(subscribers);\r\n subscriberArray.forEach((fn) => {\r\n try {\r\n fn();\r\n } catch (err) {\r\n console.error(\"[nixStore] Subscriber error:\", err);\r\n // Remove failing subscriber to prevent repeated errors\r\n subscribers.delete(fn);\r\n }\r\n });\r\n },\r\n subscribe(fn: () => void) {\r\n if (isDestroyed) {\r\n console.warn(\r\n \"[nixStore] Attempting to subscribe to destroyed store:\",\r\n path\r\n );\r\n return () => {};\r\n }\r\n\r\n if (typeof fn !== \"function\") {\r\n console.error(\"[nixStore] Subscriber must be a function\");\r\n return () => {};\r\n }\r\n\r\n if (subscribers.size >= maxSubscribers) {\r\n console.warn(\r\n `[nixStore] Maximum subscribers (${maxSubscribers}) reached for store:`,\r\n path\r\n );\r\n return () => {};\r\n }\r\n\r\n subscribers.add(fn);\r\n // Return cleanup function\r\n return () => {\r\n subscribers.delete(fn);\r\n };\r\n },\r\n cleanup() {\r\n if (isDestroyed) return;\r\n\r\n isDestroyed = true;\r\n subscribers.clear();\r\n console.debug(`[nixStore] Cleaned up store: ${path}`);\r\n },\r\n getSubscriberCount: () => subscribers.size,\r\n isDestroyed: () => isDestroyed,\r\n path,\r\n _isNixState: true,\r\n };\r\n\r\n // Register cleanup on component unmount\r\n if (!ctx.cleanups) ctx.cleanups = [];\r\n ctx.cleanups.push(() => s.cleanup());\r\n\r\n ctx.hooks[idx] = s;\r\n }\r\n\r\n return ctx.hooks[idx] as StoreHook;\r\n}\r\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vite-plugin-res.d.ts","sourceRoot":"","sources":["../../plugins/vite-plugin-res.ts"],"names":[],"mappings":"AAuBA,OAAO,EAAa,KAAK,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAG3D,OAAO,KAAK,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"vite-plugin-res.d.ts","sourceRoot":"","sources":["../../plugins/vite-plugin-res.ts"],"names":[],"mappings":"AAuBA,OAAO,EAAa,KAAK,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAG3D,OAAO,KAAK,EAAE,MAAM,YAAY,CAAC;AAoJjC,UAAU,kBAAkB;IAK1B,UAAU,CAAC,EAAE,MAAM,CAAC;IAMpB,WAAW,CAAC,EAAE,MAAM,CAAC;IAMrB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IAMnB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IAMnB,SAAS,CAAC,EAAE,OAAO,CAAC;IAKpB,cAAc,CAAC,EAAE,OAAO,CAAC,gBAAgB,CAAC,CAAC;IAM3C,SAAS,CAAC,EAAE,OAAO,CAAC;IAMpB,KAAK,CAAC,EAAE,OAAO,CAAC;IAMhB,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAM5B,SAAS,CAAC,EAAE,OAAO,CAAC;IAKpB,QAAQ,CAAC,EAAE,EAAE,CAAC,eAAe,CAAC;CAC/B;AAaD,UAAU,eAAe;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,IAAI,GAAG,IAAI,CAAC;IACvB,QAAQ,EAAE,OAAO,CAAC;IAClB,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,OAAO,CAAC;IAClB,aAAa,EAAE,OAAO,CAAC;IACvB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAgkBD,cAAM,iBAAiB;IACrB,OAAO,CAAC,eAAe,CAAqB;IAC5C,OAAO,CAAC,YAAY,CAAkC;IACtD,OAAO,CAAC,OAAO,CAA2B;gBAE9B,aAAa,CAAC,EAAE,EAAE,CAAC,eAAe;IAuB9C,OAAO,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI;IAKhD,OAAO,CAAC,kBAAkB;IA8C1B,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE;IA0ErC,KAAK,IAAI,IAAI;CAId;AAKD,MAAM,CAAC,OAAO,UAAU,WAAW,CAAC,OAAO,GAAE,kBAAuB,GAAG,GAAG,CAoQzE;AAED,OAAO,EAAE,WAAW,EAAE,iBAAiB,EAAE,CAAC;AAC1C,YAAY,EAAE,kBAAkB,EAAE,eAAe,EAAE,CAAC"}
|