memorio 5.0.0 → 5.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/README.md +80 -449
  2. package/SECURITY.md +152 -42
  3. package/SUMMARY.md +1 -1
  4. package/adr/001-state-proxy-model.md +95 -96
  5. package/adr/002-observer-semantics.md +179 -180
  6. package/adr/003-deep-mutation-semantics.md +7 -8
  7. package/adr/004-array-mutation-semantics.md +127 -128
  8. package/adr/005-scheduler-contract.md +148 -149
  9. package/adr/006-context-isolation.md +91 -92
  10. package/adr/007-mutation-records.md +5 -6
  11. package/adr/008-transactions.md +6 -7
  12. package/adr/009-history-model.md +6 -7
  13. package/adr/README.md +46 -46
  14. package/adr/template.md +48 -49
  15. package/bin/cli.js +68 -0
  16. package/global.cjs +1462 -323
  17. package/global.js +1459 -324
  18. package/index.cjs +1462 -323
  19. package/index.d.ts +1 -0
  20. package/index.js +1459 -324
  21. package/llms.txt +42 -5
  22. package/markdown/AUDIT-REPORT.md +7 -8
  23. package/markdown/CACHE.md +190 -99
  24. package/markdown/DEVTOOLS.md +0 -1
  25. package/markdown/DISPATCH.md +0 -1
  26. package/markdown/HISTORY.md +0 -1
  27. package/markdown/IDB.md +0 -1
  28. package/markdown/IMPORT.md +0 -1
  29. package/markdown/INSPECT.md +0 -1
  30. package/markdown/LOGGER.md +0 -1
  31. package/markdown/MEMORY-ATTACHMENT.md +0 -1
  32. package/markdown/MEMORY.md +0 -1
  33. package/markdown/OBSERVER.md +0 -1
  34. package/markdown/PLATFORM.md +277 -271
  35. package/markdown/REDUX.md +54 -0
  36. package/markdown/SCHEMA.md +0 -1
  37. package/markdown/SESSION.md +0 -1
  38. package/markdown/SQLITE.md +0 -1
  39. package/markdown/STATE.md +0 -1
  40. package/markdown/STORE.md +0 -1
  41. package/markdown/SYNC.md +0 -1
  42. package/markdown/TYPED.md +0 -1
  43. package/markdown/USEOBSERVER.md +0 -1
  44. package/modules/redux.cjs +381 -10
  45. package/modules/redux.cjs.map +1 -1
  46. package/modules/redux.js +381 -10
  47. package/modules/redux.js.map +1 -1
  48. package/package.json +14 -2
  49. package/types/broadcast.d.ts +61 -0
  50. package/types/computed.d.ts +96 -0
  51. package/types/encryption.d.ts +129 -0
  52. package/types/exports.d.ts +9 -0
  53. package/types/memorio.d.ts +19 -12
  54. package/types/security.d.ts +67 -0
  55. package/types/session.d.ts +23 -5
  56. package/types/store.d.ts +19 -3
  57. package/vsix/memorio.vsix +0 -0
  58. package/markdown/CHANGELOG.md +0 -243
  59. package/markdown/PROJECT.md +0 -311
  60. package/markdown/SECURITY.md +0 -330
package/markdown/TYPED.md CHANGED
@@ -1,6 +1,5 @@
1
1
  > **Status:** Published
2
2
  > **Date:** 2026-09-12
3
- > **Deciders:** Memorio 5.x Core Team
4
3
  > **Scope**: API Reference
5
4
  > **Standard**: Memorio API Specification v5
6
5
  >
@@ -1,6 +1,5 @@
1
1
  > **Status:** Published
2
2
  > **Date:** 2026-09-12
3
- > **Deciders:** Memorio 5.x Core Team
4
3
  > **Scope**: API Reference
5
4
  > **Standard**: Memorio API Specification v5
6
5
  >
package/modules/redux.cjs CHANGED
@@ -201,6 +201,241 @@ var init_message = __esm({
201
201
  }
202
202
  });
203
203
 
204
+ // functions/encryption/index.ts
205
+ function _hasWebCrypto() {
206
+ const g2 = globalThis;
207
+ return typeof g2.crypto !== "undefined" && typeof g2.crypto.subtle !== "undefined" && typeof g2.crypto.subtle.encrypt === "function";
208
+ }
209
+ function _b64encode(buf) {
210
+ const bytes = buf instanceof Uint8Array ? buf : new Uint8Array(buf);
211
+ if (typeof btoa === "function") {
212
+ let binary = "";
213
+ for (let i = 0; i < bytes.length; i++) binary += String.fromCharCode(bytes[i]);
214
+ return btoa(binary);
215
+ }
216
+ if (typeof Buffer !== "undefined") {
217
+ return Buffer.from(bytes).toString("base64");
218
+ }
219
+ return "";
220
+ }
221
+ function _b64decode(b64) {
222
+ if (typeof Buffer !== "undefined") {
223
+ return new Uint8Array(Buffer.from(b64, "base64"));
224
+ }
225
+ if (typeof atob === "function") {
226
+ const binary = atob(b64);
227
+ const out = new Uint8Array(binary.length);
228
+ for (let i = 0; i < binary.length; i++) out[i] = binary.charCodeAt(i);
229
+ return out;
230
+ }
231
+ throw new Error("memorio encryption: no base64 decoder available");
232
+ }
233
+ function _isEncrypted(value) {
234
+ return typeof value === "object" && value !== null && value.__memorio_encrypted === true && typeof value.alg === "string";
235
+ }
236
+ var AES_GCM, PBKDF2_ITERATIONS, encryption;
237
+ var init_encryption = __esm({
238
+ "functions/encryption/index.ts"() {
239
+ init_cjs_shims();
240
+ init_env();
241
+ init_message();
242
+ __name(_hasWebCrypto, "_hasWebCrypto");
243
+ __name(_b64encode, "_b64encode");
244
+ __name(_b64decode, "_b64decode");
245
+ __name(_isEncrypted, "_isEncrypted");
246
+ AES_GCM = { name: "AES-GCM", length: 256 };
247
+ PBKDF2_ITERATIONS = 6e5;
248
+ encryption = {
249
+ ///
250
+ // Capability detection
251
+ ///
252
+ isAvailable() {
253
+ return _hasWebCrypto();
254
+ },
255
+ ///
256
+ // Derive a CryptoKey from a password using PBKDF2.
257
+ //
258
+ // @param password The password string.
259
+ // @param salt Salt (string or Uint8Array). Defaults to a static value.
260
+ // @param alg 'AES-GCM' (default) or 'AES-CBC'.
261
+ // @returns A Promise<CryptoKey>.
262
+ ///
263
+ async deriveKey(password, salt, alg = "AES-GCM", iterations = PBKDF2_ITERATIONS) {
264
+ if (!_hasWebCrypto()) {
265
+ throw new Error("memorio encryption: Web Crypto API is not available in this environment");
266
+ }
267
+ const enc = new TextEncoder();
268
+ const saltBytes = typeof salt === "string" ? enc.encode(salt) : salt ?? enc.encode("memorio:salt");
269
+ const keyMaterial = await globalThis.crypto.subtle.importKey(
270
+ "raw",
271
+ enc.encode(password),
272
+ { name: "PBKDF2" },
273
+ false,
274
+ ["deriveKey"]
275
+ );
276
+ return globalThis.crypto.subtle.deriveKey(
277
+ { name: "PBKDF2", salt: saltBytes, iterations, hash: "SHA-256" },
278
+ keyMaterial,
279
+ { name: alg, length: 256 },
280
+ false,
281
+ ["encrypt", "decrypt"]
282
+ );
283
+ },
284
+ ///
285
+ // Generate a fresh random CryptoKey (AES-GCM, 256-bit).
286
+ ///
287
+ async generateKey(alg = "AES-GCM") {
288
+ if (!_hasWebCrypto()) {
289
+ throw new Error("memorio encryption: Web Crypto API is not available in this environment");
290
+ }
291
+ return globalThis.crypto.subtle.generateKey(
292
+ { name: alg, length: 256 },
293
+ true,
294
+ ["encrypt", "decrypt"]
295
+ );
296
+ },
297
+ ///
298
+ // Generate a random salt for PBKDF2 key derivation.
299
+ ///
300
+ generateSalt(bytes = 16) {
301
+ const arr = new Uint8Array(bytes);
302
+ globalThis.crypto?.getRandomValues?.(arr);
303
+ return _b64encode(arr);
304
+ },
305
+ ///
306
+ // Export a CryptoKey to portable base64 strings so it can be stored
307
+ // (e.g. in an httpOnly cookie) and re-imported later with importKey().
308
+ //
309
+ // @param cryptoKey A raw CryptoKey (not a password string).
310
+ // @returns { key: string, iv: string } base64-encoded.
311
+ ///
312
+ async exportKey(cryptoKey) {
313
+ if (!_hasWebCrypto()) {
314
+ throw new Error("memorio encryption: Web Crypto API is not available in this environment");
315
+ }
316
+ const exported = await globalThis.crypto.subtle.exportKey("raw", cryptoKey);
317
+ return {
318
+ key: _b64encode(exported),
319
+ iv: ""
320
+ };
321
+ },
322
+ ///
323
+ // Import a CryptoKey from base64 strings produced by exportKey().
324
+ //
325
+ // @param keyData Base64-encoded raw key bytes.
326
+ // @param _ivData Ignored (kept for API compatibility with exportKey return).
327
+ // @returns A Promise<CryptoKey>.
328
+ ///
329
+ async importKey(keyData, _ivData) {
330
+ if (!_hasWebCrypto()) {
331
+ throw new Error("memorio encryption: Web Crypto API is not available in this environment");
332
+ }
333
+ const keyBytes = _b64decode(keyData);
334
+ if (keyBytes.length !== 32) {
335
+ throw new Error(`memorio encryption: AES-256 key must be exactly 32 bytes, got ${keyBytes.length}`);
336
+ }
337
+ return globalThis.crypto.subtle.importKey(
338
+ "raw",
339
+ keyBytes,
340
+ AES_GCM,
341
+ true,
342
+ ["encrypt", "decrypt"]
343
+ );
344
+ },
345
+ ///
346
+ // Encrypt a JavaScript value.
347
+ //
348
+ // @param value The value to encrypt (any JSON-serializable object).
349
+ // @param key A CryptoKey or a password string (password is derived via PBKDF2).
350
+ // @param keyId Optional label, stored in the envelope for key rotation.
351
+ // @returns An EncryptedEnvelope object.
352
+ ///
353
+ async encrypt(value, key, keyId) {
354
+ const cryptoKey = typeof key === "string" ? await this.deriveKey(key, void 0, "AES-GCM") : key;
355
+ const data = new TextEncoder().encode(JSON.stringify(value));
356
+ const iv = new Uint8Array(12);
357
+ globalThis.crypto.getRandomValues(iv);
358
+ const ciphertext = await globalThis.crypto.subtle.encrypt(
359
+ { name: "AES-GCM", iv },
360
+ cryptoKey,
361
+ data
362
+ );
363
+ const envelope = {
364
+ __memorio_encrypted: true,
365
+ v: 1,
366
+ alg: "AES-GCM",
367
+ iv: _b64encode(iv),
368
+ data: _b64encode(ciphertext)
369
+ };
370
+ if (keyId) envelope.keyId = keyId;
371
+ if (DEV) message_default(`encryption: encrypted value (alg=${envelope.alg})`);
372
+ return envelope;
373
+ },
374
+ ///
375
+ // Decrypt an EncryptedEnvelope.
376
+ //
377
+ // @param envelope The envelope object returned by encrypt().
378
+ // @param key A CryptoKey or a password string.
379
+ // @returns The original value (parsed from JSON).
380
+ ///
381
+ async decrypt(envelope, key) {
382
+ if (!_isEncrypted(envelope)) {
383
+ throw new Error("memorio encryption: value is not a valid encrypted envelope");
384
+ }
385
+ const cryptoKey = typeof key === "string" ? await this.deriveKey(key, void 0, "AES-GCM") : key;
386
+ const iv = _b64decode(envelope.iv);
387
+ const data = _b64decode(envelope.data);
388
+ const decrypted = await globalThis.crypto.subtle.decrypt(
389
+ { name: "AES-GCM", iv },
390
+ cryptoKey,
391
+ data
392
+ );
393
+ return JSON.parse(new TextDecoder().decode(new Uint8Array(decrypted)));
394
+ },
395
+ ///
396
+ // Convenience: encrypt with a password (derives the key internally).
397
+ // Returns a JSON string you can store anywhere.
398
+ ///
399
+ async encryptWithPassword(value, password, salt, keyId) {
400
+ const env = await this.encrypt(value, password, keyId);
401
+ return JSON.stringify(env);
402
+ },
403
+ ///
404
+ // Convenience: decrypt a JSON envelope string with a password.
405
+ ///
406
+ async decryptWithPassword(envelopeJson, password, _salt) {
407
+ const envelope = JSON.parse(envelopeJson);
408
+ return this.decrypt(envelope, password);
409
+ },
410
+ ///
411
+ // Type guard: check whether a value is an encrypted envelope.
412
+ ///
413
+ isEncrypted(value) {
414
+ return _isEncrypted(value);
415
+ },
416
+ ///
417
+ // Configure a default encryption key for auto-encrypt mode.
418
+ // When set on `store` or `session`, all writes are encrypted and all
419
+ // reads attempt decryption automatically.
420
+ ///
421
+ config(opts) {
422
+ if (opts.key !== void 0) this._defaultKey = opts.key;
423
+ if (opts.keyId !== void 0) this._defaultKeyId = opts.keyId;
424
+ return this;
425
+ },
426
+ ///
427
+ // Internal: resolve the configured default key.
428
+ ///
429
+ get _key() {
430
+ return this._defaultKey ?? null;
431
+ },
432
+ get _keyId() {
433
+ return this._defaultKeyId;
434
+ }
435
+ };
436
+ }
437
+ });
438
+
204
439
  // core/platform.ts
205
440
  function getSessionStorage() {
206
441
  try {
@@ -237,6 +472,7 @@ var init_platform = __esm({
237
472
  "core/platform.ts"() {
238
473
  init_cjs_shims();
239
474
  init_internal();
475
+ init_encryption();
240
476
  __name(getSessionStorage, "getSessionStorage");
241
477
  __name(getLocalStorage, "getLocalStorage");
242
478
  __name(hasIndexedDB, "hasIndexedDB");
@@ -967,6 +1203,7 @@ init_cjs_shims();
967
1203
  init_platform();
968
1204
  init_internal();
969
1205
  init_message();
1206
+ init_encryption();
970
1207
  var _localStorage = getLocalStorage();
971
1208
  var _fallbackStorageByContext = /* @__PURE__ */ new Map();
972
1209
  function _getFallbackStorage() {
@@ -979,6 +1216,14 @@ function _getFallbackStorage() {
979
1216
  }
980
1217
  __name(_getFallbackStorage, "_getFallbackStorage");
981
1218
  var _hasStorage = _localStorage !== null;
1219
+ var _encryptionKey = null;
1220
+ var _onError = null;
1221
+ var _maxObjectSize = 0;
1222
+ var _maxTotalSize = 0;
1223
+ function _isEncryptedEnvelope(value) {
1224
+ return encryption.isEncrypted(value);
1225
+ }
1226
+ __name(_isEncryptedEnvelope, "_isEncryptedEnvelope");
982
1227
  function _getPrefix() {
983
1228
  const contextName = internal_default.currentContext || "";
984
1229
  return contextName ? `${contextName}-` : `memorio_store_`;
@@ -1032,16 +1277,24 @@ if (globalThis[STORE_INSTANCE_KEY]) {
1032
1277
  /**
1033
1278
  * Gets a value from storage.
1034
1279
  * @param name The key to retrieve.
1035
- * @returns The parsed value or null if not found.
1280
+ * @param opts Optional { decrypt: key } to decrypt an encrypted envelope.
1281
+ * When no key is passed, encrypted envelopes are returned as-is.
1282
+ * If a default key was configured via store.config(), it is used automatically.
1283
+ * @returns The parsed value, a Promise<parsed value> when decrypting, or null if not found.
1036
1284
  */
1037
1285
  get: {
1038
- value: /* @__PURE__ */ __name((name) => {
1286
+ value: /* @__PURE__ */ __name((name, opts) => {
1039
1287
  if (!name) {
1040
1288
  console.warn("[memorio store] store.get() called with empty or null key - returning null");
1041
1289
  return null;
1042
1290
  }
1043
1291
  try {
1044
- return _read(_prefixKey(name));
1292
+ const raw = _read(_prefixKey(name));
1293
+ const key = opts?.decrypt ?? _encryptionKey;
1294
+ if (key && _isEncryptedEnvelope(raw)) {
1295
+ return encryption.decrypt(raw, key);
1296
+ }
1297
+ return raw;
1045
1298
  } catch (err) {
1046
1299
  message(`Error parsing store item '${name}': ${err}`);
1047
1300
  }
@@ -1066,10 +1319,13 @@ if (globalThis[STORE_INSTANCE_KEY]) {
1066
1319
  * Sets a value in storage.
1067
1320
  * @param name The key to set.
1068
1321
  * @param value The value to store (objects, arrays, primitives).
1069
- * @returns true if written, false if the key was rejected.
1322
+ * @param opts Optional { encrypt: key } to encrypt the value before storage.
1323
+ * If a default key was configured via store.config(), it is used automatically.
1324
+ * When encryption is used, returns a Promise<boolean>.
1325
+ * @returns true if written, false if rejected or error.
1070
1326
  */
1071
1327
  set: {
1072
- value: /* @__PURE__ */ __name((name, value) => {
1328
+ value: /* @__PURE__ */ __name((name, value, opts) => {
1073
1329
  if (!name) {
1074
1330
  console.warn("[memorio store] store.set() called with empty or null key - write aborted");
1075
1331
  return false;
@@ -1081,15 +1337,66 @@ if (globalThis[STORE_INSTANCE_KEY]) {
1081
1337
  message("Storing functions is not secure and is blocked for safety.");
1082
1338
  return false;
1083
1339
  }
1340
+ if (_maxObjectSize > 0 || _maxTotalSize > 0) {
1341
+ let serializedSize = 0;
1342
+ try {
1343
+ serializedSize = new TextEncoder().encode(JSON.stringify(value)).length;
1344
+ } catch {
1345
+ serializedSize = 0;
1346
+ }
1347
+ if (_maxObjectSize > 0 && serializedSize > _maxObjectSize) {
1348
+ const err = new Error(`Object size (${serializedSize} bytes) exceeds maxObjectSize (${_maxObjectSize} bytes)`);
1349
+ _onError?.(err, { operation: "set" });
1350
+ return false;
1351
+ }
1352
+ if (_maxTotalSize > 0) {
1353
+ const currentTotal = store.size() * 1024;
1354
+ const existingItemSize = _hasStorage ? _localStorage.getItem(prefixedName)?.length ?? 0 : _getFallbackStorage().get(prefixedName)?.length ?? 0;
1355
+ const projected = currentTotal * 1024 - existingItemSize + serializedSize;
1356
+ if (projected > _maxTotalSize) {
1357
+ const err = new Error(`Total store size (${projected} bytes) exceeds maxTotalSize (${_maxTotalSize} bytes)`);
1358
+ _onError?.(err, { operation: "set" });
1359
+ return false;
1360
+ }
1361
+ }
1362
+ }
1363
+ const key = opts?.encrypt ?? _encryptionKey;
1364
+ if (key) {
1365
+ return encryption.encrypt(value, key).then((env) => {
1366
+ _write(prefixedName, env);
1367
+ return true;
1368
+ }).catch((err) => {
1369
+ message(`Error encrypting store item '${name}': ${err}`);
1370
+ _onError?.(err, { operation: "set" });
1371
+ return false;
1372
+ });
1373
+ }
1084
1374
  try {
1085
1375
  _write(prefixedName, value);
1086
1376
  } catch (err) {
1087
1377
  message(`Error setting store item '${name}': ${err}`);
1378
+ _onError?.(err, { operation: "set" });
1088
1379
  return false;
1089
1380
  }
1090
1381
  return true;
1091
1382
  }, "value")
1092
1383
  },
1384
+ /**
1385
+ * Configure store-wide behavior.
1386
+ * @param opts Optional { encryptionKey } to enable automatic encryption
1387
+ * for all set/get operations. When set, values are encrypted
1388
+ * on write and decrypted on read (if the value is an envelope).
1389
+ * @returns The store instance for chaining.
1390
+ */
1391
+ config: {
1392
+ value: /* @__PURE__ */ __name((opts) => {
1393
+ if (opts.encryptionKey !== void 0) _encryptionKey = opts.encryptionKey;
1394
+ if (opts.onError !== void 0) _onError = opts.onError;
1395
+ if (opts.maxObjectSize !== void 0) _maxObjectSize = opts.maxObjectSize;
1396
+ if (opts.maxTotalSize !== void 0) _maxTotalSize = opts.maxTotalSize;
1397
+ return store;
1398
+ }, "value")
1399
+ },
1093
1400
  /**
1094
1401
  * Removes a value from storage.
1095
1402
  * @param name The key to remove.
@@ -1761,6 +2068,7 @@ init_cjs_shims();
1761
2068
  init_platform();
1762
2069
  init_internal();
1763
2070
  init_message();
2071
+ init_encryption();
1764
2072
  var _sessionStorage = getSessionStorage();
1765
2073
  var _fallbackStorageByContext2 = /* @__PURE__ */ new Map();
1766
2074
  function _getFallbackStorage2() {
@@ -1773,6 +2081,14 @@ function _getFallbackStorage2() {
1773
2081
  }
1774
2082
  __name(_getFallbackStorage2, "_getFallbackStorage");
1775
2083
  var _hasStorage2 = _sessionStorage !== null;
2084
+ var _encryptionKey2 = null;
2085
+ var _onError2 = null;
2086
+ var _maxObjectSize2 = 0;
2087
+ var _maxTotalSize2 = 0;
2088
+ function _isEncryptedEnvelope2(value) {
2089
+ return encryption.isEncrypted(value);
2090
+ }
2091
+ __name(_isEncryptedEnvelope2, "_isEncryptedEnvelope");
1776
2092
  function _getPrefix2() {
1777
2093
  const contextName = internal_default.currentContext || "";
1778
2094
  return contextName ? `${contextName}-` : `memorio_session_`;
@@ -1826,16 +2142,22 @@ if (globalThis[SESSION_INSTANCE_KEY]) {
1826
2142
  /**
1827
2143
  * Gets a value from storage.
1828
2144
  * @param name The key to retrieve.
1829
- * @returns The parsed value or null if not found.
2145
+ * @param opts Optional { decrypt: key } to decrypt an encrypted envelope.
2146
+ * @returns The parsed value, a Promise when decrypting, or null if not found.
1830
2147
  */
1831
2148
  get: {
1832
- value: /* @__PURE__ */ __name((name) => {
2149
+ value: /* @__PURE__ */ __name((name, opts) => {
1833
2150
  if (!name) {
1834
2151
  console.warn("[memorio session] session.get() called with empty or null key - returning null");
1835
2152
  return null;
1836
2153
  }
1837
2154
  try {
1838
- return _read2(_prefixKey2(name));
2155
+ const raw = _read2(_prefixKey2(name));
2156
+ const key = opts?.decrypt ?? _encryptionKey2;
2157
+ if (key && _isEncryptedEnvelope2(raw)) {
2158
+ return encryption.decrypt(raw, key);
2159
+ }
2160
+ return raw;
1839
2161
  } catch (err) {
1840
2162
  message(`Error parsing session item '${name}':${err}`);
1841
2163
  }
@@ -1846,10 +2168,11 @@ if (globalThis[SESSION_INSTANCE_KEY]) {
1846
2168
  * Sets a value in storage.
1847
2169
  * @param name The key to set.
1848
2170
  * @param value The value to store.
1849
- * @returns true if written, false if the key was rejected.
2171
+ * @param opts Optional { encrypt: key } to encrypt before storage.
2172
+ * @returns true if written, false if rejected. Returns a Promise when encrypting.
1850
2173
  */
1851
2174
  set: {
1852
- value: /* @__PURE__ */ __name((name, value) => {
2175
+ value: /* @__PURE__ */ __name((name, value, opts) => {
1853
2176
  if (!name) {
1854
2177
  console.warn("[memorio session] session.set() called with empty or null key - write aborted");
1855
2178
  return false;
@@ -1861,15 +2184,63 @@ if (globalThis[SESSION_INSTANCE_KEY]) {
1861
2184
  message("It's not secure to session functions.");
1862
2185
  return false;
1863
2186
  }
2187
+ if (_maxObjectSize2 > 0 || _maxTotalSize2 > 0) {
2188
+ let serializedSize = 0;
2189
+ try {
2190
+ serializedSize = new TextEncoder().encode(JSON.stringify(value)).length;
2191
+ } catch {
2192
+ serializedSize = 0;
2193
+ }
2194
+ if (_maxObjectSize2 > 0 && serializedSize > _maxObjectSize2) {
2195
+ const err = new Error(`Object size (${serializedSize} bytes) exceeds maxObjectSize (${_maxObjectSize2} bytes)`);
2196
+ _onError2?.(err, { operation: "set" });
2197
+ return false;
2198
+ }
2199
+ if (_maxTotalSize2 > 0) {
2200
+ const currentTotal = session.size() * 1024;
2201
+ const existingItemSize = _sessionStorage ? _sessionStorage.getItem(prefixedName)?.length ?? 0 : _getFallbackStorage2().get(prefixedName)?.length ?? 0;
2202
+ const projected = currentTotal - existingItemSize + serializedSize;
2203
+ if (projected > _maxTotalSize2) {
2204
+ const err = new Error(`Total session size (${projected} bytes) exceeds maxTotalSize (${_maxTotalSize2} bytes)`);
2205
+ _onError2?.(err, { operation: "set" });
2206
+ return false;
2207
+ }
2208
+ }
2209
+ }
2210
+ const key = opts?.encrypt ?? _encryptionKey2;
2211
+ if (key) {
2212
+ return encryption.encrypt(value, key).then((env) => {
2213
+ _write2(prefixedName, env);
2214
+ return true;
2215
+ }).catch((err) => {
2216
+ message(`Error encrypting session item '${name}': ${err}`);
2217
+ _onError2?.(err, { operation: "set" });
2218
+ return false;
2219
+ });
2220
+ }
1864
2221
  try {
1865
2222
  _write2(prefixedName, value);
1866
2223
  } catch (err) {
1867
2224
  message(`Error setting session item '${name}': ${err}`);
2225
+ _onError2?.(err, { operation: "set" });
1868
2226
  return false;
1869
2227
  }
1870
2228
  return true;
1871
2229
  }, "value")
1872
2230
  },
2231
+ /**
2232
+ * Configure session-wide behavior.
2233
+ * @param opts { encryptionKey, onError, maxObjectSize, maxTotalSize }
2234
+ */
2235
+ config: {
2236
+ value: /* @__PURE__ */ __name((opts) => {
2237
+ if (opts.encryptionKey !== void 0) _encryptionKey2 = opts.encryptionKey;
2238
+ if (opts.onError !== void 0) _onError2 = opts.onError;
2239
+ if (opts.maxObjectSize !== void 0) _maxObjectSize2 = opts.maxObjectSize;
2240
+ if (opts.maxTotalSize !== void 0) _maxTotalSize2 = opts.maxTotalSize;
2241
+ return session;
2242
+ }, "value")
2243
+ },
1873
2244
  /**
1874
2245
  * Removes a value from storage.
1875
2246
  * @param name The key to remove.