@fluixi/storage 0.1.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (134) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +30 -0
  3. package/dist/adapters/memory.cjs +46 -0
  4. package/dist/adapters/memory.d.ts +8 -0
  5. package/dist/adapters/memory.d.ts.map +1 -0
  6. package/dist/adapters/memory.js +26 -0
  7. package/dist/adapters/memory.mjs +25 -0
  8. package/dist/browser/document-cookie.cjs +101 -0
  9. package/dist/browser/document-cookie.d.ts +14 -0
  10. package/dist/browser/document-cookie.d.ts.map +1 -0
  11. package/dist/browser/document-cookie.js +53 -0
  12. package/dist/browser/document-cookie.mjs +80 -0
  13. package/dist/browser/index.cjs +209 -0
  14. package/dist/browser/index.d.ts +4 -0
  15. package/dist/browser/index.d.ts.map +1 -0
  16. package/dist/browser/index.js +3 -0
  17. package/dist/browser/index.mjs +187 -0
  18. package/dist/browser/web-storage.cjs +150 -0
  19. package/dist/browser/web-storage.d.ts +6 -0
  20. package/dist/browser/web-storage.d.ts.map +1 -0
  21. package/dist/browser/web-storage.js +105 -0
  22. package/dist/browser/web-storage.mjs +127 -0
  23. package/dist/core/async-storage.cjs +583 -0
  24. package/dist/core/async-storage.d.ts +17 -0
  25. package/dist/core/async-storage.d.ts.map +1 -0
  26. package/dist/core/async-storage.js +275 -0
  27. package/dist/core/async-storage.mjs +560 -0
  28. package/dist/core/bus.cjs +52 -0
  29. package/dist/core/bus.d.ts +19 -0
  30. package/dist/core/bus.d.ts.map +1 -0
  31. package/dist/core/bus.js +31 -0
  32. package/dist/core/bus.mjs +31 -0
  33. package/dist/core/envelope.cjs +119 -0
  34. package/dist/core/envelope.d.ts +38 -0
  35. package/dist/core/envelope.d.ts.map +1 -0
  36. package/dist/core/envelope.js +83 -0
  37. package/dist/core/envelope.mjs +96 -0
  38. package/dist/core/errors.cjs +115 -0
  39. package/dist/core/errors.d.ts +109 -0
  40. package/dist/core/errors.d.ts.map +1 -0
  41. package/dist/core/errors.js +99 -0
  42. package/dist/core/errors.mjs +94 -0
  43. package/dist/core/storage.cjs +782 -0
  44. package/dist/core/storage.d.ts +63 -0
  45. package/dist/core/storage.d.ts.map +1 -0
  46. package/dist/core/storage.js +260 -0
  47. package/dist/core/storage.mjs +759 -0
  48. package/dist/core/types.cjs +18 -0
  49. package/dist/core/types.d.ts +239 -0
  50. package/dist/core/types.d.ts.map +1 -0
  51. package/dist/core/types.js +1 -0
  52. package/dist/core/types.mjs +0 -0
  53. package/dist/encryption/base64.cjs +38 -0
  54. package/dist/encryption/base64.d.ts +4 -0
  55. package/dist/encryption/base64.d.ts.map +1 -0
  56. package/dist/encryption/base64.js +16 -0
  57. package/dist/encryption/base64.mjs +17 -0
  58. package/dist/encryption/keys.cjs +77 -0
  59. package/dist/encryption/keys.d.ts +14 -0
  60. package/dist/encryption/keys.d.ts.map +1 -0
  61. package/dist/encryption/keys.js +28 -0
  62. package/dist/encryption/keys.mjs +54 -0
  63. package/dist/encryption/seal.cjs +154 -0
  64. package/dist/encryption/seal.d.ts +24 -0
  65. package/dist/encryption/seal.d.ts.map +1 -0
  66. package/dist/encryption/seal.js +83 -0
  67. package/dist/encryption/seal.mjs +131 -0
  68. package/dist/encryption/types.cjs +18 -0
  69. package/dist/encryption/types.d.ts +50 -0
  70. package/dist/encryption/types.d.ts.map +1 -0
  71. package/dist/encryption/types.js +1 -0
  72. package/dist/encryption/types.mjs +0 -0
  73. package/dist/encryption/web-crypto.cjs +81 -0
  74. package/dist/encryption/web-crypto.d.ts +9 -0
  75. package/dist/encryption/web-crypto.d.ts.map +1 -0
  76. package/dist/encryption/web-crypto.js +45 -0
  77. package/dist/encryption/web-crypto.mjs +58 -0
  78. package/dist/index.cjs +870 -0
  79. package/dist/index.d.ts +16 -0
  80. package/dist/index.d.ts.map +1 -0
  81. package/dist/index.js +13 -0
  82. package/dist/index.mjs +848 -0
  83. package/dist/serialization/json.cjs +83 -0
  84. package/dist/serialization/json.d.ts +3 -0
  85. package/dist/serialization/json.d.ts.map +1 -0
  86. package/dist/serialization/json.js +51 -0
  87. package/dist/serialization/json.mjs +60 -0
  88. package/dist/serialization/serializer.cjs +28 -0
  89. package/dist/serialization/serializer.d.ts +4 -0
  90. package/dist/serialization/serializer.d.ts.map +1 -0
  91. package/dist/serialization/serializer.js +4 -0
  92. package/dist/serialization/serializer.mjs +7 -0
  93. package/dist/server/contracts.cjs +36 -0
  94. package/dist/server/contracts.d.ts +27 -0
  95. package/dist/server/contracts.d.ts.map +1 -0
  96. package/dist/server/contracts.js +16 -0
  97. package/dist/server/contracts.mjs +15 -0
  98. package/dist/server/cookie.cjs +126 -0
  99. package/dist/server/cookie.d.ts +21 -0
  100. package/dist/server/cookie.d.ts.map +1 -0
  101. package/dist/server/cookie.js +71 -0
  102. package/dist/server/cookie.mjs +105 -0
  103. package/dist/server/index.cjs +155 -0
  104. package/dist/server/index.d.ts +5 -0
  105. package/dist/server/index.d.ts.map +1 -0
  106. package/dist/server/index.js +4 -0
  107. package/dist/server/index.mjs +133 -0
  108. package/dist/server/request.cjs +46 -0
  109. package/dist/server/request.d.ts +7 -0
  110. package/dist/server/request.d.ts.map +1 -0
  111. package/dist/server/request.js +25 -0
  112. package/dist/server/request.mjs +25 -0
  113. package/dist/tsconfig.lib.tsbuildinfo +1 -0
  114. package/dist/utils/cookie-names.cjs +38 -0
  115. package/dist/utils/cookie-names.d.ts +10 -0
  116. package/dist/utils/cookie-names.d.ts.map +1 -0
  117. package/dist/utils/cookie-names.js +15 -0
  118. package/dist/utils/cookie-names.mjs +17 -0
  119. package/dist/utils/copy.cjs +38 -0
  120. package/dist/utils/copy.d.ts +17 -0
  121. package/dist/utils/copy.d.ts.map +1 -0
  122. package/dist/utils/copy.js +15 -0
  123. package/dist/utils/copy.mjs +17 -0
  124. package/dist/utils/environment.cjs +28 -0
  125. package/dist/utils/environment.d.ts +3 -0
  126. package/dist/utils/environment.d.ts.map +1 -0
  127. package/dist/utils/environment.js +4 -0
  128. package/dist/utils/environment.mjs +7 -0
  129. package/dist/utils/keys.cjs +33 -0
  130. package/dist/utils/keys.d.ts +5 -0
  131. package/dist/utils/keys.d.ts.map +1 -0
  132. package/dist/utils/keys.js +10 -0
  133. package/dist/utils/keys.mjs +12 -0
  134. package/package.json +61 -0
@@ -0,0 +1,187 @@
1
+ /*! @fluixi/storage v0.1.0-alpha.0 | (c) 2026 Ibrahima Touré and Fluixi contributors | MIT */
2
+
3
+ // src/core/errors.ts
4
+ var StorageError = class extends Error {
5
+ constructor(code, message, options = {}) {
6
+ super(message);
7
+ this.name = new.target.name;
8
+ this.code = code;
9
+ this.key = options.key;
10
+ if (options.cause !== void 0) this.cause = options.cause;
11
+ }
12
+ };
13
+ var StorageUnavailableError = class extends StorageError {
14
+ constructor(message, options = {}) {
15
+ super("unavailable", message, options);
16
+ }
17
+ };
18
+ var StorageQuotaError = class extends StorageError {
19
+ constructor(message, options = {}) {
20
+ super("quota", message, options);
21
+ }
22
+ };
23
+ var StorageSecurityError = class extends StorageError {
24
+ constructor(message, options = {}) {
25
+ super("security", message, options);
26
+ }
27
+ };
28
+ var StorageAdapterError = class extends StorageError {
29
+ constructor(message, options = {}) {
30
+ super("adapter", message, options);
31
+ }
32
+ };
33
+
34
+ // src/utils/environment.ts
35
+ function hasWindow() {
36
+ return typeof window !== "undefined";
37
+ }
38
+
39
+ // src/browser/web-storage.ts
40
+ function mapError(error, area, key) {
41
+ if (error instanceof StorageError) return error;
42
+ const name = error?.name;
43
+ const code = error?.code;
44
+ if (name === "QuotaExceededError" || name === "NS_ERROR_DOM_QUOTA_REACHED" || code === 22 || code === 1014) {
45
+ return new StorageQuotaError(`${area} is full${key ? ` writing ${key}` : ""}.`, { key, cause: error });
46
+ }
47
+ if (name === "SecurityError") {
48
+ return new StorageSecurityError(`${area} is blocked by the browser (privacy settings or a sandboxed frame).`, { key, cause: error });
49
+ }
50
+ return new StorageAdapterError(`${area} failed${key ? ` on ${key}` : ""}.`, { key, cause: error });
51
+ }
52
+ function webStorage(area, capabilities) {
53
+ const resolve = (key) => {
54
+ let storage;
55
+ try {
56
+ storage = globalThis[area];
57
+ } catch (error) {
58
+ throw mapError(error, area, key);
59
+ }
60
+ if (!storage) throw new StorageUnavailableError(`${area} is not available here (a server, a worker, or a browser without it).`, { key });
61
+ return storage;
62
+ };
63
+ const run = (key, fn) => {
64
+ const storage = resolve(key);
65
+ try {
66
+ return fn(storage);
67
+ } catch (error) {
68
+ throw mapError(error, area, key);
69
+ }
70
+ };
71
+ return {
72
+ name: area,
73
+ scope: area === "localStorage" ? "local" : "session",
74
+ capabilities,
75
+ get: (key) => run(key, (s) => s.getItem(key)),
76
+ set: (key, value) => run(key, (s) => s.setItem(key, value)),
77
+ remove: (key) => run(key, (s) => s.removeItem(key)),
78
+ has: (key) => run(key, (s) => s.getItem(key) !== null),
79
+ keys: () => run(void 0, (s) => {
80
+ const keys = [];
81
+ for (let i = 0; i < s.length; i++) {
82
+ const key = s.key(i);
83
+ if (key !== null) keys.push(key);
84
+ }
85
+ return keys;
86
+ }),
87
+ clear: () => run(void 0, (s) => s.clear()),
88
+ subscribe(listener) {
89
+ if (!hasWindow()) return () => {
90
+ };
91
+ const onStorage = (event) => {
92
+ let storage;
93
+ try {
94
+ storage = resolve();
95
+ } catch {
96
+ return;
97
+ }
98
+ if (event.storageArea !== storage) return;
99
+ listener({ key: event.key, value: event.newValue });
100
+ };
101
+ window.addEventListener("storage", onStorage);
102
+ return () => window.removeEventListener("storage", onStorage);
103
+ }
104
+ };
105
+ }
106
+ function localStorageAdapter() {
107
+ return webStorage("localStorage", {
108
+ synchronous: true,
109
+ persistent: true,
110
+ reactive: true,
111
+ crossContext: true,
112
+ expiration: false,
113
+ transactions: false
114
+ });
115
+ }
116
+ function sessionStorageAdapter() {
117
+ return webStorage("sessionStorage", {
118
+ synchronous: true,
119
+ persistent: false,
120
+ reactive: false,
121
+ crossContext: false,
122
+ expiration: false,
123
+ transactions: false
124
+ });
125
+ }
126
+
127
+ // src/browser/document-cookie.ts
128
+ import { parseCookies, serializeCookie } from "@fluixi/utils/cookie";
129
+
130
+ // src/utils/cookie-names.ts
131
+ var encodeCookieName = (key) => encodeURIComponent(key).replace(/[()]/g, (c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`);
132
+ var decodeCookieName = (name) => {
133
+ try {
134
+ return decodeURIComponent(name);
135
+ } catch {
136
+ return name;
137
+ }
138
+ };
139
+ var cookiePairSize = (header) => new TextEncoder().encode(header.slice(0, header.indexOf(";"))).length;
140
+ var withEntryExpiry = (cookie, expiresAt) => expiresAt === void 0 ? cookie : { ...cookie, maxAge: void 0, expires: new Date(expiresAt) };
141
+
142
+ // src/browser/document-cookie.ts
143
+ function documentCookieAdapter(options = {}) {
144
+ const { maxSize = 4096, ...cookie } = options;
145
+ const doc = (key) => {
146
+ if (typeof document === "undefined") throw new StorageUnavailableError("document.cookie is not available here; on a server use cookieStorage.", { key });
147
+ return document;
148
+ };
149
+ const all = () => {
150
+ const out = /* @__PURE__ */ new Map();
151
+ for (const [name, value] of Object.entries(parseCookies(doc().cookie))) out.set(decodeCookieName(name), value);
152
+ return out;
153
+ };
154
+ const write = (key, header) => {
155
+ doc(key).cookie = header;
156
+ };
157
+ return {
158
+ name: "documentCookie",
159
+ scope: "cookie",
160
+ capabilities: {
161
+ synchronous: true,
162
+ persistent: true,
163
+ reactive: false,
164
+ crossContext: true,
165
+ expiration: false,
166
+ transactions: false
167
+ },
168
+ get: (key) => all().get(key) ?? null,
169
+ set(key, value, writeOptions) {
170
+ const header = serializeCookie(encodeCookieName(key), value, { ...withEntryExpiry(cookie, writeOptions?.expiresAt), httpOnly: false });
171
+ const size = cookiePairSize(header);
172
+ if (size > maxSize) throw new StorageQuotaError(`Cookie ${key} is ${size} bytes, over the ${maxSize} limit.`, { key });
173
+ write(key, header);
174
+ },
175
+ remove: (key) => write(key, serializeCookie(encodeCookieName(key), "", { ...cookie, httpOnly: false, maxAge: 0, expires: /* @__PURE__ */ new Date(0) })),
176
+ has: (key) => all().has(key),
177
+ keys: () => [...all().keys()],
178
+ clear() {
179
+ for (const key of all().keys()) this.remove(key);
180
+ }
181
+ };
182
+ }
183
+ export {
184
+ documentCookieAdapter,
185
+ localStorageAdapter,
186
+ sessionStorageAdapter
187
+ };
@@ -0,0 +1,150 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/browser/web-storage.ts
21
+ var web_storage_exports = {};
22
+ __export(web_storage_exports, {
23
+ localStorageAdapter: () => localStorageAdapter,
24
+ sessionStorageAdapter: () => sessionStorageAdapter
25
+ });
26
+ module.exports = __toCommonJS(web_storage_exports);
27
+
28
+ // src/core/errors.ts
29
+ var StorageError = class extends Error {
30
+ constructor(code, message, options = {}) {
31
+ super(message);
32
+ this.name = new.target.name;
33
+ this.code = code;
34
+ this.key = options.key;
35
+ if (options.cause !== void 0) this.cause = options.cause;
36
+ }
37
+ };
38
+ var StorageUnavailableError = class extends StorageError {
39
+ constructor(message, options = {}) {
40
+ super("unavailable", message, options);
41
+ }
42
+ };
43
+ var StorageQuotaError = class extends StorageError {
44
+ constructor(message, options = {}) {
45
+ super("quota", message, options);
46
+ }
47
+ };
48
+ var StorageSecurityError = class extends StorageError {
49
+ constructor(message, options = {}) {
50
+ super("security", message, options);
51
+ }
52
+ };
53
+ var StorageAdapterError = class extends StorageError {
54
+ constructor(message, options = {}) {
55
+ super("adapter", message, options);
56
+ }
57
+ };
58
+
59
+ // src/utils/environment.ts
60
+ function hasWindow() {
61
+ return typeof window !== "undefined";
62
+ }
63
+
64
+ // src/browser/web-storage.ts
65
+ function mapError(error, area, key) {
66
+ if (error instanceof StorageError) return error;
67
+ const name = error?.name;
68
+ const code = error?.code;
69
+ if (name === "QuotaExceededError" || name === "NS_ERROR_DOM_QUOTA_REACHED" || code === 22 || code === 1014) {
70
+ return new StorageQuotaError(`${area} is full${key ? ` writing ${key}` : ""}.`, { key, cause: error });
71
+ }
72
+ if (name === "SecurityError") {
73
+ return new StorageSecurityError(`${area} is blocked by the browser (privacy settings or a sandboxed frame).`, { key, cause: error });
74
+ }
75
+ return new StorageAdapterError(`${area} failed${key ? ` on ${key}` : ""}.`, { key, cause: error });
76
+ }
77
+ function webStorage(area, capabilities) {
78
+ const resolve = (key) => {
79
+ let storage;
80
+ try {
81
+ storage = globalThis[area];
82
+ } catch (error) {
83
+ throw mapError(error, area, key);
84
+ }
85
+ if (!storage) throw new StorageUnavailableError(`${area} is not available here (a server, a worker, or a browser without it).`, { key });
86
+ return storage;
87
+ };
88
+ const run = (key, fn) => {
89
+ const storage = resolve(key);
90
+ try {
91
+ return fn(storage);
92
+ } catch (error) {
93
+ throw mapError(error, area, key);
94
+ }
95
+ };
96
+ return {
97
+ name: area,
98
+ scope: area === "localStorage" ? "local" : "session",
99
+ capabilities,
100
+ get: (key) => run(key, (s) => s.getItem(key)),
101
+ set: (key, value) => run(key, (s) => s.setItem(key, value)),
102
+ remove: (key) => run(key, (s) => s.removeItem(key)),
103
+ has: (key) => run(key, (s) => s.getItem(key) !== null),
104
+ keys: () => run(void 0, (s) => {
105
+ const keys = [];
106
+ for (let i = 0; i < s.length; i++) {
107
+ const key = s.key(i);
108
+ if (key !== null) keys.push(key);
109
+ }
110
+ return keys;
111
+ }),
112
+ clear: () => run(void 0, (s) => s.clear()),
113
+ subscribe(listener) {
114
+ if (!hasWindow()) return () => {
115
+ };
116
+ const onStorage = (event) => {
117
+ let storage;
118
+ try {
119
+ storage = resolve();
120
+ } catch {
121
+ return;
122
+ }
123
+ if (event.storageArea !== storage) return;
124
+ listener({ key: event.key, value: event.newValue });
125
+ };
126
+ window.addEventListener("storage", onStorage);
127
+ return () => window.removeEventListener("storage", onStorage);
128
+ }
129
+ };
130
+ }
131
+ function localStorageAdapter() {
132
+ return webStorage("localStorage", {
133
+ synchronous: true,
134
+ persistent: true,
135
+ reactive: true,
136
+ crossContext: true,
137
+ expiration: false,
138
+ transactions: false
139
+ });
140
+ }
141
+ function sessionStorageAdapter() {
142
+ return webStorage("sessionStorage", {
143
+ synchronous: true,
144
+ persistent: false,
145
+ reactive: false,
146
+ crossContext: false,
147
+ expiration: false,
148
+ transactions: false
149
+ });
150
+ }
@@ -0,0 +1,6 @@
1
+ import type { StorageAdapter } from '../core/types.js';
2
+ /** Survives a browser restart; shared by every tab of the origin, which see each other's writes. */
3
+ export declare function localStorageAdapter(): StorageAdapter;
4
+ /** One tab's storage: survives a reload of that tab, not its closing. Not shared between tabs. */
5
+ export declare function sessionStorageAdapter(): StorageAdapter;
6
+ //# sourceMappingURL=web-storage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"web-storage.d.ts","sourceRoot":"","sources":["../../src/browser/web-storage.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAiB,cAAc,EAAuB,MAAM,kBAAkB,CAAC;AA6E3F,oGAAoG;AACpG,wBAAgB,mBAAmB,IAAI,cAAc,CASpD;AAED,kGAAkG;AAClG,wBAAgB,qBAAqB,IAAI,cAAc,CAStD"}
@@ -0,0 +1,105 @@
1
+ /**
2
+ * localStorage and sessionStorage. The browser global is read when the adapter is first used,
3
+ * never when it is created or imported, so a module that builds one is safe to load on a server;
4
+ * using it there throws `StorageUnavailableError`.
5
+ */
6
+ import { StorageAdapterError, StorageError, StorageQuotaError, StorageSecurityError, StorageUnavailableError } from '../core/errors.js';
7
+ import { hasWindow } from '../utils/environment.js';
8
+ /** The browser's own exception, as one of ours. */
9
+ function mapError(error, area, key) {
10
+ if (error instanceof StorageError)
11
+ return error;
12
+ const name = error?.name;
13
+ const code = error?.code;
14
+ if (name === 'QuotaExceededError' || name === 'NS_ERROR_DOM_QUOTA_REACHED' || code === 22 || code === 1014) {
15
+ return new StorageQuotaError(`${area} is full${key ? ` writing ${key}` : ''}.`, { key, cause: error });
16
+ }
17
+ if (name === 'SecurityError') {
18
+ return new StorageSecurityError(`${area} is blocked by the browser (privacy settings or a sandboxed frame).`, { key, cause: error });
19
+ }
20
+ return new StorageAdapterError(`${area} failed${key ? ` on ${key}` : ''}.`, { key, cause: error });
21
+ }
22
+ function webStorage(area, capabilities) {
23
+ const resolve = (key) => {
24
+ let storage;
25
+ try {
26
+ storage = globalThis[area];
27
+ }
28
+ catch (error) {
29
+ // Some browsers throw on the property access itself when storage is disabled.
30
+ throw mapError(error, area, key);
31
+ }
32
+ if (!storage)
33
+ throw new StorageUnavailableError(`${area} is not available here (a server, a worker, or a browser without it).`, { key });
34
+ return storage;
35
+ };
36
+ const run = (key, fn) => {
37
+ const storage = resolve(key);
38
+ try {
39
+ return fn(storage);
40
+ }
41
+ catch (error) {
42
+ throw mapError(error, area, key);
43
+ }
44
+ };
45
+ return {
46
+ name: area,
47
+ scope: area === 'localStorage' ? 'local' : 'session',
48
+ capabilities,
49
+ get: (key) => run(key, (s) => s.getItem(key)),
50
+ set: (key, value) => run(key, (s) => s.setItem(key, value)),
51
+ remove: (key) => run(key, (s) => s.removeItem(key)),
52
+ has: (key) => run(key, (s) => s.getItem(key) !== null),
53
+ keys: () => run(undefined, (s) => {
54
+ const keys = [];
55
+ for (let i = 0; i < s.length; i++) {
56
+ const key = s.key(i);
57
+ if (key !== null)
58
+ keys.push(key);
59
+ }
60
+ return keys;
61
+ }),
62
+ clear: () => run(undefined, (s) => s.clear()),
63
+ subscribe(listener) {
64
+ // The browser reports a write from another tab of the same origin; never this tab's own.
65
+ if (!hasWindow())
66
+ return () => { };
67
+ const onStorage = (event) => {
68
+ let storage;
69
+ try {
70
+ storage = resolve();
71
+ }
72
+ catch {
73
+ return;
74
+ }
75
+ if (event.storageArea !== storage)
76
+ return;
77
+ listener({ key: event.key, value: event.newValue });
78
+ };
79
+ window.addEventListener('storage', onStorage);
80
+ return () => window.removeEventListener('storage', onStorage);
81
+ },
82
+ };
83
+ }
84
+ /** Survives a browser restart; shared by every tab of the origin, which see each other's writes. */
85
+ export function localStorageAdapter() {
86
+ return webStorage('localStorage', {
87
+ synchronous: true,
88
+ persistent: true,
89
+ reactive: true,
90
+ crossContext: true,
91
+ expiration: false,
92
+ transactions: false,
93
+ });
94
+ }
95
+ /** One tab's storage: survives a reload of that tab, not its closing. Not shared between tabs. */
96
+ export function sessionStorageAdapter() {
97
+ return webStorage('sessionStorage', {
98
+ synchronous: true,
99
+ persistent: false,
100
+ reactive: false,
101
+ crossContext: false,
102
+ expiration: false,
103
+ transactions: false,
104
+ });
105
+ }
@@ -0,0 +1,127 @@
1
+ // src/core/errors.ts
2
+ var StorageError = class extends Error {
3
+ constructor(code, message, options = {}) {
4
+ super(message);
5
+ this.name = new.target.name;
6
+ this.code = code;
7
+ this.key = options.key;
8
+ if (options.cause !== void 0) this.cause = options.cause;
9
+ }
10
+ };
11
+ var StorageUnavailableError = class extends StorageError {
12
+ constructor(message, options = {}) {
13
+ super("unavailable", message, options);
14
+ }
15
+ };
16
+ var StorageQuotaError = class extends StorageError {
17
+ constructor(message, options = {}) {
18
+ super("quota", message, options);
19
+ }
20
+ };
21
+ var StorageSecurityError = class extends StorageError {
22
+ constructor(message, options = {}) {
23
+ super("security", message, options);
24
+ }
25
+ };
26
+ var StorageAdapterError = class extends StorageError {
27
+ constructor(message, options = {}) {
28
+ super("adapter", message, options);
29
+ }
30
+ };
31
+
32
+ // src/utils/environment.ts
33
+ function hasWindow() {
34
+ return typeof window !== "undefined";
35
+ }
36
+
37
+ // src/browser/web-storage.ts
38
+ function mapError(error, area, key) {
39
+ if (error instanceof StorageError) return error;
40
+ const name = error?.name;
41
+ const code = error?.code;
42
+ if (name === "QuotaExceededError" || name === "NS_ERROR_DOM_QUOTA_REACHED" || code === 22 || code === 1014) {
43
+ return new StorageQuotaError(`${area} is full${key ? ` writing ${key}` : ""}.`, { key, cause: error });
44
+ }
45
+ if (name === "SecurityError") {
46
+ return new StorageSecurityError(`${area} is blocked by the browser (privacy settings or a sandboxed frame).`, { key, cause: error });
47
+ }
48
+ return new StorageAdapterError(`${area} failed${key ? ` on ${key}` : ""}.`, { key, cause: error });
49
+ }
50
+ function webStorage(area, capabilities) {
51
+ const resolve = (key) => {
52
+ let storage;
53
+ try {
54
+ storage = globalThis[area];
55
+ } catch (error) {
56
+ throw mapError(error, area, key);
57
+ }
58
+ if (!storage) throw new StorageUnavailableError(`${area} is not available here (a server, a worker, or a browser without it).`, { key });
59
+ return storage;
60
+ };
61
+ const run = (key, fn) => {
62
+ const storage = resolve(key);
63
+ try {
64
+ return fn(storage);
65
+ } catch (error) {
66
+ throw mapError(error, area, key);
67
+ }
68
+ };
69
+ return {
70
+ name: area,
71
+ scope: area === "localStorage" ? "local" : "session",
72
+ capabilities,
73
+ get: (key) => run(key, (s) => s.getItem(key)),
74
+ set: (key, value) => run(key, (s) => s.setItem(key, value)),
75
+ remove: (key) => run(key, (s) => s.removeItem(key)),
76
+ has: (key) => run(key, (s) => s.getItem(key) !== null),
77
+ keys: () => run(void 0, (s) => {
78
+ const keys = [];
79
+ for (let i = 0; i < s.length; i++) {
80
+ const key = s.key(i);
81
+ if (key !== null) keys.push(key);
82
+ }
83
+ return keys;
84
+ }),
85
+ clear: () => run(void 0, (s) => s.clear()),
86
+ subscribe(listener) {
87
+ if (!hasWindow()) return () => {
88
+ };
89
+ const onStorage = (event) => {
90
+ let storage;
91
+ try {
92
+ storage = resolve();
93
+ } catch {
94
+ return;
95
+ }
96
+ if (event.storageArea !== storage) return;
97
+ listener({ key: event.key, value: event.newValue });
98
+ };
99
+ window.addEventListener("storage", onStorage);
100
+ return () => window.removeEventListener("storage", onStorage);
101
+ }
102
+ };
103
+ }
104
+ function localStorageAdapter() {
105
+ return webStorage("localStorage", {
106
+ synchronous: true,
107
+ persistent: true,
108
+ reactive: true,
109
+ crossContext: true,
110
+ expiration: false,
111
+ transactions: false
112
+ });
113
+ }
114
+ function sessionStorageAdapter() {
115
+ return webStorage("sessionStorage", {
116
+ synchronous: true,
117
+ persistent: false,
118
+ reactive: false,
119
+ crossContext: false,
120
+ expiration: false,
121
+ transactions: false
122
+ });
123
+ }
124
+ export {
125
+ localStorageAdapter,
126
+ sessionStorageAdapter
127
+ };