valyrian.js 8.0.13 → 8.1.0

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.
Files changed (60) hide show
  1. package/dist/lib/native-store/index.d.ts +1 -1
  2. package/dist/lib/native-store/index.d.ts.map +1 -1
  3. package/dist/lib/node/index.d.ts +2 -2
  4. package/dist/lib/node/index.d.ts.map +1 -1
  5. package/dist/lib/node/utils/server-storage.d.ts +12 -0
  6. package/dist/lib/node/utils/server-storage.d.ts.map +1 -0
  7. package/dist/lib/node/utils/tree-adapter.d.ts.map +1 -1
  8. package/dist/lib/pulses/index.d.ts +4 -0
  9. package/dist/lib/pulses/index.d.ts.map +1 -1
  10. package/dist/lib/redux-devtools/index.d.ts +15 -0
  11. package/dist/lib/redux-devtools/index.d.ts.map +1 -0
  12. package/dist/lib/request/index.d.ts.map +1 -1
  13. package/dist/lib/translate/index.d.ts +6 -2
  14. package/dist/lib/translate/index.d.ts.map +1 -1
  15. package/dist/native-store/index.js +26 -13
  16. package/dist/native-store/index.js.map +3 -3
  17. package/dist/native-store/index.min.js +1 -1
  18. package/dist/native-store/index.min.js.map +1 -1
  19. package/dist/native-store/index.mjs +26 -13
  20. package/dist/native-store/index.mjs.map +3 -3
  21. package/dist/node/index.js +57 -94
  22. package/dist/node/index.js.map +4 -4
  23. package/dist/node/index.mjs +57 -94
  24. package/dist/node/index.mjs.map +4 -4
  25. package/dist/pulses/index.js +32 -1
  26. package/dist/pulses/index.js.map +2 -2
  27. package/dist/pulses/index.min.js +1 -1
  28. package/dist/pulses/index.min.js.map +1 -1
  29. package/dist/pulses/index.mjs +32 -1
  30. package/dist/pulses/index.mjs.map +2 -2
  31. package/dist/redux-devtools/index.js +81 -0
  32. package/dist/redux-devtools/index.js.map +7 -0
  33. package/dist/redux-devtools/index.min.js +1 -0
  34. package/dist/redux-devtools/index.min.js.map +1 -0
  35. package/dist/redux-devtools/index.mjs +60 -0
  36. package/dist/redux-devtools/index.mjs.map +7 -0
  37. package/dist/request/index.js +20 -7
  38. package/dist/request/index.js.map +2 -2
  39. package/dist/request/index.min.js +1 -1
  40. package/dist/request/index.min.js.map +1 -1
  41. package/dist/request/index.mjs +20 -7
  42. package/dist/request/index.mjs.map +2 -2
  43. package/dist/translate/index.js +36 -26
  44. package/dist/translate/index.js.map +3 -3
  45. package/dist/translate/index.min.js +1 -1
  46. package/dist/translate/index.min.js.map +1 -1
  47. package/dist/translate/index.mjs +36 -26
  48. package/dist/translate/index.mjs.map +3 -3
  49. package/lib/native-store/index.ts +22 -8
  50. package/lib/node/index.ts +4 -4
  51. package/lib/node/utils/server-storage.ts +71 -0
  52. package/lib/node/utils/tree-adapter.ts +25 -1
  53. package/lib/pulses/index.ts +54 -6
  54. package/lib/redux-devtools/index.ts +86 -0
  55. package/lib/request/index.ts +28 -8
  56. package/lib/translate/index.ts +37 -26
  57. package/package.json +14 -14
  58. package/dist/lib/node/utils/session-storage.d.ts +0 -22
  59. package/dist/lib/node/utils/session-storage.d.ts.map +0 -1
  60. package/lib/node/utils/session-storage.ts +0 -120
@@ -56,7 +56,11 @@ function createStore<StateType extends State, PulsesType extends Pulses<StateTyp
56
56
  initialState: StateType | (() => StateType) | null,
57
57
  pulses: PulsesType,
58
58
  immutable = false
59
- ): StorePulses<PulsesType> & { state: ProxyState<StateType> } {
59
+ ): StorePulses<PulsesType> & {
60
+ state: ProxyState<StateType>;
61
+ on: (event: string, callback: Function) => void;
62
+ off: (event: string, callback: Function) => void;
63
+ } {
60
64
  const subscribers = new Set<Function>();
61
65
  const domWithVnodesToUpdate = new WeakSet<DomElement>();
62
66
 
@@ -160,7 +164,7 @@ function createStore<StateType extends State, PulsesType extends Pulses<StateTyp
160
164
  const emptyFlush = async () => {};
161
165
 
162
166
  try {
163
- const pulseResult = pulses[key].apply(this, [currentState, ...args]);
167
+ const pulseResult = pulses[key].apply(this, [currentState, ...args] as any);
164
168
  if (pulseResult instanceof Promise) {
165
169
  return pulseResult
166
170
  .then((resolvedValue) => {
@@ -190,38 +194,82 @@ function createStore<StateType extends State, PulsesType extends Pulses<StateTyp
190
194
 
191
195
  syncState(localState);
192
196
 
197
+ const listeners: Record<string, Function[]> = {};
198
+ const trigger = (event: string, ...args: any[]) => {
199
+ if (listeners[event]) {
200
+ listeners[event].forEach((callback) => callback(...args));
201
+ }
202
+ };
203
+
193
204
  const pulsesProxy = new Proxy(boundPulses, {
194
205
  get: (pulses, prop: string) => {
195
206
  if (prop === "state") {
196
207
  return proxyState;
197
208
  }
209
+ if (prop === "on") {
210
+ return (event: string, callback: Function) => {
211
+ if (!listeners[event]) {
212
+ listeners[event] = [];
213
+ }
214
+ listeners[event].push(callback);
215
+ };
216
+ }
217
+ if (prop === "off") {
218
+ return (event: string, callback: Function) => {
219
+ if (listeners[event]) {
220
+ listeners[event] = listeners[event].filter((cb) => cb !== callback);
221
+ }
222
+ };
223
+ }
198
224
  if (!(prop in pulses)) {
199
225
  throw new Error(`Pulse '${prop}' does not exist`);
200
226
  }
201
227
  const pulseMethod = pulses[prop];
202
228
 
203
229
  if (typeof pulseMethod === "function") {
204
- return pulseMethod.bind(pulsesProxy);
230
+ return (...args: any[]) => {
231
+ const result = pulseMethod.apply(pulsesProxy, args as any);
232
+ if (result instanceof Promise) {
233
+ return result.then((r) => {
234
+ trigger("pulse", prop, args);
235
+ return r;
236
+ });
237
+ }
238
+ trigger("pulse", prop, args);
239
+ return result;
240
+ };
205
241
  }
206
242
 
207
243
  return pulseMethod;
208
244
  }
209
245
  });
210
246
 
211
- return pulsesProxy as StorePulses<PulsesType> & { state: ProxyState<StateType> };
247
+ return pulsesProxy as StorePulses<PulsesType> & {
248
+ state: ProxyState<StateType>;
249
+ on: (event: string, callback: Function) => void;
250
+ off: (event: string, callback: Function) => void;
251
+ };
212
252
  }
213
253
 
214
254
  export function createPulseStore<StateType extends State, PulsesType extends Pulses<StateType>>(
215
255
  initialState: StateType,
216
256
  pulses: PulsesType
217
- ): StorePulses<PulsesType> & { state: ProxyState<StateType> } {
257
+ ): StorePulses<PulsesType> & {
258
+ state: ProxyState<StateType>;
259
+ on: (event: string, callback: Function) => void;
260
+ off: (event: string, callback: Function) => void;
261
+ } {
218
262
  return createStore(initialState, pulses, true);
219
263
  }
220
264
 
221
265
  export function createMutableStore<StateType extends State, PulsesType extends Pulses<StateType>>(
222
266
  initialState: StateType,
223
267
  pulses: PulsesType
224
- ): StorePulses<PulsesType> & { state: ProxyState<StateType> } {
268
+ ): StorePulses<PulsesType> & {
269
+ state: ProxyState<StateType>;
270
+ on: (event: string, callback: Function) => void;
271
+ off: (event: string, callback: Function) => void;
272
+ } {
225
273
  console.warn(
226
274
  "Warning: You are working with a mutable state. This can lead to unpredictable behavior. All state changes made outside of a pulse will not trigger a re-render."
227
275
  );
@@ -0,0 +1,86 @@
1
+ /* eslint-disable no-console */
2
+ import { FluxStore } from "valyrian.js/flux-store";
3
+
4
+ declare global {
5
+ interface Window {
6
+ __REDUX_DEVTOOLS_EXTENSION__: any;
7
+ }
8
+ }
9
+
10
+ interface DevToolsOptions {
11
+ name?: string;
12
+ [key: string]: any;
13
+ }
14
+
15
+ function getDevTools() {
16
+ const isBrowser = typeof window !== "undefined";
17
+ if (isBrowser && window.__REDUX_DEVTOOLS_EXTENSION__) {
18
+ return window.__REDUX_DEVTOOLS_EXTENSION__;
19
+ }
20
+ return null;
21
+ }
22
+
23
+ export function connectFluxStore(store: FluxStore, options: DevToolsOptions = {}) {
24
+ const devTools = getDevTools();
25
+ if (!devTools) {
26
+ return;
27
+ }
28
+
29
+ const name = options.name || "FluxStore";
30
+ const dt = devTools.connect({ name, ...options });
31
+ dt.init(store.state);
32
+
33
+ store.on("commit", (_: any, mutation: string, ...args: any[]) => {
34
+ dt.send({ type: mutation, payload: args }, store.state);
35
+ });
36
+
37
+ store.on("registerModule", (_: any, namespace: string) => {
38
+ dt.send({ type: `[Module] Register: ${namespace}` }, store.state);
39
+ });
40
+
41
+ store.on("unregisterModule", (_: any, namespace: string) => {
42
+ dt.send({ type: `[Module] Unregister: ${namespace}` }, store.state);
43
+ });
44
+ }
45
+
46
+ export function connectPulseStore(store: any, options: DevToolsOptions = {}) {
47
+ const devTools = getDevTools();
48
+ if (!devTools) {
49
+ return;
50
+ }
51
+
52
+ const name = options.name || "PulseStore";
53
+ const dt = devTools.connect({ name, ...options });
54
+ dt.init(store.state);
55
+
56
+ if (store.on) {
57
+ store.on("pulse", (pulse: string, args: any[]) => {
58
+ dt.send({ type: pulse, payload: args }, store.state);
59
+ });
60
+ }
61
+ }
62
+
63
+ export function connectPulse(pulse: any, options: DevToolsOptions = {}) {
64
+ // Pulse is [read, write, runSubscribers]
65
+ // We can't easily hook into the write function without wrapping it.
66
+ // But the user asked for "simple" and "using existing apis".
67
+ // If we want to debug individual pulses, we might need to wrap them.
68
+
69
+ const devTools = getDevTools();
70
+ if (!devTools) {
71
+ return pulse;
72
+ }
73
+
74
+ const name = options.name || "Pulse";
75
+ const dt = devTools.connect({ name, ...options });
76
+ const [read, write, run] = pulse;
77
+
78
+ dt.init(read());
79
+
80
+ const newWrite = (newValue: any) => {
81
+ write(newValue);
82
+ dt.send({ type: "update", payload: newValue }, read());
83
+ };
84
+
85
+ return [read, newWrite, run];
86
+ }
@@ -68,10 +68,17 @@ function serialize(obj: Record<string, any>, prefix: string = ""): URLSearchPara
68
68
  }
69
69
 
70
70
  function serializeFormData(data: Record<string, any>): FormData {
71
- return Object.entries(data).reduce((fd, [key, value]) => {
72
- fd.append(key, value);
73
- return fd;
74
- }, new FormData());
71
+ const fd = new FormData();
72
+ Object.entries(data).forEach(([key, value]) => {
73
+ if (value === null || value === undefined) return; // Ignorar nulos
74
+
75
+ if (Array.isArray(value)) {
76
+ value.forEach((v) => fd.append(key, v));
77
+ } else {
78
+ fd.append(key, value);
79
+ }
80
+ });
81
+ return fd;
75
82
  }
76
83
 
77
84
  function parseUrl(url: string, options: RequestOptionsWithUrls) {
@@ -97,6 +104,14 @@ function parseUrl(url: string, options: RequestOptionsWithUrls) {
97
104
 
98
105
  const defaultOptions: RequestOptions = { allowedMethods: ["get", "post", "put", "patch", "delete", "head", "options"] };
99
106
 
107
+ const isNativeBody = (data: any) =>
108
+ data instanceof FormData ||
109
+ data instanceof URLSearchParams ||
110
+ data instanceof Blob ||
111
+ data instanceof ArrayBuffer ||
112
+ (typeof DataView !== "undefined" && data instanceof DataView) ||
113
+ (typeof ReadableStream !== "undefined" && data instanceof ReadableStream);
114
+
100
115
  // eslint-disable-next-line sonarjs/cognitive-complexity
101
116
  function Requester(baseUrl = "", options: RequestOptions = defaultOptions) {
102
117
  const url = baseUrl.replace(/\/$/gi, "").trim();
@@ -157,12 +172,17 @@ function Requester(baseUrl = "", options: RequestOptions = defaultOptions) {
157
172
  }
158
173
 
159
174
  if (data) {
160
- const isJson = /json/gi.test(contentType);
161
-
162
175
  if (innerOptions.method === "GET" && typeof data === "object") {
163
176
  finalUrl.search = serialize(data).toString();
164
- } else if (innerOptions.method !== "GET") {
165
- innerOptions.body = isJson ? JSON.stringify(data) : serializeFormData(data);
177
+ } else if (isNativeBody(data) || typeof data === "string") {
178
+ innerOptions.body = data as BodyInit;
179
+ } else {
180
+ const isJson = /json/gi.test(contentType);
181
+ if (isJson) {
182
+ innerOptions.body = JSON.stringify(data);
183
+ } else {
184
+ innerOptions.body = serializeFormData(data);
185
+ }
166
186
  }
167
187
  }
168
188
 
@@ -1,11 +1,45 @@
1
- import { directive, setPropNameReserved, update, VnodeWithDom } from "valyrian.js";
1
+ import { current, directive, setPropNameReserved, update, VnodeWithDom } from "valyrian.js";
2
2
  import { get } from "valyrian.js/utils";
3
3
 
4
4
  const translations: Record<string, Record<string, any>> = {};
5
- let lang = "en";
5
+ let currentLang = "en";
6
+
7
+ let storeStrategy = {
8
+ get: () => currentLang,
9
+ set: (lang: string) => {
10
+ currentLang = lang;
11
+ }
12
+ };
13
+
14
+ export function setStoreStrategy(strategy: { get: () => string; set: (lang: string) => void }) {
15
+ storeStrategy = strategy;
16
+ }
17
+
18
+ export function getLang(): string {
19
+ return storeStrategy.get();
20
+ }
21
+
22
+ export function setLang(newLang: string): void {
23
+ if (typeof newLang !== "string") {
24
+ throw new Error(`Language ${newLang} not found`);
25
+ }
26
+
27
+ const parsedLang = newLang.toLowerCase().split("-").shift()?.split("_").shift();
28
+
29
+ if (typeof parsedLang !== "string") {
30
+ throw new Error(`Language ${newLang} not found`);
31
+ }
32
+
33
+ if (!translations[parsedLang]) {
34
+ throw new Error(`Language ${newLang} not found`);
35
+ }
36
+
37
+ storeStrategy.set(parsedLang);
38
+ update();
39
+ }
6
40
 
7
41
  export function t(path: string, params?: Record<string, string>): string {
8
- const langDef = translations[lang];
42
+ const langDef = translations[getLang()];
9
43
  const translation = get(langDef, path);
10
44
 
11
45
  if (typeof translation !== "string") {
@@ -49,29 +83,6 @@ export function getTranslations(): Record<string, Record<string, any>> {
49
83
  return translations;
50
84
  }
51
85
 
52
- export function setLang(newLang: string): void {
53
- if (typeof newLang !== "string") {
54
- throw new Error(`Language ${newLang} not found`);
55
- }
56
-
57
- const parsedLang = newLang.toLowerCase().split("-").shift()?.split("_").shift();
58
-
59
- if (typeof parsedLang !== "string") {
60
- throw new Error(`Language ${newLang} not found`);
61
- }
62
-
63
- if (!translations[parsedLang]) {
64
- throw new Error(`Language ${newLang} not found`);
65
- }
66
-
67
- lang = parsedLang;
68
- update();
69
- }
70
-
71
- export function getLang(): string {
72
- return lang;
73
- }
74
-
75
86
  export class NumberFormatter {
76
87
  #value: number = 0;
77
88
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "valyrian.js",
3
- "version": "8.0.13",
3
+ "version": "8.1.0",
4
4
  "description": "Lightweight steel to forge PWAs. (Minimal Frontend Framework with server side rendering and other capabilities)",
5
5
  "repository": "git@github.com:Masquerade-Circus/valyrian.js.git",
6
6
  "author": "Masquerade <christian@masquerade-circus.net>",
@@ -70,44 +70,44 @@
70
70
  },
71
71
  "dependencies": {
72
72
  "clean-css": "^5.3.3",
73
- "esbuild": "^0.25.11",
73
+ "esbuild": "^0.27.0",
74
74
  "favicons": "^7.2.0",
75
- "form-data": "^4.0.4",
75
+ "form-data": "^4.0.5",
76
76
  "purgecss": "^7.0.2",
77
77
  "remark": "^15.0.1",
78
- "terser": "^5.44.0",
78
+ "terser": "^5.44.1",
79
79
  "ts-node": "^10.9.2",
80
80
  "tsc-prog": "^2.3.0",
81
81
  "tslib": "^2.8.1",
82
82
  "typescript": "^5.9.3"
83
83
  },
84
84
  "devDependencies": {
85
- "@release-it/conventional-changelog": "^10.0.1",
86
- "@types/bun": "^1.3.0",
85
+ "@release-it/conventional-changelog": "^10.0.2",
86
+ "@types/bun": "^1.3.3",
87
87
  "@types/clean-css": "^4.2.11",
88
- "@types/node": "^24.8.1",
88
+ "@types/node": "^24.10.1",
89
89
  "@types/node-fetch": "^2.6.13",
90
90
  "@types/parse-path": "^7.1.0",
91
91
  "@types/sharp": "^0.32.0",
92
92
  "@types/source-map": "^0.5.7",
93
- "@typescript-eslint/eslint-plugin": "^8.46.1",
94
- "@typescript-eslint/parser": "^8.46.1",
93
+ "@typescript-eslint/eslint-plugin": "^8.47.0",
94
+ "@typescript-eslint/parser": "^8.47.0",
95
95
  "buffalo-test": "^2.0.0",
96
96
  "chokidar-cli": "^3.0.0",
97
97
  "compression": "^1.8.1",
98
98
  "cross-env": "^10.1.0",
99
99
  "cz-conventional-changelog": "^3.3.0",
100
- "dayjs": "^1.11.18",
101
- "eslint": "^9.37.0",
100
+ "dayjs": "^1.11.19",
101
+ "eslint": "^9.39.1",
102
102
  "eslint-plugin-sonarjs": "^3.0.5",
103
103
  "expect": "^30.2.0",
104
- "fastify": "^5.6.1",
104
+ "fastify": "^5.6.2",
105
105
  "gzip-size": "^7.0.0",
106
106
  "pirates": "^4.0.7",
107
- "release-it": "^19.0.5",
107
+ "release-it": "^19.0.6",
108
108
  "remark-cli": "^12.0.1",
109
109
  "remark-toc": "^9.0.0",
110
- "typescript-eslint": "^8.46.1"
110
+ "typescript-eslint": "^8.47.0"
111
111
  },
112
112
  "overrides": {
113
113
  "minimist": "^1.2.8",
@@ -1,22 +0,0 @@
1
- export declare class SessionStorage {
2
- private storage;
3
- private limit;
4
- private persist;
5
- private filePath;
6
- private directory;
7
- constructor({ persist, filePath }?: {
8
- persist?: boolean;
9
- filePath?: string;
10
- });
11
- private getStorageSize;
12
- private checkSizeLimit;
13
- setItem(key: string | null | undefined, value: string | null | undefined): void;
14
- getItem(key: string | null | undefined): string | null;
15
- removeItem(key: string | null | undefined): void;
16
- clear(): void;
17
- get length(): number;
18
- key(index: number): string | null;
19
- private saveToFile;
20
- private loadFromFile;
21
- }
22
- //# sourceMappingURL=session-storage.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"session-storage.d.ts","sourceRoot":"","sources":["../../../../lib/node/utils/session-storage.ts"],"names":[],"mappings":"AAGA,qBAAa,cAAc;IACzB,OAAO,CAAC,OAAO,CAA4B;IAC3C,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,OAAO,CAAU;IACzB,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,SAAS,CAA8B;gBAEnC,EAAE,OAAe,EAAE,QAA+B,EAAE,GAAE;QAAE,OAAO,CAAC,EAAE,OAAO,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAO;IAiB/G,OAAO,CAAC,cAAc;IAKtB,OAAO,CAAC,cAAc;IAQtB,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,IAAI;IAkB/E,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,GAAG,IAAI;IAUtD,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,IAAI;IAUhD,KAAK,IAAI,IAAI;IAMb,IAAI,MAAM,IAAI,MAAM,CAEnB;IAGD,GAAG,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAOjC,OAAO,CAAC,UAAU;IAWlB,OAAO,CAAC,YAAY;CAYrB"}
@@ -1,120 +0,0 @@
1
- import fs from "fs";
2
- import path from "path";
3
-
4
- export class SessionStorage {
5
- private storage: { [key: string]: string };
6
- private limit: number;
7
- private persist: boolean;
8
- private filePath: string;
9
- private directory: string = ".session-storage";
10
-
11
- constructor({ persist = false, filePath = "./sessionData.json" }: { persist?: boolean; filePath?: string } = {}) {
12
- this.storage = {};
13
- this.limit = 5 * 1024 * 1024; // 5MB storage limit
14
- this.persist = persist;
15
- this.filePath = path.resolve(this.directory, filePath);
16
-
17
- if (this.persist) {
18
- if (!fs.existsSync(this.directory)) {
19
- fs.mkdirSync(this.directory, { recursive: true });
20
- }
21
-
22
- // Load data from file if persistence is enabled
23
- this.loadFromFile();
24
- }
25
- }
26
-
27
- // Calculate total size in bytes of stored data
28
- private getStorageSize(): number {
29
- return new TextEncoder().encode(JSON.stringify(this.storage)).length;
30
- }
31
-
32
- // Check if storage limit is exceeded
33
- private checkSizeLimit(): void {
34
- const size = this.getStorageSize();
35
- if (size > this.limit) {
36
- throw new DOMException("Storage limit exceeded", "QuotaExceededError");
37
- }
38
- }
39
-
40
- // Store value under the specified key
41
- setItem(key: string | null | undefined, value: string | null | undefined): void {
42
- if (key === null || key === undefined) {
43
- throw new TypeError("Failed to execute 'setItem' on 'Storage': 1 argument required, but only 0 present.");
44
- }
45
-
46
- if (value === null) {
47
- value = "null"; // Convert null to "null"
48
- } else if (value === undefined) {
49
- value = "undefined"; // Convert undefined to "undefined"
50
- }
51
-
52
- this.loadFromFile();
53
- this.storage[key] = String(value); // Store as string
54
- this.checkSizeLimit(); // Check storage limit
55
- this.saveToFile(); // Save to file if persistence is enabled
56
- }
57
-
58
- // Retrieve value stored under the specified key
59
- getItem(key: string | null | undefined): string | null {
60
- if (key === null || key === undefined) {
61
- throw new TypeError("Failed to execute 'getItem' on 'Storage': 1 argument required, but only 0 present.");
62
- }
63
-
64
- this.loadFromFile();
65
- return this.storage[key] || null; // Return null if key doesn't exist
66
- }
67
-
68
- // Remove the value under the specified key
69
- removeItem(key: string | null | undefined): void {
70
- if (key === null || key === undefined) {
71
- throw new TypeError("Failed to execute 'removeItem' on 'Storage': 1 argument required, but only 0 present.");
72
- }
73
- this.loadFromFile();
74
- Reflect.deleteProperty(this.storage, key); // Remove key from storage
75
- this.saveToFile(); // Save to file if persistence is enabled
76
- }
77
-
78
- // Clear all stored values
79
- clear(): void {
80
- this.storage = {};
81
- this.saveToFile(); // Save to file if persistence is enabled
82
- }
83
-
84
- // Return the number of stored items
85
- get length(): number {
86
- return Object.keys(this.storage).length;
87
- }
88
-
89
- // Return the key at the specified index
90
- key(index: number): string | null {
91
- this.loadFromFile();
92
- const keys = Object.keys(this.storage);
93
- return keys[index] || null;
94
- }
95
-
96
- // Save data to a file (only if persistence is enabled)
97
- private saveToFile(): void {
98
- if (this.persist) {
99
- try {
100
- fs.writeFileSync(this.filePath, JSON.stringify(this.storage), "utf-8");
101
- } catch (error) {
102
- throw new Error(`Error saving data to file: ${(error as any).message}`);
103
- }
104
- }
105
- }
106
-
107
- // Load data from a file (only if persistence is enabled)
108
- private loadFromFile(): void {
109
- if (this.persist) {
110
- try {
111
- if (fs.existsSync(this.filePath)) {
112
- const data = fs.readFileSync(this.filePath, "utf-8");
113
- this.storage = JSON.parse(data || "{}");
114
- }
115
- } catch (error) {
116
- throw new Error(`Error loading data from file: ${(error as any).message}`);
117
- }
118
- }
119
- }
120
- }