@biglogic/rgs 3.7.3 → 3.7.8

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 (51) hide show
  1. package/COPYRIGHT.md +4 -0
  2. package/FUNDING.yml +12 -0
  3. package/LICENSE.md +9 -9
  4. package/README.md +470 -470
  5. package/SECURITY.md +13 -0
  6. package/advanced.d.ts +9 -0
  7. package/core/advanced.d.ts +5 -0
  8. package/core/async.d.ts +8 -0
  9. package/core/env.d.ts +4 -0
  10. package/core/hooks.d.ts +17 -0
  11. package/core/minimal.d.ts +8 -0
  12. package/core/minimal.js +19 -0
  13. package/core/persistence.d.ts +23 -0
  14. package/core/plugins.d.ts +8 -0
  15. package/core/reactivity.d.ts +19 -0
  16. package/core/security.d.ts +56 -0
  17. package/core/store.d.ts +7 -0
  18. package/core/sync.d.ts +76 -0
  19. package/core/types.d.ts +164 -0
  20. package/core/utils.d.ts +2 -0
  21. package/docs/README.md +470 -0
  22. package/docs/SUMMARY.md +64 -0
  23. package/docs/_config.yml +1 -0
  24. package/docs/api.md +381 -0
  25. package/docs/chapters/01-philosophy.md +54 -0
  26. package/docs/chapters/02-getting-started.md +68 -0
  27. package/docs/chapters/03-the-magnetar-way.md +69 -0
  28. package/docs/chapters/04-persistence-and-safety.md +125 -0
  29. package/docs/chapters/05-plugin-sdk.md +290 -0
  30. package/docs/chapters/05-plugins-and-extensibility.md +190 -0
  31. package/docs/chapters/06-case-studies.md +69 -0
  32. package/docs/chapters/07-faq.md +53 -0
  33. package/docs/chapters/08-migration-guide.md +284 -0
  34. package/docs/chapters/09-security-architecture.md +50 -0
  35. package/docs/chapters/10-local-first-sync.md +146 -0
  36. package/docs/qa.md +47 -0
  37. package/index.d.ts +41 -0
  38. package/index.js +2100 -0
  39. package/package.json +13 -22
  40. package/plugins/index.d.ts +15 -0
  41. package/plugins/official/analytics.plugin.d.ts +9 -0
  42. package/plugins/official/cloud-sync.plugin.d.ts +22 -0
  43. package/plugins/official/debug.plugin.d.ts +2 -0
  44. package/plugins/official/devtools.plugin.d.ts +4 -0
  45. package/plugins/official/guard.plugin.d.ts +2 -0
  46. package/plugins/official/immer.plugin.d.ts +2 -0
  47. package/plugins/official/indexeddb.plugin.d.ts +7 -0
  48. package/plugins/official/schema.plugin.d.ts +2 -0
  49. package/plugins/official/snapshot.plugin.d.ts +2 -0
  50. package/plugins/official/sync.plugin.d.ts +4 -0
  51. package/plugins/official/undo-redo.plugin.d.ts +4 -0
package/index.js ADDED
@@ -0,0 +1,2100 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
+
5
+ // core/store.ts
6
+ import { produce as _immerProduce2, freeze as _immerFreeze2 } from "immer";
7
+
8
+ // core/security.ts
9
+ var REGEX_TIMEOUT_MS = 100;
10
+ var safeRegexTest = (pattern, key) => {
11
+ const startTime = Date.now();
12
+ if (/\(\.*\+\?\)\+/.test(pattern) || /\(\.*\?\)\*/.test(pattern)) {
13
+ console.warn(`[gstate] Potentially dangerous regex pattern blocked: ${pattern}`);
14
+ return false;
15
+ }
16
+ if (pattern.length > 500) {
17
+ console.warn(`[gstate] Regex pattern exceeds maximum length limit`);
18
+ return false;
19
+ }
20
+ try {
21
+ const regex = new RegExp(pattern);
22
+ const result = regex.test(key);
23
+ const elapsed = Date.now() - startTime;
24
+ if (elapsed > REGEX_TIMEOUT_MS) {
25
+ console.warn(`[gstate] Slow regex detected (${elapsed}ms) for pattern: ${pattern}`);
26
+ }
27
+ return result;
28
+ } catch {
29
+ return false;
30
+ }
31
+ };
32
+ var safeRandomUUID = () => {
33
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
34
+ try {
35
+ return crypto.randomUUID();
36
+ } catch {
37
+ }
38
+ }
39
+ throw new Error("Cryptographically secure random UUID generation is required but crypto.randomUUID is unavailable. Please use a browser or environment with Web Crypto API support.");
40
+ };
41
+ var isCryptoAvailable = typeof crypto !== "undefined" && typeof crypto.subtle !== "undefined" && typeof crypto.subtle.generateKey === "function";
42
+ var deriveKeyFromPassword = async (password, salt, iterations = 1e5) => {
43
+ if (!isCryptoAvailable) throw new Error("Web Crypto API not available");
44
+ const passwordKey = await crypto.subtle.importKey(
45
+ "raw",
46
+ new TextEncoder().encode(password),
47
+ "PBKDF2",
48
+ false,
49
+ ["deriveKey"]
50
+ );
51
+ const key = await crypto.subtle.deriveKey(
52
+ {
53
+ name: "PBKDF2",
54
+ salt: new Uint8Array(salt),
55
+ iterations,
56
+ hash: "SHA-256"
57
+ },
58
+ passwordKey,
59
+ { name: "AES-GCM", length: 256 },
60
+ true,
61
+ ["encrypt", "decrypt"]
62
+ );
63
+ const iv = crypto.getRandomValues(new Uint8Array(12));
64
+ return { key, iv };
65
+ };
66
+ var generateSalt = (length = 16) => {
67
+ return crypto.getRandomValues(new Uint8Array(length));
68
+ };
69
+ var generateEncryptionKey = async () => {
70
+ if (!isCryptoAvailable) throw new Error("Web Crypto API not available");
71
+ const key = await crypto.subtle.generateKey(
72
+ { name: "AES-GCM", length: 256 },
73
+ true,
74
+ ["encrypt", "decrypt"]
75
+ ), iv = crypto.getRandomValues(new Uint8Array(12));
76
+ return { key, iv };
77
+ };
78
+ var exportKey = async (encryptionKey) => {
79
+ const exportedKey = await crypto.subtle.exportKey("raw", encryptionKey.key);
80
+ return {
81
+ key: btoa(String.fromCharCode(...new Uint8Array(exportedKey))),
82
+ iv: btoa(String.fromCharCode(...encryptionKey.iv))
83
+ };
84
+ };
85
+ var importKey = async (keyData, ivData) => {
86
+ const keyBytes = Uint8Array.from(atob(keyData), (c) => c.charCodeAt(0)), ivBytes = Uint8Array.from(atob(ivData), (c) => c.charCodeAt(0)), key = await crypto.subtle.importKey(
87
+ "raw",
88
+ keyBytes,
89
+ { name: "AES-GCM", length: 256 },
90
+ true,
91
+ ["encrypt", "decrypt"]
92
+ );
93
+ return { key, iv: ivBytes };
94
+ };
95
+ var encrypt = async (data, encryptionKey) => {
96
+ const encoder = new TextEncoder(), encoded = encoder.encode(JSON.stringify(data)), encrypted = await crypto.subtle.encrypt(
97
+ { name: "AES-GCM", iv: encryptionKey.iv },
98
+ encryptionKey.key,
99
+ encoded
100
+ ), combined = new Uint8Array(encryptionKey.iv.length + encrypted.byteLength);
101
+ combined.set(encryptionKey.iv);
102
+ combined.set(new Uint8Array(encrypted), encryptionKey.iv.length);
103
+ return btoa(String.fromCharCode(...combined));
104
+ };
105
+ var decrypt = async (encryptedData, encryptionKey) => {
106
+ const combined = Uint8Array.from(atob(encryptedData), (c) => c.charCodeAt(0)), iv = combined.slice(0, 12), ciphertext = combined.slice(12), decrypted = await crypto.subtle.decrypt(
107
+ { name: "AES-GCM", iv },
108
+ encryptionKey.key,
109
+ ciphertext
110
+ );
111
+ return JSON.parse(new TextDecoder().decode(decrypted));
112
+ };
113
+ var _auditLogger = null;
114
+ var setAuditLogger = (logger) => {
115
+ _auditLogger = logger;
116
+ };
117
+ var isAuditActive = () => _auditLogger !== null;
118
+ var logAudit = (entry) => {
119
+ if (_auditLogger) _auditLogger(entry);
120
+ };
121
+ var addAccessRule = (rules, pattern, perms) => {
122
+ rules.set(pattern instanceof RegExp ? pattern.source : pattern, perms);
123
+ };
124
+ var hasPermission = (rules, key, action, _userId) => {
125
+ if (rules.size === 0) return true;
126
+ for (const [pattern, perms] of rules) {
127
+ let matches;
128
+ if (typeof pattern === "function") {
129
+ matches = pattern(key, _userId);
130
+ } else {
131
+ matches = safeRegexTest(pattern, key);
132
+ }
133
+ if (matches) {
134
+ return perms.includes(action) || perms.includes("admin");
135
+ }
136
+ }
137
+ return false;
138
+ };
139
+ var sanitizeValue = (value) => {
140
+ if (typeof value === "string") {
141
+ let decoded = value.replace(/&#[xX]?[0-9a-fA-F]+;?/g, (match) => {
142
+ const hexMatch = match.match(/&#x([0-9a-fA-F]+);?/i);
143
+ if (hexMatch && hexMatch[1]) {
144
+ return String.fromCharCode(parseInt(hexMatch[1], 16));
145
+ }
146
+ const decMatch = match.match(/&#([0-9]+);?/);
147
+ if (decMatch && decMatch[1]) {
148
+ return String.fromCharCode(parseInt(decMatch[1], 10));
149
+ }
150
+ return match;
151
+ });
152
+ try {
153
+ decoded = decodeURIComponent(decoded);
154
+ } catch {
155
+ }
156
+ const schemeCheck = decoded.replace(/\b(javascript|vbscript|data:text\/html|about:blank|chrome:)/gi, "[SEC-REMOVED]");
157
+ return schemeCheck.replace(/<script\b[^>]*>[\s\S]*?<\s*\/\s*script\b[^>]*>/gi, "[SEC-REMOVED]").replace(/on\w+\s*=/gi, "[SEC-REMOVED]=").replace(/<iframe\b[^<]*(?:(?!<\/iframe>)<[^<]*)*<\/iframe>/gi, "[SEC-REMOVED]").replace(/<object\b[^<]*(?:(?!<\/object>)<[^<]*)*<\/object>/gi, "[SEC-REMOVED]").replace(/<embed\b[^<]*(?:(?!<\/embed>)<[^<]*)*<\/embed>/gi, "[SEC-REMOVED]").replace(/<svg\b[^<]*(?:(?!<\/svg>)<[^<]*)*<\/svg>/gi, "[SEC-REMOVED]").replace(/<form\b[^<]*(?:(?!<\/form>)<[^<]*)*<\/form>/gi, "[SEC-REMOVED]").replace(/<base\b[^<]*(?:(?!<\/base>)<[^<]*)*<\/base>/gi, "[SEC-REMOVED]").replace(/<link\b[^<]*(?:(?!<\/link>)<[^<]*)*<\/link>/gi, "[SEC-REMOVED]").replace(/<meta\b[^<]*(?:(?!<\/meta>)<[^<]*)*<\/meta>/gi, "[SEC-REMOVED]").replace(/<style\b[^<]*(?:(?!<\/style>)<[^<]*)*<\/style>/gi, "[SEC-REMOVED]");
158
+ }
159
+ if (value && typeof value === "object" && !Array.isArray(value)) {
160
+ if (Object.getPrototypeOf(value) === Object.prototype) {
161
+ const sanitized = {};
162
+ for (const [k, v] of Object.entries(value)) {
163
+ sanitized[k] = sanitizeValue(v);
164
+ }
165
+ return sanitized;
166
+ }
167
+ return value;
168
+ }
169
+ if (Array.isArray(value)) {
170
+ return value.map((v) => sanitizeValue(v));
171
+ }
172
+ return value;
173
+ };
174
+ var validateKey = (key) => /^[a-zA-Z0-9_.-]+$/.test(key) && key.length <= 256;
175
+ var recordConsent = (consents, userId, purpose, granted) => {
176
+ const record = { id: safeRandomUUID(), purpose, granted, timestamp: Date.now() }, user = consents.get(userId) || [];
177
+ user.push(record);
178
+ consents.set(userId, user);
179
+ logAudit({ timestamp: Date.now(), action: "set", key: `consent:${purpose}`, userId, success: true });
180
+ return record;
181
+ };
182
+ var hasConsent = (consents, userId, purpose) => {
183
+ const userConsents = consents.get(userId);
184
+ if (!userConsents) return false;
185
+ for (let i = userConsents.length - 1; i >= 0; i--) {
186
+ const record = userConsents[i];
187
+ if (record && record.purpose === purpose) {
188
+ return record.granted;
189
+ }
190
+ }
191
+ return false;
192
+ };
193
+ var revokeConsent = (consents, userId, purpose) => {
194
+ return recordConsent(consents, userId, purpose, false);
195
+ };
196
+ var getConsents = (consents, userId) => consents.get(userId) || [];
197
+ var exportUserData = (consents, userId) => ({ userId, exportedAt: Date.now(), consents: consents.get(userId) || [] });
198
+ var deleteUserData = (consents, userId) => {
199
+ const count = consents.get(userId)?.length || 0;
200
+ consents.delete(userId);
201
+ return { success: true, deletedConsents: count };
202
+ };
203
+
204
+ // core/utils.ts
205
+ var deepClone = (obj) => {
206
+ if (obj === null || typeof obj !== "object") return obj;
207
+ if (typeof structuredClone === "function") {
208
+ try {
209
+ return structuredClone(obj);
210
+ } catch (_e) {
211
+ }
212
+ }
213
+ const seen = /* @__PURE__ */ new WeakMap();
214
+ const clone = (value) => {
215
+ if (value === null || typeof value !== "object") return value;
216
+ if (typeof value === "function") return value;
217
+ if (seen.has(value)) return seen.get(value);
218
+ if (value instanceof Date) return new Date(value.getTime());
219
+ if (value instanceof RegExp) return new RegExp(value.source, value.flags);
220
+ if (value instanceof Map) {
221
+ const result2 = /* @__PURE__ */ new Map();
222
+ seen.set(value, result2);
223
+ value.forEach((v, k) => result2.set(clone(k), clone(v)));
224
+ return result2;
225
+ }
226
+ if (value instanceof Set) {
227
+ const result2 = /* @__PURE__ */ new Set();
228
+ seen.set(value, result2);
229
+ value.forEach((v) => result2.add(clone(v)));
230
+ return result2;
231
+ }
232
+ const result = Array.isArray(value) ? [] : Object.create(Object.getPrototypeOf(value));
233
+ seen.set(value, result);
234
+ const keys = [...Object.keys(value), ...Object.getOwnPropertySymbols(value)];
235
+ for (const key of keys) {
236
+ result[key] = clone(value[key]);
237
+ }
238
+ return result;
239
+ };
240
+ return clone(obj);
241
+ };
242
+ var isEqual = (a, b) => {
243
+ if (a === b) return true;
244
+ if (a === null || b === null) return a === b;
245
+ if (typeof a !== "object" || typeof b !== "object") return a === b;
246
+ if (Array.isArray(a) && Array.isArray(b)) {
247
+ if (a.length !== b.length) return false;
248
+ for (let i = 0; i < a.length; i++) if (!isEqual(a[i], b[i])) return false;
249
+ return true;
250
+ }
251
+ const keysA = Object.keys(a);
252
+ const keysB = Object.keys(b);
253
+ if (keysA.length !== keysB.length) return false;
254
+ for (let i = 0; i < keysA.length; i++) {
255
+ const key = keysA[i];
256
+ if (!(key in b) || !isEqual(a[key], b[key])) return false;
257
+ }
258
+ return true;
259
+ };
260
+
261
+ // core/persistence.ts
262
+ import { freeze as _immerFreeze } from "immer";
263
+ var _getPrefix = (namespace) => `${namespace}_`;
264
+ var flushDisk = async (ctx) => {
265
+ if (!ctx.storage) return;
266
+ const { store, config, diskQueue, storage, encryptionKey, audit, onError, silent, currentVersion } = ctx;
267
+ const prefix = _getPrefix(config.namespace || "gstate");
268
+ try {
269
+ const stateObj = {};
270
+ store.forEach((v, k) => {
271
+ stateObj[k] = v;
272
+ });
273
+ let dataValue;
274
+ const isEncoded = config?.encoded;
275
+ if (isEncoded) {
276
+ dataValue = btoa(JSON.stringify(stateObj));
277
+ } else {
278
+ dataValue = JSON.stringify(stateObj);
279
+ }
280
+ storage.setItem(prefix.replace("_", ""), JSON.stringify({
281
+ v: 1,
282
+ t: Date.now(),
283
+ e: null,
284
+ d: dataValue,
285
+ _sys_v: currentVersion,
286
+ _b64: isEncoded ? true : void 0
287
+ }));
288
+ audit("set", "FULL_STATE", true);
289
+ } catch (e) {
290
+ const error = e instanceof Error ? e : new Error(String(e));
291
+ if (onError) onError(error, { operation: "persist", key: "FULL_STATE" });
292
+ else if (!silent) console.error(`[gstate] Persist failed: `, error);
293
+ }
294
+ const queue = Array.from(diskQueue.entries());
295
+ diskQueue.clear();
296
+ for (const [key, data] of queue) {
297
+ try {
298
+ if (!key || !/^[a-zA-Z0-9_.-]+$/.test(key) || key.length > 256) {
299
+ console.warn(`[gstate] Invalid storage key: ${key}`);
300
+ continue;
301
+ }
302
+ let dataValue = data.value;
303
+ const isEncoded = data.options.encoded || data.options.encrypted || data.options.secure;
304
+ if (data.options.encrypted) {
305
+ if (!encryptionKey) throw new Error(`Encryption key missing for "${key}"`);
306
+ dataValue = await encrypt(data.value, encryptionKey);
307
+ } else if (isEncoded) {
308
+ dataValue = btoa(JSON.stringify(data.value));
309
+ } else if (typeof data.value === "object" && data.value !== null) {
310
+ dataValue = JSON.stringify(data.value);
311
+ }
312
+ storage.setItem(`${prefix}${key}`, JSON.stringify({
313
+ v: ctx.versions.get(key) || 1,
314
+ t: Date.now(),
315
+ e: data.options.ttl ? Date.now() + data.options.ttl : null,
316
+ d: dataValue,
317
+ _sys_v: currentVersion,
318
+ _enc: data.options.encrypted ? true : void 0,
319
+ _b64: data.options.encoded || data.options.secure ? true : void 0
320
+ }));
321
+ audit("set", key, true);
322
+ } catch (e) {
323
+ const error = e instanceof Error ? e : new Error(String(e));
324
+ if (onError) onError(error, { operation: "persist", key });
325
+ else if (!silent) console.error(`[gstate] Persist failed: `, error);
326
+ }
327
+ }
328
+ };
329
+ var hydrateStore = async (ctx, calculateSize, emit) => {
330
+ const { storage, config, encryptionKey, audit, onError, silent, currentVersion, store, sizes, versions } = ctx;
331
+ const prefix = _getPrefix(config.namespace || "gstate");
332
+ const immer = config.immer ?? true;
333
+ if (!storage) return;
334
+ try {
335
+ const persisted = {};
336
+ let savedV = 0;
337
+ for (let i = 0; i < (storage.length || 0); i++) {
338
+ const k = storage.key(i);
339
+ if (!k || !k.startsWith(prefix)) continue;
340
+ const raw = storage.getItem(k);
341
+ if (!raw) continue;
342
+ try {
343
+ const meta = JSON.parse(raw), key = k.substring(prefix.length);
344
+ savedV = Math.max(savedV, meta._sys_v !== void 0 ? meta._sys_v : meta.v || 0);
345
+ if (meta.e && Date.now() > meta.e) {
346
+ storage.removeItem(k);
347
+ i--;
348
+ continue;
349
+ }
350
+ let d = meta.d;
351
+ if (meta._enc && encryptionKey) {
352
+ d = await decrypt(d, encryptionKey);
353
+ } else if (typeof d === "string") {
354
+ if (meta._b64) {
355
+ try {
356
+ d = JSON.parse(atob(d));
357
+ } catch (_e) {
358
+ }
359
+ } else if (d.startsWith("{") || d.startsWith("[")) {
360
+ try {
361
+ d = JSON.parse(d);
362
+ } catch (_e) {
363
+ }
364
+ }
365
+ }
366
+ persisted[key] = d;
367
+ audit("hydrate", key, true);
368
+ } catch (err) {
369
+ audit("hydrate", k, false, String(err));
370
+ const error = err instanceof Error ? err : new Error(String(err));
371
+ if (onError) onError(error, { operation: "hydration", key: k });
372
+ else if (!silent) console.error(`[gstate] Hydration failed for "${k}": `, err);
373
+ }
374
+ }
375
+ const final = savedV < currentVersion && config.migrate ? config.migrate(persisted, savedV) : persisted;
376
+ Object.entries(final).forEach(([k, v]) => {
377
+ const frozen = immer && v !== null && typeof v === "object" ? _immerFreeze(deepClone(v), true) : v;
378
+ const size = calculateSize(frozen);
379
+ const oldSize = sizes.get(k) || 0;
380
+ ctx.totalSize = ctx.totalSize - oldSize + size;
381
+ sizes.set(k, size);
382
+ store.set(k, frozen);
383
+ versions.set(k, 1);
384
+ });
385
+ emit();
386
+ } catch (e) {
387
+ const error = e instanceof Error ? e : new Error(String(e));
388
+ if (onError) onError(error, { operation: "hydration" });
389
+ else if (!silent) console.error(`[gstate] Hydration failed: `, error);
390
+ }
391
+ };
392
+
393
+ // core/plugins.ts
394
+ var runHook = (ctx, name, hookContext) => {
395
+ if (ctx.plugins.size === 0) return;
396
+ for (const p of ctx.plugins.values()) {
397
+ const hook = p.hooks?.[name];
398
+ if (hook) {
399
+ try {
400
+ hook(hookContext);
401
+ } catch (e) {
402
+ const error = e instanceof Error ? e : new Error(String(e));
403
+ if (ctx.onError) ctx.onError(error, { operation: `plugin:${p.name}:${name}`, key: hookContext.key });
404
+ else if (!ctx.silent) console.error(`[gstate] Plugin "${p.name}" error:`, e);
405
+ }
406
+ }
407
+ }
408
+ };
409
+ var installPlugin = (ctx, plugin, storeInstance) => {
410
+ try {
411
+ ctx.plugins.set(plugin.name, plugin);
412
+ plugin.hooks?.onInstall?.({ store: storeInstance });
413
+ } catch (e) {
414
+ const error = e instanceof Error ? e : new Error(String(e));
415
+ if (ctx.onError) ctx.onError(error, { operation: "plugin:install", key: plugin.name });
416
+ else if (!ctx.silent) console.error(`[gstate] Failed to install plugin "${plugin.name}": `, e);
417
+ }
418
+ };
419
+
420
+ // core/sync.ts
421
+ var SyncEngine = class {
422
+ constructor(store, config) {
423
+ __publicField(this, "store");
424
+ __publicField(this, "config");
425
+ __publicField(this, "pendingQueue", /* @__PURE__ */ new Map());
426
+ __publicField(this, "remoteVersions", /* @__PURE__ */ new Map());
427
+ __publicField(this, "syncTimer", null);
428
+ __publicField(this, "onlineStatusListeners", /* @__PURE__ */ new Set());
429
+ __publicField(this, "syncStateListeners", /* @__PURE__ */ new Set());
430
+ __publicField(this, "_isOnline", true);
431
+ __publicField(this, "_isSyncing", false);
432
+ this.store = store;
433
+ this.config = {
434
+ endpoint: config.endpoint,
435
+ authToken: config.authToken || "",
436
+ strategy: config.strategy || "last-write-wins",
437
+ autoSyncInterval: config.autoSyncInterval ?? 3e4,
438
+ syncOnReconnect: config.syncOnReconnect ?? true,
439
+ debounceTime: config.debounceTime ?? 1e3,
440
+ fetch: config.fetch || fetch,
441
+ onSync: config.onSync || (() => {
442
+ }),
443
+ onConflict: config.onConflict || (() => ({ action: "accept-local" })),
444
+ maxRetries: config.maxRetries ?? 3
445
+ };
446
+ this._isOnline = typeof navigator !== "undefined" ? navigator.onLine : true;
447
+ this._setupOnlineListener();
448
+ this._setupStoreListener();
449
+ if (this.config.autoSyncInterval > 0) {
450
+ this._startAutoSync();
451
+ }
452
+ }
453
+ /**
454
+ * Get current auth token (supports both static string and getter function)
455
+ */
456
+ _getAuthToken() {
457
+ const token = this.config.authToken;
458
+ if (typeof token === "function") {
459
+ return token() || "";
460
+ }
461
+ return token || "";
462
+ }
463
+ _setupOnlineListener() {
464
+ if (typeof window === "undefined") return;
465
+ window.addEventListener("online", () => {
466
+ this._isOnline = true;
467
+ this._notifyOnlineChange(true);
468
+ if (this.config.syncOnReconnect) {
469
+ this.sync();
470
+ }
471
+ });
472
+ window.addEventListener("offline", () => {
473
+ this._isOnline = false;
474
+ this._notifyOnlineChange(false);
475
+ });
476
+ }
477
+ _setupStoreListener() {
478
+ this.store._subscribe(() => {
479
+ });
480
+ }
481
+ _startAutoSync() {
482
+ setInterval(() => {
483
+ if (this._isOnline && !this._isSyncing && this.pendingQueue.size > 0) {
484
+ this.sync();
485
+ }
486
+ }, this.config.autoSyncInterval);
487
+ }
488
+ _notifyOnlineChange(online) {
489
+ this.onlineStatusListeners.forEach((cb) => cb(online));
490
+ this._notifyStateChange();
491
+ }
492
+ _notifyStateChange() {
493
+ const state = this.getState();
494
+ this.syncStateListeners.forEach((cb) => cb(state));
495
+ }
496
+ /**
497
+ * Queue a change for synchronization
498
+ */
499
+ queueChange(key, value) {
500
+ const version = this.store._getVersion(key) || 1;
501
+ this.pendingQueue.set(key, {
502
+ key,
503
+ value: deepClone(value),
504
+ timestamp: Date.now(),
505
+ version
506
+ });
507
+ this._notifyStateChange();
508
+ if (this.syncTimer) clearTimeout(this.syncTimer);
509
+ this.syncTimer = setTimeout(() => {
510
+ if (this._isOnline) this.sync();
511
+ }, this.config.debounceTime);
512
+ }
513
+ /**
514
+ * Perform synchronization with remote server
515
+ */
516
+ async sync() {
517
+ if (this._isSyncing) {
518
+ return {
519
+ success: false,
520
+ syncedKeys: [],
521
+ conflicts: [],
522
+ errors: ["Sync already in progress"],
523
+ timestamp: Date.now(),
524
+ duration: 0
525
+ };
526
+ }
527
+ this._isSyncing = true;
528
+ this._notifyStateChange();
529
+ const startTime = Date.now();
530
+ const syncedKeys = [];
531
+ const conflicts = [];
532
+ const errors = [];
533
+ try {
534
+ const pendingChanges = Array.from(this.pendingQueue.values());
535
+ if (pendingChanges.length === 0) {
536
+ this._isSyncing = false;
537
+ this._notifyStateChange();
538
+ return {
539
+ success: true,
540
+ syncedKeys: [],
541
+ conflicts: [],
542
+ errors: [],
543
+ timestamp: Date.now(),
544
+ duration: Date.now() - startTime
545
+ };
546
+ }
547
+ await this._fetchRemoteVersions(pendingChanges.map((p) => p.key));
548
+ for (const change of pendingChanges) {
549
+ try {
550
+ const remoteVersion = this.remoteVersions.get(change.key);
551
+ if (!remoteVersion) {
552
+ await this._pushChange(change);
553
+ syncedKeys.push(change.key);
554
+ this.pendingQueue.delete(change.key);
555
+ } else if (remoteVersion.version >= change.version) {
556
+ const conflict = {
557
+ key: change.key,
558
+ localValue: change.value,
559
+ remoteValue: remoteVersion.value,
560
+ localVersion: change.version,
561
+ remoteVersion: remoteVersion.version,
562
+ timestamp: change.timestamp
563
+ };
564
+ conflicts.push(conflict);
565
+ const resolution = this.config.onConflict(conflict);
566
+ await this._resolveConflict(change, remoteVersion, resolution);
567
+ syncedKeys.push(change.key);
568
+ this.pendingQueue.delete(change.key);
569
+ } else {
570
+ await this._pushChange(change);
571
+ syncedKeys.push(change.key);
572
+ this.pendingQueue.delete(change.key);
573
+ }
574
+ } catch (err) {
575
+ errors.push(`Failed to sync "${change.key}": ${err}`);
576
+ }
577
+ }
578
+ const result = {
579
+ success: errors.length === 0,
580
+ syncedKeys,
581
+ conflicts,
582
+ errors,
583
+ timestamp: Date.now(),
584
+ duration: Date.now() - startTime
585
+ };
586
+ this.config.onSync(result);
587
+ return result;
588
+ } catch (err) {
589
+ const errorMsg = `Sync failed: ${err}`;
590
+ errors.push(errorMsg);
591
+ return {
592
+ success: false,
593
+ syncedKeys,
594
+ conflicts,
595
+ errors,
596
+ timestamp: Date.now(),
597
+ duration: Date.now() - startTime
598
+ };
599
+ } finally {
600
+ this._isSyncing = false;
601
+ this._notifyStateChange();
602
+ }
603
+ }
604
+ async _fetchRemoteVersions(keys) {
605
+ try {
606
+ const authToken = this._getAuthToken();
607
+ const response = await this.config.fetch(`${this.config.endpoint}/versions`, {
608
+ method: "POST",
609
+ headers: {
610
+ "Content-Type": "application/json",
611
+ ...authToken && { "Authorization": `Bearer ${authToken}` }
612
+ },
613
+ body: JSON.stringify({ keys })
614
+ });
615
+ if (response.ok) {
616
+ const data = await response.json();
617
+ if (data.versions) {
618
+ for (const [key, version] of Object.entries(data.versions)) {
619
+ this.remoteVersions.set(key, version);
620
+ }
621
+ }
622
+ }
623
+ } catch (err) {
624
+ console.warn("[SyncEngine] Failed to fetch remote versions:", err);
625
+ }
626
+ }
627
+ async _pushChange(change) {
628
+ let retries = 0;
629
+ while (retries < this.config.maxRetries) {
630
+ try {
631
+ const authToken = this._getAuthToken();
632
+ const response = await this.config.fetch(`${this.config.endpoint}/sync`, {
633
+ method: "POST",
634
+ headers: {
635
+ "Content-Type": "application/json",
636
+ ...authToken && { "Authorization": `Bearer ${authToken}` }
637
+ },
638
+ body: JSON.stringify({
639
+ key: change.key,
640
+ value: change.value,
641
+ version: change.version,
642
+ timestamp: change.timestamp
643
+ })
644
+ });
645
+ if (response.ok) {
646
+ const data = await response.json();
647
+ if (data.version) {
648
+ this.remoteVersions.set(change.key, {
649
+ version: data.version,
650
+ timestamp: data.timestamp || Date.now(),
651
+ value: change.value
652
+ });
653
+ }
654
+ return;
655
+ }
656
+ retries++;
657
+ } catch (err) {
658
+ retries++;
659
+ if (retries >= this.config.maxRetries) throw err;
660
+ }
661
+ }
662
+ }
663
+ async _resolveConflict(localChange, remoteVersion, resolution) {
664
+ switch (resolution.action) {
665
+ case "accept-local":
666
+ await this._pushChange({
667
+ ...localChange,
668
+ version: remoteVersion.version + 1,
669
+ timestamp: Date.now()
670
+ });
671
+ break;
672
+ case "accept-remote":
673
+ this.store.set(localChange.key, remoteVersion.value);
674
+ break;
675
+ case "merge":
676
+ this.store.set(localChange.key, resolution.value);
677
+ await this._pushChange({
678
+ key: localChange.key,
679
+ value: resolution.value,
680
+ version: Math.max(localChange.version, remoteVersion.version) + 1,
681
+ timestamp: Date.now()
682
+ });
683
+ break;
684
+ case "discard":
685
+ break;
686
+ }
687
+ }
688
+ /**
689
+ * Get current sync state
690
+ */
691
+ getState() {
692
+ return {
693
+ isOnline: this._isOnline,
694
+ isSyncing: this._isSyncing,
695
+ lastSyncTimestamp: null,
696
+ // Could track this
697
+ pendingChanges: this.pendingQueue.size,
698
+ conflicts: 0
699
+ // Could track unresolved conflicts
700
+ };
701
+ }
702
+ /**
703
+ * Subscribe to online status changes
704
+ */
705
+ onOnlineChange(callback) {
706
+ this.onlineStatusListeners.add(callback);
707
+ return () => this.onlineStatusListeners.delete(callback);
708
+ }
709
+ /**
710
+ * Subscribe to sync state changes
711
+ */
712
+ onStateChange(callback) {
713
+ this.syncStateListeners.add(callback);
714
+ return () => this.syncStateListeners.delete(callback);
715
+ }
716
+ /**
717
+ * Force push all pending changes
718
+ */
719
+ async flush() {
720
+ return this.sync();
721
+ }
722
+ /**
723
+ * Destroy the sync engine
724
+ */
725
+ destroy() {
726
+ if (this.syncTimer) clearTimeout(this.syncTimer);
727
+ this.pendingQueue.clear();
728
+ this.onlineStatusListeners.clear();
729
+ this.syncStateListeners.clear();
730
+ }
731
+ };
732
+ var createSyncEngine = (store, config) => {
733
+ return new SyncEngine(store, config);
734
+ };
735
+
736
+ // core/env.ts
737
+ var isProduction = () => {
738
+ try {
739
+ if (typeof process !== "undefined" && false) return true;
740
+ const glob = typeof globalThis !== "undefined" ? globalThis : typeof window !== "undefined" ? window : {};
741
+ if (typeof glob.__DEV__ !== "undefined" && glob.__DEV__ === false) return true;
742
+ return false;
743
+ } catch {
744
+ return false;
745
+ }
746
+ };
747
+ var isDevelopment = () => !isProduction();
748
+
749
+ // core/store.ts
750
+ var StorageAdapters = {
751
+ local: () => typeof window !== "undefined" ? window.localStorage : null,
752
+ session: () => typeof window !== "undefined" ? window.sessionStorage : null,
753
+ memory: () => {
754
+ const _m = /* @__PURE__ */ new Map();
755
+ return {
756
+ getItem: (k) => _m.get(k) || null,
757
+ setItem: (k, v) => _m.set(k, v),
758
+ removeItem: (k) => _m.delete(k),
759
+ key: (i) => Array.from(_m.keys())[i] || null,
760
+ get length() {
761
+ return _m.size;
762
+ }
763
+ };
764
+ }
765
+ };
766
+ var createStore = (config) => {
767
+ const _store = /* @__PURE__ */ new Map(), _versions = /* @__PURE__ */ new Map(), _sizes = /* @__PURE__ */ new Map(), _listeners = /* @__PURE__ */ new Set(), _keyListeners = /* @__PURE__ */ new Map(), _middlewares = /* @__PURE__ */ new Set(), _watchers = /* @__PURE__ */ new Map(), _computed = /* @__PURE__ */ new Map(), _computedDeps = /* @__PURE__ */ new Map(), _plugins = /* @__PURE__ */ new Map(), _diskQueue = /* @__PURE__ */ new Map(), _regexCache = /* @__PURE__ */ new Map(), _accessRules = /* @__PURE__ */ new Map(), _consents = /* @__PURE__ */ new Map(), _namespace = config?.namespace || "gstate", _silent = config?.silent ?? false, _debounceTime = config?.debounceTime ?? 150, _currentVersion = config?.version ?? 0, _storage = config?.storage || StorageAdapters.local(), _onError = config?.onError, _maxObjectSize = config?.maxObjectSize ?? 0, _maxTotalSize = config?.maxTotalSize ?? 0, _encryptionKey = config?.encryptionKey ?? null, _validateInput = config?.validateInput ?? true, _auditEnabled = config?.auditEnabled ?? true, _userId = config?.userId, _immer = config?.immer ?? true, _persistByDefault = config?.persistByDefault ?? config?.persistence ?? config?.persist ?? false;
768
+ if (config?.accessRules) {
769
+ config.accessRules.forEach((rule) => addAccessRule(_accessRules, rule.pattern, rule.permissions));
770
+ }
771
+ let _isTransaction = false, _pendingEmit = false, _isReady = false, _totalSize = 0, _diskTimer = null, _snapshot = null;
772
+ let _readyResolver;
773
+ const _readyPromise = new Promise((resolve) => {
774
+ _readyResolver = resolve;
775
+ });
776
+ const _getPrefix2 = () => `${_namespace}_`;
777
+ const getPersistenceContext = () => ({
778
+ store: _store,
779
+ versions: _versions,
780
+ sizes: _sizes,
781
+ totalSize: _totalSize,
782
+ storage: _storage,
783
+ config: config || {},
784
+ diskQueue: _diskQueue,
785
+ encryptionKey: _encryptionKey,
786
+ audit: _audit,
787
+ onError: _onError,
788
+ silent: _silent,
789
+ debounceTime: _debounceTime,
790
+ currentVersion: _currentVersion
791
+ });
792
+ const getPluginContext = () => ({
793
+ plugins: _plugins,
794
+ onError: _onError,
795
+ silent: _silent
796
+ });
797
+ const _calculateSize = (val) => {
798
+ if (val === null || val === void 0) return 0;
799
+ const type = typeof val;
800
+ if (type === "boolean") return 4;
801
+ if (type === "number") return 8;
802
+ if (type === "string") return val.length * 2;
803
+ if (type !== "object") return 0;
804
+ let bytes = 0;
805
+ const stack = [val];
806
+ const seen = /* @__PURE__ */ new WeakSet();
807
+ while (stack.length > 0) {
808
+ const value = stack.pop();
809
+ if (typeof value === "boolean") {
810
+ bytes += 4;
811
+ } else if (typeof value === "number") {
812
+ bytes += 8;
813
+ } else if (typeof value === "string") {
814
+ bytes += value.length * 2;
815
+ } else if (typeof value === "object" && value !== null) {
816
+ const obj = value;
817
+ if (seen.has(obj)) continue;
818
+ seen.add(obj);
819
+ if (Array.isArray(obj)) {
820
+ for (let i = 0; i < obj.length; i++) stack.push(obj[i]);
821
+ } else {
822
+ for (const key of Object.keys(obj)) {
823
+ bytes += key.length * 2;
824
+ stack.push(obj[key]);
825
+ }
826
+ }
827
+ }
828
+ }
829
+ return bytes;
830
+ };
831
+ const _runHook = (name, context) => {
832
+ runHook(getPluginContext(), name, context);
833
+ };
834
+ const _audit = (action, key, success, error) => {
835
+ if (_auditEnabled && isAuditActive() && logAudit) {
836
+ logAudit({ timestamp: Date.now(), action, key, userId: _userId, success, error });
837
+ }
838
+ };
839
+ const _updateComputed = (key) => {
840
+ const comp = _computed.get(key);
841
+ if (!comp) return;
842
+ const depsFound = /* @__PURE__ */ new Set();
843
+ const getter = (k) => {
844
+ depsFound.add(k);
845
+ if (_computed.has(k)) return _computed.get(k).lastValue;
846
+ return instance.get(k);
847
+ };
848
+ const newValue = comp.selector(getter);
849
+ comp.deps.forEach((d) => {
850
+ if (!depsFound.has(d)) {
851
+ const dependents = _computedDeps.get(d);
852
+ if (dependents) {
853
+ dependents.delete(key);
854
+ if (dependents.size === 0) _computedDeps.delete(d);
855
+ }
856
+ }
857
+ });
858
+ depsFound.forEach((d) => {
859
+ if (!comp.deps.has(d)) {
860
+ if (!_computedDeps.has(d)) _computedDeps.set(d, /* @__PURE__ */ new Set());
861
+ _computedDeps.get(d).add(key);
862
+ }
863
+ });
864
+ comp.deps = depsFound;
865
+ if (!isEqual(comp.lastValue, newValue)) {
866
+ comp.lastValue = _immer && newValue !== null && typeof newValue === "object" ? _immerFreeze2(deepClone(newValue), true) : newValue;
867
+ _versions.set(key, (_versions.get(key) || 0) + 1);
868
+ _emit(key);
869
+ }
870
+ };
871
+ const _emit = (changedKey) => {
872
+ if (changedKey) {
873
+ if (_computedDeps.has(changedKey)) {
874
+ const dependents = _computedDeps.get(changedKey);
875
+ for (const dependentKey of dependents) {
876
+ _updateComputed(dependentKey);
877
+ }
878
+ }
879
+ const watchers = _watchers.get(changedKey);
880
+ if (watchers) {
881
+ const val = instance.get(changedKey);
882
+ for (const w of watchers) {
883
+ try {
884
+ w(val);
885
+ } catch (e) {
886
+ const error = e instanceof Error ? e : new Error(String(e));
887
+ if (_onError) _onError(error, { operation: "watcher", key: changedKey });
888
+ else if (!_silent) console.error(`[gstate] Watcher error for "${changedKey}":`, e);
889
+ }
890
+ }
891
+ }
892
+ const keyListeners = _keyListeners.get(changedKey);
893
+ if (keyListeners) {
894
+ for (const l of keyListeners) {
895
+ try {
896
+ l();
897
+ } catch (e) {
898
+ const error = e instanceof Error ? e : new Error(String(e));
899
+ if (_onError) _onError(error, { operation: "keyListener", key: changedKey });
900
+ else if (!_silent) console.error(`[gstate] Listener error for "${changedKey}":`, e);
901
+ }
902
+ }
903
+ }
904
+ }
905
+ if (_isTransaction) {
906
+ _pendingEmit = true;
907
+ return;
908
+ }
909
+ for (const l of _listeners) {
910
+ try {
911
+ l();
912
+ } catch (e) {
913
+ const error = e instanceof Error ? e : new Error(String(e));
914
+ if (_onError) _onError(error, { operation: "listener" });
915
+ else if (!_silent) console.error(`[gstate] Global listener error: `, e);
916
+ }
917
+ }
918
+ };
919
+ const _flushDisk = async () => {
920
+ flushDisk(getPersistenceContext());
921
+ };
922
+ const _methodNamespace = {};
923
+ const instance = {
924
+ _setSilently: (key, value) => {
925
+ const oldSize = _sizes.get(key) || 0, frozen = _immer && value !== null && typeof value === "object" ? _immerFreeze2(deepClone(value), true) : value;
926
+ const hasLimits = (_maxObjectSize > 0 || _maxTotalSize > 0) && !isProduction();
927
+ const newSize = hasLimits ? _calculateSize(frozen) : 0;
928
+ _totalSize = _totalSize - oldSize + newSize;
929
+ _sizes.set(key, newSize);
930
+ _store.set(key, frozen);
931
+ _versions.set(key, (_versions.get(key) || 0) + 1);
932
+ _snapshot = null;
933
+ },
934
+ /**
935
+ * Registers a custom method on the store instance.
936
+ * @param pluginName - Plugin name
937
+ * @param methodName - Method name
938
+ * @param fn - Method function
939
+ */
940
+ _registerMethod: (pluginName, methodName, fn) => {
941
+ const isUnsafeKey = (key) => key === "__proto__" || key === "constructor" || key === "prototype";
942
+ if (isUnsafeKey(pluginName) || isUnsafeKey(methodName)) {
943
+ console.warn("[gstate] Refusing to register method with unsafe key:", pluginName, methodName);
944
+ return;
945
+ }
946
+ if (!_methodNamespace[pluginName]) _methodNamespace[pluginName] = {};
947
+ _methodNamespace[pluginName][methodName] = fn;
948
+ },
949
+ set: (key, valOrUp, options = {}) => {
950
+ const oldVal = _store.get(key), newVal = _immer && typeof valOrUp === "function" ? _immerProduce2(oldVal, valOrUp) : valOrUp;
951
+ if (_validateInput && !validateKey(key)) {
952
+ if (!_silent) console.warn(`[gstate] Invalid key: ${key}`);
953
+ return false;
954
+ }
955
+ if (!hasPermission(_accessRules, key, "write", _userId)) {
956
+ _audit("set", key, false, "RBAC Denied");
957
+ if (!_silent) console.error(`[gstate] RBAC Denied for "${key}"`);
958
+ return false;
959
+ }
960
+ const sani = _validateInput ? sanitizeValue(newVal) : newVal;
961
+ const oldSize = _sizes.get(key) || 0;
962
+ _runHook("onBeforeSet", { key, value: sani, store: instance, version: _versions.get(key) || 0 });
963
+ const frozen = _immer && sani !== null && typeof sani === "object" ? _immerFreeze2(deepClone(sani), true) : sani;
964
+ if (!isEqual(oldVal, frozen)) {
965
+ const hasLimits = (_maxObjectSize > 0 || _maxTotalSize > 0) && !isProduction();
966
+ const finalSize = hasLimits ? _calculateSize(frozen) : 0;
967
+ if (_maxObjectSize > 0 && finalSize > _maxObjectSize) {
968
+ const error = new Error(`Object size (${finalSize} bytes) exceeds maxObjectSize (${_maxObjectSize} bytes)`);
969
+ if (_onError) _onError(error, { operation: "set", key });
970
+ else if (!_silent) console.warn(`[gstate] ${error.message} for "${key}"`);
971
+ }
972
+ if (_maxTotalSize > 0) {
973
+ const est = _totalSize - oldSize + finalSize;
974
+ if (est > _maxTotalSize) {
975
+ const error = new Error(`Total store size (${est} bytes) exceeds limit (${_maxTotalSize} bytes)`);
976
+ if (_onError) _onError(error, { operation: "set" });
977
+ else if (!_silent) console.warn(`[gstate] ${error.message}`);
978
+ }
979
+ }
980
+ _totalSize = _totalSize - oldSize + finalSize;
981
+ _sizes.set(key, finalSize);
982
+ _store.set(key, frozen);
983
+ _versions.set(key, (_versions.get(key) || 0) + 1);
984
+ _snapshot = null;
985
+ const shouldPersist = options.persist ?? _persistByDefault;
986
+ if (shouldPersist) {
987
+ _diskQueue.set(key, { value: frozen, options: { ...options, persist: shouldPersist, encoded: options.encoded || config?.encoded } });
988
+ if (_diskTimer) clearTimeout(_diskTimer);
989
+ _diskTimer = setTimeout(_flushDisk, _debounceTime);
990
+ }
991
+ _runHook("onSet", { key, value: frozen, store: instance, version: _versions.get(key) });
992
+ _audit("set", key, true);
993
+ _emit(key);
994
+ return true;
995
+ }
996
+ return false;
997
+ },
998
+ get: (key) => {
999
+ if (!hasPermission(_accessRules, key, "read", _userId)) {
1000
+ _audit("get", key, false, "RBAC Denied");
1001
+ return null;
1002
+ }
1003
+ const val = _store.get(key);
1004
+ _runHook("onGet", { store: instance, key, value: val });
1005
+ _audit("get", key, true);
1006
+ return val;
1007
+ },
1008
+ compute: (key, selector) => {
1009
+ try {
1010
+ if (!_computed.has(key)) {
1011
+ _computed.set(key, { selector, lastValue: null, deps: /* @__PURE__ */ new Set() });
1012
+ _updateComputed(key);
1013
+ }
1014
+ return _computed.get(key).lastValue;
1015
+ } catch (e) {
1016
+ const error = e instanceof Error ? e : new Error(String(e));
1017
+ if (_onError) _onError(error, { operation: "compute", key });
1018
+ else if (!_silent) console.error(`[gstate] Compute error for "${key}": `, e);
1019
+ return null;
1020
+ }
1021
+ },
1022
+ watch: (key, callback) => {
1023
+ if (!_watchers.has(key)) _watchers.set(key, /* @__PURE__ */ new Set());
1024
+ const set = _watchers.get(key);
1025
+ set.add(callback);
1026
+ return () => {
1027
+ set.delete(callback);
1028
+ if (set.size === 0) _watchers.delete(key);
1029
+ };
1030
+ },
1031
+ remove: (key) => {
1032
+ if (!hasPermission(_accessRules, key, "delete", _userId)) {
1033
+ _audit("delete", key, false, "RBAC Denied");
1034
+ return false;
1035
+ }
1036
+ const old = _store.get(key), deleted = _store.delete(key);
1037
+ if (deleted) {
1038
+ _totalSize -= _sizes.get(key) || 0;
1039
+ _sizes.delete(key);
1040
+ _runHook("onRemove", { store: instance, key, value: old });
1041
+ _snapshot = null;
1042
+ }
1043
+ _versions.set(key, (_versions.get(key) || 0) + 1);
1044
+ if (_storage) _storage.removeItem(`${_getPrefix2()}${key}`);
1045
+ _audit("delete", key, true);
1046
+ _emit(key);
1047
+ return deleted;
1048
+ },
1049
+ delete: (key) => instance.remove(key),
1050
+ deleteAll: () => {
1051
+ Array.from(_store.keys()).forEach((k) => instance.remove(k));
1052
+ if (_storage) {
1053
+ const prefix = _namespace + "_";
1054
+ for (let i = 0; i < (_storage.length || 0); i++) {
1055
+ const k = _storage.key(i);
1056
+ if (k?.startsWith(prefix)) {
1057
+ _storage.removeItem(k);
1058
+ i--;
1059
+ }
1060
+ }
1061
+ }
1062
+ _totalSize = 0;
1063
+ _sizes.clear();
1064
+ _snapshot = null;
1065
+ return true;
1066
+ },
1067
+ list: () => Object.fromEntries(_store.entries()),
1068
+ use: (m) => {
1069
+ _middlewares.add(m);
1070
+ },
1071
+ transaction: (fn) => {
1072
+ _isTransaction = true;
1073
+ _runHook("onTransaction", { store: instance, key: "START" });
1074
+ try {
1075
+ fn();
1076
+ } finally {
1077
+ _isTransaction = false;
1078
+ _runHook("onTransaction", { store: instance, key: "END" });
1079
+ if (_pendingEmit) {
1080
+ _pendingEmit = false;
1081
+ _emit();
1082
+ }
1083
+ }
1084
+ },
1085
+ destroy: () => {
1086
+ if (_diskTimer) {
1087
+ clearTimeout(_diskTimer);
1088
+ _diskTimer = null;
1089
+ }
1090
+ _diskQueue.clear();
1091
+ if (typeof window !== "undefined") window.removeEventListener("beforeunload", _unloadHandler);
1092
+ _runHook("onDestroy", { store: instance });
1093
+ _listeners.clear();
1094
+ _keyListeners.clear();
1095
+ _watchers.clear();
1096
+ _computed.clear();
1097
+ _computedDeps.clear();
1098
+ _plugins.clear();
1099
+ _store.clear();
1100
+ _sizes.clear();
1101
+ _totalSize = 0;
1102
+ _accessRules.clear();
1103
+ _consents.clear();
1104
+ _versions.clear();
1105
+ _regexCache.clear();
1106
+ _middlewares.clear();
1107
+ },
1108
+ _addPlugin: (p) => {
1109
+ installPlugin(getPluginContext(), p, instance);
1110
+ },
1111
+ _removePlugin: (name) => {
1112
+ _plugins.delete(name);
1113
+ },
1114
+ _subscribe: (cb, key) => {
1115
+ if (key) {
1116
+ if (!_keyListeners.has(key)) _keyListeners.set(key, /* @__PURE__ */ new Set());
1117
+ const set = _keyListeners.get(key);
1118
+ set.add(cb);
1119
+ return () => {
1120
+ set.delete(cb);
1121
+ if (set.size === 0) _keyListeners.delete(key);
1122
+ };
1123
+ }
1124
+ _listeners.add(cb);
1125
+ return () => _listeners.delete(cb);
1126
+ },
1127
+ _getVersion: (key) => _versions.get(key) ?? 0,
1128
+ // Enterprise Security & Compliance
1129
+ addAccessRule: (pattern, permissions) => addAccessRule(_accessRules, pattern, permissions),
1130
+ hasPermission: (key, action, userId) => hasPermission(_accessRules, key, action, userId),
1131
+ recordConsent: (userId, purpose, granted) => recordConsent(_consents, userId, purpose, granted),
1132
+ hasConsent: (userId, purpose) => hasConsent(_consents, userId, purpose),
1133
+ getConsents: (userId) => getConsents(_consents, userId),
1134
+ revokeConsent: (userId, purpose) => revokeConsent(_consents, userId, purpose),
1135
+ exportUserData: (userId) => exportUserData(_consents, userId),
1136
+ deleteUserData: (userId) => deleteUserData(_consents, userId),
1137
+ getSnapshot: () => {
1138
+ if (!_snapshot) {
1139
+ _snapshot = Object.fromEntries(_store.entries());
1140
+ }
1141
+ return _snapshot;
1142
+ },
1143
+ get plugins() {
1144
+ return _methodNamespace;
1145
+ },
1146
+ get isReady() {
1147
+ return _isReady;
1148
+ },
1149
+ get namespace() {
1150
+ return _namespace;
1151
+ },
1152
+ get userId() {
1153
+ return _userId;
1154
+ },
1155
+ whenReady: () => _readyPromise
1156
+ };
1157
+ const secMethods = ["addAccessRule", "recordConsent", "hasConsent", "getConsents", "revokeConsent", "exportUserData", "deleteUserData"];
1158
+ secMethods.forEach((m) => {
1159
+ const fn = instance[m];
1160
+ if (fn) instance._registerMethod("security", m, fn);
1161
+ });
1162
+ const _unloadHandler = () => {
1163
+ if (_diskQueue.size > 0) _flushDisk();
1164
+ };
1165
+ if (typeof window !== "undefined") window.addEventListener("beforeunload", _unloadHandler);
1166
+ if (_storage) {
1167
+ hydrateStore(
1168
+ getPersistenceContext(),
1169
+ // We pass the calculateSize function to update memory usage correctly after hydration
1170
+ (val) => {
1171
+ const hasLimits = (_maxObjectSize > 0 || _maxTotalSize > 0) && !isProduction();
1172
+ return hasLimits ? _calculateSize(val) : 0;
1173
+ },
1174
+ () => {
1175
+ _isReady = true;
1176
+ _snapshot = null;
1177
+ _readyResolver();
1178
+ _emit();
1179
+ }
1180
+ ).then(() => {
1181
+ });
1182
+ } else {
1183
+ _isReady = true;
1184
+ _readyResolver();
1185
+ }
1186
+ let _syncEngine = null;
1187
+ if (config?.sync) {
1188
+ _syncEngine = new SyncEngine(instance, config.sync);
1189
+ instance._registerMethod("sync", "flush", () => _syncEngine?.flush());
1190
+ instance._registerMethod("sync", "getState", () => _syncEngine?.getState());
1191
+ instance._registerMethod("sync", "onStateChange", (cb) => _syncEngine?.onStateChange(cb));
1192
+ }
1193
+ return instance;
1194
+ };
1195
+
1196
+ // core/hooks.ts
1197
+ import { useSyncExternalStore, useDebugValue, useMemo, useCallback, useEffect, useState } from "react";
1198
+ var _defaultStore = null;
1199
+ var initState = (config) => {
1200
+ if (_defaultStore && !config?.namespace) {
1201
+ if (!config?.silent) {
1202
+ console.warn(
1203
+ "[gstate] Store already exists. Pass a unique namespace to create additional stores."
1204
+ );
1205
+ }
1206
+ }
1207
+ const store = createStore(config);
1208
+ _defaultStore = store;
1209
+ return store;
1210
+ };
1211
+ var destroyState = () => {
1212
+ if (_defaultStore) {
1213
+ _defaultStore.destroy();
1214
+ _defaultStore = null;
1215
+ }
1216
+ };
1217
+ var useIsStoreReady = (store) => {
1218
+ const targetStore = store || _defaultStore;
1219
+ const subscribe = useMemo(
1220
+ () => (callback) => targetStore ? targetStore._subscribe(callback) : () => {
1221
+ },
1222
+ [targetStore]
1223
+ );
1224
+ return useSyncExternalStore(
1225
+ subscribe,
1226
+ () => targetStore ? targetStore.isReady : false,
1227
+ () => true
1228
+ // SSR is always "ready" as it doesn't hydrate from local storage
1229
+ );
1230
+ };
1231
+ var getStore = () => _defaultStore;
1232
+ function useStore(keyOrSelector, store) {
1233
+ const targetStore = useMemo(
1234
+ () => store || _defaultStore,
1235
+ [store]
1236
+ );
1237
+ const ghostStore = useMemo(() => {
1238
+ const noop = () => {
1239
+ };
1240
+ const noopFalse = () => false;
1241
+ const noopNull = () => null;
1242
+ return {
1243
+ set: noopFalse,
1244
+ get: noopNull,
1245
+ remove: noopFalse,
1246
+ delete: noopFalse,
1247
+ deleteAll: noopFalse,
1248
+ list: () => ({}),
1249
+ compute: noopNull,
1250
+ watch: () => () => {
1251
+ },
1252
+ use: noop,
1253
+ transaction: noop,
1254
+ destroy: noop,
1255
+ _subscribe: () => () => {
1256
+ },
1257
+ _setSilently: noop,
1258
+ _registerMethod: noop,
1259
+ _addPlugin: noop,
1260
+ _removePlugin: noop,
1261
+ _getVersion: () => 0,
1262
+ get isReady() {
1263
+ return false;
1264
+ },
1265
+ whenReady: () => Promise.resolve(),
1266
+ get plugins() {
1267
+ return {};
1268
+ },
1269
+ getSnapshot: () => ({}),
1270
+ // Ghost snapshot
1271
+ get namespace() {
1272
+ return "ghost";
1273
+ },
1274
+ get userId() {
1275
+ return void 0;
1276
+ }
1277
+ };
1278
+ }, []);
1279
+ const safeStore = targetStore || ghostStore;
1280
+ const isSelector = typeof keyOrSelector === "function";
1281
+ const key = !isSelector ? keyOrSelector : null;
1282
+ const selector = isSelector ? keyOrSelector : null;
1283
+ const subscribe = useCallback(
1284
+ (callback) => {
1285
+ if (isSelector) {
1286
+ return safeStore._subscribe(callback);
1287
+ } else {
1288
+ return safeStore._subscribe(callback, key);
1289
+ }
1290
+ },
1291
+ [safeStore, isSelector, key]
1292
+ );
1293
+ const getSnapshot = useCallback(() => {
1294
+ if (isSelector) {
1295
+ return selector(safeStore.getSnapshot());
1296
+ } else {
1297
+ return safeStore.get(key) ?? void 0;
1298
+ }
1299
+ }, [safeStore, isSelector, key, selector]);
1300
+ const getServerSnapshot = useCallback(() => {
1301
+ if (isSelector) {
1302
+ try {
1303
+ return selector({});
1304
+ } catch {
1305
+ return void 0;
1306
+ }
1307
+ } else {
1308
+ return void 0;
1309
+ }
1310
+ }, [selector, isSelector]);
1311
+ const value = useSyncExternalStore(
1312
+ subscribe,
1313
+ getSnapshot,
1314
+ // Cast needed for union types
1315
+ getServerSnapshot
1316
+ );
1317
+ const setter = useCallback(
1318
+ (val, options) => {
1319
+ if (isSelector) {
1320
+ if (!isProduction()) {
1321
+ console.warn("[gstate] Cannot set value when using a selector.");
1322
+ }
1323
+ return false;
1324
+ }
1325
+ return safeStore.set(key, val, options);
1326
+ },
1327
+ [safeStore, isSelector, key]
1328
+ );
1329
+ useDebugValue(value, (v) => isSelector ? `Selector: ${JSON.stringify(v)}` : `${key}: ${JSON.stringify(v)}`);
1330
+ if (isSelector) {
1331
+ return value;
1332
+ }
1333
+ return [value, setter];
1334
+ }
1335
+ var _syncEngines = /* @__PURE__ */ new Map();
1336
+ var initSync = (store, config) => {
1337
+ const key = store.namespace;
1338
+ if (_syncEngines.has(key)) {
1339
+ console.warn(`[gstate] Sync engine already exists for namespace "${key}". Call destroySync first.`);
1340
+ return _syncEngines.get(key);
1341
+ }
1342
+ const engine = new SyncEngine(store, config);
1343
+ _syncEngines.set(key, engine);
1344
+ return engine;
1345
+ };
1346
+ var destroySync = (namespace) => {
1347
+ const engine = _syncEngines.get(namespace);
1348
+ if (engine) {
1349
+ engine.destroy();
1350
+ _syncEngines.delete(namespace);
1351
+ }
1352
+ };
1353
+ function useSyncedState(key, store) {
1354
+ const targetStore = store || _defaultStore;
1355
+ const namespace = targetStore?.namespace || "default";
1356
+ const engine = _syncEngines.get(namespace);
1357
+ const result = useStore(key, targetStore);
1358
+ const value = result[0];
1359
+ const setter = result[1];
1360
+ const [syncState, setSyncState] = useState(() => engine?.getState() || {
1361
+ isOnline: true,
1362
+ isSyncing: false,
1363
+ lastSyncTimestamp: null,
1364
+ pendingChanges: 0,
1365
+ conflicts: 0
1366
+ });
1367
+ useEffect(() => {
1368
+ if (!engine) return;
1369
+ const unsubscribe = engine.onStateChange(setSyncState);
1370
+ return unsubscribe;
1371
+ }, [engine]);
1372
+ const syncedSetter = useCallback(
1373
+ (val, options) => {
1374
+ const result2 = setter(val, options);
1375
+ if (result2 && engine) {
1376
+ const currentValue = targetStore?.get(key);
1377
+ engine.queueChange(key, currentValue);
1378
+ }
1379
+ return result2;
1380
+ },
1381
+ [setter, engine, key, targetStore]
1382
+ );
1383
+ return [value, syncedSetter, syncState];
1384
+ }
1385
+ var useSyncStatus = () => {
1386
+ const [state, setState] = useState({
1387
+ isOnline: true,
1388
+ isSyncing: false,
1389
+ lastSyncTimestamp: null,
1390
+ pendingChanges: 0,
1391
+ conflicts: 0
1392
+ });
1393
+ useEffect(() => {
1394
+ const updateState = () => {
1395
+ let isOnline = true;
1396
+ let isSyncing = false;
1397
+ let pendingChanges = 0;
1398
+ let conflicts = 0;
1399
+ _syncEngines.forEach((engine) => {
1400
+ const s = engine.getState();
1401
+ isOnline = isOnline && s.isOnline;
1402
+ isSyncing = isSyncing || s.isSyncing;
1403
+ pendingChanges += s.pendingChanges;
1404
+ conflicts += s.conflicts;
1405
+ });
1406
+ setState({
1407
+ isOnline,
1408
+ isSyncing,
1409
+ lastSyncTimestamp: null,
1410
+ pendingChanges,
1411
+ conflicts
1412
+ });
1413
+ };
1414
+ updateState();
1415
+ const unsubscribes = Array.from(_syncEngines.values()).map(
1416
+ (engine) => engine.onStateChange(updateState)
1417
+ );
1418
+ return () => unsubscribes.forEach((fn) => fn());
1419
+ }, []);
1420
+ return state;
1421
+ };
1422
+ var triggerSync = async (namespace) => {
1423
+ const targetNamespace = namespace || _defaultStore?.namespace;
1424
+ if (!targetNamespace) return;
1425
+ const engine = _syncEngines.get(targetNamespace);
1426
+ if (engine) {
1427
+ await engine.flush();
1428
+ }
1429
+ };
1430
+
1431
+ // core/async.ts
1432
+ var createAsyncStore = (resolver, options) => {
1433
+ const key = options?.key || "async_data";
1434
+ const store = options?.store || createStore({
1435
+ namespace: `async_${key}`,
1436
+ silent: true
1437
+ });
1438
+ if (store.get(key) == null) {
1439
+ store.set(key, { data: null, loading: false, error: null, updatedAt: null });
1440
+ }
1441
+ const run = async () => {
1442
+ const current = store.get(key);
1443
+ store.set(key, {
1444
+ ...current || { data: null, loading: false, error: null, updatedAt: null },
1445
+ loading: true,
1446
+ error: null
1447
+ });
1448
+ if ("whenReady" in store && !store.isReady) await store.whenReady();
1449
+ try {
1450
+ const result = await resolver();
1451
+ const prev = store.get(key);
1452
+ store.set(key, {
1453
+ ...prev || { data: null, loading: false, error: null, updatedAt: null },
1454
+ data: result,
1455
+ loading: false,
1456
+ updatedAt: Date.now()
1457
+ }, { persist: options?.persist });
1458
+ } catch (e) {
1459
+ const prev = store.get(key);
1460
+ store.set(key, {
1461
+ ...prev || { data: null, loading: false, error: null, updatedAt: null },
1462
+ error: e instanceof Error ? e : new Error(String(e)),
1463
+ loading: false
1464
+ });
1465
+ }
1466
+ };
1467
+ return Object.assign(store, { execute: run });
1468
+ };
1469
+
1470
+ // plugins/official/immer.plugin.ts
1471
+ var immerPlugin = () => ({
1472
+ name: "gstate-immer",
1473
+ hooks: {
1474
+ onInstall: ({ store }) => {
1475
+ store._registerMethod("immer", "setWithProduce", ((key, updater) => {
1476
+ return store.set(key, updater);
1477
+ }));
1478
+ }
1479
+ }
1480
+ });
1481
+
1482
+ // plugins/official/undo-redo.plugin.ts
1483
+ var undoRedoPlugin = (options) => {
1484
+ let _history = [];
1485
+ let _cursor = -1;
1486
+ let _isRestoring = false;
1487
+ const _limit = options?.limit || 50;
1488
+ return {
1489
+ name: "gstate-undo-redo",
1490
+ hooks: {
1491
+ onInstall: ({ store }) => {
1492
+ _history.push(store.list());
1493
+ _cursor = 0;
1494
+ store._registerMethod("undoRedo", "undo", () => {
1495
+ if (_cursor > 0) {
1496
+ _isRestoring = true;
1497
+ _cursor--;
1498
+ const snapshot = _history[_cursor];
1499
+ if (!snapshot) return false;
1500
+ Object.entries(snapshot).forEach(([k, v]) => {
1501
+ store._setSilently(k, v);
1502
+ });
1503
+ _isRestoring = false;
1504
+ return true;
1505
+ }
1506
+ return false;
1507
+ });
1508
+ store._registerMethod("undoRedo", "redo", () => {
1509
+ if (_cursor < _history.length - 1) {
1510
+ _isRestoring = true;
1511
+ _cursor++;
1512
+ const snapshot = _history[_cursor];
1513
+ if (!snapshot) return false;
1514
+ Object.entries(snapshot).forEach(([k, v]) => {
1515
+ store._setSilently(k, v);
1516
+ });
1517
+ _isRestoring = false;
1518
+ return true;
1519
+ }
1520
+ return false;
1521
+ });
1522
+ store._registerMethod("undoRedo", "canUndo", () => _cursor > 0);
1523
+ store._registerMethod("undoRedo", "canRedo", () => _cursor < _history.length - 1);
1524
+ },
1525
+ onSet: ({ store }) => {
1526
+ if (_isRestoring) return;
1527
+ if (_cursor < _history.length - 1) {
1528
+ _history = _history.slice(0, _cursor + 1);
1529
+ }
1530
+ _history.push(store.list());
1531
+ if (_history.length > _limit) {
1532
+ _history.shift();
1533
+ } else {
1534
+ _cursor++;
1535
+ }
1536
+ }
1537
+ }
1538
+ };
1539
+ };
1540
+
1541
+ // plugins/official/schema.plugin.ts
1542
+ var schemaPlugin = (schemas) => ({
1543
+ name: "gstate-schema",
1544
+ hooks: {
1545
+ onSet: ({ key, value }) => {
1546
+ if (!key) return;
1547
+ const validator = schemas[key];
1548
+ if (validator) {
1549
+ const result = validator(value);
1550
+ if (result !== true) {
1551
+ throw new Error(`[Schema Error] Validation failed for key "${key}": ${result === false ? "Invalid type" : result}`);
1552
+ }
1553
+ }
1554
+ }
1555
+ }
1556
+ });
1557
+
1558
+ // plugins/official/devtools.plugin.ts
1559
+ var devToolsPlugin = (options) => {
1560
+ const ext = globalThis;
1561
+ const global = ext;
1562
+ const extension = global.__REDUX_DEVTOOLS_EXTENSION__;
1563
+ if (!extension?.connect) {
1564
+ return { name: "gstate-devtools-noop", hooks: {} };
1565
+ }
1566
+ let _devTools = null;
1567
+ return {
1568
+ name: "gstate-devtools",
1569
+ hooks: {
1570
+ onInstall: ({ store }) => {
1571
+ _devTools = extension.connect({ name: options?.name || "Magnetar Store" });
1572
+ _devTools.init(store.list());
1573
+ },
1574
+ onSet: ({ key, store }) => {
1575
+ if (!key || !_devTools) return;
1576
+ _devTools.send(`SET_${key.toUpperCase()}`, store.list());
1577
+ },
1578
+ onRemove: ({ key, store }) => {
1579
+ if (!key || !_devTools) return;
1580
+ _devTools.send(`REMOVE_${key.toUpperCase()}`, store.list());
1581
+ }
1582
+ }
1583
+ };
1584
+ };
1585
+
1586
+ // plugins/official/snapshot.plugin.ts
1587
+ var snapshotPlugin = () => {
1588
+ const _snapshots = /* @__PURE__ */ new Map();
1589
+ return {
1590
+ name: "gstate-snapshot",
1591
+ hooks: {
1592
+ onInstall: ({ store }) => {
1593
+ store._registerMethod("snapshot", "takeSnapshot", ((name) => {
1594
+ _snapshots.set(name, store.list());
1595
+ }));
1596
+ store._registerMethod("snapshot", "restoreSnapshot", ((name) => {
1597
+ const snap = _snapshots.get(name);
1598
+ if (!snap) return false;
1599
+ store.transaction(() => {
1600
+ Object.entries(snap).forEach(([k, v]) => {
1601
+ store.set(k, v);
1602
+ });
1603
+ });
1604
+ return true;
1605
+ }));
1606
+ store._registerMethod("snapshot", "listSnapshots", (() => Array.from(_snapshots.keys())));
1607
+ store._registerMethod("snapshot", "deleteSnapshot", ((name) => _snapshots.delete(name)));
1608
+ store._registerMethod("snapshot", "clearSnapshots", (() => _snapshots.clear()));
1609
+ }
1610
+ }
1611
+ };
1612
+ };
1613
+
1614
+ // plugins/official/guard.plugin.ts
1615
+ var guardPlugin = (guards) => ({
1616
+ name: "gstate-guard",
1617
+ hooks: {
1618
+ onBeforeSet: ({ key, value, store: _store }) => {
1619
+ if (!key) return;
1620
+ const guard = guards[key];
1621
+ if (guard) {
1622
+ const transformed = guard(value);
1623
+ if (transformed !== value) {
1624
+ }
1625
+ }
1626
+ }
1627
+ }
1628
+ });
1629
+
1630
+ // plugins/official/analytics.plugin.ts
1631
+ var analyticsPlugin = (options) => ({
1632
+ name: "gstate-analytics",
1633
+ hooks: {
1634
+ onSet: ({ key, value }) => {
1635
+ if (!key) return;
1636
+ if (!options.keys || options.keys.includes(key)) {
1637
+ options.provider({ key, value, action: "SET" });
1638
+ }
1639
+ },
1640
+ onRemove: ({ key }) => {
1641
+ if (!key) return;
1642
+ if (!options.keys || options.keys.includes(key)) {
1643
+ options.provider({ key, value: null, action: "REMOVE" });
1644
+ }
1645
+ }
1646
+ }
1647
+ });
1648
+
1649
+ // plugins/official/sync.plugin.ts
1650
+ var syncPlugin = (options) => {
1651
+ const _channel = new BroadcastChannel(options?.channelName || "gstate_sync");
1652
+ let _isSyncing = false;
1653
+ return {
1654
+ name: "gstate-sync",
1655
+ hooks: {
1656
+ onInstall: ({ store }) => {
1657
+ _channel.onmessage = (event) => {
1658
+ const { key, value, action } = event.data;
1659
+ if (!key) return;
1660
+ _isSyncing = true;
1661
+ if (action === "REMOVE") {
1662
+ store.remove(key);
1663
+ } else {
1664
+ store.set(key, value);
1665
+ }
1666
+ _isSyncing = false;
1667
+ };
1668
+ },
1669
+ onSet: ({ key, value }) => {
1670
+ if (!key || _isSyncing) return;
1671
+ _channel.postMessage({ key, value, action: "SET" });
1672
+ },
1673
+ onRemove: ({ key }) => {
1674
+ if (!key || _isSyncing) return;
1675
+ _channel.postMessage({ key, action: "REMOVE" });
1676
+ },
1677
+ onDestroy: () => {
1678
+ _channel.close();
1679
+ }
1680
+ }
1681
+ };
1682
+ };
1683
+
1684
+ // plugins/official/debug.plugin.ts
1685
+ var debugPlugin = () => {
1686
+ if (isProduction()) {
1687
+ return { name: "gstate-debug-noop", hooks: {} };
1688
+ }
1689
+ const isDev = !isProduction();
1690
+ const debugLog = (...args) => {
1691
+ if (isDev) console.debug(...args);
1692
+ };
1693
+ return {
1694
+ name: "gstate-debug",
1695
+ hooks: {
1696
+ onInstall: ({ store }) => {
1697
+ if (typeof window !== "undefined") {
1698
+ window.gstate = {
1699
+ /** Get all state */
1700
+ list: () => {
1701
+ return store.list();
1702
+ },
1703
+ /** Get a specific key */
1704
+ get: (key) => {
1705
+ const val = store.get(key);
1706
+ debugLog(`[gstate] get('${key}'):`, val);
1707
+ return val;
1708
+ },
1709
+ /** Set a value */
1710
+ set: (key, value) => {
1711
+ const result = store.set(key, value);
1712
+ debugLog(`[gstate] set('${key}', ${JSON.stringify(value)}):`, result);
1713
+ return result;
1714
+ },
1715
+ /** Watch a key */
1716
+ watch: (key, callback) => {
1717
+ const unwatch = store.watch(key, callback);
1718
+ debugLog(`[gstate] watching '${key}'`);
1719
+ return unwatch;
1720
+ },
1721
+ /** Get store info */
1722
+ info: () => {
1723
+ const info = {
1724
+ namespace: store.namespace,
1725
+ isReady: store.isReady,
1726
+ keys: Object.keys(store.list()),
1727
+ size: Object.keys(store.list()).length
1728
+ };
1729
+ debugLog("[gstate] Store Info:", info);
1730
+ return info;
1731
+ },
1732
+ /** Clear console and show banner */
1733
+ banner: () => {
1734
+ debugLog(`
1735
+ \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557
1736
+ \u2551 \u{1F9F2} gState Debug \u2551
1737
+ \u2551 Type: gstate.list() \u2551
1738
+ \u2551 gstate.get(key) \u2551
1739
+ \u2551 gstate.set(key, value) \u2551
1740
+ \u2551 gstate.info() \u2551
1741
+ \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D
1742
+ `);
1743
+ }
1744
+ };
1745
+ debugLog("[gstate] Debug plugin installed. Type gstate.banner() for help.");
1746
+ }
1747
+ },
1748
+ onDestroy: () => {
1749
+ if (typeof window !== "undefined") {
1750
+ delete window.gstate;
1751
+ }
1752
+ }
1753
+ }
1754
+ };
1755
+ };
1756
+
1757
+ // plugins/official/indexeddb.plugin.ts
1758
+ var indexedDBPlugin = (options = {}) => {
1759
+ const dbName = options.dbName || "rgs-db";
1760
+ const storeName = options.storeName || "states";
1761
+ const dbVersion = options.version || 1;
1762
+ let db = null;
1763
+ const getDB = () => {
1764
+ return new Promise((resolve, reject) => {
1765
+ if (db) return resolve(db);
1766
+ const request = indexedDB.open(dbName, dbVersion);
1767
+ request.onerror = () => reject(request.error);
1768
+ request.onsuccess = () => {
1769
+ db = request.result;
1770
+ resolve(db);
1771
+ };
1772
+ request.onupgradeneeded = (event) => {
1773
+ const database = event.target.result;
1774
+ if (!database.objectStoreNames.contains(storeName)) {
1775
+ database.createObjectStore(storeName);
1776
+ }
1777
+ };
1778
+ });
1779
+ };
1780
+ const save = async (key, value) => {
1781
+ const database = await getDB();
1782
+ return new Promise((resolve, reject) => {
1783
+ const tx = database.transaction(storeName, "readwrite");
1784
+ const store = tx.objectStore(storeName);
1785
+ const request = store.put(value, key);
1786
+ request.onsuccess = () => resolve();
1787
+ request.onerror = () => reject(request.error);
1788
+ });
1789
+ };
1790
+ const load = async (key) => {
1791
+ const database = await getDB();
1792
+ return new Promise((resolve, reject) => {
1793
+ const tx = database.transaction(storeName, "readonly");
1794
+ const store = tx.objectStore(storeName);
1795
+ const request = store.get(key);
1796
+ request.onsuccess = () => resolve(request.result);
1797
+ request.onerror = () => reject(request.error);
1798
+ });
1799
+ };
1800
+ const remove = async (key) => {
1801
+ const database = await getDB();
1802
+ return new Promise((resolve, reject) => {
1803
+ const tx = database.transaction(storeName, "readwrite");
1804
+ const store = tx.objectStore(storeName);
1805
+ const request = store.delete(key);
1806
+ request.onsuccess = () => resolve();
1807
+ request.onerror = () => reject(request.error);
1808
+ });
1809
+ };
1810
+ return {
1811
+ name: "indexedDB",
1812
+ hooks: {
1813
+ onInstall: ({ store }) => {
1814
+ store._registerMethod("indexedDB", "clear", async () => {
1815
+ const database = await getDB();
1816
+ const tx = database.transaction(storeName, "readwrite");
1817
+ tx.objectStore(storeName).clear();
1818
+ });
1819
+ },
1820
+ onInit: async ({ store }) => {
1821
+ const database = await getDB();
1822
+ const tx = database.transaction(storeName, "readonly");
1823
+ const objectStore = tx.objectStore(storeName);
1824
+ const request = objectStore.getAllKeys();
1825
+ request.onsuccess = async () => {
1826
+ const keys = request.result;
1827
+ const prefix = store.namespace + "_";
1828
+ for (const key of keys) {
1829
+ if (key.startsWith(prefix)) {
1830
+ const val = await load(key);
1831
+ if (val) {
1832
+ const storeKey = key.substring(prefix.length);
1833
+ store._setSilently(storeKey, val.d);
1834
+ }
1835
+ }
1836
+ }
1837
+ };
1838
+ },
1839
+ onSet: async ({ key, value, store }) => {
1840
+ if (!key) return;
1841
+ const prefix = store.namespace + "_";
1842
+ const data = {
1843
+ d: value,
1844
+ t: Date.now(),
1845
+ v: store._getVersion?.(key) || 1
1846
+ };
1847
+ await save(`${prefix}${key}`, data);
1848
+ },
1849
+ onRemove: async ({ key, store }) => {
1850
+ if (!key) return;
1851
+ const prefix = store.namespace + "_";
1852
+ await remove(`${prefix}${key}`);
1853
+ }
1854
+ }
1855
+ };
1856
+ };
1857
+
1858
+ // plugins/official/cloud-sync.plugin.ts
1859
+ var cloudSyncPlugin = (options) => {
1860
+ const { adapter, autoSyncInterval } = options;
1861
+ const lastSyncedVersions = /* @__PURE__ */ new Map();
1862
+ const stats = {
1863
+ lastSyncTimestamp: null,
1864
+ totalKeysSynced: 0,
1865
+ totalBytesSynced: 0,
1866
+ syncCount: 0,
1867
+ lastDuration: 0,
1868
+ errors: 0
1869
+ };
1870
+ let timer = null;
1871
+ return {
1872
+ name: "cloudSync",
1873
+ hooks: {
1874
+ onInstall: ({ store }) => {
1875
+ store._registerMethod("cloudSync", "sync", async () => {
1876
+ const startTime = performance.now();
1877
+ const dirtyData = {};
1878
+ let bytesCount = 0;
1879
+ try {
1880
+ const allData = store.list();
1881
+ const keys = Object.keys(allData);
1882
+ for (const key of keys) {
1883
+ const currentVersion = store._getVersion?.(key) || 0;
1884
+ const lastVersion = lastSyncedVersions.get(key) || 0;
1885
+ if (currentVersion > lastVersion) {
1886
+ const val = allData[key];
1887
+ dirtyData[key] = val;
1888
+ bytesCount += JSON.stringify(val).length;
1889
+ lastSyncedVersions.set(key, currentVersion);
1890
+ }
1891
+ }
1892
+ if (Object.keys(dirtyData).length === 0) return { status: "no-change", stats };
1893
+ const success = await adapter.save(dirtyData);
1894
+ if (success) {
1895
+ stats.lastSyncTimestamp = Date.now();
1896
+ stats.totalKeysSynced += Object.keys(dirtyData).length;
1897
+ stats.totalBytesSynced += bytesCount;
1898
+ stats.syncCount++;
1899
+ stats.lastDuration = performance.now() - startTime;
1900
+ if (options.onSync) options.onSync(stats);
1901
+ return { status: "success", stats };
1902
+ } else {
1903
+ throw new Error(`Adapter ${adapter.name} failed to save.`);
1904
+ }
1905
+ } catch (err) {
1906
+ stats.errors++;
1907
+ console.error(`[gstate] Cloud Sync Failed (${adapter.name}):`, err);
1908
+ return { status: "error", error: String(err), stats };
1909
+ }
1910
+ });
1911
+ store._registerMethod("cloudSync", "getStats", () => stats);
1912
+ if (autoSyncInterval && autoSyncInterval > 0) {
1913
+ timer = setInterval(() => {
1914
+ const plugins = store.plugins;
1915
+ const cs = plugins.cloudSync;
1916
+ if (cs) cs.sync();
1917
+ }, autoSyncInterval);
1918
+ }
1919
+ },
1920
+ onDestroy: () => {
1921
+ if (timer) clearInterval(timer);
1922
+ }
1923
+ }
1924
+ };
1925
+ };
1926
+ var createMongoAdapter = (apiUrl, apiKey) => ({
1927
+ name: "MongoDB-Atlas",
1928
+ save: async (data) => {
1929
+ const response = await fetch(`${apiUrl}/action/updateOne`, {
1930
+ method: "POST",
1931
+ headers: { "Content-Type": "application/json", "api-key": apiKey },
1932
+ body: JSON.stringify({
1933
+ dataSource: "Cluster0",
1934
+ database: "rgs_cloud",
1935
+ collection: "user_states",
1936
+ filter: { id: "global_state" },
1937
+ // Or specific user ID
1938
+ update: { $set: { data, updatedAt: Date.now() } },
1939
+ upsert: true
1940
+ })
1941
+ });
1942
+ return response.ok;
1943
+ }
1944
+ });
1945
+ var createFirestoreAdapter = (db, docPath) => ({
1946
+ name: "Firebase-Firestore",
1947
+ save: async (data) => {
1948
+ try {
1949
+ const isDev = !isProduction();
1950
+ const debugLog = (...args) => {
1951
+ if (isDev) console.debug(...args);
1952
+ };
1953
+ debugLog("[Mock] Firestore Syncing:", data);
1954
+ return true;
1955
+ } catch (e) {
1956
+ return false;
1957
+ }
1958
+ }
1959
+ });
1960
+ var createSqlRestAdapter = (endpoint, getAuthToken) => ({
1961
+ name: "SQL-REST-API",
1962
+ save: async (data) => {
1963
+ const authToken = getAuthToken();
1964
+ if (!authToken) {
1965
+ console.warn("[gstate] No auth token available for SQL-REST sync");
1966
+ return false;
1967
+ }
1968
+ const response = await fetch(endpoint, {
1969
+ method: "PATCH",
1970
+ headers: {
1971
+ "Content-Type": "application/json",
1972
+ // NOTE: In production, use HTTP-only cookies instead of Bearer tokens
1973
+ // This is provided as a template only - production should use secure auth
1974
+ "Authorization": `Bearer ${authToken}`
1975
+ },
1976
+ body: JSON.stringify(data),
1977
+ credentials: "same-origin"
1978
+ });
1979
+ return response.ok;
1980
+ }
1981
+ });
1982
+
1983
+ // plugins/index.ts
1984
+ var loggerPlugin = (options) => ({
1985
+ name: "gstate-logger",
1986
+ hooks: {
1987
+ onSet: ({ key, value, version }) => {
1988
+ const time = (/* @__PURE__ */ new Date()).toLocaleTimeString(), groupLabel = `[gstate] SET: ${key} (v${version}) @ ${time}`;
1989
+ if (options?.collapsed) console.groupCollapsed(groupLabel);
1990
+ else console.group(groupLabel);
1991
+ console.info("%c Value:", "color: #4CAF50; font-weight: bold;", value);
1992
+ console.groupEnd();
1993
+ },
1994
+ onRemove: ({ key }) => {
1995
+ console.warn(`[gstate] REMOVED: ${key}`);
1996
+ },
1997
+ onTransaction: ({ key }) => {
1998
+ if (key === "START") console.group("\u2500\u2500 TRANSACTION START \u2500\u2500");
1999
+ else console.groupEnd();
2000
+ }
2001
+ }
2002
+ });
2003
+
2004
+ // index.ts
2005
+ var gstate = (initialState, configOrNamespace) => {
2006
+ const config = typeof configOrNamespace === "string" ? { namespace: configOrNamespace } : configOrNamespace;
2007
+ const store = createStore(config);
2008
+ if (initialState) {
2009
+ Object.entries(initialState).forEach(([k, v]) => {
2010
+ if (store.get(k) === null) {
2011
+ store._setSilently(k, v);
2012
+ }
2013
+ });
2014
+ }
2015
+ const magic = (key) => useStore(key, store);
2016
+ if (typeof window !== "undefined" && isDevelopment()) {
2017
+ window.gstate = store;
2018
+ window.gState = store;
2019
+ window.rgs = store;
2020
+ }
2021
+ return Object.assign(magic, store);
2022
+ };
2023
+ var addAccessRule2 = (pattern, perms) => getStore()?.addAccessRule(pattern, perms);
2024
+ var hasPermission2 = (key, action, uid) => getStore()?.hasPermission(key, action, uid) ?? true;
2025
+ var recordConsent2 = (uid, p, g) => {
2026
+ const s = getStore();
2027
+ if (!s) throw new Error("[gstate] recordConsent failed: No store found. call initState() first.");
2028
+ return s.recordConsent(uid, p, g);
2029
+ };
2030
+ var hasConsent2 = (uid, p) => getStore()?.hasConsent(uid, p) ?? false;
2031
+ var getConsents2 = (uid) => getStore()?.getConsents(uid) ?? [];
2032
+ var revokeConsent2 = (uid, p) => getStore()?.revokeConsent(uid, p);
2033
+ var exportUserData2 = (uid) => {
2034
+ const s = getStore();
2035
+ if (!s) throw new Error("[gstate] exportUserData failed: No store found.");
2036
+ return s.exportUserData(uid);
2037
+ };
2038
+ var deleteUserData2 = (uid) => {
2039
+ const s = getStore();
2040
+ if (!s) throw new Error("[gstate] deleteUserData failed: No store found.");
2041
+ return s.deleteUserData(uid);
2042
+ };
2043
+ var clearAccessRules = () => {
2044
+ };
2045
+ var clearAllConsents = () => {
2046
+ };
2047
+ export {
2048
+ SyncEngine,
2049
+ addAccessRule2 as addAccessRule,
2050
+ analyticsPlugin,
2051
+ clearAccessRules,
2052
+ clearAllConsents,
2053
+ cloudSyncPlugin,
2054
+ createAsyncStore,
2055
+ createFirestoreAdapter,
2056
+ createMongoAdapter,
2057
+ createSqlRestAdapter,
2058
+ createStore,
2059
+ createSyncEngine,
2060
+ debugPlugin,
2061
+ deleteUserData2 as deleteUserData,
2062
+ deriveKeyFromPassword,
2063
+ destroyState,
2064
+ destroySync,
2065
+ devToolsPlugin,
2066
+ exportKey,
2067
+ exportUserData2 as exportUserData,
2068
+ generateEncryptionKey,
2069
+ generateSalt,
2070
+ getConsents2 as getConsents,
2071
+ getStore,
2072
+ gstate,
2073
+ guardPlugin,
2074
+ hasConsent2 as hasConsent,
2075
+ hasPermission2 as hasPermission,
2076
+ immerPlugin,
2077
+ importKey,
2078
+ indexedDBPlugin,
2079
+ initState,
2080
+ initSync,
2081
+ isCryptoAvailable,
2082
+ logAudit,
2083
+ loggerPlugin,
2084
+ recordConsent2 as recordConsent,
2085
+ revokeConsent2 as revokeConsent,
2086
+ sanitizeValue,
2087
+ schemaPlugin,
2088
+ setAuditLogger,
2089
+ snapshotPlugin,
2090
+ syncPlugin,
2091
+ triggerSync,
2092
+ undoRedoPlugin,
2093
+ useStore as useGState,
2094
+ useIsStoreReady,
2095
+ useStore as useSimpleState,
2096
+ useStore,
2097
+ useSyncStatus,
2098
+ useSyncedState,
2099
+ validateKey
2100
+ };