@qlover/fe-corekit 3.1.1 → 3.2.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/dist/index.cjs CHANGED
@@ -51,15 +51,20 @@ __export(index_exports, {
51
51
  ResponsePlugin: () => ResponsePlugin,
52
52
  RetryPlugin: () => RetryPlugin,
53
53
  SimpleUrlBuilder: () => SimpleUrlBuilder,
54
- SyncStorage: () => SyncStorage,
54
+ StorageExecutor: () => StorageExecutor,
55
55
  appendHeaders: () => appendHeaders,
56
56
  createAbortPromise: () => createAbortPromise,
57
+ createStoragePlugin: () => createStoragePlugin,
58
+ createStoragePluginWithStorage: () => createStoragePluginWithStorage,
57
59
  hasObjectKey: () => hasObjectKey,
58
60
  hasObjectKeyWithValue: () => hasObjectKeyWithValue,
59
61
  isAbortError: () => isAbortError,
60
62
  isAbsoluteUrl: () => isAbsoluteUrl,
61
63
  isAsString: () => isAsString,
64
+ isEncryptor: () => isEncryptor,
62
65
  isRequestAdapterResponse: () => isRequestAdapterResponse,
66
+ isSerializer: () => isSerializer,
67
+ isStorage: () => isStorage,
63
68
  normalizeHookNames: () => normalizeHookNames,
64
69
  raceWithAbort: () => raceWithAbort,
65
70
  runPluginHook: () => runPluginHook,
@@ -2881,6 +2886,9 @@ var RequestAdapterFetch = class {
2881
2886
  if (!baseURL) {
2882
2887
  return url;
2883
2888
  }
2889
+ if (url.startsWith(baseURL)) {
2890
+ return url;
2891
+ }
2884
2892
  if (baseURL.endsWith("/") && url.startsWith("/")) {
2885
2893
  return baseURL.slice(0, -1) + url;
2886
2894
  }
@@ -3827,12 +3835,18 @@ var SimpleUrlBuilder = class {
3827
3835
  basePath = "/" + basePath;
3828
3836
  }
3829
3837
  let combinedPath = basePath;
3830
- if (!combinedPath.endsWith("/") && !url.startsWith("/")) {
3831
- combinedPath += "/";
3838
+ if (!combinedPath.endsWith("/") || !url.startsWith("/")) {
3839
+ if (!combinedPath.endsWith("/")) {
3840
+ combinedPath += "/";
3841
+ }
3832
3842
  }
3833
3843
  combinedPath += url.replace(/^\//, "");
3834
3844
  urlObject = new URL(combinedPath, "http://temp");
3835
3845
  shouldReturnPathOnly = true;
3846
+ } else if (base && base === "api") {
3847
+ const combinedPath = "/api/" + url.replace(/^\//, "");
3848
+ urlObject = new URL(combinedPath, "http://temp");
3849
+ shouldReturnPathOnly = true;
3836
3850
  } else if (base) {
3837
3851
  urlObject = new URL(url, "http://temp");
3838
3852
  shouldReturnPathOnly = true;
@@ -4038,6 +4052,9 @@ var RequestPlugin = class {
4038
4052
  onBefore(ctx) {
4039
4053
  ctx.setParameters(this.mergeConfig(ctx.parameters));
4040
4054
  }
4055
+ startsWith(url, baseUrl) {
4056
+ return url.startsWith(baseUrl);
4057
+ }
4041
4058
  /**
4042
4059
  * Main request handler
4043
4060
  *
@@ -4051,6 +4068,9 @@ var RequestPlugin = class {
4051
4068
  const mergedConfig = this.createConfig(config);
4052
4069
  const processedData = this.processRequestData(mergedConfig);
4053
4070
  const builtUrl = this.buildUrl(mergedConfig);
4071
+ if (builtUrl && mergedConfig.baseURL && this.startsWith(builtUrl, mergedConfig.baseURL)) {
4072
+ delete mergedConfig.baseURL;
4073
+ }
4054
4074
  const injectedHeaders = this.injectHeaders(mergedConfig);
4055
4075
  return Object.assign(mergedConfig, {
4056
4076
  data: processedData,
@@ -4911,71 +4931,45 @@ var Base64Serializer = class {
4911
4931
 
4912
4932
  // src/storage/impl/KeyStorage.ts
4913
4933
  var KeyStorage = class {
4914
- constructor(key, options = {}) {
4934
+ constructor(key, storage) {
4915
4935
  this.key = key;
4916
- this.options = options;
4917
- try {
4918
- const localValue = options.storage?.getItem(key);
4919
- this.value = localValue ?? null;
4920
- } catch {
4921
- this.value = null;
4922
- }
4936
+ this.storage = storage;
4923
4937
  }
4924
4938
  value;
4925
- mergeOptions(options) {
4926
- return {
4927
- ...this.options,
4928
- ...options
4929
- };
4930
- }
4931
- /**
4932
- * @override
4933
- */
4934
- getKey() {
4935
- return this.key;
4936
- }
4937
- /**
4938
- * @override
4939
- */
4940
- getValue() {
4941
- return this.value;
4942
- }
4943
4939
  /**
4944
4940
  * @override
4945
4941
  */
4946
4942
  get(options) {
4947
- const { storage, ...reset } = this.mergeOptions(options);
4948
- if (this.value != null) {
4949
- return this.value;
4943
+ if (!this.storage) {
4944
+ return this.value ?? null;
4950
4945
  }
4951
- if (storage) {
4952
- const val = storage.getItem(this.key, void 0, reset);
4953
- if (val == null) {
4954
- this.remove();
4955
- return null;
4956
- }
4957
- this.value = val;
4958
- return val;
4946
+ const val = this.storage.getItem(this.key, options);
4947
+ if (val == null) {
4948
+ this.remove();
4949
+ return null;
4959
4950
  }
4960
- return this.value;
4951
+ this.value = val;
4952
+ return val;
4961
4953
  }
4962
4954
  /**
4963
4955
  * @override
4964
4956
  */
4965
- set(token, options) {
4966
- const { storage, ...reset } = this.mergeOptions(options);
4967
- this.value = token;
4968
- if (storage) {
4969
- storage.setItem(this.key, token, reset);
4957
+ set(value, options) {
4958
+ if (!this.storage) {
4959
+ this.value = value;
4960
+ return;
4970
4961
  }
4962
+ this.storage.setItem(this.key, value, options);
4971
4963
  }
4972
4964
  /**
4973
4965
  * @override
4974
4966
  */
4975
4967
  remove(options) {
4976
- const { storage, ...reset } = this.mergeOptions(options);
4977
- this.value = null;
4978
- storage?.removeItem(this.key, reset);
4968
+ if (!this.storage) {
4969
+ this.value = null;
4970
+ return;
4971
+ }
4972
+ this.storage.removeItem(this.key, options);
4979
4973
  }
4980
4974
  };
4981
4975
 
@@ -5010,7 +5004,6 @@ var ObjectStorage = class {
5010
5004
  /**
5011
5005
  * Gets the number of items stored in the memory cache
5012
5006
  *
5013
- * @override
5014
5007
  * @returns The number of stored items in memory
5015
5008
  *
5016
5009
  * @example
@@ -5088,12 +5081,9 @@ var ObjectStorage = class {
5088
5081
  if (!storeValue) {
5089
5082
  return _dv;
5090
5083
  }
5091
- const value = this.serializer ? this.serializer.deserialize(storeValue, _dv) : storeValue;
5084
+ const value = typeof storeValue === "string" && this.serializer ? this.serializer.deserialize(storeValue, _dv) : storeValue;
5092
5085
  return this.getRawValue(value, _dv);
5093
5086
  }
5094
- /**
5095
- * @override
5096
- */
5097
5087
  getRawValue(value, defaultValue) {
5098
5088
  if (this.isStorageValue(value)) {
5099
5089
  if (this.isExpired(value)) {
@@ -5187,268 +5177,146 @@ var ObjectStorage = class {
5187
5177
  isStorageValue(value) {
5188
5178
  return typeof value === "object" && value !== null && "key" in value && "value" in value;
5189
5179
  }
5190
- /**
5191
- * Gets the serializer instance
5192
- *
5193
- * Significance: Provides access to the serialization logic
5194
- * Core idea: Expose serializer for advanced use cases
5195
- * Main function: Return the serializer instance
5196
- * Main purpose: Enable direct access to serialization when needed
5197
- *
5198
- * @returns The serializer instance
5199
- *
5200
- * @example
5201
- * ```typescript
5202
- * const serializer = storage.getSerializer();
5203
- * if (serializer) {
5204
- * // Direct access to serializer
5205
- * }
5206
- * ```
5207
- */
5208
- getSerializer() {
5209
- return this.serializer;
5210
- }
5211
5180
  };
5212
5181
 
5213
- // src/storage/impl/SyncStorage.ts
5214
- function toPipeValue(pipe) {
5215
- if ("type" in pipe && "pipe" in pipe) {
5216
- return pipe;
5217
- }
5218
- if ("serialize" in pipe && "deserialize" in pipe) {
5219
- return { pipe, type: "serialize" };
5220
- }
5221
- if ("encrypt" in pipe && "decrypt" in pipe) {
5222
- return { pipe, type: "encrypt" };
5182
+ // src/storage/utils/isStorage.ts
5183
+ var storageMethods = ["setItem", "getItem", "removeItem", "clear"];
5184
+ function isStorage(storage) {
5185
+ if (storage == null || typeof storage !== "object") {
5186
+ return false;
5223
5187
  }
5224
- if ("setItem" in pipe && "getItem" in pipe && "removeItem" in pipe && "clear" in pipe) {
5225
- return { pipe, type: "storage" };
5188
+ for (const method of storageMethods) {
5189
+ if (storage[method] == null || typeof storage[method] !== "function") {
5190
+ return false;
5191
+ }
5226
5192
  }
5227
- return null;
5193
+ return true;
5228
5194
  }
5229
- var operationMaps = {
5230
- setItem: {
5231
- serialize: (pipe, args) => pipe.serialize(...args),
5232
- encrypt: (pipe, args) => pipe.encrypt(...args),
5233
- storage: (pipe, args) => pipe.setItem(...args)
5234
- },
5235
- getItem: {
5236
- serialize: (pipe, args) => pipe.deserialize(...args),
5237
- encrypt: (pipe, args) => pipe.decrypt(...args),
5238
- storage: (pipe, args) => pipe.getItem(...args)
5239
- }
5240
- };
5241
- var SyncStorage = class {
5242
- /**
5243
- * Creates a new SyncStorage instance with pipeline support
5244
- *
5245
- * @param storage - Primary storage backend (e.g., localStorage, sessionStorage)
5246
- * @param pipes - Optional pipe or array of pipes for data transformation
5247
- *
5248
- * @example Single pipe
5249
- * ```typescript
5250
- * const storage = new SyncStorage(
5251
- * localStorage,
5252
- * new JSONSerializer()
5253
- * );
5254
- * ```
5255
- *
5256
- * @example Multiple pipes
5257
- * ```typescript
5258
- * const storage = new SyncStorage(
5259
- * localStorage,
5260
- * [
5261
- * new JSONSerializer(),
5262
- * new AESEncryptor('secret-key')
5263
- * ]
5264
- * );
5265
- * ```
5266
- *
5267
- * @example No pipes (direct storage)
5268
- * ```typescript
5269
- * const storage = new SyncStorage(localStorage);
5270
- * // Data stored as-is without transformation
5271
- * ```
5272
- */
5273
- constructor(storage, pipes = []) {
5274
- this.storage = storage;
5275
- this.pipes = (Array.isArray(pipes) ? pipes : [pipes]).map((p) => toPipeValue(p)).filter((p) => p != null);
5195
+
5196
+ // src/storage/utils/createStoragePlugin.ts
5197
+ function createStoragePluginWithStorage(storage) {
5198
+ return {
5199
+ get: storage.getItem.bind(storage),
5200
+ set: storage.setItem.bind(storage),
5201
+ remove: storage.removeItem.bind(storage),
5202
+ clear: storage.clear.bind(storage),
5203
+ type: "storage"
5204
+ };
5205
+ }
5206
+ function isSerializer(plugin) {
5207
+ return typeof plugin === "object" && plugin !== null && "serialize" in plugin && "deserialize" in plugin;
5208
+ }
5209
+ function isEncryptor(plugin) {
5210
+ return typeof plugin === "object" && plugin !== null && "encrypt" in plugin && "decrypt" in plugin;
5211
+ }
5212
+ function createStoragePlugin(plugins) {
5213
+ if (Array.isArray(plugins)) {
5214
+ return plugins.map((plugin) => {
5215
+ if (isSerializer(plugin)) {
5216
+ return {
5217
+ get: (_key, value) => plugin.deserialize(
5218
+ value
5219
+ ),
5220
+ set: (_key, value) => plugin.serialize(value),
5221
+ type: "serializer"
5222
+ };
5223
+ } else if (isEncryptor(plugin)) {
5224
+ return {
5225
+ get: (_key, value) => plugin.decrypt(
5226
+ value
5227
+ ),
5228
+ set: (_key, value) => plugin.encrypt(value),
5229
+ type: "encryptor"
5230
+ };
5231
+ } else if (isStorage(plugin)) {
5232
+ return createStoragePluginWithStorage(plugin);
5233
+ }
5234
+ return plugin;
5235
+ });
5276
5236
  }
5237
+ return [createStoragePluginWithStorage(plugins)];
5238
+ }
5239
+
5240
+ // src/storage/impl/StorageExecutor.ts
5241
+ var StorageExecutor = class {
5242
+ plugins = [];
5277
5243
  /**
5278
- * Internal pipe value list with pre-determined types
5279
- *
5280
- * Stores the processed pipeline of transformations that will be
5281
- * applied to data during storage and retrieval operations.
5282
- *
5283
- * @protected
5284
- */
5285
- pipes;
5286
- /**
5287
- * Get the number of items in the primary storage
5288
- *
5289
- * Returns the count of items in the primary storage backend only.
5290
- * Does not include items in intermediate storage layers.
5291
- *
5292
- * @override
5293
- * @returns Number of items in primary storage
5244
+ * Builds the plugin list from either a single `StorageInterface` or an array whose last element
5245
+ * is the backing storage and preceding elements are transformers (e.g. serializer, encryptor).
5294
5246
  *
5295
- * @example
5296
- * ```typescript
5297
- * console.log(storage.length); // 5
5298
- * storage.setItem('newKey', 'value');
5299
- * console.log(storage.length); // 6
5300
- * ```
5247
+ * @param plugins - Single storage instance or tuple of `[ ...transformers, storage ]`
5301
5248
  */
5302
- get length() {
5303
- return this.storage.length;
5249
+ constructor(plugins) {
5250
+ this.plugins = createStoragePlugin(plugins);
5304
5251
  }
5305
5252
  /**
5306
- * Store a value with pipeline processing
5307
- *
5308
- * Processes the value through the configured pipeline (serialization,
5309
- * encryption, intermediate storage) before storing in the primary storage.
5310
- *
5311
- * Pipeline execution:
5312
- * 1. Apply serialization (if configured)
5313
- * 2. Apply encryption (if configured)
5314
- * 3. Store in intermediate storage layers (if configured)
5315
- * 4. Store in primary storage
5253
+ * Writes value through the plugin chain (forward). Each plugin may return a transformed value for the next.
5316
5254
  *
5317
5255
  * @override
5318
- * @template T - Type of value to store
5319
- * @param key - Storage key
5320
- * @param value - Value to store
5321
- * @param options - Optional storage options (e.g., expiration)
5322
- *
5323
- * @example Basic storage
5324
- * ```typescript
5325
- * storage.setItem('user', { id: 1, name: 'John' });
5326
- * ```
5327
- *
5328
- * @example With options
5329
- * ```typescript
5330
- * storage.setItem('session', { token: 'abc' }, { expire: 3600 });
5331
- * ```
5332
5256
  */
5333
5257
  setItem(key, value, options) {
5334
- let processedValue = value;
5335
- for (const currentPipe of this.pipes) {
5336
- const { type, pipe } = currentPipe;
5337
- if (type === "storage") {
5338
- pipe.setItem(
5339
- key,
5340
- processedValue,
5341
- options
5342
- );
5343
- } else {
5344
- const result = operationMaps.setItem[type](
5345
- // @ts-expect-error
5346
- pipe,
5347
- [processedValue]
5348
- );
5349
- if (result != null) {
5350
- processedValue = result;
5351
- }
5258
+ let finalValue = value;
5259
+ for (const plugin of this.plugins) {
5260
+ const result = plugin.set(key, finalValue, options);
5261
+ if (result !== void 0) {
5262
+ finalValue = result;
5352
5263
  }
5353
5264
  }
5354
- this.storage.setItem(key, processedValue, options);
5355
5265
  }
5356
5266
  /**
5357
- * Retrieve a value with pipeline processing
5267
+ * Reads value through the plugin chain in reverse order.
5358
5268
  *
5359
- * Retrieves the value from storage and processes it through the pipeline
5360
- * in reverse order (decryption, deserialization) to restore the original value.
5269
+ * **Multiple storage plugins:** When the pipeline contains more than one storage plugin (e.g.
5270
+ * `[sessionStorage, localStorage]`), getItem **only uses the value from the last storage** (the
5271
+ * plugin at the end of the array). The iteration runs from tail to head; the first storage plugin
5272
+ * encountered (which is the last in the array) is the only one whose `get` result is used. All
5273
+ * other storage plugins are skipped and their values are ignored. This ensures a single, well-defined
5274
+ * read source (e.g. prefer localStorage over sessionStorage when both are in the chain).
5361
5275
  *
5362
- * Retrieval strategy:
5363
- * 1. Try to retrieve from primary storage
5364
- * 2. If not found, try intermediate storage layers (in reverse order)
5365
- * 3. Apply decryption (if configured)
5366
- * 4. Apply deserialization (if configured)
5367
- * 5. Return processed value or default
5276
+ * Pipe plugins (serializer, encryptor, etc.) are always applied to transform the value read from
5277
+ * that single storage. If no value is found, returns `defaultValue` when provided, otherwise `null`.
5368
5278
  *
5369
5279
  * @override
5370
- * @template T - Type of value to retrieve
5371
- * @param key - Storage key
5372
- * @param defaultValue - Default value if key not found
5373
- * @param options - Optional retrieval options
5374
- * @returns Retrieved value or default, `null` if not found and no default
5375
- *
5376
- * @example Basic retrieval
5377
- * ```typescript
5378
- * const user = storage.getItem('user');
5379
- * if (user) {
5380
- * console.log(user.name);
5381
- * }
5382
- * ```
5383
- *
5384
- * @example With default value
5385
- * ```typescript
5386
- * const config = storage.getItem('config', { theme: 'light' });
5387
- * console.log(config.theme); // 'light' if not found
5388
- * ```
5389
5280
  */
5390
5281
  getItem(key, defaultValue, options) {
5391
- let processedValue = this.storage.getItem(key, defaultValue, options);
5392
- if (processedValue == null) {
5393
- const reversedPipes2 = [...this.pipes].reverse();
5394
- for (const currentPipe of reversedPipes2) {
5395
- const { type, pipe } = currentPipe;
5396
- if (type !== "storage") {
5397
- continue;
5398
- }
5399
- const res = operationMaps.getItem[type](pipe, [
5400
- key,
5401
- processedValue,
5402
- options
5403
- ]);
5404
- if (res != null) {
5405
- processedValue = res;
5406
- break;
5407
- }
5408
- }
5409
- }
5410
- if (processedValue == null) {
5411
- return defaultValue ?? null;
5412
- }
5413
- const reversedPipes = [...this.pipes].reverse();
5414
- for (const currentPipe of reversedPipes) {
5415
- const { type, pipe } = currentPipe;
5416
- if (type === "storage") {
5282
+ const lastIndex = this.plugins.length - 1;
5283
+ let finalValue;
5284
+ let storageHasValue = false;
5285
+ for (let i = lastIndex; i >= 0; i--) {
5286
+ const plugin = this.plugins[i];
5287
+ const isStoragePlugin = plugin.type === "storage";
5288
+ if (isStoragePlugin && storageHasValue) {
5417
5289
  continue;
5418
5290
  }
5419
- processedValue = operationMaps.getItem[type](
5420
- pipe,
5421
- [processedValue]
5422
- );
5423
- }
5424
- if (processedValue !== null && this.storage.getRawValue) {
5425
- processedValue = this.storage.getRawValue(processedValue, options);
5291
+ const result = plugin.get(key, finalValue, options);
5292
+ if (result !== void 0) {
5293
+ finalValue = result;
5294
+ if (isStoragePlugin) {
5295
+ storageHasValue = true;
5296
+ }
5297
+ }
5426
5298
  }
5427
- return processedValue ?? null;
5299
+ return finalValue ?? defaultValue ?? null;
5428
5300
  }
5429
5301
  /**
5430
- * Delete data items, delete from all storage layers
5302
+ * Removes item for the given key from all plugins that implement `remove`.
5431
5303
  *
5432
5304
  * @override
5433
- * @param key - Storage key
5434
- * @param options - Delete options
5435
5305
  */
5436
5306
  removeItem(key, options) {
5437
- this.storage.removeItem(key, options);
5438
- this.pipes.filter((p) => p.type === "storage").forEach((p) => {
5439
- p.pipe.removeItem(key, options);
5440
- });
5307
+ for (const plugin of this.plugins) {
5308
+ plugin.remove?.(key, options);
5309
+ }
5441
5310
  }
5442
5311
  /**
5443
- * Clear all data, including storage in the pipeline
5444
-
5445
- * @override
5446
- */
5312
+ * Clears data in all plugins that implement `clear`.
5313
+ *
5314
+ * @override
5315
+ */
5447
5316
  clear() {
5448
- this.storage.clear();
5449
- this.pipes.filter((p) => p.type === "storage").forEach((p) => {
5450
- p.pipe.clear();
5451
- });
5317
+ for (const plugin of this.plugins) {
5318
+ plugin.clear?.();
5319
+ }
5452
5320
  }
5453
5321
  };
5454
5322
  // Annotate the CommonJS export names for ESM import in node:
@@ -5484,15 +5352,20 @@ var SyncStorage = class {
5484
5352
  ResponsePlugin,
5485
5353
  RetryPlugin,
5486
5354
  SimpleUrlBuilder,
5487
- SyncStorage,
5355
+ StorageExecutor,
5488
5356
  appendHeaders,
5489
5357
  createAbortPromise,
5358
+ createStoragePlugin,
5359
+ createStoragePluginWithStorage,
5490
5360
  hasObjectKey,
5491
5361
  hasObjectKeyWithValue,
5492
5362
  isAbortError,
5493
5363
  isAbsoluteUrl,
5494
5364
  isAsString,
5365
+ isEncryptor,
5495
5366
  isRequestAdapterResponse,
5367
+ isSerializer,
5368
+ isStorage,
5496
5369
  normalizeHookNames,
5497
5370
  raceWithAbort,
5498
5371
  runPluginHook,