@al8b/io 0.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.
package/README.md ADDED
@@ -0,0 +1,21 @@
1
+ # @al8b/io
2
+
3
+ Storage and IO helpers for the L8B runtime. Today this package centers on persistent storage through a single service abstraction.
4
+
5
+ ## Public API
6
+
7
+ - `StorageService`
8
+
9
+ ## Notes
10
+
11
+ - Used by `@al8b/vm` and `@al8b/runtime`.
12
+ - Keep persistence concerns here rather than coupling them to VM or runtime orchestration.
13
+
14
+ ## Scripts
15
+
16
+ ```bash
17
+ bun run build
18
+ bun run test
19
+ bun run typecheck
20
+ bun run clean
21
+ ```
package/dist/index.cjs ADDED
@@ -0,0 +1,203 @@
1
+ 'use strict';
2
+
3
+ var diagnostics = require('@al8b/diagnostics');
4
+
5
+ var __defProp = Object.defineProperty;
6
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
7
+ var StorageService = class {
8
+ static {
9
+ __name(this, "StorageService");
10
+ }
11
+ namespace;
12
+ cache = /* @__PURE__ */ new Map();
13
+ pendingWrites = /* @__PURE__ */ new Map();
14
+ writeTimer = null;
15
+ runtime;
16
+ constructor(namespace = "/l8b", preserve = false, runtime) {
17
+ this.namespace = namespace;
18
+ this.runtime = runtime;
19
+ if (!preserve && typeof localStorage !== "undefined") {
20
+ this.clear();
21
+ }
22
+ }
23
+ /**
24
+ * Get value from storage
25
+ */
26
+ get(name) {
27
+ if (!name || typeof name !== "string" || name.trim() === "") {
28
+ diagnostics.reportRuntimeError(this.runtime?.listener, diagnostics.APIErrorCode.E7063, {
29
+ key: String(name)
30
+ });
31
+ return null;
32
+ }
33
+ if (this.cache.has(name)) {
34
+ return this.cache.get(name);
35
+ }
36
+ if (typeof localStorage !== "undefined") {
37
+ try {
38
+ const key = `${this.namespace}.${name}`;
39
+ const value = localStorage.getItem(key);
40
+ if (value !== null) {
41
+ const parsed = JSON.parse(value);
42
+ this.cache.set(name, parsed);
43
+ return parsed;
44
+ }
45
+ } catch (err) {
46
+ diagnostics.reportRuntimeError(this.runtime?.listener, diagnostics.APIErrorCode.E7062, {
47
+ error: `Get operation failed: ${String(err)}`
48
+ });
49
+ }
50
+ }
51
+ return null;
52
+ }
53
+ /**
54
+ * Set value in storage (batched write)
55
+ */
56
+ set(name, value) {
57
+ if (!name || typeof name !== "string" || name.trim() === "") {
58
+ diagnostics.reportRuntimeError(this.runtime?.listener, diagnostics.APIErrorCode.E7063, {
59
+ key: String(name)
60
+ });
61
+ return;
62
+ }
63
+ this.cache.set(name, value);
64
+ this.pendingWrites.set(name, value);
65
+ if (this.writeTimer === null) {
66
+ const schedule = typeof window !== "undefined" && typeof window.setTimeout === "function" ? window.setTimeout.bind(window) : setTimeout;
67
+ this.writeTimer = schedule(() => {
68
+ this.flush();
69
+ }, 100);
70
+ }
71
+ }
72
+ /**
73
+ * Flush pending writes to localStorage
74
+ */
75
+ flush() {
76
+ if (this.writeTimer !== null) {
77
+ clearTimeout(this.writeTimer);
78
+ this.writeTimer = null;
79
+ }
80
+ if (typeof localStorage === "undefined") {
81
+ this.pendingWrites.clear();
82
+ return;
83
+ }
84
+ for (const [name, value] of this.pendingWrites) {
85
+ try {
86
+ const key = `${this.namespace}.${name}`;
87
+ const serialized = JSON.stringify(this.sanitize(value));
88
+ localStorage.setItem(key, serialized);
89
+ } catch (err) {
90
+ if (err.name === "QuotaExceededError" || err.code === 22) {
91
+ diagnostics.reportRuntimeError(this.runtime?.listener, diagnostics.APIErrorCode.E7061, {});
92
+ } else {
93
+ diagnostics.reportRuntimeError(this.runtime?.listener, diagnostics.APIErrorCode.E7062, {
94
+ error: `Set operation failed: ${String(err)}`
95
+ });
96
+ }
97
+ }
98
+ }
99
+ this.pendingWrites.clear();
100
+ }
101
+ /**
102
+ * Check if there are pending writes and flush if needed
103
+ */
104
+ check() {
105
+ if (this.pendingWrites.size > 0) {
106
+ this.flush();
107
+ }
108
+ }
109
+ /**
110
+ * Delete a single key from storage (cache + localStorage + any pending write)
111
+ */
112
+ delete(name) {
113
+ if (!name || typeof name !== "string" || name.trim() === "") {
114
+ diagnostics.reportRuntimeError(this.runtime?.listener, diagnostics.APIErrorCode.E7063, {
115
+ key: String(name)
116
+ });
117
+ return;
118
+ }
119
+ this.cache.delete(name);
120
+ this.pendingWrites.delete(name);
121
+ if (typeof localStorage !== "undefined") {
122
+ try {
123
+ localStorage.removeItem(`${this.namespace}.${name}`);
124
+ } catch (err) {
125
+ diagnostics.reportRuntimeError(this.runtime?.listener, diagnostics.APIErrorCode.E7062, {
126
+ error: `Delete operation failed: ${String(err)}`
127
+ });
128
+ }
129
+ }
130
+ }
131
+ /**
132
+ * Clear all storage for this namespace
133
+ */
134
+ clear() {
135
+ if (typeof localStorage === "undefined") {
136
+ return;
137
+ }
138
+ const prefix = `${this.namespace}.`;
139
+ const keysToRemove = [];
140
+ for (let i = 0; i < localStorage.length; i++) {
141
+ const key = localStorage.key(i);
142
+ if (key && key.startsWith(prefix)) {
143
+ keysToRemove.push(key);
144
+ }
145
+ }
146
+ for (const key of keysToRemove) {
147
+ localStorage.removeItem(key);
148
+ }
149
+ this.cache.clear();
150
+ this.pendingWrites.clear();
151
+ }
152
+ /**
153
+ * Sanitize value for JSON serialization
154
+ * Removes functions and handles circular references
155
+ */
156
+ sanitize(value, seen = /* @__PURE__ */ new WeakSet()) {
157
+ if (value === null || value === void 0) {
158
+ return value;
159
+ }
160
+ if (typeof value !== "object") {
161
+ if (typeof value === "function") {
162
+ return void 0;
163
+ }
164
+ return value;
165
+ }
166
+ if (seen.has(value)) {
167
+ return void 0;
168
+ }
169
+ seen.add(value);
170
+ if (Array.isArray(value)) {
171
+ return value.map((item) => this.sanitize(item, seen)).filter((item) => item !== void 0);
172
+ }
173
+ const result = {};
174
+ for (const key in value) {
175
+ if (Object.hasOwn(value, key)) {
176
+ const sanitized = this.sanitize(value[key], seen);
177
+ if (sanitized !== void 0) {
178
+ result[key] = sanitized;
179
+ }
180
+ }
181
+ }
182
+ return result;
183
+ }
184
+ interfaceCache = null;
185
+ /**
186
+ * Get storage interface for game code
187
+ */
188
+ getInterface() {
189
+ if (this.interfaceCache) {
190
+ return this.interfaceCache;
191
+ }
192
+ this.interfaceCache = {
193
+ set: /* @__PURE__ */ __name((name, value) => this.set(name, value), "set"),
194
+ get: /* @__PURE__ */ __name((name) => this.get(name), "get"),
195
+ delete: /* @__PURE__ */ __name((name) => this.delete(name), "delete")
196
+ };
197
+ return this.interfaceCache;
198
+ }
199
+ };
200
+
201
+ exports.StorageService = StorageService;
202
+ //# sourceMappingURL=index.cjs.map
203
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/storage/index.ts"],"names":["StorageService","namespace","cache","Map","pendingWrites","writeTimer","runtime","preserve","localStorage","clear","get","name","trim","reportRuntimeError","listener","APIErrorCode","E7063","key","String","has","value","getItem","parsed","JSON","parse","set","err","E7062","error","schedule","window","setTimeout","bind","flush","clearTimeout","serialized","stringify","sanitize","setItem","code","E7061","check","size","delete","removeItem","prefix","keysToRemove","i","length","startsWith","push","seen","WeakSet","undefined","add","Array","isArray","map","item","filter","result","Object","hasOwn","sanitized","interfaceCache","getInterface"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Storage service - localStorage wrapper with automatic serialization\n */\n\nimport { APIErrorCode, reportRuntimeError } from \"@al8b/diagnostics\";\n\nexport class StorageService {\n\tprivate namespace: string;\n\tprivate cache: Map<string, any> = new Map();\n\tprivate pendingWrites: Map<string, any> = new Map();\n\tprivate writeTimer: ReturnType<typeof setTimeout> | null = null;\n\tprivate runtime?: any;\n\n\tconstructor(namespace = \"/l8b\", preserve = false, runtime?: any) {\n\t\tthis.namespace = namespace;\n\t\tthis.runtime = runtime;\n\n\t\t// Clear storage if not preserving\n\t\tif (!preserve && typeof localStorage !== \"undefined\") {\n\t\t\tthis.clear();\n\t\t}\n\t}\n\n\t/**\n\t * Get value from storage\n\t */\n\tget(name: string): any {\n\t\t// Validate storage key\n\t\tif (!name || typeof name !== \"string\" || name.trim() === \"\") {\n\t\t\treportRuntimeError(this.runtime?.listener, APIErrorCode.E7063, { key: String(name) });\n\t\t\treturn null;\n\t\t}\n\n\t\t// Check cache first\n\t\tif (this.cache.has(name)) {\n\t\t\treturn this.cache.get(name);\n\t\t}\n\n\t\t// Try localStorage\n\t\tif (typeof localStorage !== \"undefined\") {\n\t\t\ttry {\n\t\t\t\tconst key = `${this.namespace}.${name}`;\n\t\t\t\tconst value = localStorage.getItem(key);\n\t\t\t\tif (value !== null) {\n\t\t\t\t\tconst parsed = JSON.parse(value);\n\t\t\t\t\tthis.cache.set(name, parsed);\n\t\t\t\t\treturn parsed;\n\t\t\t\t}\n\t\t\t} catch (err: any) {\n\t\t\t\treportRuntimeError(this.runtime?.listener, APIErrorCode.E7062, { error: `Get operation failed: ${String(err)}` });\n\t\t\t}\n\t\t}\n\n\t\treturn null;\n\t}\n\n\t/**\n\t * Set value in storage (batched write)\n\t */\n\tset(name: string, value: any): void {\n\t\t// Validate storage key\n\t\tif (!name || typeof name !== \"string\" || name.trim() === \"\") {\n\t\t\treportRuntimeError(this.runtime?.listener, APIErrorCode.E7063, { key: String(name) });\n\t\t\treturn;\n\t\t}\n\n\t\t// Update cache\n\t\tthis.cache.set(name, value);\n\n\t\t// Queue write\n\t\tthis.pendingWrites.set(name, value);\n\n\t\t// Schedule batch write\n\t\tif (this.writeTimer === null) {\n\t\t\tconst schedule =\n\t\t\t\ttypeof window !== \"undefined\" && typeof window.setTimeout === \"function\"\n\t\t\t\t\t? window.setTimeout.bind(window)\n\t\t\t\t\t: setTimeout;\n\n\t\t\tthis.writeTimer = schedule(() => {\n\t\t\t\tthis.flush();\n\t\t\t}, 100);\n\t\t}\n\t}\n\n\t/**\n\t * Flush pending writes to localStorage\n\t */\n\tflush(): void {\n\t\tif (this.writeTimer !== null) {\n\t\t\tclearTimeout(this.writeTimer);\n\t\t\tthis.writeTimer = null;\n\t\t}\n\n\t\tif (typeof localStorage === \"undefined\") {\n\t\t\tthis.pendingWrites.clear();\n\t\t\treturn;\n\t\t}\n\n\t\tfor (const [name, value] of this.pendingWrites) {\n\t\t\ttry {\n\t\t\t\tconst key = `${this.namespace}.${name}`;\n\t\t\t\tconst serialized = JSON.stringify(this.sanitize(value));\n\t\t\t\tlocalStorage.setItem(key, serialized);\n\t\t\t} catch (err: any) {\n\t\t\t\t// Check for quota exceeded error\n\t\t\t\tif (err.name === \"QuotaExceededError\" || err.code === 22) {\n\t\t\t\t\treportRuntimeError(this.runtime?.listener, APIErrorCode.E7061, {});\n\t\t\t\t} else {\n\t\t\t\t\treportRuntimeError(this.runtime?.listener, APIErrorCode.E7062, { error: `Set operation failed: ${String(err)}` });\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tthis.pendingWrites.clear();\n\t}\n\n\t/**\n\t * Check if there are pending writes and flush if needed\n\t */\n\tcheck(): void {\n\t\tif (this.pendingWrites.size > 0) {\n\t\t\tthis.flush();\n\t\t}\n\t}\n\n\t/**\n\t * Delete a single key from storage (cache + localStorage + any pending write)\n\t */\n\tdelete(name: string): void {\n\t\tif (!name || typeof name !== \"string\" || name.trim() === \"\") {\n\t\t\treportRuntimeError(this.runtime?.listener, APIErrorCode.E7063, { key: String(name) });\n\t\t\treturn;\n\t\t}\n\n\t\t// Remove from in-memory state\n\t\tthis.cache.delete(name);\n\t\tthis.pendingWrites.delete(name);\n\n\t\t// Remove from localStorage\n\t\tif (typeof localStorage !== \"undefined\") {\n\t\t\ttry {\n\t\t\t\tlocalStorage.removeItem(`${this.namespace}.${name}`);\n\t\t\t} catch (err: any) {\n\t\t\t\treportRuntimeError(this.runtime?.listener, APIErrorCode.E7062, {\n\t\t\t\t\terror: `Delete operation failed: ${String(err)}`,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Clear all storage for this namespace\n\t */\n\tclear(): void {\n\t\tif (typeof localStorage === \"undefined\") {\n\t\t\treturn;\n\t\t}\n\n\t\tconst prefix = `${this.namespace}.`;\n\t\tconst keysToRemove: string[] = [];\n\n\t\t// Find all keys with this namespace\n\t\tfor (let i = 0; i < localStorage.length; i++) {\n\t\t\tconst key = localStorage.key(i);\n\t\t\tif (key && key.startsWith(prefix)) {\n\t\t\t\tkeysToRemove.push(key);\n\t\t\t}\n\t\t}\n\n\t\t// Remove them\n\t\tfor (const key of keysToRemove) {\n\t\t\tlocalStorage.removeItem(key);\n\t\t}\n\n\t\t// Clear cache\n\t\tthis.cache.clear();\n\t\tthis.pendingWrites.clear();\n\t}\n\n\t/**\n\t * Sanitize value for JSON serialization\n\t * Removes functions and handles circular references\n\t */\n\tprivate sanitize(value: any, seen = new WeakSet()): any {\n\t\tif (value === null || value === undefined) {\n\t\t\treturn value;\n\t\t}\n\n\t\t// Primitives\n\t\tif (typeof value !== \"object\") {\n\t\t\t// Remove functions\n\t\t\tif (typeof value === \"function\") {\n\t\t\t\treturn undefined;\n\t\t\t}\n\t\t\treturn value;\n\t\t}\n\n\t\t// Check for circular reference\n\t\tif (seen.has(value)) {\n\t\t\treturn undefined;\n\t\t}\n\t\tseen.add(value);\n\n\t\t// Arrays\n\t\tif (Array.isArray(value)) {\n\t\t\treturn value.map((item) => this.sanitize(item, seen)).filter((item) => item !== undefined);\n\t\t}\n\n\t\t// Objects\n\t\tconst result: any = {};\n\t\tfor (const key in value) {\n\t\t\tif (Object.hasOwn(value, key)) {\n\t\t\t\tconst sanitized = this.sanitize(value[key], seen);\n\t\t\t\tif (sanitized !== undefined) {\n\t\t\t\t\tresult[key] = sanitized;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn result;\n\t}\n\n\tprivate interfaceCache: {\n\t\tset: (name: string, value: unknown) => void;\n\t\tget: (name: string) => unknown;\n\t\tdelete: (name: string) => void;\n\t} | null = null;\n\n\t/**\n\t * Get storage interface for game code\n\t */\n\tgetInterface() {\n\t\tif (this.interfaceCache) {\n\t\t\treturn this.interfaceCache;\n\t\t}\n\t\tthis.interfaceCache = {\n\t\t\tset: (name: string, value: any) => this.set(name, value),\n\t\t\tget: (name: string) => this.get(name),\n\t\t\tdelete: (name: string) => this.delete(name),\n\t\t};\n\t\treturn this.interfaceCache;\n\t}\n}\n"]}
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Storage service - localStorage wrapper with automatic serialization
3
+ */
4
+ declare class StorageService {
5
+ private namespace;
6
+ private cache;
7
+ private pendingWrites;
8
+ private writeTimer;
9
+ private runtime?;
10
+ constructor(namespace?: string, preserve?: boolean, runtime?: any);
11
+ /**
12
+ * Get value from storage
13
+ */
14
+ get(name: string): any;
15
+ /**
16
+ * Set value in storage (batched write)
17
+ */
18
+ set(name: string, value: any): void;
19
+ /**
20
+ * Flush pending writes to localStorage
21
+ */
22
+ flush(): void;
23
+ /**
24
+ * Check if there are pending writes and flush if needed
25
+ */
26
+ check(): void;
27
+ /**
28
+ * Delete a single key from storage (cache + localStorage + any pending write)
29
+ */
30
+ delete(name: string): void;
31
+ /**
32
+ * Clear all storage for this namespace
33
+ */
34
+ clear(): void;
35
+ /**
36
+ * Sanitize value for JSON serialization
37
+ * Removes functions and handles circular references
38
+ */
39
+ private sanitize;
40
+ private interfaceCache;
41
+ /**
42
+ * Get storage interface for game code
43
+ */
44
+ getInterface(): {
45
+ set: (name: string, value: unknown) => void;
46
+ get: (name: string) => unknown;
47
+ delete: (name: string) => void;
48
+ };
49
+ }
50
+
51
+ export { StorageService };
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Storage service - localStorage wrapper with automatic serialization
3
+ */
4
+ declare class StorageService {
5
+ private namespace;
6
+ private cache;
7
+ private pendingWrites;
8
+ private writeTimer;
9
+ private runtime?;
10
+ constructor(namespace?: string, preserve?: boolean, runtime?: any);
11
+ /**
12
+ * Get value from storage
13
+ */
14
+ get(name: string): any;
15
+ /**
16
+ * Set value in storage (batched write)
17
+ */
18
+ set(name: string, value: any): void;
19
+ /**
20
+ * Flush pending writes to localStorage
21
+ */
22
+ flush(): void;
23
+ /**
24
+ * Check if there are pending writes and flush if needed
25
+ */
26
+ check(): void;
27
+ /**
28
+ * Delete a single key from storage (cache + localStorage + any pending write)
29
+ */
30
+ delete(name: string): void;
31
+ /**
32
+ * Clear all storage for this namespace
33
+ */
34
+ clear(): void;
35
+ /**
36
+ * Sanitize value for JSON serialization
37
+ * Removes functions and handles circular references
38
+ */
39
+ private sanitize;
40
+ private interfaceCache;
41
+ /**
42
+ * Get storage interface for game code
43
+ */
44
+ getInterface(): {
45
+ set: (name: string, value: unknown) => void;
46
+ get: (name: string) => unknown;
47
+ delete: (name: string) => void;
48
+ };
49
+ }
50
+
51
+ export { StorageService };
package/dist/index.js ADDED
@@ -0,0 +1,201 @@
1
+ import { reportRuntimeError, APIErrorCode } from '@al8b/diagnostics';
2
+
3
+ var __defProp = Object.defineProperty;
4
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
5
+ var StorageService = class {
6
+ static {
7
+ __name(this, "StorageService");
8
+ }
9
+ namespace;
10
+ cache = /* @__PURE__ */ new Map();
11
+ pendingWrites = /* @__PURE__ */ new Map();
12
+ writeTimer = null;
13
+ runtime;
14
+ constructor(namespace = "/l8b", preserve = false, runtime) {
15
+ this.namespace = namespace;
16
+ this.runtime = runtime;
17
+ if (!preserve && typeof localStorage !== "undefined") {
18
+ this.clear();
19
+ }
20
+ }
21
+ /**
22
+ * Get value from storage
23
+ */
24
+ get(name) {
25
+ if (!name || typeof name !== "string" || name.trim() === "") {
26
+ reportRuntimeError(this.runtime?.listener, APIErrorCode.E7063, {
27
+ key: String(name)
28
+ });
29
+ return null;
30
+ }
31
+ if (this.cache.has(name)) {
32
+ return this.cache.get(name);
33
+ }
34
+ if (typeof localStorage !== "undefined") {
35
+ try {
36
+ const key = `${this.namespace}.${name}`;
37
+ const value = localStorage.getItem(key);
38
+ if (value !== null) {
39
+ const parsed = JSON.parse(value);
40
+ this.cache.set(name, parsed);
41
+ return parsed;
42
+ }
43
+ } catch (err) {
44
+ reportRuntimeError(this.runtime?.listener, APIErrorCode.E7062, {
45
+ error: `Get operation failed: ${String(err)}`
46
+ });
47
+ }
48
+ }
49
+ return null;
50
+ }
51
+ /**
52
+ * Set value in storage (batched write)
53
+ */
54
+ set(name, value) {
55
+ if (!name || typeof name !== "string" || name.trim() === "") {
56
+ reportRuntimeError(this.runtime?.listener, APIErrorCode.E7063, {
57
+ key: String(name)
58
+ });
59
+ return;
60
+ }
61
+ this.cache.set(name, value);
62
+ this.pendingWrites.set(name, value);
63
+ if (this.writeTimer === null) {
64
+ const schedule = typeof window !== "undefined" && typeof window.setTimeout === "function" ? window.setTimeout.bind(window) : setTimeout;
65
+ this.writeTimer = schedule(() => {
66
+ this.flush();
67
+ }, 100);
68
+ }
69
+ }
70
+ /**
71
+ * Flush pending writes to localStorage
72
+ */
73
+ flush() {
74
+ if (this.writeTimer !== null) {
75
+ clearTimeout(this.writeTimer);
76
+ this.writeTimer = null;
77
+ }
78
+ if (typeof localStorage === "undefined") {
79
+ this.pendingWrites.clear();
80
+ return;
81
+ }
82
+ for (const [name, value] of this.pendingWrites) {
83
+ try {
84
+ const key = `${this.namespace}.${name}`;
85
+ const serialized = JSON.stringify(this.sanitize(value));
86
+ localStorage.setItem(key, serialized);
87
+ } catch (err) {
88
+ if (err.name === "QuotaExceededError" || err.code === 22) {
89
+ reportRuntimeError(this.runtime?.listener, APIErrorCode.E7061, {});
90
+ } else {
91
+ reportRuntimeError(this.runtime?.listener, APIErrorCode.E7062, {
92
+ error: `Set operation failed: ${String(err)}`
93
+ });
94
+ }
95
+ }
96
+ }
97
+ this.pendingWrites.clear();
98
+ }
99
+ /**
100
+ * Check if there are pending writes and flush if needed
101
+ */
102
+ check() {
103
+ if (this.pendingWrites.size > 0) {
104
+ this.flush();
105
+ }
106
+ }
107
+ /**
108
+ * Delete a single key from storage (cache + localStorage + any pending write)
109
+ */
110
+ delete(name) {
111
+ if (!name || typeof name !== "string" || name.trim() === "") {
112
+ reportRuntimeError(this.runtime?.listener, APIErrorCode.E7063, {
113
+ key: String(name)
114
+ });
115
+ return;
116
+ }
117
+ this.cache.delete(name);
118
+ this.pendingWrites.delete(name);
119
+ if (typeof localStorage !== "undefined") {
120
+ try {
121
+ localStorage.removeItem(`${this.namespace}.${name}`);
122
+ } catch (err) {
123
+ reportRuntimeError(this.runtime?.listener, APIErrorCode.E7062, {
124
+ error: `Delete operation failed: ${String(err)}`
125
+ });
126
+ }
127
+ }
128
+ }
129
+ /**
130
+ * Clear all storage for this namespace
131
+ */
132
+ clear() {
133
+ if (typeof localStorage === "undefined") {
134
+ return;
135
+ }
136
+ const prefix = `${this.namespace}.`;
137
+ const keysToRemove = [];
138
+ for (let i = 0; i < localStorage.length; i++) {
139
+ const key = localStorage.key(i);
140
+ if (key && key.startsWith(prefix)) {
141
+ keysToRemove.push(key);
142
+ }
143
+ }
144
+ for (const key of keysToRemove) {
145
+ localStorage.removeItem(key);
146
+ }
147
+ this.cache.clear();
148
+ this.pendingWrites.clear();
149
+ }
150
+ /**
151
+ * Sanitize value for JSON serialization
152
+ * Removes functions and handles circular references
153
+ */
154
+ sanitize(value, seen = /* @__PURE__ */ new WeakSet()) {
155
+ if (value === null || value === void 0) {
156
+ return value;
157
+ }
158
+ if (typeof value !== "object") {
159
+ if (typeof value === "function") {
160
+ return void 0;
161
+ }
162
+ return value;
163
+ }
164
+ if (seen.has(value)) {
165
+ return void 0;
166
+ }
167
+ seen.add(value);
168
+ if (Array.isArray(value)) {
169
+ return value.map((item) => this.sanitize(item, seen)).filter((item) => item !== void 0);
170
+ }
171
+ const result = {};
172
+ for (const key in value) {
173
+ if (Object.hasOwn(value, key)) {
174
+ const sanitized = this.sanitize(value[key], seen);
175
+ if (sanitized !== void 0) {
176
+ result[key] = sanitized;
177
+ }
178
+ }
179
+ }
180
+ return result;
181
+ }
182
+ interfaceCache = null;
183
+ /**
184
+ * Get storage interface for game code
185
+ */
186
+ getInterface() {
187
+ if (this.interfaceCache) {
188
+ return this.interfaceCache;
189
+ }
190
+ this.interfaceCache = {
191
+ set: /* @__PURE__ */ __name((name, value) => this.set(name, value), "set"),
192
+ get: /* @__PURE__ */ __name((name) => this.get(name), "get"),
193
+ delete: /* @__PURE__ */ __name((name) => this.delete(name), "delete")
194
+ };
195
+ return this.interfaceCache;
196
+ }
197
+ };
198
+
199
+ export { StorageService };
200
+ //# sourceMappingURL=index.js.map
201
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/storage/index.ts"],"names":["StorageService","namespace","cache","Map","pendingWrites","writeTimer","runtime","preserve","localStorage","clear","get","name","trim","reportRuntimeError","listener","APIErrorCode","E7063","key","String","has","value","getItem","parsed","JSON","parse","set","err","E7062","error","schedule","window","setTimeout","bind","flush","clearTimeout","serialized","stringify","sanitize","setItem","code","E7061","check","size","delete","removeItem","prefix","keysToRemove","i","length","startsWith","push","seen","WeakSet","undefined","add","Array","isArray","map","item","filter","result","Object","hasOwn","sanitized","interfaceCache","getInterface"],"mappings":";;;;AAMO,IAAMA,iBAAN,MAAMA;EANb;;;AAOSC,EAAAA,SAAAA;AACAC,EAAAA,KAAAA,uBAA8BC,GAAAA,EAAAA;AAC9BC,EAAAA,aAAAA,uBAAsCD,GAAAA,EAAAA;EACtCE,UAAAA,GAAmD,IAAA;AACnDC,EAAAA,OAAAA;AAER,EAAA,WAAA,CAAYL,SAAAA,GAAY,MAAA,EAAQM,QAAAA,GAAW,KAAA,EAAOD,OAAAA,EAAe;AAChE,IAAA,IAAA,CAAKL,SAAAA,GAAYA,SAAAA;AACjB,IAAA,IAAA,CAAKK,OAAAA,GAAUA,OAAAA;AAGf,IAAA,IAAI,CAACC,QAAAA,IAAY,OAAOC,YAAAA,KAAiB,WAAA,EAAa;AACrD,MAAA,IAAA,CAAKC,KAAAA,EAAK;AACX,IAAA;AACD,EAAA;;;;AAKAC,EAAAA,GAAAA,CAAIC,IAAAA,EAAmB;AAEtB,IAAA,IAAI,CAACA,QAAQ,OAAOA,IAAAA,KAAS,YAAYA,IAAAA,CAAKC,IAAAA,OAAW,EAAA,EAAI;AAC5DC,MAAAA,kBAAAA,CAAmB,IAAA,CAAKP,OAAAA,EAASQ,QAAAA,EAAUC,YAAAA,CAAaC,KAAAA,EAAO;AAAEC,QAAAA,GAAAA,EAAKC,OAAOP,IAAAA;OAAM,CAAA;AACnF,MAAA,OAAO,IAAA;AACR,IAAA;AAGA,IAAA,IAAI,IAAA,CAAKT,KAAAA,CAAMiB,GAAAA,CAAIR,IAAAA,CAAAA,EAAO;AACzB,MAAA,OAAO,IAAA,CAAKT,KAAAA,CAAMQ,GAAAA,CAAIC,IAAAA,CAAAA;AACvB,IAAA;AAGA,IAAA,IAAI,OAAOH,iBAAiB,WAAA,EAAa;AACxC,MAAA,IAAI;AACH,QAAA,MAAMS,GAAAA,GAAM,CAAA,EAAG,IAAA,CAAKhB,SAAS,IAAIU,IAAAA,CAAAA,CAAAA;AACjC,QAAA,MAAMS,KAAAA,GAAQZ,YAAAA,CAAaa,OAAAA,CAAQJ,GAAAA,CAAAA;AACnC,QAAA,IAAIG,UAAU,IAAA,EAAM;AACnB,UAAA,MAAME,MAAAA,GAASC,IAAAA,CAAKC,KAAAA,CAAMJ,KAAAA,CAAAA;AAC1B,UAAA,IAAA,CAAKlB,KAAAA,CAAMuB,GAAAA,CAAId,IAAAA,EAAMW,MAAAA,CAAAA;AACrB,UAAA,OAAOA,MAAAA;AACR,QAAA;AACD,MAAA,CAAA,CAAA,OAASI,GAAAA,EAAU;AAClBb,QAAAA,kBAAAA,CAAmB,IAAA,CAAKP,OAAAA,EAASQ,QAAAA,EAAUC,YAAAA,CAAaY,KAAAA,EAAO;UAAEC,KAAAA,EAAO,CAAA,sBAAA,EAAyBV,MAAAA,CAAOQ,GAAAA,CAAAA,CAAAA;SAAO,CAAA;AAChH,MAAA;AACD,IAAA;AAEA,IAAA,OAAO,IAAA;AACR,EAAA;;;;AAKAD,EAAAA,GAAAA,CAAId,MAAcS,KAAAA,EAAkB;AAEnC,IAAA,IAAI,CAACT,QAAQ,OAAOA,IAAAA,KAAS,YAAYA,IAAAA,CAAKC,IAAAA,OAAW,EAAA,EAAI;AAC5DC,MAAAA,kBAAAA,CAAmB,IAAA,CAAKP,OAAAA,EAASQ,QAAAA,EAAUC,YAAAA,CAAaC,KAAAA,EAAO;AAAEC,QAAAA,GAAAA,EAAKC,OAAOP,IAAAA;OAAM,CAAA;AACnF,MAAA;AACD,IAAA;AAGA,IAAA,IAAA,CAAKT,KAAAA,CAAMuB,GAAAA,CAAId,IAAAA,EAAMS,KAAAA,CAAAA;AAGrB,IAAA,IAAA,CAAKhB,aAAAA,CAAcqB,GAAAA,CAAId,IAAAA,EAAMS,KAAAA,CAAAA;AAG7B,IAAA,IAAI,IAAA,CAAKf,eAAe,IAAA,EAAM;AAC7B,MAAA,MAAMwB,QAAAA,GACL,OAAOC,MAAAA,KAAW,WAAA,IAAe,OAAOA,MAAAA,CAAOC,UAAAA,KAAe,UAAA,GAC3DD,MAAAA,CAAOC,UAAAA,CAAWC,IAAAA,CAAKF,MAAAA,CAAAA,GACvBC,UAAAA;AAEJ,MAAA,IAAA,CAAK1B,UAAAA,GAAawB,SAAS,MAAA;AAC1B,QAAA,IAAA,CAAKI,KAAAA,EAAK;AACX,MAAA,CAAA,EAAG,GAAA,CAAA;AACJ,IAAA;AACD,EAAA;;;;EAKAA,KAAAA,GAAc;AACb,IAAA,IAAI,IAAA,CAAK5B,eAAe,IAAA,EAAM;AAC7B6B,MAAAA,YAAAA,CAAa,KAAK7B,UAAU,CAAA;AAC5B,MAAA,IAAA,CAAKA,UAAAA,GAAa,IAAA;AACnB,IAAA;AAEA,IAAA,IAAI,OAAOG,iBAAiB,WAAA,EAAa;AACxC,MAAA,IAAA,CAAKJ,cAAcK,KAAAA,EAAK;AACxB,MAAA;AACD,IAAA;AAEA,IAAA,KAAA,MAAW,CAACE,IAAAA,EAAMS,KAAAA,CAAAA,IAAU,KAAKhB,aAAAA,EAAe;AAC/C,MAAA,IAAI;AACH,QAAA,MAAMa,GAAAA,GAAM,CAAA,EAAG,IAAA,CAAKhB,SAAS,IAAIU,IAAAA,CAAAA,CAAAA;AACjC,QAAA,MAAMwB,aAAaZ,IAAAA,CAAKa,SAAAA,CAAU,IAAA,CAAKC,QAAAA,CAASjB,KAAAA,CAAAA,CAAAA;AAChDZ,QAAAA,YAAAA,CAAa8B,OAAAA,CAAQrB,KAAKkB,UAAAA,CAAAA;AAC3B,MAAA,CAAA,CAAA,OAAST,GAAAA,EAAU;AAElB,QAAA,IAAIA,GAAAA,CAAIf,IAAAA,KAAS,oBAAA,IAAwBe,GAAAA,CAAIa,SAAS,EAAA,EAAI;AACzD1B,UAAAA,kBAAAA,CAAmB,KAAKP,OAAAA,EAASQ,QAAAA,EAAUC,YAAAA,CAAayB,KAAAA,EAAO,EAAC,CAAA;QACjE,CAAA,MAAO;AACN3B,UAAAA,kBAAAA,CAAmB,IAAA,CAAKP,OAAAA,EAASQ,QAAAA,EAAUC,YAAAA,CAAaY,KAAAA,EAAO;YAAEC,KAAAA,EAAO,CAAA,sBAAA,EAAyBV,MAAAA,CAAOQ,GAAAA,CAAAA,CAAAA;WAAO,CAAA;AAChH,QAAA;AACD,MAAA;AACD,IAAA;AAEA,IAAA,IAAA,CAAKtB,cAAcK,KAAAA,EAAK;AACzB,EAAA;;;;EAKAgC,KAAAA,GAAc;AACb,IAAA,IAAI,IAAA,CAAKrC,aAAAA,CAAcsC,IAAAA,GAAO,CAAA,EAAG;AAChC,MAAA,IAAA,CAAKT,KAAAA,EAAK;AACX,IAAA;AACD,EAAA;;;;AAKAU,EAAAA,MAAAA,CAAOhC,IAAAA,EAAoB;AAC1B,IAAA,IAAI,CAACA,QAAQ,OAAOA,IAAAA,KAAS,YAAYA,IAAAA,CAAKC,IAAAA,OAAW,EAAA,EAAI;AAC5DC,MAAAA,kBAAAA,CAAmB,IAAA,CAAKP,OAAAA,EAASQ,QAAAA,EAAUC,YAAAA,CAAaC,KAAAA,EAAO;AAAEC,QAAAA,GAAAA,EAAKC,OAAOP,IAAAA;OAAM,CAAA;AACnF,MAAA;AACD,IAAA;AAGA,IAAA,IAAA,CAAKT,KAAAA,CAAMyC,OAAOhC,IAAAA,CAAAA;AAClB,IAAA,IAAA,CAAKP,aAAAA,CAAcuC,OAAOhC,IAAAA,CAAAA;AAG1B,IAAA,IAAI,OAAOH,iBAAiB,WAAA,EAAa;AACxC,MAAA,IAAI;AACHA,QAAAA,YAAAA,CAAaoC,WAAW,CAAA,EAAG,IAAA,CAAK3C,SAAS,CAAA,CAAA,EAAIU,IAAAA,CAAAA,CAAM,CAAA;AACpD,MAAA,CAAA,CAAA,OAASe,GAAAA,EAAU;AAClBb,QAAAA,kBAAAA,CAAmB,IAAA,CAAKP,OAAAA,EAASQ,QAAAA,EAAUC,YAAAA,CAAaY,KAAAA,EAAO;UAC9DC,KAAAA,EAAO,CAAA,yBAAA,EAA4BV,MAAAA,CAAOQ,GAAAA,CAAAA,CAAAA;SAC3C,CAAA;AACD,MAAA;AACD,IAAA;AACD,EAAA;;;;EAKAjB,KAAAA,GAAc;AACb,IAAA,IAAI,OAAOD,iBAAiB,WAAA,EAAa;AACxC,MAAA;AACD,IAAA;AAEA,IAAA,MAAMqC,MAAAA,GAAS,CAAA,EAAG,IAAA,CAAK5C,SAAS,CAAA,CAAA,CAAA;AAChC,IAAA,MAAM6C,eAAyB,EAAA;AAG/B,IAAA,KAAA,IAASC,CAAAA,GAAI,CAAA,EAAGA,CAAAA,GAAIvC,YAAAA,CAAawC,QAAQD,CAAAA,EAAAA,EAAK;AAC7C,MAAA,MAAM9B,GAAAA,GAAMT,YAAAA,CAAaS,GAAAA,CAAI8B,CAAAA,CAAAA;AAC7B,MAAA,IAAI9B,GAAAA,IAAOA,GAAAA,CAAIgC,UAAAA,CAAWJ,MAAAA,CAAAA,EAAS;AAClCC,QAAAA,YAAAA,CAAaI,KAAKjC,GAAAA,CAAAA;AACnB,MAAA;AACD,IAAA;AAGA,IAAA,KAAA,MAAWA,OAAO6B,YAAAA,EAAc;AAC/BtC,MAAAA,YAAAA,CAAaoC,WAAW3B,GAAAA,CAAAA;AACzB,IAAA;AAGA,IAAA,IAAA,CAAKf,MAAMO,KAAAA,EAAK;AAChB,IAAA,IAAA,CAAKL,cAAcK,KAAAA,EAAK;AACzB,EAAA;;;;;AAMQ4B,EAAAA,QAAAA,CAASjB,KAAAA,EAAY+B,IAAAA,mBAAO,IAAIC,OAAAA,EAAAA,EAAgB;AACvD,IAAA,IAAIhC,KAAAA,KAAU,IAAA,IAAQA,KAAAA,KAAUiC,MAAAA,EAAW;AAC1C,MAAA,OAAOjC,KAAAA;AACR,IAAA;AAGA,IAAA,IAAI,OAAOA,UAAU,QAAA,EAAU;AAE9B,MAAA,IAAI,OAAOA,UAAU,UAAA,EAAY;AAChC,QAAA,OAAOiC,MAAAA;AACR,MAAA;AACA,MAAA,OAAOjC,KAAAA;AACR,IAAA;AAGA,IAAA,IAAI+B,IAAAA,CAAKhC,GAAAA,CAAIC,KAAAA,CAAAA,EAAQ;AACpB,MAAA,OAAOiC,MAAAA;AACR,IAAA;AACAF,IAAAA,IAAAA,CAAKG,IAAIlC,KAAAA,CAAAA;AAGT,IAAA,IAAImC,KAAAA,CAAMC,OAAAA,CAAQpC,KAAAA,CAAAA,EAAQ;AACzB,MAAA,OAAOA,KAAAA,CAAMqC,GAAAA,CAAI,CAACC,IAAAA,KAAS,KAAKrB,QAAAA,CAASqB,IAAAA,EAAMP,IAAAA,CAAAA,CAAAA,CAAOQ,MAAAA,CAAO,CAACD,IAAAA,KAASA,SAASL,MAAAA,CAAAA;AACjF,IAAA;AAGA,IAAA,MAAMO,SAAc,EAAC;AACrB,IAAA,KAAA,MAAW3C,OAAOG,KAAAA,EAAO;AACxB,MAAA,IAAIyC,MAAAA,CAAOC,MAAAA,CAAO1C,KAAAA,EAAOH,GAAAA,CAAAA,EAAM;AAC9B,QAAA,MAAM8C,YAAY,IAAA,CAAK1B,QAAAA,CAASjB,KAAAA,CAAMH,GAAAA,GAAMkC,IAAAA,CAAAA;AAC5C,QAAA,IAAIY,cAAcV,MAAAA,EAAW;AAC5BO,UAAAA,MAAAA,CAAO3C,GAAAA,CAAAA,GAAO8C,SAAAA;AACf,QAAA;AACD,MAAA;AACD,IAAA;AACA,IAAA,OAAOH,MAAAA;AACR,EAAA;EAEQI,cAAAA,GAIG,IAAA;;;;EAKXC,YAAAA,GAAe;AACd,IAAA,IAAI,KAAKD,cAAAA,EAAgB;AACxB,MAAA,OAAO,IAAA,CAAKA,cAAAA;AACb,IAAA;AACA,IAAA,IAAA,CAAKA,cAAAA,GAAiB;AACrBvC,MAAAA,GAAAA,0BAAMd,IAAAA,EAAcS,KAAAA,KAAe,KAAKK,GAAAA,CAAId,IAAAA,EAAMS,KAAAA,CAAAA,EAA7C,KAAA,CAAA;AACLV,MAAAA,GAAAA,kBAAK,MAAA,CAAA,CAACC,IAAAA,KAAiB,IAAA,CAAKD,GAAAA,CAAIC,IAAAA,CAAAA,EAA3B,KAAA,CAAA;AACLgC,MAAAA,MAAAA,kBAAQ,MAAA,CAAA,CAAChC,IAAAA,KAAiB,IAAA,CAAKgC,MAAAA,CAAOhC,IAAAA,CAAAA,EAA9B,QAAA;AACT,KAAA;AACA,IAAA,OAAO,IAAA,CAAKqD,cAAAA;AACb,EAAA;AACD","file":"index.js","sourcesContent":["/**\n * Storage service - localStorage wrapper with automatic serialization\n */\n\nimport { APIErrorCode, reportRuntimeError } from \"@al8b/diagnostics\";\n\nexport class StorageService {\n\tprivate namespace: string;\n\tprivate cache: Map<string, any> = new Map();\n\tprivate pendingWrites: Map<string, any> = new Map();\n\tprivate writeTimer: ReturnType<typeof setTimeout> | null = null;\n\tprivate runtime?: any;\n\n\tconstructor(namespace = \"/l8b\", preserve = false, runtime?: any) {\n\t\tthis.namespace = namespace;\n\t\tthis.runtime = runtime;\n\n\t\t// Clear storage if not preserving\n\t\tif (!preserve && typeof localStorage !== \"undefined\") {\n\t\t\tthis.clear();\n\t\t}\n\t}\n\n\t/**\n\t * Get value from storage\n\t */\n\tget(name: string): any {\n\t\t// Validate storage key\n\t\tif (!name || typeof name !== \"string\" || name.trim() === \"\") {\n\t\t\treportRuntimeError(this.runtime?.listener, APIErrorCode.E7063, { key: String(name) });\n\t\t\treturn null;\n\t\t}\n\n\t\t// Check cache first\n\t\tif (this.cache.has(name)) {\n\t\t\treturn this.cache.get(name);\n\t\t}\n\n\t\t// Try localStorage\n\t\tif (typeof localStorage !== \"undefined\") {\n\t\t\ttry {\n\t\t\t\tconst key = `${this.namespace}.${name}`;\n\t\t\t\tconst value = localStorage.getItem(key);\n\t\t\t\tif (value !== null) {\n\t\t\t\t\tconst parsed = JSON.parse(value);\n\t\t\t\t\tthis.cache.set(name, parsed);\n\t\t\t\t\treturn parsed;\n\t\t\t\t}\n\t\t\t} catch (err: any) {\n\t\t\t\treportRuntimeError(this.runtime?.listener, APIErrorCode.E7062, { error: `Get operation failed: ${String(err)}` });\n\t\t\t}\n\t\t}\n\n\t\treturn null;\n\t}\n\n\t/**\n\t * Set value in storage (batched write)\n\t */\n\tset(name: string, value: any): void {\n\t\t// Validate storage key\n\t\tif (!name || typeof name !== \"string\" || name.trim() === \"\") {\n\t\t\treportRuntimeError(this.runtime?.listener, APIErrorCode.E7063, { key: String(name) });\n\t\t\treturn;\n\t\t}\n\n\t\t// Update cache\n\t\tthis.cache.set(name, value);\n\n\t\t// Queue write\n\t\tthis.pendingWrites.set(name, value);\n\n\t\t// Schedule batch write\n\t\tif (this.writeTimer === null) {\n\t\t\tconst schedule =\n\t\t\t\ttypeof window !== \"undefined\" && typeof window.setTimeout === \"function\"\n\t\t\t\t\t? window.setTimeout.bind(window)\n\t\t\t\t\t: setTimeout;\n\n\t\t\tthis.writeTimer = schedule(() => {\n\t\t\t\tthis.flush();\n\t\t\t}, 100);\n\t\t}\n\t}\n\n\t/**\n\t * Flush pending writes to localStorage\n\t */\n\tflush(): void {\n\t\tif (this.writeTimer !== null) {\n\t\t\tclearTimeout(this.writeTimer);\n\t\t\tthis.writeTimer = null;\n\t\t}\n\n\t\tif (typeof localStorage === \"undefined\") {\n\t\t\tthis.pendingWrites.clear();\n\t\t\treturn;\n\t\t}\n\n\t\tfor (const [name, value] of this.pendingWrites) {\n\t\t\ttry {\n\t\t\t\tconst key = `${this.namespace}.${name}`;\n\t\t\t\tconst serialized = JSON.stringify(this.sanitize(value));\n\t\t\t\tlocalStorage.setItem(key, serialized);\n\t\t\t} catch (err: any) {\n\t\t\t\t// Check for quota exceeded error\n\t\t\t\tif (err.name === \"QuotaExceededError\" || err.code === 22) {\n\t\t\t\t\treportRuntimeError(this.runtime?.listener, APIErrorCode.E7061, {});\n\t\t\t\t} else {\n\t\t\t\t\treportRuntimeError(this.runtime?.listener, APIErrorCode.E7062, { error: `Set operation failed: ${String(err)}` });\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tthis.pendingWrites.clear();\n\t}\n\n\t/**\n\t * Check if there are pending writes and flush if needed\n\t */\n\tcheck(): void {\n\t\tif (this.pendingWrites.size > 0) {\n\t\t\tthis.flush();\n\t\t}\n\t}\n\n\t/**\n\t * Delete a single key from storage (cache + localStorage + any pending write)\n\t */\n\tdelete(name: string): void {\n\t\tif (!name || typeof name !== \"string\" || name.trim() === \"\") {\n\t\t\treportRuntimeError(this.runtime?.listener, APIErrorCode.E7063, { key: String(name) });\n\t\t\treturn;\n\t\t}\n\n\t\t// Remove from in-memory state\n\t\tthis.cache.delete(name);\n\t\tthis.pendingWrites.delete(name);\n\n\t\t// Remove from localStorage\n\t\tif (typeof localStorage !== \"undefined\") {\n\t\t\ttry {\n\t\t\t\tlocalStorage.removeItem(`${this.namespace}.${name}`);\n\t\t\t} catch (err: any) {\n\t\t\t\treportRuntimeError(this.runtime?.listener, APIErrorCode.E7062, {\n\t\t\t\t\terror: `Delete operation failed: ${String(err)}`,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Clear all storage for this namespace\n\t */\n\tclear(): void {\n\t\tif (typeof localStorage === \"undefined\") {\n\t\t\treturn;\n\t\t}\n\n\t\tconst prefix = `${this.namespace}.`;\n\t\tconst keysToRemove: string[] = [];\n\n\t\t// Find all keys with this namespace\n\t\tfor (let i = 0; i < localStorage.length; i++) {\n\t\t\tconst key = localStorage.key(i);\n\t\t\tif (key && key.startsWith(prefix)) {\n\t\t\t\tkeysToRemove.push(key);\n\t\t\t}\n\t\t}\n\n\t\t// Remove them\n\t\tfor (const key of keysToRemove) {\n\t\t\tlocalStorage.removeItem(key);\n\t\t}\n\n\t\t// Clear cache\n\t\tthis.cache.clear();\n\t\tthis.pendingWrites.clear();\n\t}\n\n\t/**\n\t * Sanitize value for JSON serialization\n\t * Removes functions and handles circular references\n\t */\n\tprivate sanitize(value: any, seen = new WeakSet()): any {\n\t\tif (value === null || value === undefined) {\n\t\t\treturn value;\n\t\t}\n\n\t\t// Primitives\n\t\tif (typeof value !== \"object\") {\n\t\t\t// Remove functions\n\t\t\tif (typeof value === \"function\") {\n\t\t\t\treturn undefined;\n\t\t\t}\n\t\t\treturn value;\n\t\t}\n\n\t\t// Check for circular reference\n\t\tif (seen.has(value)) {\n\t\t\treturn undefined;\n\t\t}\n\t\tseen.add(value);\n\n\t\t// Arrays\n\t\tif (Array.isArray(value)) {\n\t\t\treturn value.map((item) => this.sanitize(item, seen)).filter((item) => item !== undefined);\n\t\t}\n\n\t\t// Objects\n\t\tconst result: any = {};\n\t\tfor (const key in value) {\n\t\t\tif (Object.hasOwn(value, key)) {\n\t\t\t\tconst sanitized = this.sanitize(value[key], seen);\n\t\t\t\tif (sanitized !== undefined) {\n\t\t\t\t\tresult[key] = sanitized;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn result;\n\t}\n\n\tprivate interfaceCache: {\n\t\tset: (name: string, value: unknown) => void;\n\t\tget: (name: string) => unknown;\n\t\tdelete: (name: string) => void;\n\t} | null = null;\n\n\t/**\n\t * Get storage interface for game code\n\t */\n\tgetInterface() {\n\t\tif (this.interfaceCache) {\n\t\t\treturn this.interfaceCache;\n\t\t}\n\t\tthis.interfaceCache = {\n\t\t\tset: (name: string, value: any) => this.set(name, value),\n\t\t\tget: (name: string) => this.get(name),\n\t\t\tdelete: (name: string) => this.delete(name),\n\t\t};\n\t\treturn this.interfaceCache;\n\t}\n}\n"]}
package/package.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "@al8b/io",
3
+ "version": "0.1.0",
4
+ "description": "IO and Storage utilities for L8B Engine",
5
+ "type": "module",
6
+ "files": [
7
+ "dist/**/*",
8
+ "package.json",
9
+ "README.md"
10
+ ],
11
+ "exports": {
12
+ ".": "./dist/index.js"
13
+ },
14
+ "scripts": {
15
+ "build": "tsup",
16
+ "clean": "bun --bun ../../../scripts/clean-package.mjs dist",
17
+ "test": "vitest run --passWithNoTests",
18
+ "typecheck": "tsc --noEmit"
19
+ },
20
+ "dependencies": {
21
+ "@al8b/diagnostics": "workspace:*"
22
+ },
23
+ "devDependencies": {
24
+ "tsup": "^8.0.0",
25
+ "typescript": "^5.9.3"
26
+ },
27
+ "publishConfig": {
28
+ "access": "public"
29
+ }
30
+ }