@katlux/providers 0.1.1-alpha.105 → 0.1.1-alpha.106

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/dist/index.cjs CHANGED
@@ -304,6 +304,18 @@ class ADataProvider {
304
304
  set sortList(s) {
305
305
  this.setSortList(s);
306
306
  }
307
+ get hasMore() {
308
+ if (this._rowCount <= 0) return false;
309
+ return this._currentPage * this._pageSize < this._rowCount;
310
+ }
311
+ async nextPage() {
312
+ if (this.hasMore && !this._loading) {
313
+ await this.setCurrentPage(this._currentPage + 1);
314
+ }
315
+ }
316
+ async loadMore() {
317
+ await this.nextPage();
318
+ }
307
319
  _listeners = /* @__PURE__ */ new Set();
308
320
  _stateListeners = /* @__PURE__ */ new Set();
309
321
  _pendingChangedKeys = /* @__PURE__ */ new Set();
@@ -584,11 +596,13 @@ class ADataProvider {
584
596
  }
585
597
  this.notify();
586
598
  }
587
- setPageSize(pageSize) {
599
+ setPageSize(pageSize, resetPage = true) {
588
600
  this._pageSize = pageSize;
589
- this._currentPage = 1;
601
+ if (resetPage) {
602
+ this._currentPage = 1;
603
+ this._pendingChangedKeys.add("currentPage");
604
+ }
590
605
  this._pendingChangedKeys.add("pageSize");
591
- this._pendingChangedKeys.add("currentPage");
592
606
  this.loadPageData();
593
607
  this.notify();
594
608
  }
@@ -2024,9 +2038,6 @@ class RequestQueue {
2024
2038
  Object.assign(headers, existingHeaders);
2025
2039
  }
2026
2040
  }
2027
- if (typeof document !== "undefined" && document.cookie && !headers["cookie"] && !headers["Cookie"]) {
2028
- headers["cookie"] = document.cookie;
2029
- }
2030
2041
  if (this._headerProvider) {
2031
2042
  try {
2032
2043
  const dynamicHeaders = await this._headerProvider();
@@ -2037,6 +2048,9 @@ class RequestQueue {
2037
2048
  console.error("[RequestQueue] Header provider failed:", err);
2038
2049
  }
2039
2050
  }
2051
+ if (typeof document !== "undefined" && document.cookie && !headers["cookie"] && !headers["Cookie"]) {
2052
+ headers["cookie"] = document.cookie;
2053
+ }
2040
2054
  return headers;
2041
2055
  }
2042
2056
  static async executeRequest(item) {
@@ -2060,6 +2074,8 @@ class RequestQueue {
2060
2074
  };
2061
2075
  this.queue = this.queue.filter((i) => i.id !== item.id);
2062
2076
  if (this.deadLetterQueue.length >= this.MAX_DEAD_LETTER_SIZE) {
2077
+ const evicted = this.deadLetterQueue[0];
2078
+ console.warn(`[RequestQueue] Dead-letter queue reached capacity (${this.MAX_DEAD_LETTER_SIZE}). Evicting oldest item: ${evicted?.id}`);
2063
2079
  this.deadLetterQueue = this.deadLetterQueue.slice(1);
2064
2080
  }
2065
2081
  this.deadLetterQueue = [...this.deadLetterQueue, deadItem];
@@ -2143,24 +2159,39 @@ function toBase64(buffer) {
2143
2159
  }
2144
2160
  let objKeyCounter = 0;
2145
2161
  const objKeyMap = /* @__PURE__ */ new WeakMap();
2146
- function getKeyIdentifier(keyProvider, defaultKey) {
2147
- const target = keyProvider ?? defaultKey;
2148
- if (typeof target === "string") {
2149
- return target;
2162
+ function fastKeyHash(str) {
2163
+ let h1 = 3735928559 ^ 0;
2164
+ let h2 = 1103547991 ^ 0;
2165
+ for (let i = 0; i < str.length; i++) {
2166
+ const ch = str.charCodeAt(i);
2167
+ h1 = Math.imul(h1 ^ ch, 2654435761);
2168
+ h2 = Math.imul(h2 ^ ch, 1597334677);
2169
+ }
2170
+ h1 = Math.imul(h1 ^ h1 >>> 16, 2246822507) ^ Math.imul(h2 ^ h2 >>> 13, 3266489909);
2171
+ h2 = Math.imul(h2 ^ h2 >>> 16, 2246822507) ^ Math.imul(h1 ^ h1 >>> 13, 3266489909);
2172
+ return (4294967296 * (2097151 & h2) + (h1 >>> 0)).toString(16);
2173
+ }
2174
+ function getSafeKeyIdentifier(rawKey) {
2175
+ if (typeof rawKey === "string") {
2176
+ return `h_${fastKeyHash(rawKey)}`;
2150
2177
  }
2151
- if (target instanceof Uint8Array || target instanceof ArrayBuffer) {
2152
- return toBase64(target);
2178
+ if (rawKey instanceof Uint8Array || rawKey instanceof ArrayBuffer) {
2179
+ return `b_${fastKeyHash(toBase64(rawKey))}`;
2153
2180
  }
2154
- if (typeof target === "function" || typeof target === "object" && target !== null) {
2155
- let id = objKeyMap.get(target);
2181
+ if ((typeof rawKey === "object" || typeof rawKey === "function") && rawKey !== null) {
2182
+ let id = objKeyMap.get(rawKey);
2156
2183
  if (!id) {
2157
- id = `__ref_${++objKeyCounter}__`;
2158
- objKeyMap.set(target, id);
2184
+ id = `__key_${++objKeyCounter}__`;
2185
+ objKeyMap.set(rawKey, id);
2159
2186
  }
2160
2187
  return id;
2161
2188
  }
2162
2189
  return "__default__";
2163
2190
  }
2191
+ function getKeyIdentifier(keyProvider, defaultKey) {
2192
+ const target = keyProvider ?? defaultKey;
2193
+ return getSafeKeyIdentifier(target);
2194
+ }
2164
2195
 
2165
2196
  class AEncryptionProvider {
2166
2197
  defaultKey;
@@ -2412,6 +2443,11 @@ class CKeyManager {
2412
2443
  // ── Symmetric Key Management ─────────────────────────────────────────────
2413
2444
  /**
2414
2445
  * Generates a random AES CryptoKey suitable for AES-GCM or AES-CBC.
2446
+ *
2447
+ * E2EE Threat Model Note: Focuses on protecting GDPR/tenant data against database and
2448
+ * server-side leaks. Keys remain extractable by default to enable role-based key sharing,
2449
+ * wrapping, and transfer between authorized client sessions in multi-user SaaS apps.
2450
+ * Client-side malware/trojan protection is out of scope.
2415
2451
  */
2416
2452
  static async generateAESKey(length = 256, algorithm = "AES-GCM", extractable = true) {
2417
2453
  const subtle = getSubtleCrypto();
@@ -2871,6 +2907,8 @@ class CWebCryptoEncryptionProvider extends AEncryptionProvider {
2871
2907
  _cachedKdf;
2872
2908
  _cachedIter;
2873
2909
  _cachedHash;
2910
+ _cachedKeyId;
2911
+ _configuredKid;
2874
2912
  _decryptSaltCache = /* @__PURE__ */ new Map();
2875
2913
  setDefaultKey(key) {
2876
2914
  super.setDefaultKey(key);
@@ -2879,6 +2917,7 @@ class CWebCryptoEncryptionProvider extends AEncryptionProvider {
2879
2917
  this._cachedKdf = void 0;
2880
2918
  this._cachedIter = void 0;
2881
2919
  this._cachedHash = void 0;
2920
+ this._cachedKeyId = void 0;
2882
2921
  this._decryptSaltCache.clear();
2883
2922
  }
2884
2923
  clearKeys() {
@@ -2888,11 +2927,15 @@ class CWebCryptoEncryptionProvider extends AEncryptionProvider {
2888
2927
  this._cachedKdf = void 0;
2889
2928
  this._cachedIter = void 0;
2890
2929
  this._cachedHash = void 0;
2930
+ this._cachedKeyId = void 0;
2891
2931
  this._decryptSaltCache.clear();
2892
2932
  }
2893
2933
  constructor(key, options) {
2894
2934
  super(key);
2895
2935
  if (options) {
2936
+ if (options.kid || options.keyId) {
2937
+ this._configuredKid = options.kid || options.keyId;
2938
+ }
2896
2939
  this._defaultKdf = options.kdf || "pbkdf2";
2897
2940
  if (options.kdf && options.kdf !== "none") {
2898
2941
  this._kdfOptions = {
@@ -2909,6 +2952,9 @@ class CWebCryptoEncryptionProvider extends AEncryptionProvider {
2909
2952
  * Derives or returns a CryptoKey for encryption/decryption based on KDF settings.
2910
2953
  */
2911
2954
  async resolveCryptoKey(keyProvider, kdfOverride, saltOverride, options, iterationsOverride) {
2955
+ if (options?._warmedKey) {
2956
+ return options._warmedKey;
2957
+ }
2912
2958
  if (saltOverride) {
2913
2959
  const saltKey = typeof saltOverride === "string" ? saltOverride : CKeyManager.toBase64(saltOverride);
2914
2960
  const keyId = getKeyIdentifier(keyProvider, this.defaultKey);
@@ -2972,13 +3018,16 @@ class CWebCryptoEncryptionProvider extends AEncryptionProvider {
2972
3018
  const matchesHash = !targetHash || this._cachedHash !== void 0 && targetHash.toLowerCase() === this._cachedHash.toLowerCase();
2973
3019
  const matchesKdf = this._cachedKdf !== void 0 && kdf === this._cachedKdf.toLowerCase();
2974
3020
  const matchesSalt = !saltOverride || this._cachedSalt !== void 0 && saltOverride === this._cachedSalt;
2975
- if (!keyProvider && this._cachedKey && matchesSalt && !options?.kdfSalt && matchesKdf && matchesIter && matchesHash) {
2976
- return { cryptoKey: this._cachedKey, salt: this._cachedSalt, kdf: this._cachedKdf, iter: this._cachedIter };
3021
+ const currentKeyId = getKeyIdentifier(keyProvider, this.defaultKey);
3022
+ if (this._cachedKey && matchesSalt && !options?.kdfSalt && matchesKdf && matchesIter && matchesHash) {
3023
+ if (!this._cachedKeyId || this._cachedKeyId === currentKeyId) {
3024
+ return { cryptoKey: this._cachedKey, salt: this._cachedSalt, kdf: this._cachedKdf, iter: this._cachedIter };
3025
+ }
2977
3026
  }
2978
3027
  if (kdf === "pbkdf2") {
2979
3028
  const salt = saltOverride || options?.kdfSalt || this._kdfOptions?.salt;
2980
3029
  const rawIterations = iterationsOverride || options?.kdfIterations || this._kdfOptions?.iterations || 31e4;
2981
- const iterations = Math.min(Math.max(rawIterations, 1e3), 1e6);
3030
+ const iterations = Math.min(Math.max(rawIterations, 1e3), 6e5);
2982
3031
  const hash = options?.kdfHash || this._kdfOptions?.hash || "SHA-256";
2983
3032
  const derived = await CKeyManager.deriveKey(passphrase, {
2984
3033
  kdf: "pbkdf2",
@@ -2986,16 +3035,22 @@ class CWebCryptoEncryptionProvider extends AEncryptionProvider {
2986
3035
  iterations,
2987
3036
  hash
2988
3037
  });
2989
- if (!keyProvider && !saltOverride && !options?.kdfSalt) {
3038
+ if (!saltOverride && !options?.kdfSalt) {
2990
3039
  this._cachedKey = derived.key;
2991
3040
  this._cachedSalt = derived.salt;
2992
3041
  this._cachedKdf = "PBKDF2";
2993
3042
  this._cachedIter = iterations;
2994
3043
  this._cachedHash = hash;
3044
+ this._cachedKeyId = currentKeyId;
2995
3045
  }
2996
3046
  return { cryptoKey: derived.key, salt: derived.salt, kdf: "PBKDF2", iter: iterations };
2997
3047
  }
2998
3048
  if (kdf === "hkdf") {
3049
+ if (typeof rawKey === "string") {
3050
+ console.warn(
3051
+ '[CWebCryptoEncryptionProvider] ADVISORY: HKDF is designed for high-entropy input key material (IKM), not low-entropy human passphrases. Use "pbkdf2" for password-derived encryption.'
3052
+ );
3053
+ }
2999
3054
  const salt = saltOverride || options?.kdfSalt || this._kdfOptions?.salt;
3000
3055
  const hash = options?.kdfHash || this._kdfOptions?.hash || "SHA-256";
3001
3056
  const info = options?.info || this._kdfOptions?.info;
@@ -3005,18 +3060,21 @@ class CWebCryptoEncryptionProvider extends AEncryptionProvider {
3005
3060
  hash,
3006
3061
  info
3007
3062
  });
3008
- if (!keyProvider && !saltOverride && !options?.kdfSalt) {
3063
+ if (!saltOverride && !options?.kdfSalt) {
3009
3064
  this._cachedKey = derived.key;
3010
3065
  this._cachedSalt = derived.salt;
3011
3066
  this._cachedKdf = "HKDF";
3012
3067
  this._cachedHash = hash;
3068
+ this._cachedKeyId = currentKeyId;
3013
3069
  }
3014
3070
  return { cryptoKey: derived.key, salt: derived.salt, kdf: "HKDF" };
3015
3071
  }
3016
3072
  if (kdf === "none") {
3017
- console.warn(
3018
- "[CWebCryptoEncryptionProvider] WARNING: Decrypting a legacy envelope with no KDF (raw SHA-256 key derivation). This is cryptographically weak. Migrate to pbkdf2 or hkdf."
3019
- );
3073
+ if (saltOverride) {
3074
+ console.warn(
3075
+ "[CWebCryptoEncryptionProvider] WARNING: Decrypting a legacy envelope with no KDF (raw SHA-256 key derivation). This is cryptographically weak. Migrate to pbkdf2 or hkdf."
3076
+ );
3077
+ }
3020
3078
  }
3021
3079
  if (!keyProvider && this._cachedKey && this._cachedKdf === "none") {
3022
3080
  return { cryptoKey: this._cachedKey, kdf: "none" };
@@ -3056,12 +3114,14 @@ class CWebCryptoEncryptionProvider extends AEncryptionProvider {
3056
3114
  }
3057
3115
  async warmupKey(options) {
3058
3116
  const res = await this.resolveCryptoKey(options?.key, void 0, void 0, options);
3059
- if (!options?.key) {
3060
- this._cachedKey = res.cryptoKey;
3061
- this._cachedSalt = res.salt;
3062
- this._cachedKdf = res.kdf;
3063
- this._cachedIter = res.iter;
3064
- this._cachedHash = options?.kdfHash || this._kdfOptions?.hash || "SHA-256";
3117
+ this._cachedKey = res.cryptoKey;
3118
+ this._cachedSalt = res.salt;
3119
+ this._cachedKdf = res.kdf;
3120
+ this._cachedIter = res.iter;
3121
+ this._cachedHash = options?.kdfHash || this._kdfOptions?.hash || "SHA-256";
3122
+ this._cachedKeyId = getKeyIdentifier(options?.key, this.defaultKey);
3123
+ if (options) {
3124
+ options._warmedKey = res;
3065
3125
  }
3066
3126
  }
3067
3127
  /**
@@ -3077,11 +3137,25 @@ class CWebCryptoEncryptionProvider extends AEncryptionProvider {
3077
3137
  const subtle = getSubtleCrypto();
3078
3138
  const jsonString = JSON.stringify(data);
3079
3139
  const iv = CKeyManager.randomBytes(12);
3140
+ const encryptParams = {
3141
+ name: "AES-GCM",
3142
+ iv
3143
+ };
3144
+ const aad = options?.aad;
3145
+ let hasAad = false;
3146
+ if (aad) {
3147
+ const aadBytes = typeof aad === "string" ? textEncoder.encode(aad) : aad instanceof Uint8Array ? aad : new Uint8Array(aad);
3148
+ if (aadBytes.byteLength > 0) {
3149
+ encryptParams.additionalData = aadBytes;
3150
+ hasAad = true;
3151
+ }
3152
+ }
3080
3153
  const ciphertextBuffer = await subtle.encrypt(
3081
- { name: "AES-GCM", iv },
3154
+ encryptParams,
3082
3155
  cryptoKey,
3083
3156
  textEncoder.encode(jsonString)
3084
3157
  );
3158
+ const kid = options?.kid || options?.keyId || this._configuredKid;
3085
3159
  return {
3086
3160
  __encrypted: true,
3087
3161
  ciphertext: CKeyManager.toBase64(ciphertextBuffer),
@@ -3089,7 +3163,9 @@ class CWebCryptoEncryptionProvider extends AEncryptionProvider {
3089
3163
  alg: "AES-GCM",
3090
3164
  ...salt ? { salt } : {},
3091
3165
  ...kdf && kdf !== "none" && kdf !== "raw" && kdf !== "direct" ? { kdf } : {},
3092
- ...iter ? { iter } : {}
3166
+ ...iter ? { iter } : {},
3167
+ ...kid ? { kid } : {},
3168
+ ...hasAad ? { aad: true } : {}
3093
3169
  };
3094
3170
  }
3095
3171
  /**
@@ -3115,13 +3191,31 @@ class CWebCryptoEncryptionProvider extends AEncryptionProvider {
3115
3191
  let ciphertextBytes = CKeyManager.fromBase64(payload.ciphertext);
3116
3192
  if (payload.tag) {
3117
3193
  const tagBytes = CKeyManager.fromBase64(payload.tag);
3194
+ if (tagBytes.length !== 16) {
3195
+ throw new Error("[CWebCryptoEncryptionProvider] Authentication tag must be exactly 16 bytes for AES-256-GCM");
3196
+ }
3118
3197
  const combined = new Uint8Array(ciphertextBytes.length + tagBytes.length);
3119
3198
  combined.set(ciphertextBytes, 0);
3120
3199
  combined.set(tagBytes, ciphertextBytes.length);
3121
3200
  ciphertextBytes = combined;
3122
3201
  }
3202
+ const decryptParams = {
3203
+ name: "AES-GCM",
3204
+ iv
3205
+ };
3206
+ const aad = options?.aad;
3207
+ if (payload.aad || aad) {
3208
+ if (aad) {
3209
+ const aadBytes = typeof aad === "string" ? textEncoder.encode(aad) : aad instanceof Uint8Array ? aad : new Uint8Array(aad);
3210
+ if (aadBytes.byteLength > 0) {
3211
+ decryptParams.additionalData = aadBytes;
3212
+ }
3213
+ } else if (payload.aad) {
3214
+ throw new Error("[CWebCryptoEncryptionProvider] Payload requires Authenticated Additional Data (AAD) for decryption, but none was provided.");
3215
+ }
3216
+ }
3123
3217
  const decryptedBuffer = await subtle.decrypt(
3124
- { name: "AES-GCM", iv },
3218
+ decryptParams,
3125
3219
  cryptoKey,
3126
3220
  ciphertextBytes
3127
3221
  );
@@ -3146,11 +3240,15 @@ class CWebCryptoAESCBCProvider extends AEncryptionProvider {
3146
3240
  _cachedKdf;
3147
3241
  _cachedIter;
3148
3242
  _cachedHash;
3243
+ _configuredKid;
3149
3244
  _kdfOptions;
3150
3245
  _decryptSaltCache = /* @__PURE__ */ new Map();
3151
3246
  constructor(key, options) {
3152
3247
  super(key);
3153
3248
  if (options) {
3249
+ if (options.kid || options.keyId) {
3250
+ this._configuredKid = options.kid || options.keyId;
3251
+ }
3154
3252
  this._defaultKdf = options.kdf || "pbkdf2";
3155
3253
  if (options.kdf && options.kdf !== "none") {
3156
3254
  this._kdfOptions = {
@@ -3341,6 +3439,11 @@ class CWebCryptoAESCBCProvider extends AEncryptionProvider {
3341
3439
  }
3342
3440
  const passphraseBytes = textEncoder.encode(rawKey);
3343
3441
  const kdf = (kdfOverride || options?.kdf || this._defaultKdf || "none").toLowerCase();
3442
+ if (kdf === "hkdf" && typeof rawKey === "string") {
3443
+ console.warn(
3444
+ '[CWebCryptoAESCBCProvider] ADVISORY: HKDF is designed for high-entropy input key material (IKM), not low-entropy human passphrases. Use "pbkdf2" for password-derived encryption.'
3445
+ );
3446
+ }
3344
3447
  let raw512;
3345
3448
  let saltStr;
3346
3449
  let kdfType;
@@ -3352,7 +3455,7 @@ class CWebCryptoAESCBCProvider extends AEncryptionProvider {
3352
3455
  saltStr = CKeyManager.toBase64(saltBytes);
3353
3456
  kdfType = "PBKDF2";
3354
3457
  const rawIter = iterationsOverride || options?.kdfIterations || this._kdfOptions?.iterations || 31e4;
3355
- const iterations = Math.min(Math.max(rawIter, 1e3), 1e6);
3458
+ const iterations = Math.min(Math.max(rawIter, 1e3), 6e5);
3356
3459
  iterNum = iterations;
3357
3460
  const baseKey = await subtle.importKey(
3358
3461
  "raw",
@@ -3473,6 +3576,7 @@ class CWebCryptoAESCBCProvider extends AEncryptionProvider {
3473
3576
  macPayload.set(iv, 0);
3474
3577
  macPayload.set(ciphertextBytes, iv.length);
3475
3578
  const tagBuffer = await subtle.sign("HMAC", macKey, macPayload);
3579
+ const kid = options?.kid || options?.keyId || this._configuredKid;
3476
3580
  return {
3477
3581
  __encrypted: true,
3478
3582
  ciphertext: CKeyManager.toBase64(ciphertextBuffer),
@@ -3481,7 +3585,8 @@ class CWebCryptoAESCBCProvider extends AEncryptionProvider {
3481
3585
  alg: "AES-CBC-256+HMAC-SHA-256",
3482
3586
  ...salt ? { salt } : {},
3483
3587
  ...kdf && kdf !== "none" && kdf !== "raw" && kdf !== "hkdf-direct" ? { kdf } : {},
3484
- ...iter ? { iter } : {}
3588
+ ...iter ? { iter } : {},
3589
+ ...kid ? { kid } : {}
3485
3590
  };
3486
3591
  }
3487
3592
  /**
@@ -3671,12 +3776,14 @@ class CRSAEncryptionProvider extends AEncryptionProvider {
3671
3776
  new Uint8Array(rawKeyBuffer).fill(0);
3672
3777
  }
3673
3778
  const modulusLength = pubKey.algorithm?.modulusLength || 4096;
3779
+ const kid = options?.kid || options?.keyId;
3674
3780
  return {
3675
3781
  __encrypted: true,
3676
3782
  ciphertext: CKeyManager.toBase64(ciphertextBuffer),
3677
3783
  iv: CKeyManager.toBase64(iv),
3678
3784
  wrappedKey: CKeyManager.toBase64(wrappedKeyBuffer),
3679
- alg: `RSA-OAEP-${modulusLength}+AES-GCM-256`
3785
+ alg: `RSA-OAEP-${modulusLength}+AES-GCM-256`,
3786
+ ...kid ? { kid } : {}
3680
3787
  };
3681
3788
  }
3682
3789
  /**
@@ -3785,13 +3892,15 @@ class CRSAEncryptionProvider extends AEncryptionProvider {
3785
3892
  dataKey,
3786
3893
  textEncoder.encode(jsonString)
3787
3894
  );
3895
+ const kid = options?.kid || options?.keyId;
3788
3896
  const fieldPayload = {
3789
3897
  __encrypted: true,
3790
3898
  ciphertext: CKeyManager.toBase64(ciphertextBuffer),
3791
3899
  iv: CKeyManager.toBase64(iv),
3792
3900
  wrappedKey: wrappedKeyBase64,
3793
3901
  sharedKey: true,
3794
- alg
3902
+ alg,
3903
+ ...kid ? { kid } : {}
3795
3904
  };
3796
3905
  safeSetProperty(result, field, fieldPayload);
3797
3906
  }
@@ -4067,13 +4176,15 @@ class CECDHEncryptionProvider extends AEncryptionProvider {
4067
4176
  textEncoder.encode(jsonString)
4068
4177
  );
4069
4178
  const epkJwk = await subtle.exportKey("jwk", ephemeralPair.publicKey);
4179
+ const kid = options?.kid || options?.keyId;
4070
4180
  return {
4071
4181
  __encrypted: true,
4072
4182
  ciphertext: CKeyManager.toBase64(ciphertextBuffer),
4073
4183
  iv: CKeyManager.toBase64(iv),
4074
4184
  epk: epkJwk,
4075
4185
  salt: CKeyManager.toBase64(saltBytes),
4076
- alg: `ECDH-${curve}+AES-GCM-256`
4186
+ alg: `ECDH-${curve}+AES-GCM-256`,
4187
+ ...kid ? { kid } : {}
4077
4188
  };
4078
4189
  }
4079
4190
  /**
@@ -4140,13 +4251,15 @@ class CECDHEncryptionProvider extends AEncryptionProvider {
4140
4251
  dataKey,
4141
4252
  textEncoder.encode(jsonString)
4142
4253
  );
4254
+ const kid = options?.kid || options?.keyId;
4143
4255
  safeSetProperty(result, field, {
4144
4256
  __encrypted: true,
4145
4257
  ciphertext: CKeyManager.toBase64(ciphertextBuffer),
4146
4258
  iv: CKeyManager.toBase64(iv),
4147
4259
  epk: epkJwk,
4148
4260
  salt: saltBase64,
4149
- alg
4261
+ alg,
4262
+ ...kid ? { kid } : {}
4150
4263
  });
4151
4264
  }
4152
4265
  return result;
@@ -4324,6 +4437,7 @@ class CNodeCryptoEncryptionProvider extends AEncryptionProvider {
4324
4437
  _cachedKdf;
4325
4438
  _cachedIter;
4326
4439
  _cachedHash;
4440
+ _configuredKid;
4327
4441
  setDefaultKey(key) {
4328
4442
  super.setDefaultKey(key);
4329
4443
  if (this._cachedKey) {
@@ -4379,6 +4493,9 @@ class CNodeCryptoEncryptionProvider extends AEncryptionProvider {
4379
4493
  constructor(key, options) {
4380
4494
  super(key);
4381
4495
  if (options) {
4496
+ if (options.kid || options.keyId) {
4497
+ this._configuredKid = options.kid || options.keyId;
4498
+ }
4382
4499
  this._defaultKdf = options.kdf || "pbkdf2";
4383
4500
  if (options.kdf && options.kdf !== "none") {
4384
4501
  this._kdfOptions = {
@@ -4481,7 +4598,7 @@ class CNodeCryptoEncryptionProvider extends AEncryptionProvider {
4481
4598
  const saltBytes = CKeyManager.resolveSalt(s);
4482
4599
  const saltBuf = Buffer.from(saltBytes.buffer, saltBytes.byteOffset, saltBytes.byteLength);
4483
4600
  const rawIter = iterationsOverride || options?.kdfIterations || this._kdfOptions?.iterations || 31e4;
4484
- const iterations = Math.min(Math.max(rawIter, 1e3), 1e6);
4601
+ const iterations = Math.min(Math.max(rawIter, 1e3), 6e5);
4485
4602
  const hash = (options?.kdfHash || this._kdfOptions?.hash || "SHA-256").toLowerCase().replace("-", "");
4486
4603
  const key2 = crypto__namespace.pbkdf2Sync(passphrase, saltBuf, iterations, 32, hash);
4487
4604
  if (!keyProvider && !saltOverride && !options?.kdfSalt) {
@@ -4499,6 +4616,11 @@ class CNodeCryptoEncryptionProvider extends AEncryptionProvider {
4499
4616
  };
4500
4617
  }
4501
4618
  if (kdf === "hkdf") {
4619
+ if (typeof raw === "string") {
4620
+ console.warn(
4621
+ '[CNodeCryptoEncryptionProvider] ADVISORY: HKDF is designed for high-entropy input key material (IKM), not low-entropy human passphrases. Use "pbkdf2" for password-derived encryption.'
4622
+ );
4623
+ }
4502
4624
  const s = saltOverride || options?.kdfSalt || this._kdfOptions?.salt;
4503
4625
  const saltBytes = CKeyManager.resolveSalt(s);
4504
4626
  const saltBuf = Buffer.from(saltBytes.buffer, saltBytes.byteOffset, saltBytes.byteLength);
@@ -4565,7 +4687,7 @@ class CNodeCryptoEncryptionProvider extends AEncryptionProvider {
4565
4687
  const saltBytes = CKeyManager.resolveSalt(s);
4566
4688
  const saltBuf = Buffer.from(saltBytes.buffer, saltBytes.byteOffset, saltBytes.byteLength);
4567
4689
  const rawIter = iterationsOverride || options?.kdfIterations || this._kdfOptions?.iterations || 31e4;
4568
- const iterations = Math.min(Math.max(rawIter, 1e3), 1e6);
4690
+ const iterations = Math.min(Math.max(rawIter, 1e3), 6e5);
4569
4691
  const hash = (options?.kdfHash || this._kdfOptions?.hash || "SHA-256").toLowerCase().replace("-", "");
4570
4692
  const key2 = await new Promise((resolve, reject) => {
4571
4693
  crypto__namespace.pbkdf2(passphrase, saltBuf, iterations, 32, hash, (err, derived) => {
@@ -4588,6 +4710,11 @@ class CNodeCryptoEncryptionProvider extends AEncryptionProvider {
4588
4710
  };
4589
4711
  }
4590
4712
  if (kdf === "hkdf") {
4713
+ if (typeof raw === "string") {
4714
+ console.warn(
4715
+ '[CNodeCryptoEncryptionProvider] ADVISORY: HKDF is designed for high-entropy input key material (IKM), not low-entropy human passphrases. Use "pbkdf2" for password-derived encryption.'
4716
+ );
4717
+ }
4591
4718
  const s = saltOverride || options?.kdfSalt || this._kdfOptions?.salt;
4592
4719
  const saltBytes = CKeyManager.resolveSalt(s);
4593
4720
  const saltBuf = Buffer.from(saltBytes.buffer, saltBytes.byteOffset, saltBytes.byteLength);
@@ -4649,12 +4776,22 @@ class CNodeCryptoEncryptionProvider extends AEncryptionProvider {
4649
4776
  const jsonString = JSON.stringify(data);
4650
4777
  const iv = crypto__namespace.randomBytes(12);
4651
4778
  const cipher = crypto__namespace.createCipheriv("aes-256-gcm", key, iv);
4779
+ const aad = options?.aad;
4780
+ let hasAad = false;
4781
+ if (aad) {
4782
+ const aadBuffer = Buffer.isBuffer(aad) ? aad : Buffer.from(typeof aad === "string" ? aad : new Uint8Array(aad));
4783
+ if (aadBuffer.length > 0) {
4784
+ cipher.setAAD(aadBuffer);
4785
+ hasAad = true;
4786
+ }
4787
+ }
4652
4788
  const encryptedBuffer = Buffer.concat([
4653
4789
  cipher.update(jsonString, "utf8"),
4654
4790
  cipher.final()
4655
4791
  ]);
4656
4792
  const authTag = cipher.getAuthTag();
4657
4793
  const combinedCiphertext = Buffer.concat([encryptedBuffer, authTag]);
4794
+ const kid = options?.kid || options?.keyId || this._configuredKid;
4658
4795
  return {
4659
4796
  __encrypted: true,
4660
4797
  ciphertext: combinedCiphertext.toString("base64"),
@@ -4662,7 +4799,9 @@ class CNodeCryptoEncryptionProvider extends AEncryptionProvider {
4662
4799
  alg: "AES-GCM",
4663
4800
  ...salt ? { salt } : {},
4664
4801
  ...kdf && kdf !== "none" && kdf !== "raw" ? { kdf } : {},
4665
- ...iter ? { iter } : {}
4802
+ ...iter ? { iter } : {},
4803
+ ...kid ? { kid } : {},
4804
+ ...hasAad ? { aad: true } : {}
4666
4805
  };
4667
4806
  } finally {
4668
4807
  if (key !== this._cachedKey) {
@@ -4670,7 +4809,7 @@ class CNodeCryptoEncryptionProvider extends AEncryptionProvider {
4670
4809
  }
4671
4810
  }
4672
4811
  }
4673
- decryptCiphertextWithKey(payload, key) {
4812
+ decryptCiphertextWithKey(payload, key, options) {
4674
4813
  const iv = Buffer.from(payload.iv, "base64");
4675
4814
  const fullCiphertext = Buffer.from(payload.ciphertext, "base64");
4676
4815
  let ciphertext;
@@ -4678,6 +4817,9 @@ class CNodeCryptoEncryptionProvider extends AEncryptionProvider {
4678
4817
  if (payload.tag) {
4679
4818
  ciphertext = fullCiphertext;
4680
4819
  authTag = Buffer.from(payload.tag, "base64");
4820
+ if (authTag.length !== 16) {
4821
+ throw new Error("[CNodeCryptoEncryptionProvider] Authentication tag must be exactly 16 bytes for AES-256-GCM");
4822
+ }
4681
4823
  } else {
4682
4824
  if (fullCiphertext.length < 16) {
4683
4825
  throw new Error("Ciphertext too short to contain authentication tag");
@@ -4685,8 +4827,19 @@ class CNodeCryptoEncryptionProvider extends AEncryptionProvider {
4685
4827
  ciphertext = fullCiphertext.subarray(0, fullCiphertext.length - 16);
4686
4828
  authTag = fullCiphertext.subarray(fullCiphertext.length - 16);
4687
4829
  }
4688
- const decipher = crypto__namespace.createDecipheriv("aes-256-gcm", key, iv);
4830
+ const decipher = crypto__namespace.createDecipheriv("aes-256-gcm", key, iv, { authTagLength: 16 });
4689
4831
  decipher.setAuthTag(authTag);
4832
+ const aad = options?.aad;
4833
+ if (payload.aad || aad) {
4834
+ if (aad) {
4835
+ const aadBuffer = Buffer.isBuffer(aad) ? aad : Buffer.from(typeof aad === "string" ? aad : new Uint8Array(aad));
4836
+ if (aadBuffer.length > 0) {
4837
+ decipher.setAAD(aadBuffer);
4838
+ }
4839
+ } else if (payload.aad) {
4840
+ throw new Error("[CNodeCryptoEncryptionProvider] Payload requires Authenticated Additional Data (AAD) for decryption, but none was provided.");
4841
+ }
4842
+ }
4690
4843
  const decrypted = Buffer.concat([
4691
4844
  decipher.update(ciphertext),
4692
4845
  decipher.final()
@@ -4715,7 +4868,7 @@ class CNodeCryptoEncryptionProvider extends AEncryptionProvider {
4715
4868
  payload.iter
4716
4869
  );
4717
4870
  try {
4718
- return this.decryptCiphertextWithKey(payload, key);
4871
+ return this.decryptCiphertextWithKey(payload, key, options);
4719
4872
  } catch (err) {
4720
4873
  if (err instanceof SyntaxError) throw err;
4721
4874
  throw new Error(`[CNodeCryptoEncryptionProvider] Decryption failed: ${err?.message || "Authentication tag mismatch or corrupted ciphertext"}`);
@@ -4790,7 +4943,7 @@ class CNodeCryptoEncryptionProvider extends AEncryptionProvider {
4790
4943
  derivedKey = derived.key;
4791
4944
  keyCache.set(salt, derivedKey);
4792
4945
  }
4793
- safeSetProperty(result, key, this.decryptCiphertextWithKey(fieldPayload, derivedKey));
4946
+ safeSetProperty(result, key, this.decryptCiphertextWithKey(fieldPayload, derivedKey, options));
4794
4947
  mutated = true;
4795
4948
  }
4796
4949
  }
@@ -4830,12 +4983,22 @@ class CNodeCryptoEncryptionProvider extends AEncryptionProvider {
4830
4983
  const jsonString = JSON.stringify(data);
4831
4984
  const iv = crypto__namespace.randomBytes(12);
4832
4985
  const cipher = crypto__namespace.createCipheriv("aes-256-gcm", key, iv);
4986
+ const aad = options?.aad;
4987
+ let hasAad = false;
4988
+ if (aad) {
4989
+ const aadBuffer = Buffer.isBuffer(aad) ? aad : Buffer.from(typeof aad === "string" ? aad : new Uint8Array(aad));
4990
+ if (aadBuffer.length > 0) {
4991
+ cipher.setAAD(aadBuffer);
4992
+ hasAad = true;
4993
+ }
4994
+ }
4833
4995
  const ciphertext = Buffer.concat([
4834
4996
  cipher.update(jsonString, "utf8"),
4835
4997
  cipher.final()
4836
4998
  ]);
4837
4999
  const authTag = cipher.getAuthTag();
4838
5000
  const combined = Buffer.concat([ciphertext, authTag]);
5001
+ const kid = options?.kid || options?.keyId || this._configuredKid;
4839
5002
  return {
4840
5003
  __encrypted: true,
4841
5004
  ciphertext: combined.toString("base64"),
@@ -4843,7 +5006,9 @@ class CNodeCryptoEncryptionProvider extends AEncryptionProvider {
4843
5006
  alg: "AES-GCM",
4844
5007
  ...salt ? { salt } : {},
4845
5008
  ...kdf && kdf !== "none" && kdf !== "raw" ? { kdf } : {},
4846
- ...iter ? { iter } : {}
5009
+ ...iter ? { iter } : {},
5010
+ ...kid ? { kid } : {},
5011
+ ...hasAad ? { aad: true } : {}
4847
5012
  };
4848
5013
  } finally {
4849
5014
  if (key !== this._cachedKey) {
@@ -4865,7 +5030,7 @@ class CNodeCryptoEncryptionProvider extends AEncryptionProvider {
4865
5030
  payload.iter
4866
5031
  );
4867
5032
  try {
4868
- return this.decryptCiphertextWithKey(payload, key);
5033
+ return this.decryptCiphertextWithKey(payload, key, options);
4869
5034
  } catch (err) {
4870
5035
  if (err instanceof SyntaxError) throw err;
4871
5036
  throw new Error(`[CNodeCryptoEncryptionProvider] Decryption failed: ${err?.message || "Authentication tag mismatch or corrupted ciphertext"}`);
@@ -4906,7 +5071,7 @@ class CNodeCryptoEncryptionProvider extends AEncryptionProvider {
4906
5071
  derivedKey = derived.key;
4907
5072
  keyCache.set(salt, derivedKey);
4908
5073
  }
4909
- safeSetProperty(result, key, this.decryptCiphertextWithKey(fieldPayload, derivedKey));
5074
+ safeSetProperty(result, key, this.decryptCiphertextWithKey(fieldPayload, derivedKey, options));
4910
5075
  mutated = true;
4911
5076
  }
4912
5077
  }
@@ -5067,40 +5232,66 @@ class RequestProvider {
5067
5232
  setDeduplicate(deduplicate) {
5068
5233
  this.deduplicate = deduplicate;
5069
5234
  }
5235
+ setQueue(queue) {
5236
+ this.queue = queue;
5237
+ }
5070
5238
  encryption;
5071
5239
  _cachedCipherProvider;
5072
5240
  _lastEncryptionConfig;
5241
+ _scopedCipherProviders = /* @__PURE__ */ new WeakMap();
5242
+ /**
5243
+ * Configures E2EE payload encryption for outbound mutations and inbound responses.
5244
+ *
5245
+ * Threat Model: Protects sensitive SaaS tenant data against backend/database breaches.
5246
+ * Multi-user sharing is supported by role-based session keys passed dynamically.
5247
+ * Local client endpoint compromise (malware, trojans, keyloggers) is out of scope.
5248
+ */
5249
+ disposeCipherInstance(provider) {
5250
+ if (!provider) return;
5251
+ if (typeof provider.dispose === "function") {
5252
+ try {
5253
+ provider.dispose();
5254
+ } catch {
5255
+ }
5256
+ } else if (typeof provider.clearKeys === "function") {
5257
+ try {
5258
+ provider.clearKeys();
5259
+ } catch {
5260
+ }
5261
+ }
5262
+ }
5073
5263
  setEncryption(encryption) {
5074
- if (this._cachedCipherProvider && this._lastEncryptionConfig !== encryption) {
5075
- if (typeof this._cachedCipherProvider.dispose === "function") {
5076
- try {
5077
- this._cachedCipherProvider.dispose();
5078
- } catch {
5079
- }
5080
- } else if (typeof this._cachedCipherProvider.clearKeys === "function") {
5081
- try {
5082
- this._cachedCipherProvider.clearKeys();
5083
- } catch {
5084
- }
5264
+ if (this._lastEncryptionConfig && this._lastEncryptionConfig !== encryption) {
5265
+ const scopedOld = this._scopedCipherProviders.get(this._lastEncryptionConfig);
5266
+ if (scopedOld && scopedOld !== this._cachedCipherProvider) {
5267
+ this.disposeCipherInstance(scopedOld);
5085
5268
  }
5269
+ this._scopedCipherProviders.delete(this._lastEncryptionConfig);
5270
+ }
5271
+ if (this._cachedCipherProvider && this._lastEncryptionConfig !== encryption) {
5272
+ this.disposeCipherInstance(this._cachedCipherProvider);
5086
5273
  }
5087
5274
  this.encryption = encryption;
5088
5275
  this._cachedCipherProvider = void 0;
5089
5276
  this._lastEncryptionConfig = void 0;
5090
5277
  }
5091
5278
  clearEncryption() {
5092
- if (this._cachedCipherProvider) {
5093
- if (typeof this._cachedCipherProvider.dispose === "function") {
5094
- try {
5095
- this._cachedCipherProvider.dispose();
5096
- } catch {
5097
- }
5098
- } else if (typeof this._cachedCipherProvider.clearKeys === "function") {
5099
- try {
5100
- this._cachedCipherProvider.clearKeys();
5101
- } catch {
5102
- }
5279
+ if (this.encryption) {
5280
+ const scoped = this._scopedCipherProviders.get(this.encryption);
5281
+ if (scoped && scoped !== this._cachedCipherProvider) {
5282
+ this.disposeCipherInstance(scoped);
5283
+ }
5284
+ this._scopedCipherProviders.delete(this.encryption);
5285
+ }
5286
+ if (this._lastEncryptionConfig) {
5287
+ const scopedLast = this._scopedCipherProviders.get(this._lastEncryptionConfig);
5288
+ if (scopedLast && scopedLast !== this._cachedCipherProvider) {
5289
+ this.disposeCipherInstance(scopedLast);
5103
5290
  }
5291
+ this._scopedCipherProviders.delete(this._lastEncryptionConfig);
5292
+ }
5293
+ if (this._cachedCipherProvider) {
5294
+ this.disposeCipherInstance(this._cachedCipherProvider);
5104
5295
  }
5105
5296
  this.encryption = void 0;
5106
5297
  this._cachedCipherProvider = void 0;
@@ -5166,6 +5357,7 @@ class RequestProvider {
5166
5357
  }
5167
5358
  return event.context.__katluxRequestsInFlight;
5168
5359
  }
5360
+ return /* @__PURE__ */ new Map();
5169
5361
  }
5170
5362
  return RequestProvider.requestsInFlight;
5171
5363
  }
@@ -5190,11 +5382,6 @@ class RequestProvider {
5190
5382
  }
5191
5383
  serverAuthIdentity = authHeader || authCookies || "";
5192
5384
  }
5193
- if (options?.key) {
5194
- const authSuffix = serverAuthIdentity ? `|${hashIdentity(serverAuthIdentity)}` : "";
5195
- const fullKey = `${options.key}${authSuffix}`;
5196
- return namespace ? `${namespace}:${fullKey}` : fullKey;
5197
- }
5198
5385
  let headersForCache = headers || {};
5199
5386
  if (headers) {
5200
5387
  headersForCache = {};
@@ -5205,21 +5392,26 @@ class RequestProvider {
5205
5392
  }
5206
5393
  }
5207
5394
  }
5395
+ const encOptions = options?.encryption || this.encryption;
5396
+ const encSuffix = encOptions?.enabled ? `|enc_${encOptions.keyId || encOptions.kid || (typeof encOptions.key === "string" ? fastKeyHash(encOptions.key) : "1")}` : "";
5208
5397
  if (options?.key) {
5209
5398
  const authSuffix = serverAuthIdentity ? `|${hashIdentity(serverAuthIdentity)}` : "";
5210
- const fullKey = `${options.key}${authSuffix}`;
5399
+ const fullKey = `${options.key}${encSuffix}${authSuffix}`;
5211
5400
  return namespace ? `${namespace}:${fullKey}` : fullKey;
5212
5401
  }
5213
5402
  const parts = [
5214
5403
  url,
5215
5404
  stableStringify(query || {}),
5216
5405
  stableStringify(body || null),
5217
- stableStringify(headersForCache)
5406
+ stableStringify(headersForCache),
5407
+ method || "GET"
5218
5408
  ];
5219
5409
  if (serverAuthIdentity) {
5220
5410
  parts.push(hashIdentity(serverAuthIdentity));
5221
5411
  }
5222
- parts.push(method || "GET");
5412
+ if (encSuffix) {
5413
+ parts.push(encSuffix.slice(1));
5414
+ }
5223
5415
  const baseKey = parts.join("|");
5224
5416
  return namespace ? `${namespace}:${baseKey}` : baseKey;
5225
5417
  }
@@ -5233,13 +5425,31 @@ class RequestProvider {
5233
5425
  const tags = effectiveOptions.tags || this.tags;
5234
5426
  const label = effectiveOptions.label || effectiveOptions.queueLabel;
5235
5427
  if (shouldQueue && method !== "GET") {
5236
- return RequestQueue.enqueue({
5237
- url,
5238
- method,
5239
- options: options || {},
5240
- tags,
5241
- label
5242
- });
5428
+ const enqueuePromise = (async () => {
5429
+ const encOptions = effectiveOptions.encryption;
5430
+ let effectiveBody = effectiveOptions.body;
5431
+ const isBinaryOrFormBody = effectiveBody !== void 0 && effectiveBody !== null && (typeof FormData !== "undefined" && effectiveBody instanceof FormData || typeof Blob !== "undefined" && effectiveBody instanceof Blob || typeof URLSearchParams !== "undefined" && effectiveBody instanceof URLSearchParams || typeof ArrayBuffer !== "undefined" && (effectiveBody instanceof ArrayBuffer || ArrayBuffer.isView(effectiveBody)));
5432
+ if (encOptions?.enabled && effectiveBody !== void 0 && effectiveBody !== null && !isBinaryOrFormBody) {
5433
+ const cipherProvider = this.getCipherProvider(encOptions);
5434
+ const isServer = typeof window === "undefined";
5435
+ effectiveBody = await cipherProvider.encrypt(
5436
+ effectiveBody,
5437
+ this.buildEncryptionContext(encOptions, url, method, isServer)
5438
+ );
5439
+ }
5440
+ const queuedOptions = {
5441
+ ...effectiveOptions,
5442
+ body: effectiveBody
5443
+ };
5444
+ return RequestQueue.enqueue({
5445
+ url,
5446
+ method,
5447
+ options: queuedOptions,
5448
+ tags,
5449
+ label
5450
+ });
5451
+ })();
5452
+ return enqueuePromise;
5243
5453
  }
5244
5454
  const inFlightMap = this.getInFlightMap(options);
5245
5455
  const uniqueKey = this.getUniqueKey(url, options);
@@ -5320,26 +5530,40 @@ class RequestProvider {
5320
5530
  if (encOptions === this.encryption && this._cachedCipherProvider) {
5321
5531
  return this._cachedCipherProvider;
5322
5532
  }
5323
- const provider = createEncryptionProvider(encOptions);
5533
+ let provider = this._scopedCipherProviders.get(encOptions);
5534
+ if (!provider) {
5535
+ provider = createEncryptionProvider(encOptions);
5536
+ this._scopedCipherProviders.set(encOptions, provider);
5537
+ }
5324
5538
  if (encOptions === this.encryption) {
5325
5539
  this._cachedCipherProvider = provider;
5326
5540
  this._lastEncryptionConfig = encOptions;
5327
5541
  }
5328
5542
  return provider;
5329
5543
  }
5330
- isDataEncrypted(provider, data, visited = /* @__PURE__ */ new WeakSet()) {
5544
+ isDataEncrypted(provider, data) {
5331
5545
  if (!data || typeof data !== "object") return false;
5332
- if (visited.has(data)) return false;
5333
- visited.add(data);
5546
+ if (data.__encrypted === true) return true;
5334
5547
  if (typeof provider.isEncrypted === "function" && provider.isEncrypted(data)) {
5335
5548
  return true;
5336
5549
  }
5337
5550
  if (Array.isArray(data)) {
5338
- return data.some((item) => this.isDataEncrypted(provider, item, visited));
5551
+ if (data.length === 0) return false;
5552
+ const first = data[0];
5553
+ return first !== null && typeof first === "object" && (first.__encrypted === true || typeof provider.isEncrypted === "function" && provider.isEncrypted(first) || this.isDataEncrypted(provider, first));
5554
+ }
5555
+ if (data.data && typeof data.data === "object") {
5556
+ return this.isDataEncrypted(provider, data.data);
5557
+ }
5558
+ if (data.items && typeof data.items === "object") {
5559
+ return this.isDataEncrypted(provider, data.items);
5560
+ }
5561
+ if (data.rows && typeof data.rows === "object") {
5562
+ return this.isDataEncrypted(provider, data.rows);
5339
5563
  }
5340
5564
  for (const key of Object.keys(data)) {
5341
5565
  const val = data[key];
5342
- if (val && typeof val === "object" && (val.__encrypted === true || this.isDataEncrypted(provider, val, visited))) {
5566
+ if (val && typeof val === "object" && val.__encrypted === true) {
5343
5567
  return true;
5344
5568
  }
5345
5569
  }
@@ -5374,13 +5598,20 @@ class RequestProvider {
5374
5598
  return data;
5375
5599
  }
5376
5600
  const cipherProvider = this.getCipherProvider(encOptions);
5377
- if (this.isDataEncrypted(cipherProvider, data) || encOptions.fields && encOptions.fields.length > 0) {
5378
- return cipherProvider.decrypt(
5379
- data,
5601
+ let result = data;
5602
+ if (this.isDataEncrypted(cipherProvider, result) || encOptions.fields && encOptions.fields.length > 0) {
5603
+ result = await cipherProvider.decrypt(
5604
+ result,
5380
5605
  this.buildEncryptionContext(encOptions, url, requestMethod, isServer)
5381
5606
  );
5607
+ if (result && typeof result === "object" && encOptions.fields && encOptions.fields.length > 0 && this.isDataEncrypted(cipherProvider, result)) {
5608
+ result = await cipherProvider.decrypt(
5609
+ result,
5610
+ this.buildEncryptionContext(encOptions, url, requestMethod, isServer)
5611
+ );
5612
+ }
5382
5613
  }
5383
- return data;
5614
+ return result;
5384
5615
  }
5385
5616
  async request(url, options) {
5386
5617
  const { cacheStrategy, lifetime, query, body, headers, method } = options || {};
@@ -5450,11 +5681,14 @@ class RequestProvider {
5450
5681
  let dataToCache = responseData;
5451
5682
  if (encOptions?.enabled) {
5452
5683
  const cipherProvider = this.getCipherProvider(encOptions);
5453
- if (!this.isDataEncrypted(cipherProvider, dataToCache)) {
5454
- dataToCache = await cipherProvider.encrypt(
5455
- dataToCache,
5456
- this.buildEncryptionContext(encOptions, url, requestMethod, isServer)
5457
- );
5684
+ const isFullEnvelope = dataToCache && typeof dataToCache === "object" && dataToCache.__encrypted === true;
5685
+ if (!isFullEnvelope) {
5686
+ const cacheContext = {
5687
+ ...this.buildEncryptionContext(encOptions, url, requestMethod, isServer),
5688
+ fields: void 0,
5689
+ valueEncryption: false
5690
+ };
5691
+ dataToCache = await cipherProvider.encrypt(dataToCache, cacheContext);
5458
5692
  }
5459
5693
  }
5460
5694
  await provider.set(uniqueKey, dataToCache, cacheLifetime, context);
@@ -5505,21 +5739,31 @@ function clearCachedCipherProvider(encryption) {
5505
5739
  providerCache.delete(encryption);
5506
5740
  }
5507
5741
  }
5508
- function isDataEncrypted(provider, data, visited = /* @__PURE__ */ new WeakSet()) {
5742
+ function isDataEncrypted(provider, data) {
5509
5743
  if (!data || typeof data !== "object") return false;
5510
- if (visited.has(data)) return false;
5511
- visited.add(data);
5744
+ if (data.__encrypted === true) return true;
5512
5745
  if (provider && typeof provider.isEncrypted === "function") {
5513
5746
  if (provider.isEncrypted(data)) return true;
5514
5747
  } else if (CWebCryptoEncryptionProvider.isEncrypted(data)) {
5515
5748
  return true;
5516
5749
  }
5517
5750
  if (Array.isArray(data)) {
5518
- return data.some((item) => isDataEncrypted(provider, item, visited));
5751
+ if (data.length === 0) return false;
5752
+ const first = data[0];
5753
+ return first !== null && typeof first === "object" && (first.__encrypted === true || (provider?.isEncrypted ? provider.isEncrypted(first) : CWebCryptoEncryptionProvider.isEncrypted(first)));
5754
+ }
5755
+ if (data.data && typeof data.data === "object") {
5756
+ return isDataEncrypted(provider, data.data);
5757
+ }
5758
+ if (data.items && typeof data.items === "object") {
5759
+ return isDataEncrypted(provider, data.items);
5760
+ }
5761
+ if (data.rows && typeof data.rows === "object") {
5762
+ return isDataEncrypted(provider, data.rows);
5519
5763
  }
5520
5764
  for (const key of Object.keys(data)) {
5521
5765
  const val = data[key];
5522
- if (val && typeof val === "object" && (val.__encrypted === true || isDataEncrypted(provider, val, visited))) {
5766
+ if (val && typeof val === "object" && val.__encrypted === true) {
5523
5767
  return true;
5524
5768
  }
5525
5769
  }
@@ -5551,7 +5795,15 @@ async function decryptPageRows(rows, encryption, apiUrl, idKey) {
5551
5795
  if (!cipherProvider) {
5552
5796
  return { rows, decrypted: false };
5553
5797
  }
5554
- if (!rows.some((row) => isDataEncrypted(cipherProvider, row))) {
5798
+ const hasEncryptedRows = rows.some((row) => {
5799
+ if (!row || typeof row !== "object") return false;
5800
+ if (row.__encrypted === true) return true;
5801
+ if (encryption?.fields && encryption.fields.length > 0) {
5802
+ return encryption.fields.some((f) => row[f]?.__encrypted === true);
5803
+ }
5804
+ return isDataEncrypted(cipherProvider, row);
5805
+ });
5806
+ if (!hasEncryptedRows) {
5555
5807
  return { rows, decrypted: false };
5556
5808
  }
5557
5809
  const ctx = buildEncryptionContext(encryption, apiUrl);
@@ -5612,8 +5864,12 @@ class CAPIFlatClientDataProvider extends CFlatClientDataProvider {
5612
5864
  cacheLifetime = 0;
5613
5865
  refreshOnMutation = void 0;
5614
5866
  encryption;
5867
+ _isAccumulating = false;
5868
+ _inFlightLoadMore = false;
5615
5869
  constructor(options) {
5616
5870
  super(options);
5871
+ if (options?.apiUrl) this.apiUrl = options.apiUrl;
5872
+ if (options?.url) this.apiUrl = options.url;
5617
5873
  if (options?.cacheStrategy) {
5618
5874
  this.cacheStrategy = options.cacheStrategy;
5619
5875
  this.requestProvider.setCacheProvider(options.cacheStrategy);
@@ -5631,10 +5887,43 @@ class CAPIFlatClientDataProvider extends CFlatClientDataProvider {
5631
5887
  this.requestProvider.onDataReceived((data) => {
5632
5888
  if (data) {
5633
5889
  const normalized = normalizeDataResult(data);
5634
- this.setData(normalized ? normalized.rows : data.rows || data);
5890
+ const rows = normalized ? normalized.rows : data.rows || data;
5891
+ if (this._isAccumulating) {
5892
+ this.allData = [...this.allData, ...rows];
5893
+ this.loadPageData();
5894
+ } else {
5895
+ this.setData(rows);
5896
+ }
5897
+ if (normalized?.rowCount !== void 0) {
5898
+ this.rowCount = normalized.rowCount;
5899
+ }
5635
5900
  }
5636
5901
  });
5637
5902
  }
5903
+ async loadMore() {
5904
+ if (!this.hasMore || this.loading || this._inFlightLoadMore) return;
5905
+ this._inFlightLoadMore = true;
5906
+ this._isAccumulating = true;
5907
+ try {
5908
+ if (this.apiUrl) {
5909
+ const nextPage = this.currentPage + 1;
5910
+ await this.requestProvider.registerRequest(this.apiUrl, {
5911
+ query: {
5912
+ pageNumber: nextPage,
5913
+ pageSize: this.pageSize
5914
+ },
5915
+ deduplicate: this.deduplicate,
5916
+ context: this.context
5917
+ });
5918
+ this.setCurrentPage(nextPage);
5919
+ } else {
5920
+ await this.setCurrentPage(this.currentPage + 1);
5921
+ }
5922
+ } finally {
5923
+ this._isAccumulating = false;
5924
+ this._inFlightLoadMore = false;
5925
+ }
5926
+ }
5638
5927
  setEncryption(encryption) {
5639
5928
  if (this.encryption && this.encryption !== encryption) {
5640
5929
  clearCachedCipherProvider(this.encryption);
@@ -5707,6 +5996,9 @@ class CAPIFlatClientDataProvider extends CFlatClientDataProvider {
5707
5996
  }
5708
5997
  try {
5709
5998
  const result = await fetchWithSSR(key, fetcher, this.context);
5999
+ if (result && typeof result === "object" && (result.__encrypted === true || result.ciphertext)) {
6000
+ return;
6001
+ }
5710
6002
  const normalized = normalizeDataResult(result);
5711
6003
  if (normalized) {
5712
6004
  this.setData(normalized.rows);
@@ -6133,18 +6425,22 @@ class CTreeClientDataProvider extends CFlatClientDataProvider {
6133
6425
  _nodeMapCache = null;
6134
6426
  _sortedNodeIdsCache = null;
6135
6427
  _filteredNodesCache = null;
6428
+ _allVisibleRowsCache = null;
6136
6429
  invalidateStaticCache() {
6137
6430
  this._staticNodesCache = null;
6138
6431
  this._nodeMapCache = null;
6139
6432
  this._sortedNodeIdsCache = null;
6140
6433
  this._filteredNodesCache = null;
6434
+ this._allVisibleRowsCache = null;
6141
6435
  }
6142
6436
  setFilter(filter, immediate = false) {
6143
6437
  this._filteredNodesCache = null;
6438
+ this._allVisibleRowsCache = null;
6144
6439
  super.setFilter(filter, immediate);
6145
6440
  }
6146
6441
  onFilterUpdated() {
6147
6442
  this._filteredNodesCache = null;
6443
+ this._allVisibleRowsCache = null;
6148
6444
  }
6149
6445
  get _staticNodes() {
6150
6446
  if (!this._staticNodesCache) {
@@ -6250,9 +6546,11 @@ class CTreeClientDataProvider extends CFlatClientDataProvider {
6250
6546
  }
6251
6547
  onSortUpdated() {
6252
6548
  this._sortedNodeIdsCache = null;
6549
+ this._allVisibleRowsCache = null;
6253
6550
  }
6254
6551
  setSortList(sortList, immediate = false) {
6255
6552
  this._sortedNodeIdsCache = null;
6553
+ this._allVisibleRowsCache = null;
6256
6554
  super.setSortList(sortList, immediate);
6257
6555
  }
6258
6556
  computeSortedIds() {
@@ -6262,16 +6560,29 @@ class CTreeClientDataProvider extends CFlatClientDataProvider {
6262
6560
  const nodeMap = this._nodeMap;
6263
6561
  const sortNodes = (nodesToSort) => {
6264
6562
  if (!this.sortList || this.sortList.length === 0) return nodesToSort;
6563
+ const nodeById = /* @__PURE__ */ new Map();
6564
+ const nodeByData = /* @__PURE__ */ new Map();
6565
+ for (const n of nodesToSort) {
6566
+ nodeById.set(n.id, n);
6567
+ if (n.data) nodeByData.set(n.data, n);
6568
+ }
6265
6569
  const dataRows = nodesToSort.map((n) => n.data);
6266
6570
  const sortedData = sortData(this.sortList, dataRows);
6267
6571
  const sortedNodes = [];
6572
+ const visited = /* @__PURE__ */ new Set();
6268
6573
  for (const row of sortedData) {
6269
6574
  const id = row?.[this.idKey] ?? row?.id;
6270
- const found = nodesToSort.find((n) => n.id === id || n.data === row);
6271
- if (found && !sortedNodes.includes(found)) sortedNodes.push(found);
6575
+ const found = (id !== void 0 && id !== null ? nodeById.get(id) : void 0) ?? nodeByData.get(row);
6576
+ if (found && !visited.has(found)) {
6577
+ sortedNodes.push(found);
6578
+ visited.add(found);
6579
+ }
6272
6580
  }
6273
6581
  for (const n of nodesToSort) {
6274
- if (!sortedNodes.includes(n)) sortedNodes.push(n);
6582
+ if (!visited.has(n)) {
6583
+ sortedNodes.push(n);
6584
+ visited.add(n);
6585
+ }
6275
6586
  }
6276
6587
  return sortedNodes;
6277
6588
  };
@@ -6483,12 +6794,18 @@ class CTreeClientDataProvider extends CFlatClientDataProvider {
6483
6794
  }
6484
6795
  return visibleNodes;
6485
6796
  }
6797
+ get rowMap() {
6798
+ return this.allVisibleRows;
6799
+ }
6486
6800
  get allVisibleRows() {
6487
- return this.allVisibleNodes.map((node) => ({
6488
- ...node.data,
6489
- ...node,
6490
- __treeNode: node
6491
- }));
6801
+ if (!this._allVisibleRowsCache) {
6802
+ this._allVisibleRowsCache = this.allVisibleNodes.map((node) => ({
6803
+ ...node.data,
6804
+ ...node,
6805
+ __treeNode: node
6806
+ }));
6807
+ }
6808
+ return this._allVisibleRowsCache;
6492
6809
  }
6493
6810
  get visibleRows() {
6494
6811
  return this.paginateNodes(this.allVisibleNodes);
@@ -6503,6 +6820,7 @@ class CTreeClientDataProvider extends CFlatClientDataProvider {
6503
6820
  if (this.filter && this.filter.active) {
6504
6821
  this._filteredNodesCache = null;
6505
6822
  }
6823
+ this._allVisibleRowsCache = null;
6506
6824
  if (this.expandedNodes.has(nodeId)) {
6507
6825
  this.expandedNodes.delete(nodeId);
6508
6826
  } else {
@@ -6515,6 +6833,7 @@ class CTreeClientDataProvider extends CFlatClientDataProvider {
6515
6833
  if (this.filter && this.filter.active) {
6516
6834
  this._filteredNodesCache = null;
6517
6835
  }
6836
+ this._allVisibleRowsCache = null;
6518
6837
  this.expandedNodes.add(nodeId);
6519
6838
  this.updatePageData();
6520
6839
  this.notify();
@@ -6523,6 +6842,7 @@ class CTreeClientDataProvider extends CFlatClientDataProvider {
6523
6842
  if (this.filter && this.filter.active) {
6524
6843
  this._filteredNodesCache = null;
6525
6844
  }
6845
+ this._allVisibleRowsCache = null;
6526
6846
  this.expandedNodes.delete(nodeId);
6527
6847
  this.updatePageData();
6528
6848
  this.notify();
@@ -6537,6 +6857,7 @@ class CTreeClientDataProvider extends CFlatClientDataProvider {
6537
6857
  if (this.filter && this.filter.active) {
6538
6858
  this._filteredNodesCache = null;
6539
6859
  }
6860
+ this._allVisibleRowsCache = null;
6540
6861
  const allIds = this._staticNodes.filter((n) => n.hasChildren).map((n) => n.id);
6541
6862
  this.expandedNodes = new Set(allIds);
6542
6863
  this.updatePageData();
@@ -6546,6 +6867,7 @@ class CTreeClientDataProvider extends CFlatClientDataProvider {
6546
6867
  if (this.filter && this.filter.active) {
6547
6868
  this._filteredNodesCache = null;
6548
6869
  }
6870
+ this._allVisibleRowsCache = null;
6549
6871
  this.expandedNodes = /* @__PURE__ */ new Set();
6550
6872
  this.updatePageData();
6551
6873
  this.notify();
@@ -6578,8 +6900,14 @@ class CAPITreeClientDataProvider extends CTreeClientDataProvider {
6578
6900
  encryption;
6579
6901
  constructor(options) {
6580
6902
  super(options);
6581
- if (options?.cacheStrategy) this.cacheStrategy = options.cacheStrategy;
6582
- if (options?.cacheLifetime) this.cacheLifetime = options.cacheLifetime;
6903
+ if (options?.cacheStrategy) {
6904
+ this.cacheStrategy = options.cacheStrategy;
6905
+ this.requestProvider.setCacheProvider(options.cacheStrategy);
6906
+ }
6907
+ if (options?.cacheLifetime !== void 0) {
6908
+ this.cacheLifetime = options.cacheLifetime;
6909
+ this.requestProvider.setCacheLifetime(options.cacheLifetime);
6910
+ }
6583
6911
  if (options?.refreshOnMutation !== void 0) this.refreshOnMutation = options.refreshOnMutation;
6584
6912
  if (options?.contextKey) this.contextKey = options.contextKey;
6585
6913
  if (options?.encryption) {
@@ -7617,6 +7945,20 @@ class CServerSessionCache extends ACacheProvider {
7617
7945
  async removeByPrefix(prefix, context) {
7618
7946
  const fullPrefix = "cache:" + prefix;
7619
7947
  try {
7948
+ if (!context || context === true || context?.allSessions === true) {
7949
+ for (const sessionData2 of CServerSessionCache.storage.values()) {
7950
+ const keysToDelete = [];
7951
+ for (const key of sessionData2.keys()) {
7952
+ if (key.startsWith(fullPrefix)) {
7953
+ keysToDelete.push(key);
7954
+ }
7955
+ }
7956
+ for (const key of keysToDelete) {
7957
+ sessionData2.delete(key);
7958
+ }
7959
+ }
7960
+ return;
7961
+ }
7620
7962
  const sessionId = await this.getSessionId(context);
7621
7963
  const sessionData = CServerSessionCache.storage.get(sessionId);
7622
7964
  if (sessionData) {
@@ -7815,6 +8157,7 @@ const CBrowserSessionCache$1 = {
7815
8157
 
7816
8158
  class CLocalStorageCache extends ACacheProvider {
7817
8159
  static instance = null;
8160
+ static _writeCount = 0;
7818
8161
  static getInstance() {
7819
8162
  if (!this.instance) {
7820
8163
  this.instance = new CLocalStorageCache();
@@ -8019,12 +8362,17 @@ class CLocalStorageCache extends ACacheProvider {
8019
8362
  accessCount: 1
8020
8363
  };
8021
8364
  if (typeof window !== "undefined" && typeof localStorage !== "undefined") {
8022
- try {
8023
- const usage = this.estimateStorageUsage();
8024
- if (usage.isNearQuota) {
8025
- this.evictLRU(0.25);
8365
+ const nearEntriesLimit = Boolean(
8366
+ this.quotaOptions.maxEntries && localStorage.length >= this.quotaOptions.maxEntries * this.quotaOptions.quotaThresholdRatio
8367
+ );
8368
+ if (nearEntriesLimit || ++CLocalStorageCache._writeCount % 50 === 0) {
8369
+ try {
8370
+ const usage = this.estimateStorageUsage();
8371
+ if (usage.isNearQuota) {
8372
+ this.evictLRU(0.25);
8373
+ }
8374
+ } catch (e) {
8026
8375
  }
8027
- } catch (e) {
8028
8376
  }
8029
8377
  const serialized = JSON.stringify(entry);
8030
8378
  try {
@@ -8242,7 +8590,7 @@ class CIndexedDBCache extends ACacheProvider {
8242
8590
  quota = estimate.quota || 0;
8243
8591
  usage = estimate.usage || 0;
8244
8592
  ratio = quota > 0 ? usage / quota : 0;
8245
- if (ratio >= this.quotaOptions.quotaThresholdRatio) {
8593
+ if (ratio >= this.quotaOptions.quotaThresholdRatio || this.quotaOptions.maxSizeBytes > 0 && usage >= this.quotaOptions.maxSizeBytes * this.quotaOptions.quotaThresholdRatio) {
8246
8594
  isNearQuota = true;
8247
8595
  }
8248
8596
  } catch {
@@ -8588,6 +8936,7 @@ exports.createEncryptionProvider = createEncryptionProvider;
8588
8936
  exports.debounce = debounce;
8589
8937
  exports.decryptPageRows = decryptPageRows;
8590
8938
  exports.evaluateFilter = evaluateFilter;
8939
+ exports.fastKeyHash = fastKeyHash;
8591
8940
  exports.fetchWithSSR = fetchWithSSR;
8592
8941
  exports.filterData = filterData;
8593
8942
  exports.filterTreeData = filterTreeData;
@@ -8595,14 +8944,23 @@ exports.getAppRoute = getAppRoute;
8595
8944
  exports.getAppRouter = getAppRouter;
8596
8945
  exports.getCipherProvider = getCipherProvider;
8597
8946
  exports.getHydrationAdapter = getHydrationAdapter;
8947
+ exports.getKeyIdentifier = getKeyIdentifier;
8598
8948
  exports.getPlatformApp = getPlatformApp;
8949
+ exports.getRandomValues = getRandomValues;
8599
8950
  exports.getSSRData = getSSRData;
8600
8951
  exports.getSSRPayload = getSSRPayload;
8952
+ exports.getSafeKeyIdentifier = getSafeKeyIdentifier;
8953
+ exports.getSubtleCrypto = getSubtleCrypto;
8601
8954
  exports.isDataEncrypted = isDataEncrypted;
8602
8955
  exports.isEncrypted = isEncrypted;
8603
8956
  exports.onPlatformReady = onPlatformReady;
8957
+ exports.resolveInfoBytes = resolveInfoBytes;
8958
+ exports.safeSetProperty = safeSetProperty;
8604
8959
  exports.setHydrationAdapter = setHydrationAdapter;
8605
8960
  exports.sortData = sortData;
8961
+ exports.textDecoder = textDecoder;
8962
+ exports.textEncoder = textEncoder;
8963
+ exports.toBase64 = toBase64;
8606
8964
  exports.useDebounce = useDebounce;
8607
8965
  exports.useFilterLogic = useFilterLogic;
8608
8966
  exports.useSortLogic = useSortLogic;