memorio 4.9.35 → 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.
- package/AGENTS.md +3 -3
- package/README.md +95 -516
- package/SECURITY.md +159 -33
- package/SUMMARY.md +59 -45
- package/adr/001-state-proxy-model.md +95 -0
- package/adr/002-observer-semantics.md +179 -0
- package/adr/003-deep-mutation-semantics.md +128 -0
- package/adr/004-array-mutation-semantics.md +127 -0
- package/adr/005-scheduler-contract.md +148 -0
- package/adr/006-context-isolation.md +91 -0
- package/adr/007-mutation-records.md +117 -0
- package/adr/008-transactions.md +105 -0
- package/adr/009-history-model.md +109 -0
- package/adr/README.md +46 -0
- package/adr/template.md +48 -0
- package/bin/cli.js +68 -0
- package/examples/basic.ts +115 -115
- package/examples/browser-vanilla.html +358 -358
- package/examples/cache.ts +72 -72
- package/examples/cross-platform-guards.ts +57 -57
- package/examples/history.ts +104 -0
- package/examples/idb.ts +109 -109
- package/examples/multi-tenant-context.ts +44 -44
- package/examples/node-server.ts +308 -308
- package/examples/observer.ts +60 -60
- package/examples/platform.ts +115 -115
- package/examples/react-app.tsx +362 -362
- package/examples/react-observer.tsx +63 -63
- package/examples/semantic-memory.ts +60 -60
- package/examples/session-advanced.ts +91 -91
- package/examples/sqlite-batched-writes.ts +57 -57
- package/examples/state-advanced.ts +89 -89
- package/examples/store-advanced.ts +117 -117
- package/examples/sync.ts +90 -0
- package/examples/typed-and-schema.ts +102 -100
- package/examples/useObserver.tsx +140 -141
- package/global.cjs +5733 -0
- package/global.d.ts +8 -0
- package/global.js +5667 -0
- package/index.cjs +2202 -1041
- package/index.d.ts +2 -0
- package/index.js +2178 -1040
- package/llms.txt +113 -8
- package/markdown/AUDIT-REPORT.md +134 -0
- package/markdown/CACHE.md +191 -0
- package/markdown/DEVTOOLS.md +128 -0
- package/markdown/DISPATCH.md +176 -0
- package/markdown/HISTORY.md +198 -0
- package/markdown/IDB.md +177 -0
- package/markdown/IMPORT.md +152 -0
- package/markdown/INSPECT.md +122 -0
- package/markdown/LOGGER.md +153 -0
- package/markdown/MEMORY-ATTACHMENT.md +95 -0
- package/markdown/MEMORY.md +161 -0
- package/markdown/OBSERVER.md +208 -0
- package/markdown/PLATFORM.md +277 -0
- package/markdown/REDUX.md +54 -0
- package/markdown/SCHEMA.md +175 -0
- package/markdown/SESSION.md +164 -0
- package/markdown/SQLITE.md +189 -0
- package/markdown/STATE.md +159 -0
- package/markdown/STORE.md +170 -0
- package/markdown/SYNC.md +318 -0
- package/markdown/TYPED.md +164 -0
- package/markdown/USEOBSERVER.md +256 -0
- package/modules/redux.cjs +701 -177
- package/modules/redux.cjs.map +1 -1
- package/modules/redux.js +701 -177
- package/modules/redux.js.map +1 -1
- package/package.json +26 -4
- package/types/broadcast.d.ts +61 -0
- package/types/computed.d.ts +96 -0
- package/types/encryption.d.ts +129 -0
- package/types/env.d.ts +19 -9
- package/types/exports.d.ts +29 -0
- package/types/history.d.ts +13 -1
- package/types/memorio.d.ts +25 -6
- package/types/mutation.d.ts +75 -0
- package/types/security.d.ts +67 -0
- package/types/session.d.ts +23 -5
- package/types/store.d.ts +19 -3
- package/vsix/memorio.vsix +0 -0
package/modules/redux.js
CHANGED
|
@@ -177,7 +177,10 @@ var DEV;
|
|
|
177
177
|
var init_env = __esm({
|
|
178
178
|
"core/env.ts"() {
|
|
179
179
|
init_esm_shims();
|
|
180
|
-
DEV =
|
|
180
|
+
DEV = false ;
|
|
181
|
+
if (typeof globalThis !== "undefined") {
|
|
182
|
+
globalThis["__DEV__"] = DEV;
|
|
183
|
+
}
|
|
181
184
|
}
|
|
182
185
|
});
|
|
183
186
|
|
|
@@ -197,6 +200,241 @@ var init_message = __esm({
|
|
|
197
200
|
}
|
|
198
201
|
});
|
|
199
202
|
|
|
203
|
+
// functions/encryption/index.ts
|
|
204
|
+
function _hasWebCrypto() {
|
|
205
|
+
const g2 = globalThis;
|
|
206
|
+
return typeof g2.crypto !== "undefined" && typeof g2.crypto.subtle !== "undefined" && typeof g2.crypto.subtle.encrypt === "function";
|
|
207
|
+
}
|
|
208
|
+
function _b64encode(buf) {
|
|
209
|
+
const bytes = buf instanceof Uint8Array ? buf : new Uint8Array(buf);
|
|
210
|
+
if (typeof btoa === "function") {
|
|
211
|
+
let binary = "";
|
|
212
|
+
for (let i = 0; i < bytes.length; i++) binary += String.fromCharCode(bytes[i]);
|
|
213
|
+
return btoa(binary);
|
|
214
|
+
}
|
|
215
|
+
if (typeof Buffer !== "undefined") {
|
|
216
|
+
return Buffer.from(bytes).toString("base64");
|
|
217
|
+
}
|
|
218
|
+
return "";
|
|
219
|
+
}
|
|
220
|
+
function _b64decode(b64) {
|
|
221
|
+
if (typeof Buffer !== "undefined") {
|
|
222
|
+
return new Uint8Array(Buffer.from(b64, "base64"));
|
|
223
|
+
}
|
|
224
|
+
if (typeof atob === "function") {
|
|
225
|
+
const binary = atob(b64);
|
|
226
|
+
const out = new Uint8Array(binary.length);
|
|
227
|
+
for (let i = 0; i < binary.length; i++) out[i] = binary.charCodeAt(i);
|
|
228
|
+
return out;
|
|
229
|
+
}
|
|
230
|
+
throw new Error("memorio encryption: no base64 decoder available");
|
|
231
|
+
}
|
|
232
|
+
function _isEncrypted(value) {
|
|
233
|
+
return typeof value === "object" && value !== null && value.__memorio_encrypted === true && typeof value.alg === "string";
|
|
234
|
+
}
|
|
235
|
+
var AES_GCM, PBKDF2_ITERATIONS, encryption;
|
|
236
|
+
var init_encryption = __esm({
|
|
237
|
+
"functions/encryption/index.ts"() {
|
|
238
|
+
init_esm_shims();
|
|
239
|
+
init_env();
|
|
240
|
+
init_message();
|
|
241
|
+
__name(_hasWebCrypto, "_hasWebCrypto");
|
|
242
|
+
__name(_b64encode, "_b64encode");
|
|
243
|
+
__name(_b64decode, "_b64decode");
|
|
244
|
+
__name(_isEncrypted, "_isEncrypted");
|
|
245
|
+
AES_GCM = { name: "AES-GCM", length: 256 };
|
|
246
|
+
PBKDF2_ITERATIONS = 6e5;
|
|
247
|
+
encryption = {
|
|
248
|
+
///
|
|
249
|
+
// Capability detection
|
|
250
|
+
///
|
|
251
|
+
isAvailable() {
|
|
252
|
+
return _hasWebCrypto();
|
|
253
|
+
},
|
|
254
|
+
///
|
|
255
|
+
// Derive a CryptoKey from a password using PBKDF2.
|
|
256
|
+
//
|
|
257
|
+
// @param password The password string.
|
|
258
|
+
// @param salt Salt (string or Uint8Array). Defaults to a static value.
|
|
259
|
+
// @param alg 'AES-GCM' (default) or 'AES-CBC'.
|
|
260
|
+
// @returns A Promise<CryptoKey>.
|
|
261
|
+
///
|
|
262
|
+
async deriveKey(password, salt, alg = "AES-GCM", iterations = PBKDF2_ITERATIONS) {
|
|
263
|
+
if (!_hasWebCrypto()) {
|
|
264
|
+
throw new Error("memorio encryption: Web Crypto API is not available in this environment");
|
|
265
|
+
}
|
|
266
|
+
const enc = new TextEncoder();
|
|
267
|
+
const saltBytes = typeof salt === "string" ? enc.encode(salt) : salt ?? enc.encode("memorio:salt");
|
|
268
|
+
const keyMaterial = await globalThis.crypto.subtle.importKey(
|
|
269
|
+
"raw",
|
|
270
|
+
enc.encode(password),
|
|
271
|
+
{ name: "PBKDF2" },
|
|
272
|
+
false,
|
|
273
|
+
["deriveKey"]
|
|
274
|
+
);
|
|
275
|
+
return globalThis.crypto.subtle.deriveKey(
|
|
276
|
+
{ name: "PBKDF2", salt: saltBytes, iterations, hash: "SHA-256" },
|
|
277
|
+
keyMaterial,
|
|
278
|
+
{ name: alg, length: 256 },
|
|
279
|
+
false,
|
|
280
|
+
["encrypt", "decrypt"]
|
|
281
|
+
);
|
|
282
|
+
},
|
|
283
|
+
///
|
|
284
|
+
// Generate a fresh random CryptoKey (AES-GCM, 256-bit).
|
|
285
|
+
///
|
|
286
|
+
async generateKey(alg = "AES-GCM") {
|
|
287
|
+
if (!_hasWebCrypto()) {
|
|
288
|
+
throw new Error("memorio encryption: Web Crypto API is not available in this environment");
|
|
289
|
+
}
|
|
290
|
+
return globalThis.crypto.subtle.generateKey(
|
|
291
|
+
{ name: alg, length: 256 },
|
|
292
|
+
true,
|
|
293
|
+
["encrypt", "decrypt"]
|
|
294
|
+
);
|
|
295
|
+
},
|
|
296
|
+
///
|
|
297
|
+
// Generate a random salt for PBKDF2 key derivation.
|
|
298
|
+
///
|
|
299
|
+
generateSalt(bytes = 16) {
|
|
300
|
+
const arr = new Uint8Array(bytes);
|
|
301
|
+
globalThis.crypto?.getRandomValues?.(arr);
|
|
302
|
+
return _b64encode(arr);
|
|
303
|
+
},
|
|
304
|
+
///
|
|
305
|
+
// Export a CryptoKey to portable base64 strings so it can be stored
|
|
306
|
+
// (e.g. in an httpOnly cookie) and re-imported later with importKey().
|
|
307
|
+
//
|
|
308
|
+
// @param cryptoKey A raw CryptoKey (not a password string).
|
|
309
|
+
// @returns { key: string, iv: string } base64-encoded.
|
|
310
|
+
///
|
|
311
|
+
async exportKey(cryptoKey) {
|
|
312
|
+
if (!_hasWebCrypto()) {
|
|
313
|
+
throw new Error("memorio encryption: Web Crypto API is not available in this environment");
|
|
314
|
+
}
|
|
315
|
+
const exported = await globalThis.crypto.subtle.exportKey("raw", cryptoKey);
|
|
316
|
+
return {
|
|
317
|
+
key: _b64encode(exported),
|
|
318
|
+
iv: ""
|
|
319
|
+
};
|
|
320
|
+
},
|
|
321
|
+
///
|
|
322
|
+
// Import a CryptoKey from base64 strings produced by exportKey().
|
|
323
|
+
//
|
|
324
|
+
// @param keyData Base64-encoded raw key bytes.
|
|
325
|
+
// @param _ivData Ignored (kept for API compatibility with exportKey return).
|
|
326
|
+
// @returns A Promise<CryptoKey>.
|
|
327
|
+
///
|
|
328
|
+
async importKey(keyData, _ivData) {
|
|
329
|
+
if (!_hasWebCrypto()) {
|
|
330
|
+
throw new Error("memorio encryption: Web Crypto API is not available in this environment");
|
|
331
|
+
}
|
|
332
|
+
const keyBytes = _b64decode(keyData);
|
|
333
|
+
if (keyBytes.length !== 32) {
|
|
334
|
+
throw new Error(`memorio encryption: AES-256 key must be exactly 32 bytes, got ${keyBytes.length}`);
|
|
335
|
+
}
|
|
336
|
+
return globalThis.crypto.subtle.importKey(
|
|
337
|
+
"raw",
|
|
338
|
+
keyBytes,
|
|
339
|
+
AES_GCM,
|
|
340
|
+
true,
|
|
341
|
+
["encrypt", "decrypt"]
|
|
342
|
+
);
|
|
343
|
+
},
|
|
344
|
+
///
|
|
345
|
+
// Encrypt a JavaScript value.
|
|
346
|
+
//
|
|
347
|
+
// @param value The value to encrypt (any JSON-serializable object).
|
|
348
|
+
// @param key A CryptoKey or a password string (password is derived via PBKDF2).
|
|
349
|
+
// @param keyId Optional label, stored in the envelope for key rotation.
|
|
350
|
+
// @returns An EncryptedEnvelope object.
|
|
351
|
+
///
|
|
352
|
+
async encrypt(value, key, keyId) {
|
|
353
|
+
const cryptoKey = typeof key === "string" ? await this.deriveKey(key, void 0, "AES-GCM") : key;
|
|
354
|
+
const data = new TextEncoder().encode(JSON.stringify(value));
|
|
355
|
+
const iv = new Uint8Array(12);
|
|
356
|
+
globalThis.crypto.getRandomValues(iv);
|
|
357
|
+
const ciphertext = await globalThis.crypto.subtle.encrypt(
|
|
358
|
+
{ name: "AES-GCM", iv },
|
|
359
|
+
cryptoKey,
|
|
360
|
+
data
|
|
361
|
+
);
|
|
362
|
+
const envelope = {
|
|
363
|
+
__memorio_encrypted: true,
|
|
364
|
+
v: 1,
|
|
365
|
+
alg: "AES-GCM",
|
|
366
|
+
iv: _b64encode(iv),
|
|
367
|
+
data: _b64encode(ciphertext)
|
|
368
|
+
};
|
|
369
|
+
if (keyId) envelope.keyId = keyId;
|
|
370
|
+
if (DEV) message_default(`encryption: encrypted value (alg=${envelope.alg})`);
|
|
371
|
+
return envelope;
|
|
372
|
+
},
|
|
373
|
+
///
|
|
374
|
+
// Decrypt an EncryptedEnvelope.
|
|
375
|
+
//
|
|
376
|
+
// @param envelope The envelope object returned by encrypt().
|
|
377
|
+
// @param key A CryptoKey or a password string.
|
|
378
|
+
// @returns The original value (parsed from JSON).
|
|
379
|
+
///
|
|
380
|
+
async decrypt(envelope, key) {
|
|
381
|
+
if (!_isEncrypted(envelope)) {
|
|
382
|
+
throw new Error("memorio encryption: value is not a valid encrypted envelope");
|
|
383
|
+
}
|
|
384
|
+
const cryptoKey = typeof key === "string" ? await this.deriveKey(key, void 0, "AES-GCM") : key;
|
|
385
|
+
const iv = _b64decode(envelope.iv);
|
|
386
|
+
const data = _b64decode(envelope.data);
|
|
387
|
+
const decrypted = await globalThis.crypto.subtle.decrypt(
|
|
388
|
+
{ name: "AES-GCM", iv },
|
|
389
|
+
cryptoKey,
|
|
390
|
+
data
|
|
391
|
+
);
|
|
392
|
+
return JSON.parse(new TextDecoder().decode(new Uint8Array(decrypted)));
|
|
393
|
+
},
|
|
394
|
+
///
|
|
395
|
+
// Convenience: encrypt with a password (derives the key internally).
|
|
396
|
+
// Returns a JSON string you can store anywhere.
|
|
397
|
+
///
|
|
398
|
+
async encryptWithPassword(value, password, salt, keyId) {
|
|
399
|
+
const env = await this.encrypt(value, password, keyId);
|
|
400
|
+
return JSON.stringify(env);
|
|
401
|
+
},
|
|
402
|
+
///
|
|
403
|
+
// Convenience: decrypt a JSON envelope string with a password.
|
|
404
|
+
///
|
|
405
|
+
async decryptWithPassword(envelopeJson, password, _salt) {
|
|
406
|
+
const envelope = JSON.parse(envelopeJson);
|
|
407
|
+
return this.decrypt(envelope, password);
|
|
408
|
+
},
|
|
409
|
+
///
|
|
410
|
+
// Type guard: check whether a value is an encrypted envelope.
|
|
411
|
+
///
|
|
412
|
+
isEncrypted(value) {
|
|
413
|
+
return _isEncrypted(value);
|
|
414
|
+
},
|
|
415
|
+
///
|
|
416
|
+
// Configure a default encryption key for auto-encrypt mode.
|
|
417
|
+
// When set on `store` or `session`, all writes are encrypted and all
|
|
418
|
+
// reads attempt decryption automatically.
|
|
419
|
+
///
|
|
420
|
+
config(opts) {
|
|
421
|
+
if (opts.key !== void 0) this._defaultKey = opts.key;
|
|
422
|
+
if (opts.keyId !== void 0) this._defaultKeyId = opts.keyId;
|
|
423
|
+
return this;
|
|
424
|
+
},
|
|
425
|
+
///
|
|
426
|
+
// Internal: resolve the configured default key.
|
|
427
|
+
///
|
|
428
|
+
get _key() {
|
|
429
|
+
return this._defaultKey ?? null;
|
|
430
|
+
},
|
|
431
|
+
get _keyId() {
|
|
432
|
+
return this._defaultKeyId;
|
|
433
|
+
}
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
});
|
|
437
|
+
|
|
200
438
|
// core/platform.ts
|
|
201
439
|
function getSessionStorage() {
|
|
202
440
|
try {
|
|
@@ -233,6 +471,7 @@ var init_platform = __esm({
|
|
|
233
471
|
"core/platform.ts"() {
|
|
234
472
|
init_esm_shims();
|
|
235
473
|
init_internal();
|
|
474
|
+
init_encryption();
|
|
236
475
|
__name(getSessionStorage, "getSessionStorage");
|
|
237
476
|
__name(getLocalStorage, "getLocalStorage");
|
|
238
477
|
__name(hasIndexedDB, "hasIndexedDB");
|
|
@@ -807,9 +1046,25 @@ var dispatch = {
|
|
|
807
1046
|
const wrapped = /* @__PURE__ */ __name((e) => {
|
|
808
1047
|
const target = e;
|
|
809
1048
|
if (globalThis.queueMicrotask) {
|
|
810
|
-
globalThis.queueMicrotask(() =>
|
|
1049
|
+
globalThis.queueMicrotask(() => {
|
|
1050
|
+
try {
|
|
1051
|
+
cb(target);
|
|
1052
|
+
} catch (err) {
|
|
1053
|
+
if (typeof console !== "undefined") {
|
|
1054
|
+
console.error("[memorio dispatch] observer error:", err);
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
});
|
|
811
1058
|
} else {
|
|
812
|
-
Promise.resolve().then(() =>
|
|
1059
|
+
Promise.resolve().then(() => {
|
|
1060
|
+
try {
|
|
1061
|
+
cb(target);
|
|
1062
|
+
} catch (err) {
|
|
1063
|
+
if (typeof console !== "undefined") {
|
|
1064
|
+
console.error("[memorio dispatch] observer error:", err);
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
});
|
|
813
1068
|
}
|
|
814
1069
|
}, "wrapped");
|
|
815
1070
|
globalThis.addEventListener(name, wrapped);
|
|
@@ -947,6 +1202,7 @@ init_esm_shims();
|
|
|
947
1202
|
init_platform();
|
|
948
1203
|
init_internal();
|
|
949
1204
|
init_message();
|
|
1205
|
+
init_encryption();
|
|
950
1206
|
var _localStorage = getLocalStorage();
|
|
951
1207
|
var _fallbackStorageByContext = /* @__PURE__ */ new Map();
|
|
952
1208
|
function _getFallbackStorage() {
|
|
@@ -959,6 +1215,14 @@ function _getFallbackStorage() {
|
|
|
959
1215
|
}
|
|
960
1216
|
__name(_getFallbackStorage, "_getFallbackStorage");
|
|
961
1217
|
var _hasStorage = _localStorage !== null;
|
|
1218
|
+
var _encryptionKey = null;
|
|
1219
|
+
var _onError = null;
|
|
1220
|
+
var _maxObjectSize = 0;
|
|
1221
|
+
var _maxTotalSize = 0;
|
|
1222
|
+
function _isEncryptedEnvelope(value) {
|
|
1223
|
+
return encryption.isEncrypted(value);
|
|
1224
|
+
}
|
|
1225
|
+
__name(_isEncryptedEnvelope, "_isEncryptedEnvelope");
|
|
962
1226
|
function _getPrefix() {
|
|
963
1227
|
const contextName = internal_default.currentContext || "";
|
|
964
1228
|
return contextName ? `${contextName}-` : `memorio_store_`;
|
|
@@ -1012,16 +1276,24 @@ if (globalThis[STORE_INSTANCE_KEY]) {
|
|
|
1012
1276
|
/**
|
|
1013
1277
|
* Gets a value from storage.
|
|
1014
1278
|
* @param name The key to retrieve.
|
|
1015
|
-
* @
|
|
1279
|
+
* @param opts Optional { decrypt: key } to decrypt an encrypted envelope.
|
|
1280
|
+
* When no key is passed, encrypted envelopes are returned as-is.
|
|
1281
|
+
* If a default key was configured via store.config(), it is used automatically.
|
|
1282
|
+
* @returns The parsed value, a Promise<parsed value> when decrypting, or null if not found.
|
|
1016
1283
|
*/
|
|
1017
1284
|
get: {
|
|
1018
|
-
value: /* @__PURE__ */ __name((name) => {
|
|
1285
|
+
value: /* @__PURE__ */ __name((name, opts) => {
|
|
1019
1286
|
if (!name) {
|
|
1020
1287
|
console.warn("[memorio store] store.get() called with empty or null key - returning null");
|
|
1021
1288
|
return null;
|
|
1022
1289
|
}
|
|
1023
1290
|
try {
|
|
1024
|
-
|
|
1291
|
+
const raw = _read(_prefixKey(name));
|
|
1292
|
+
const key = opts?.decrypt ?? _encryptionKey;
|
|
1293
|
+
if (key && _isEncryptedEnvelope(raw)) {
|
|
1294
|
+
return encryption.decrypt(raw, key);
|
|
1295
|
+
}
|
|
1296
|
+
return raw;
|
|
1025
1297
|
} catch (err) {
|
|
1026
1298
|
message(`Error parsing store item '${name}': ${err}`);
|
|
1027
1299
|
}
|
|
@@ -1046,10 +1318,13 @@ if (globalThis[STORE_INSTANCE_KEY]) {
|
|
|
1046
1318
|
* Sets a value in storage.
|
|
1047
1319
|
* @param name The key to set.
|
|
1048
1320
|
* @param value The value to store (objects, arrays, primitives).
|
|
1049
|
-
* @
|
|
1321
|
+
* @param opts Optional { encrypt: key } to encrypt the value before storage.
|
|
1322
|
+
* If a default key was configured via store.config(), it is used automatically.
|
|
1323
|
+
* When encryption is used, returns a Promise<boolean>.
|
|
1324
|
+
* @returns true if written, false if rejected or error.
|
|
1050
1325
|
*/
|
|
1051
1326
|
set: {
|
|
1052
|
-
value: /* @__PURE__ */ __name((name, value) => {
|
|
1327
|
+
value: /* @__PURE__ */ __name((name, value, opts) => {
|
|
1053
1328
|
if (!name) {
|
|
1054
1329
|
console.warn("[memorio store] store.set() called with empty or null key - write aborted");
|
|
1055
1330
|
return false;
|
|
@@ -1061,15 +1336,66 @@ if (globalThis[STORE_INSTANCE_KEY]) {
|
|
|
1061
1336
|
message("Storing functions is not secure and is blocked for safety.");
|
|
1062
1337
|
return false;
|
|
1063
1338
|
}
|
|
1339
|
+
if (_maxObjectSize > 0 || _maxTotalSize > 0) {
|
|
1340
|
+
let serializedSize = 0;
|
|
1341
|
+
try {
|
|
1342
|
+
serializedSize = new TextEncoder().encode(JSON.stringify(value)).length;
|
|
1343
|
+
} catch {
|
|
1344
|
+
serializedSize = 0;
|
|
1345
|
+
}
|
|
1346
|
+
if (_maxObjectSize > 0 && serializedSize > _maxObjectSize) {
|
|
1347
|
+
const err = new Error(`Object size (${serializedSize} bytes) exceeds maxObjectSize (${_maxObjectSize} bytes)`);
|
|
1348
|
+
_onError?.(err, { operation: "set" });
|
|
1349
|
+
return false;
|
|
1350
|
+
}
|
|
1351
|
+
if (_maxTotalSize > 0) {
|
|
1352
|
+
const currentTotal = store.size() * 1024;
|
|
1353
|
+
const existingItemSize = _hasStorage ? _localStorage.getItem(prefixedName)?.length ?? 0 : _getFallbackStorage().get(prefixedName)?.length ?? 0;
|
|
1354
|
+
const projected = currentTotal * 1024 - existingItemSize + serializedSize;
|
|
1355
|
+
if (projected > _maxTotalSize) {
|
|
1356
|
+
const err = new Error(`Total store size (${projected} bytes) exceeds maxTotalSize (${_maxTotalSize} bytes)`);
|
|
1357
|
+
_onError?.(err, { operation: "set" });
|
|
1358
|
+
return false;
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1362
|
+
const key = opts?.encrypt ?? _encryptionKey;
|
|
1363
|
+
if (key) {
|
|
1364
|
+
return encryption.encrypt(value, key).then((env) => {
|
|
1365
|
+
_write(prefixedName, env);
|
|
1366
|
+
return true;
|
|
1367
|
+
}).catch((err) => {
|
|
1368
|
+
message(`Error encrypting store item '${name}': ${err}`);
|
|
1369
|
+
_onError?.(err, { operation: "set" });
|
|
1370
|
+
return false;
|
|
1371
|
+
});
|
|
1372
|
+
}
|
|
1064
1373
|
try {
|
|
1065
1374
|
_write(prefixedName, value);
|
|
1066
1375
|
} catch (err) {
|
|
1067
1376
|
message(`Error setting store item '${name}': ${err}`);
|
|
1377
|
+
_onError?.(err, { operation: "set" });
|
|
1068
1378
|
return false;
|
|
1069
1379
|
}
|
|
1070
1380
|
return true;
|
|
1071
1381
|
}, "value")
|
|
1072
1382
|
},
|
|
1383
|
+
/**
|
|
1384
|
+
* Configure store-wide behavior.
|
|
1385
|
+
* @param opts Optional { encryptionKey } to enable automatic encryption
|
|
1386
|
+
* for all set/get operations. When set, values are encrypted
|
|
1387
|
+
* on write and decrypted on read (if the value is an envelope).
|
|
1388
|
+
* @returns The store instance for chaining.
|
|
1389
|
+
*/
|
|
1390
|
+
config: {
|
|
1391
|
+
value: /* @__PURE__ */ __name((opts) => {
|
|
1392
|
+
if (opts.encryptionKey !== void 0) _encryptionKey = opts.encryptionKey;
|
|
1393
|
+
if (opts.onError !== void 0) _onError = opts.onError;
|
|
1394
|
+
if (opts.maxObjectSize !== void 0) _maxObjectSize = opts.maxObjectSize;
|
|
1395
|
+
if (opts.maxTotalSize !== void 0) _maxTotalSize = opts.maxTotalSize;
|
|
1396
|
+
return store;
|
|
1397
|
+
}, "value")
|
|
1398
|
+
},
|
|
1073
1399
|
/**
|
|
1074
1400
|
* Removes a value from storage.
|
|
1075
1401
|
* @param name The key to remove.
|
|
@@ -1216,10 +1542,211 @@ if (globalThis[STORE_INSTANCE_KEY]) {
|
|
|
1216
1542
|
|
|
1217
1543
|
// functions/state/index.ts
|
|
1218
1544
|
init_env();
|
|
1545
|
+
|
|
1546
|
+
// core/mutation/engine.ts
|
|
1547
|
+
init_esm_shims();
|
|
1548
|
+
init_internal();
|
|
1549
|
+
|
|
1550
|
+
// core/hlc.ts
|
|
1551
|
+
init_esm_shims();
|
|
1552
|
+
var NODE_ID_KEY = "__memorio_hlc_node";
|
|
1553
|
+
function getNodeId() {
|
|
1554
|
+
if (typeof globalThis === "undefined") return "node";
|
|
1555
|
+
try {
|
|
1556
|
+
if (typeof sessionStorage !== "undefined" && sessionStorage) {
|
|
1557
|
+
let id = sessionStorage.getItem(NODE_ID_KEY);
|
|
1558
|
+
if (!id) {
|
|
1559
|
+
id = generateNodeId();
|
|
1560
|
+
sessionStorage.setItem(NODE_ID_KEY, id);
|
|
1561
|
+
}
|
|
1562
|
+
return id;
|
|
1563
|
+
}
|
|
1564
|
+
} catch {
|
|
1565
|
+
}
|
|
1566
|
+
if (!globalThis.__memorioHlcNodeId) {
|
|
1567
|
+
globalThis.__memorioHlcNodeId = generateNodeId();
|
|
1568
|
+
}
|
|
1569
|
+
return globalThis.__memorioHlcNodeId;
|
|
1570
|
+
}
|
|
1571
|
+
__name(getNodeId, "getNodeId");
|
|
1572
|
+
function generateNodeId() {
|
|
1573
|
+
if (typeof crypto !== "undefined" && crypto.randomUUID) {
|
|
1574
|
+
return crypto.randomUUID().slice(0, 8);
|
|
1575
|
+
}
|
|
1576
|
+
return Math.random().toString(36).slice(2, 10);
|
|
1577
|
+
}
|
|
1578
|
+
__name(generateNodeId, "generateNodeId");
|
|
1579
|
+
var HLC_KEY = "__memorio_hlc_local";
|
|
1580
|
+
var local = globalThis[HLC_KEY] || {
|
|
1581
|
+
physical: 0,
|
|
1582
|
+
logical: 0,
|
|
1583
|
+
node: getNodeId()
|
|
1584
|
+
};
|
|
1585
|
+
if (!globalThis[HLC_KEY]) {
|
|
1586
|
+
globalThis[HLC_KEY] = local;
|
|
1587
|
+
}
|
|
1588
|
+
function tick(_incoming) {
|
|
1589
|
+
const now2 = Date.now();
|
|
1590
|
+
const base = local.physical;
|
|
1591
|
+
let physical;
|
|
1592
|
+
let logical;
|
|
1593
|
+
if (now2 > base) {
|
|
1594
|
+
physical = now2;
|
|
1595
|
+
logical = 0;
|
|
1596
|
+
} else if (now2 < base) {
|
|
1597
|
+
physical = base;
|
|
1598
|
+
logical = local.logical + 1;
|
|
1599
|
+
} else {
|
|
1600
|
+
physical = base;
|
|
1601
|
+
logical = local.logical + 1;
|
|
1602
|
+
}
|
|
1603
|
+
local.physical = physical;
|
|
1604
|
+
local.logical = logical;
|
|
1605
|
+
return { physical, logical, node: local.node };
|
|
1606
|
+
}
|
|
1607
|
+
__name(tick, "tick");
|
|
1608
|
+
function receive(remote) {
|
|
1609
|
+
const now2 = Date.now();
|
|
1610
|
+
if (remote.physical > local.physical) {
|
|
1611
|
+
local.physical = remote.physical > now2 ? remote.physical : now2;
|
|
1612
|
+
local.logical = 1;
|
|
1613
|
+
} else if (remote.physical === local.physical) {
|
|
1614
|
+
local.logical = Math.max(local.logical, remote.logical) + 1;
|
|
1615
|
+
}
|
|
1616
|
+
local.node = getNodeId();
|
|
1617
|
+
return { ...local };
|
|
1618
|
+
}
|
|
1619
|
+
__name(receive, "receive");
|
|
1620
|
+
function compare(a, b) {
|
|
1621
|
+
if (a.physical !== b.physical) {
|
|
1622
|
+
return a.physical < b.physical ? -1 : 1;
|
|
1623
|
+
}
|
|
1624
|
+
if (a.logical !== b.logical) {
|
|
1625
|
+
return a.logical < b.logical ? -1 : 1;
|
|
1626
|
+
}
|
|
1627
|
+
if (a.node !== b.node) {
|
|
1628
|
+
return a.node < b.node ? -1 : 1;
|
|
1629
|
+
}
|
|
1630
|
+
return 0;
|
|
1631
|
+
}
|
|
1632
|
+
__name(compare, "compare");
|
|
1633
|
+
function happenedBefore(a, b) {
|
|
1634
|
+
return compare(a, b) < 0;
|
|
1635
|
+
}
|
|
1636
|
+
__name(happenedBefore, "happenedBefore");
|
|
1637
|
+
function serialize(hlc2) {
|
|
1638
|
+
return `hlc:${hlc2.physical}:${hlc2.logical}:${hlc2.node}`;
|
|
1639
|
+
}
|
|
1640
|
+
__name(serialize, "serialize");
|
|
1641
|
+
function parse(serialized) {
|
|
1642
|
+
if (!serialized.startsWith("hlc:")) return null;
|
|
1643
|
+
const parts = serialized.split(":");
|
|
1644
|
+
if (parts.length !== 4) return null;
|
|
1645
|
+
const physical = parseInt(parts[1], 10);
|
|
1646
|
+
const logical = parseInt(parts[2], 10);
|
|
1647
|
+
const node = parts[3];
|
|
1648
|
+
if (isNaN(physical) || isNaN(logical)) return null;
|
|
1649
|
+
return { physical, logical, node };
|
|
1650
|
+
}
|
|
1651
|
+
__name(parse, "parse");
|
|
1652
|
+
var hlc = {
|
|
1653
|
+
tick,
|
|
1654
|
+
receive,
|
|
1655
|
+
compare,
|
|
1656
|
+
happenedBefore,
|
|
1657
|
+
serialize,
|
|
1658
|
+
parse,
|
|
1659
|
+
getNodeId
|
|
1660
|
+
};
|
|
1661
|
+
|
|
1662
|
+
// core/mutation/id.ts
|
|
1663
|
+
init_esm_shims();
|
|
1664
|
+
function generateMutationId() {
|
|
1665
|
+
const now2 = hlc.tick();
|
|
1666
|
+
return "m_" + encodeHLC(now2);
|
|
1667
|
+
}
|
|
1668
|
+
__name(generateMutationId, "generateMutationId");
|
|
1669
|
+
function encodeHLC(hlcValue) {
|
|
1670
|
+
const timePart = hlcValue.physical.toString(36);
|
|
1671
|
+
const logicalPart = hlcValue.logical.toString(36).padStart(2, "0");
|
|
1672
|
+
const nodePart = hlcValue.node.slice(0, 4);
|
|
1673
|
+
return `${timePart}${logicalPart}${nodePart}`;
|
|
1674
|
+
}
|
|
1675
|
+
__name(encodeHLC, "encodeHLC");
|
|
1676
|
+
|
|
1677
|
+
// core/mutation/patch.ts
|
|
1678
|
+
init_esm_shims();
|
|
1679
|
+
|
|
1680
|
+
// core/mutation/transaction.ts
|
|
1681
|
+
init_esm_shims();
|
|
1682
|
+
init_internal();
|
|
1683
|
+
var TX_REGISTRY_KEY = "__memorio_transaction_registry__";
|
|
1684
|
+
var registry = globalThis[TX_REGISTRY_KEY] || /* @__PURE__ */ new Map();
|
|
1685
|
+
if (!globalThis[TX_REGISTRY_KEY]) {
|
|
1686
|
+
globalThis[TX_REGISTRY_KEY] = registry;
|
|
1687
|
+
}
|
|
1688
|
+
var TX_STACK_KEY = "__memorio_transaction_stack__";
|
|
1689
|
+
var txStack = globalThis[TX_STACK_KEY] || [];
|
|
1690
|
+
if (!globalThis[TX_STACK_KEY]) {
|
|
1691
|
+
globalThis[TX_STACK_KEY] = txStack;
|
|
1692
|
+
}
|
|
1693
|
+
function getActiveTxId() {
|
|
1694
|
+
return txStack.length > 0 ? txStack[txStack.length - 1] : null;
|
|
1695
|
+
}
|
|
1696
|
+
__name(getActiveTxId, "getActiveTxId");
|
|
1697
|
+
function tagForTransaction() {
|
|
1698
|
+
const txId = getActiveTxId();
|
|
1699
|
+
if (!txId) return void 0;
|
|
1700
|
+
const ctx = registry.get(txId);
|
|
1701
|
+
if (ctx && ctx.active) {
|
|
1702
|
+
return txId;
|
|
1703
|
+
}
|
|
1704
|
+
return void 0;
|
|
1705
|
+
}
|
|
1706
|
+
__name(tagForTransaction, "tagForTransaction");
|
|
1707
|
+
function registerMutationInTransaction(mutationId) {
|
|
1708
|
+
const txId = getActiveTxId();
|
|
1709
|
+
if (!txId) return;
|
|
1710
|
+
const ctx = registry.get(txId);
|
|
1711
|
+
if (ctx && ctx.active) {
|
|
1712
|
+
ctx.mutations.push(mutationId);
|
|
1713
|
+
}
|
|
1714
|
+
}
|
|
1715
|
+
__name(registerMutationInTransaction, "registerMutationInTransaction");
|
|
1716
|
+
|
|
1717
|
+
// core/mutation/engine.ts
|
|
1718
|
+
function createMutation(path2, operation, before, after, options) {
|
|
1719
|
+
const now2 = hlc.tick();
|
|
1720
|
+
const mutation = {
|
|
1721
|
+
id: generateMutationId(),
|
|
1722
|
+
path: path2,
|
|
1723
|
+
operation,
|
|
1724
|
+
after,
|
|
1725
|
+
before,
|
|
1726
|
+
timestamp: now2.physical,
|
|
1727
|
+
hlc: hlc.serialize(now2),
|
|
1728
|
+
context: options?.context || internal_default.currentContext || void 0,
|
|
1729
|
+
source: options?.source,
|
|
1730
|
+
transactionId: options?.transactionId || (operation !== "length" ? tagForTransaction() : void 0),
|
|
1731
|
+
// Legacy fields for backward compatibility
|
|
1732
|
+
action: operation === "set" ? "set" : "delete",
|
|
1733
|
+
newValue: after,
|
|
1734
|
+
previousValue: before
|
|
1735
|
+
};
|
|
1736
|
+
if (mutation.transactionId) {
|
|
1737
|
+
registerMutationInTransaction(mutation.id);
|
|
1738
|
+
}
|
|
1739
|
+
return mutation;
|
|
1740
|
+
}
|
|
1741
|
+
__name(createMutation, "createMutation");
|
|
1742
|
+
|
|
1743
|
+
// functions/state/index.ts
|
|
1219
1744
|
var TARGET = /* @__PURE__ */ Symbol("memorio.target");
|
|
1220
1745
|
var wrapperCache = /* @__PURE__ */ new WeakMap();
|
|
1746
|
+
var arrayRootDedup = /* @__PURE__ */ new Set();
|
|
1221
1747
|
var deepRaw = /* @__PURE__ */ __name((val, seen = null) => {
|
|
1222
1748
|
if (val === null || typeof val !== "object") return val;
|
|
1749
|
+
if (!seen) seen = /* @__PURE__ */ new WeakSet();
|
|
1223
1750
|
if (val[TARGET] !== void 0) return deepRaw(val[TARGET], seen);
|
|
1224
1751
|
if (seen) {
|
|
1225
1752
|
if (seen.has(val)) return val;
|
|
@@ -1282,8 +1809,8 @@ var buildProxy = /* @__PURE__ */ __name((obj, callback, tree = []) => {
|
|
|
1282
1809
|
if (prop === "removeAll") {
|
|
1283
1810
|
return () => {
|
|
1284
1811
|
try {
|
|
1285
|
-
for (const key
|
|
1286
|
-
if (typeof target[key] !== "function" && !["list", "remove", "removeAll", "lock", "unlock"].includes(key)) {
|
|
1812
|
+
for (const key of Reflect.ownKeys(target)) {
|
|
1813
|
+
if (typeof target[key] !== "function" && !["list", "remove", "removeAll", "lock", "unlock", "persist"].includes(key)) {
|
|
1287
1814
|
delete target[key];
|
|
1288
1815
|
}
|
|
1289
1816
|
}
|
|
@@ -1357,6 +1884,16 @@ var buildProxy = /* @__PURE__ */ __name((obj, callback, tree = []) => {
|
|
|
1357
1884
|
}
|
|
1358
1885
|
);
|
|
1359
1886
|
event("state." + path2);
|
|
1887
|
+
if (Array.isArray(target) && tree.length > 0) {
|
|
1888
|
+
const rootName = "state." + tree.join(".");
|
|
1889
|
+
if (!arrayRootDedup.has(rootName)) {
|
|
1890
|
+
arrayRootDedup.add(rootName);
|
|
1891
|
+
event(rootName);
|
|
1892
|
+
if (typeof globalThis.queueMicrotask === "function") {
|
|
1893
|
+
globalThis.queueMicrotask(() => arrayRootDedup.clear());
|
|
1894
|
+
}
|
|
1895
|
+
}
|
|
1896
|
+
}
|
|
1360
1897
|
Reflect.set(target, key, rawValue);
|
|
1361
1898
|
internal_default.bumpStateVersion();
|
|
1362
1899
|
if (target[key] && typeof target[key] === "object") {
|
|
@@ -1413,6 +1950,17 @@ var buildProxy = /* @__PURE__ */ __name((obj, callback, tree = []) => {
|
|
|
1413
1950
|
const path2 = objPath(prop, tree);
|
|
1414
1951
|
const previousValue = Reflect.get(target, prop);
|
|
1415
1952
|
callback({ action: "delete", path: path2, target, previousValue });
|
|
1953
|
+
event("state." + path2);
|
|
1954
|
+
if (Array.isArray(target) && tree.length > 0) {
|
|
1955
|
+
const rootName = "state." + tree.join(".");
|
|
1956
|
+
if (!arrayRootDedup.has(rootName)) {
|
|
1957
|
+
arrayRootDedup.add(rootName);
|
|
1958
|
+
event(rootName);
|
|
1959
|
+
if (typeof globalThis.queueMicrotask === "function") {
|
|
1960
|
+
globalThis.queueMicrotask(() => arrayRootDedup.clear());
|
|
1961
|
+
}
|
|
1962
|
+
}
|
|
1963
|
+
}
|
|
1416
1964
|
return Reflect.deleteProperty(target, prop);
|
|
1417
1965
|
} catch (error) {
|
|
1418
1966
|
console.error("Error in deleteProperty trap:", error);
|
|
@@ -1434,57 +1982,83 @@ if (globalThis[STATE_INSTANCE_KEY]) {
|
|
|
1434
1982
|
state2 = globalThis[STATE_INSTANCE_KEY];
|
|
1435
1983
|
} else {
|
|
1436
1984
|
state2 = buildProxy({}, (mutation) => {
|
|
1437
|
-
if (internal_default.historyEnabled) {
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1985
|
+
if (internal_default.historyEnabled && internal_default._recording !== false) {
|
|
1986
|
+
const operation = mutation.action === "delete" ? "delete" : "set";
|
|
1987
|
+
const record = createMutation(
|
|
1988
|
+
mutation.path,
|
|
1989
|
+
operation,
|
|
1990
|
+
mutation.previousValue,
|
|
1991
|
+
mutation.newValue,
|
|
1992
|
+
{
|
|
1993
|
+
context: mutation.context,
|
|
1994
|
+
source: mutation.source
|
|
1995
|
+
}
|
|
1996
|
+
);
|
|
1997
|
+
internal_default.recordMutation(record);
|
|
1445
1998
|
}
|
|
1446
1999
|
});
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
2000
|
+
const _rawTarget = state2[TARGET];
|
|
2001
|
+
try {
|
|
2002
|
+
Object.defineProperty(_rawTarget, "lock", {
|
|
2003
|
+
value: /* @__PURE__ */ __name(() => {
|
|
2004
|
+
internal_default.locked = true;
|
|
2005
|
+
message_default("State is now locked. All modifications are blocked.");
|
|
2006
|
+
}, "value"),
|
|
2007
|
+
writable: true,
|
|
2008
|
+
configurable: true,
|
|
2009
|
+
enumerable: false
|
|
2010
|
+
});
|
|
2011
|
+
Object.defineProperty(_rawTarget, "unlock", {
|
|
2012
|
+
value: /* @__PURE__ */ __name(() => {
|
|
2013
|
+
internal_default.locked = false;
|
|
2014
|
+
message_default("State is now unlocked. Modifications are allowed.");
|
|
2015
|
+
}, "value"),
|
|
2016
|
+
writable: true,
|
|
2017
|
+
configurable: true,
|
|
2018
|
+
enumerable: false
|
|
2019
|
+
});
|
|
2020
|
+
} catch (e) {
|
|
2021
|
+
if (DEV) console.warn("[memorio state] lock/unlock defineProperty failed:", e);
|
|
2022
|
+
}
|
|
2023
|
+
try {
|
|
2024
|
+
Object.defineProperty(_rawTarget, "persist", {
|
|
2025
|
+
value: /* @__PURE__ */ __name((path2) => {
|
|
2026
|
+
if (typeof path2 !== "string" || !path2.length) {
|
|
2027
|
+
if (DEV) console.warn('[memorio state] state.persist() requires a non-empty path string (e.g. "state.user")');
|
|
2028
|
+
return () => {
|
|
2029
|
+
};
|
|
1474
2030
|
}
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
2031
|
+
const normalized = path2.startsWith("state.") ? path2 : "state." + path2;
|
|
2032
|
+
const key = normalized.replace(/^state\./, "");
|
|
2033
|
+
const segments = key.split(".");
|
|
2034
|
+
const initial = store.get(key);
|
|
2035
|
+
if (initial !== void 0 && initial !== null) {
|
|
2036
|
+
let acc = state2;
|
|
2037
|
+
for (let i = 0; i < segments.length - 1; i++) {
|
|
2038
|
+
const seg = segments[i];
|
|
2039
|
+
if (acc[seg] === void 0 || acc[seg] === null) acc[seg] = {};
|
|
2040
|
+
acc = acc[seg];
|
|
2041
|
+
}
|
|
2042
|
+
if (acc) acc[segments[segments.length - 1]] = deepRaw(initial);
|
|
2043
|
+
}
|
|
2044
|
+
const handler = /* @__PURE__ */ __name(() => {
|
|
2045
|
+
let current = state2;
|
|
2046
|
+
for (const seg of segments) {
|
|
2047
|
+
current = current === void 0 || current === null ? void 0 : current[seg];
|
|
2048
|
+
}
|
|
2049
|
+
store.set(key, deepRaw(current));
|
|
2050
|
+
}, "handler");
|
|
2051
|
+
const off = dispatch.listen(normalized, handler);
|
|
2052
|
+
return typeof off === "function" ? off : () => {
|
|
2053
|
+
};
|
|
2054
|
+
}, "value"),
|
|
2055
|
+
writable: true,
|
|
2056
|
+
configurable: true,
|
|
2057
|
+
enumerable: false
|
|
2058
|
+
});
|
|
2059
|
+
} catch (e) {
|
|
2060
|
+
if (DEV) console.warn("[memorio state] persist defineProperty failed:", e);
|
|
2061
|
+
}
|
|
1488
2062
|
globalThis[STATE_INSTANCE_KEY] = state2;
|
|
1489
2063
|
}
|
|
1490
2064
|
|
|
@@ -1493,6 +2067,7 @@ init_esm_shims();
|
|
|
1493
2067
|
init_platform();
|
|
1494
2068
|
init_internal();
|
|
1495
2069
|
init_message();
|
|
2070
|
+
init_encryption();
|
|
1496
2071
|
var _sessionStorage = getSessionStorage();
|
|
1497
2072
|
var _fallbackStorageByContext2 = /* @__PURE__ */ new Map();
|
|
1498
2073
|
function _getFallbackStorage2() {
|
|
@@ -1505,6 +2080,14 @@ function _getFallbackStorage2() {
|
|
|
1505
2080
|
}
|
|
1506
2081
|
__name(_getFallbackStorage2, "_getFallbackStorage");
|
|
1507
2082
|
var _hasStorage2 = _sessionStorage !== null;
|
|
2083
|
+
var _encryptionKey2 = null;
|
|
2084
|
+
var _onError2 = null;
|
|
2085
|
+
var _maxObjectSize2 = 0;
|
|
2086
|
+
var _maxTotalSize2 = 0;
|
|
2087
|
+
function _isEncryptedEnvelope2(value) {
|
|
2088
|
+
return encryption.isEncrypted(value);
|
|
2089
|
+
}
|
|
2090
|
+
__name(_isEncryptedEnvelope2, "_isEncryptedEnvelope");
|
|
1508
2091
|
function _getPrefix2() {
|
|
1509
2092
|
const contextName = internal_default.currentContext || "";
|
|
1510
2093
|
return contextName ? `${contextName}-` : `memorio_session_`;
|
|
@@ -1558,16 +2141,22 @@ if (globalThis[SESSION_INSTANCE_KEY]) {
|
|
|
1558
2141
|
/**
|
|
1559
2142
|
* Gets a value from storage.
|
|
1560
2143
|
* @param name The key to retrieve.
|
|
1561
|
-
* @
|
|
2144
|
+
* @param opts Optional { decrypt: key } to decrypt an encrypted envelope.
|
|
2145
|
+
* @returns The parsed value, a Promise when decrypting, or null if not found.
|
|
1562
2146
|
*/
|
|
1563
2147
|
get: {
|
|
1564
|
-
value: /* @__PURE__ */ __name((name) => {
|
|
2148
|
+
value: /* @__PURE__ */ __name((name, opts) => {
|
|
1565
2149
|
if (!name) {
|
|
1566
2150
|
console.warn("[memorio session] session.get() called with empty or null key - returning null");
|
|
1567
2151
|
return null;
|
|
1568
2152
|
}
|
|
1569
2153
|
try {
|
|
1570
|
-
|
|
2154
|
+
const raw = _read2(_prefixKey2(name));
|
|
2155
|
+
const key = opts?.decrypt ?? _encryptionKey2;
|
|
2156
|
+
if (key && _isEncryptedEnvelope2(raw)) {
|
|
2157
|
+
return encryption.decrypt(raw, key);
|
|
2158
|
+
}
|
|
2159
|
+
return raw;
|
|
1571
2160
|
} catch (err) {
|
|
1572
2161
|
message(`Error parsing session item '${name}':${err}`);
|
|
1573
2162
|
}
|
|
@@ -1578,10 +2167,11 @@ if (globalThis[SESSION_INSTANCE_KEY]) {
|
|
|
1578
2167
|
* Sets a value in storage.
|
|
1579
2168
|
* @param name The key to set.
|
|
1580
2169
|
* @param value The value to store.
|
|
1581
|
-
* @
|
|
2170
|
+
* @param opts Optional { encrypt: key } to encrypt before storage.
|
|
2171
|
+
* @returns true if written, false if rejected. Returns a Promise when encrypting.
|
|
1582
2172
|
*/
|
|
1583
2173
|
set: {
|
|
1584
|
-
value: /* @__PURE__ */ __name((name, value) => {
|
|
2174
|
+
value: /* @__PURE__ */ __name((name, value, opts) => {
|
|
1585
2175
|
if (!name) {
|
|
1586
2176
|
console.warn("[memorio session] session.set() called with empty or null key - write aborted");
|
|
1587
2177
|
return false;
|
|
@@ -1593,15 +2183,63 @@ if (globalThis[SESSION_INSTANCE_KEY]) {
|
|
|
1593
2183
|
message("It's not secure to session functions.");
|
|
1594
2184
|
return false;
|
|
1595
2185
|
}
|
|
2186
|
+
if (_maxObjectSize2 > 0 || _maxTotalSize2 > 0) {
|
|
2187
|
+
let serializedSize = 0;
|
|
2188
|
+
try {
|
|
2189
|
+
serializedSize = new TextEncoder().encode(JSON.stringify(value)).length;
|
|
2190
|
+
} catch {
|
|
2191
|
+
serializedSize = 0;
|
|
2192
|
+
}
|
|
2193
|
+
if (_maxObjectSize2 > 0 && serializedSize > _maxObjectSize2) {
|
|
2194
|
+
const err = new Error(`Object size (${serializedSize} bytes) exceeds maxObjectSize (${_maxObjectSize2} bytes)`);
|
|
2195
|
+
_onError2?.(err, { operation: "set" });
|
|
2196
|
+
return false;
|
|
2197
|
+
}
|
|
2198
|
+
if (_maxTotalSize2 > 0) {
|
|
2199
|
+
const currentTotal = session.size() * 1024;
|
|
2200
|
+
const existingItemSize = _sessionStorage ? _sessionStorage.getItem(prefixedName)?.length ?? 0 : _getFallbackStorage2().get(prefixedName)?.length ?? 0;
|
|
2201
|
+
const projected = currentTotal - existingItemSize + serializedSize;
|
|
2202
|
+
if (projected > _maxTotalSize2) {
|
|
2203
|
+
const err = new Error(`Total session size (${projected} bytes) exceeds maxTotalSize (${_maxTotalSize2} bytes)`);
|
|
2204
|
+
_onError2?.(err, { operation: "set" });
|
|
2205
|
+
return false;
|
|
2206
|
+
}
|
|
2207
|
+
}
|
|
2208
|
+
}
|
|
2209
|
+
const key = opts?.encrypt ?? _encryptionKey2;
|
|
2210
|
+
if (key) {
|
|
2211
|
+
return encryption.encrypt(value, key).then((env) => {
|
|
2212
|
+
_write2(prefixedName, env);
|
|
2213
|
+
return true;
|
|
2214
|
+
}).catch((err) => {
|
|
2215
|
+
message(`Error encrypting session item '${name}': ${err}`);
|
|
2216
|
+
_onError2?.(err, { operation: "set" });
|
|
2217
|
+
return false;
|
|
2218
|
+
});
|
|
2219
|
+
}
|
|
1596
2220
|
try {
|
|
1597
2221
|
_write2(prefixedName, value);
|
|
1598
2222
|
} catch (err) {
|
|
1599
2223
|
message(`Error setting session item '${name}': ${err}`);
|
|
2224
|
+
_onError2?.(err, { operation: "set" });
|
|
1600
2225
|
return false;
|
|
1601
2226
|
}
|
|
1602
2227
|
return true;
|
|
1603
2228
|
}, "value")
|
|
1604
2229
|
},
|
|
2230
|
+
/**
|
|
2231
|
+
* Configure session-wide behavior.
|
|
2232
|
+
* @param opts { encryptionKey, onError, maxObjectSize, maxTotalSize }
|
|
2233
|
+
*/
|
|
2234
|
+
config: {
|
|
2235
|
+
value: /* @__PURE__ */ __name((opts) => {
|
|
2236
|
+
if (opts.encryptionKey !== void 0) _encryptionKey2 = opts.encryptionKey;
|
|
2237
|
+
if (opts.onError !== void 0) _onError2 = opts.onError;
|
|
2238
|
+
if (opts.maxObjectSize !== void 0) _maxObjectSize2 = opts.maxObjectSize;
|
|
2239
|
+
if (opts.maxTotalSize !== void 0) _maxTotalSize2 = opts.maxTotalSize;
|
|
2240
|
+
return session;
|
|
2241
|
+
}, "value")
|
|
2242
|
+
},
|
|
1605
2243
|
/**
|
|
1606
2244
|
* Removes a value from storage.
|
|
1607
2245
|
* @param name The key to remove.
|
|
@@ -1746,120 +2384,6 @@ if (globalThis[SESSION_INSTANCE_KEY]) {
|
|
|
1746
2384
|
|
|
1747
2385
|
// functions/memory/index.ts
|
|
1748
2386
|
init_idb();
|
|
1749
|
-
|
|
1750
|
-
// core/hlc.ts
|
|
1751
|
-
init_esm_shims();
|
|
1752
|
-
var NODE_ID_KEY = "__memorio_hlc_node";
|
|
1753
|
-
function getNodeId() {
|
|
1754
|
-
if (typeof globalThis === "undefined") return "node";
|
|
1755
|
-
try {
|
|
1756
|
-
if (typeof sessionStorage !== "undefined" && sessionStorage) {
|
|
1757
|
-
let id = sessionStorage.getItem(NODE_ID_KEY);
|
|
1758
|
-
if (!id) {
|
|
1759
|
-
id = generateNodeId();
|
|
1760
|
-
sessionStorage.setItem(NODE_ID_KEY, id);
|
|
1761
|
-
}
|
|
1762
|
-
return id;
|
|
1763
|
-
}
|
|
1764
|
-
} catch {
|
|
1765
|
-
}
|
|
1766
|
-
if (!globalThis.__memorioHlcNodeId) {
|
|
1767
|
-
globalThis.__memorioHlcNodeId = generateNodeId();
|
|
1768
|
-
}
|
|
1769
|
-
return globalThis.__memorioHlcNodeId;
|
|
1770
|
-
}
|
|
1771
|
-
__name(getNodeId, "getNodeId");
|
|
1772
|
-
function generateNodeId() {
|
|
1773
|
-
if (typeof crypto !== "undefined" && crypto.randomUUID) {
|
|
1774
|
-
return crypto.randomUUID().slice(0, 8);
|
|
1775
|
-
}
|
|
1776
|
-
return Math.random().toString(36).slice(2, 10);
|
|
1777
|
-
}
|
|
1778
|
-
__name(generateNodeId, "generateNodeId");
|
|
1779
|
-
var HLC_KEY = "__memorio_hlc_local";
|
|
1780
|
-
var local = globalThis[HLC_KEY] || {
|
|
1781
|
-
physical: 0,
|
|
1782
|
-
logical: 0,
|
|
1783
|
-
node: getNodeId()
|
|
1784
|
-
};
|
|
1785
|
-
if (!globalThis[HLC_KEY]) {
|
|
1786
|
-
globalThis[HLC_KEY] = local;
|
|
1787
|
-
}
|
|
1788
|
-
function tick(_incoming) {
|
|
1789
|
-
const now2 = Date.now();
|
|
1790
|
-
const base = local.physical;
|
|
1791
|
-
let physical;
|
|
1792
|
-
let logical;
|
|
1793
|
-
if (now2 > base) {
|
|
1794
|
-
physical = now2;
|
|
1795
|
-
logical = 0;
|
|
1796
|
-
} else if (now2 < base) {
|
|
1797
|
-
physical = base;
|
|
1798
|
-
logical = local.logical + 1;
|
|
1799
|
-
} else {
|
|
1800
|
-
physical = base;
|
|
1801
|
-
logical = local.logical + 1;
|
|
1802
|
-
}
|
|
1803
|
-
local.physical = physical;
|
|
1804
|
-
local.logical = logical;
|
|
1805
|
-
return { physical, logical, node: local.node };
|
|
1806
|
-
}
|
|
1807
|
-
__name(tick, "tick");
|
|
1808
|
-
function receive(remote) {
|
|
1809
|
-
const now2 = Date.now();
|
|
1810
|
-
if (remote.physical > local.physical) {
|
|
1811
|
-
local.physical = remote.physical > now2 ? remote.physical : now2;
|
|
1812
|
-
local.logical = 1;
|
|
1813
|
-
} else if (remote.physical === local.physical) {
|
|
1814
|
-
local.logical = Math.max(local.logical, remote.logical) + 1;
|
|
1815
|
-
}
|
|
1816
|
-
local.node = getNodeId();
|
|
1817
|
-
return { ...local };
|
|
1818
|
-
}
|
|
1819
|
-
__name(receive, "receive");
|
|
1820
|
-
function compare(a, b) {
|
|
1821
|
-
if (a.physical !== b.physical) {
|
|
1822
|
-
return a.physical < b.physical ? -1 : 1;
|
|
1823
|
-
}
|
|
1824
|
-
if (a.logical !== b.logical) {
|
|
1825
|
-
return a.logical < b.logical ? -1 : 1;
|
|
1826
|
-
}
|
|
1827
|
-
if (a.node !== b.node) {
|
|
1828
|
-
return a.node < b.node ? -1 : 1;
|
|
1829
|
-
}
|
|
1830
|
-
return 0;
|
|
1831
|
-
}
|
|
1832
|
-
__name(compare, "compare");
|
|
1833
|
-
function happenedBefore(a, b) {
|
|
1834
|
-
return compare(a, b) < 0;
|
|
1835
|
-
}
|
|
1836
|
-
__name(happenedBefore, "happenedBefore");
|
|
1837
|
-
function serialize(hlc2) {
|
|
1838
|
-
return `hlc:${hlc2.physical}:${hlc2.logical}:${hlc2.node}`;
|
|
1839
|
-
}
|
|
1840
|
-
__name(serialize, "serialize");
|
|
1841
|
-
function parse(serialized) {
|
|
1842
|
-
if (!serialized.startsWith("hlc:")) return null;
|
|
1843
|
-
const parts = serialized.split(":");
|
|
1844
|
-
if (parts.length !== 4) return null;
|
|
1845
|
-
const physical = parseInt(parts[1], 10);
|
|
1846
|
-
const logical = parseInt(parts[2], 10);
|
|
1847
|
-
const node = parts[3];
|
|
1848
|
-
if (isNaN(physical) || isNaN(logical)) return null;
|
|
1849
|
-
return { physical, logical, node };
|
|
1850
|
-
}
|
|
1851
|
-
__name(parse, "parse");
|
|
1852
|
-
var hlc = {
|
|
1853
|
-
tick,
|
|
1854
|
-
receive,
|
|
1855
|
-
compare,
|
|
1856
|
-
happenedBefore,
|
|
1857
|
-
serialize,
|
|
1858
|
-
parse,
|
|
1859
|
-
getNodeId
|
|
1860
|
-
};
|
|
1861
|
-
|
|
1862
|
-
// functions/memory/index.ts
|
|
1863
2387
|
init_env();
|
|
1864
2388
|
|
|
1865
2389
|
// functions/memory/journal.ts
|