@mmstack/resource 20.2.11 → 20.4.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.
|
@@ -4,8 +4,6 @@ import { takeUntilDestroyed, toObservable } from '@angular/core/rxjs-interop';
|
|
|
4
4
|
import { of, tap, map, finalize, shareReplay, interval, firstValueFrom, catchError, combineLatestWith, filter } from 'rxjs';
|
|
5
5
|
import { HttpHeaders, HttpResponse, HttpContextToken, HttpContext, HttpParams, httpResource, HttpClient } from '@angular/common/http';
|
|
6
6
|
import { mutable, toWritable, sensor } from '@mmstack/primitives';
|
|
7
|
-
import { v7 } from 'uuid';
|
|
8
|
-
import { keys, hash, entries } from '@mmstack/object';
|
|
9
7
|
|
|
10
8
|
function createNoopDB() {
|
|
11
9
|
return {
|
|
@@ -94,6 +92,12 @@ function createSingleStoreDB(name, getStoreName, version = 1) {
|
|
|
94
92
|
});
|
|
95
93
|
}
|
|
96
94
|
|
|
95
|
+
function generateID() {
|
|
96
|
+
if (globalThis.crypto?.randomUUID) {
|
|
97
|
+
return globalThis.crypto.randomUUID();
|
|
98
|
+
}
|
|
99
|
+
return Math.random().toString(36).substring(2);
|
|
100
|
+
}
|
|
97
101
|
function isSyncMessage(msg) {
|
|
98
102
|
return (typeof msg === 'object' &&
|
|
99
103
|
msg !== null &&
|
|
@@ -118,7 +122,7 @@ class Cache {
|
|
|
118
122
|
db;
|
|
119
123
|
internal = mutable(new Map());
|
|
120
124
|
cleanupOpt;
|
|
121
|
-
id =
|
|
125
|
+
id = generateID();
|
|
122
126
|
/**
|
|
123
127
|
* Destroys the cache instance, cleaning up any resources used by the cache.
|
|
124
128
|
* This method is called automatically when the cache instance is garbage collected.
|
|
@@ -441,7 +445,7 @@ function provideQueryCache(opt) {
|
|
|
441
445
|
deserialize,
|
|
442
446
|
}
|
|
443
447
|
: undefined;
|
|
444
|
-
|
|
448
|
+
const db = opt?.persist === false
|
|
445
449
|
? undefined
|
|
446
450
|
: createSingleStoreDB('mmstack-query-cache-db', (version) => `query-store_v${version}`, opt?.version).then((db) => {
|
|
447
451
|
return {
|
|
@@ -927,6 +931,77 @@ function createDedupeRequestsInterceptor(allowed = ['GET', 'DELETE', 'HEAD', 'OP
|
|
|
927
931
|
};
|
|
928
932
|
}
|
|
929
933
|
|
|
934
|
+
/**
|
|
935
|
+
* Checks if `value` is a plain JavaScript object (e.g., `{}` or `new Object()`).
|
|
936
|
+
* Distinguishes from arrays, null, and class instances. Acts as a type predicate,
|
|
937
|
+
* narrowing `value` to `UnknownObject` if `true`.
|
|
938
|
+
*
|
|
939
|
+
* @param value The value to check.
|
|
940
|
+
* @returns {value is UnknownObject} `true` if `value` is a plain object, otherwise `false`.
|
|
941
|
+
* @example
|
|
942
|
+
* isPlainObject({}) // => true
|
|
943
|
+
* isPlainObject([]) // => false
|
|
944
|
+
* isPlainObject(null) // => false
|
|
945
|
+
* isPlainObject(new Date()) // => false
|
|
946
|
+
*/
|
|
947
|
+
function isPlainObject(value) {
|
|
948
|
+
return (typeof value === 'object' && value !== null && value.constructor === Object);
|
|
949
|
+
}
|
|
950
|
+
/**
|
|
951
|
+
* Internal helper to generate a stable JSON string from an array.
|
|
952
|
+
* Sorts keys of plain objects within the array alphabetically before serialization
|
|
953
|
+
* to ensure hash stability regardless of key order.
|
|
954
|
+
*
|
|
955
|
+
* @param queryKey The array of values to serialize.
|
|
956
|
+
* @returns A stable JSON string representation.
|
|
957
|
+
* @internal
|
|
958
|
+
*/
|
|
959
|
+
function hashKey(queryKey) {
|
|
960
|
+
return JSON.stringify(queryKey, (_, val) => isPlainObject(val)
|
|
961
|
+
? Object.keys(val)
|
|
962
|
+
.toSorted()
|
|
963
|
+
.reduce((result, key) => {
|
|
964
|
+
result[key] = val[key];
|
|
965
|
+
return result;
|
|
966
|
+
}, {})
|
|
967
|
+
: val);
|
|
968
|
+
}
|
|
969
|
+
/**
|
|
970
|
+
* Generates a stable, unique string hash from one or more arguments.
|
|
971
|
+
* Useful for creating cache keys or identifiers where object key order shouldn't matter.
|
|
972
|
+
*
|
|
973
|
+
* How it works:
|
|
974
|
+
* - Plain objects within the arguments have their keys sorted alphabetically before hashing.
|
|
975
|
+
* This ensures that `{ a: 1, b: 2 }` and `{ b: 2, a: 1 }` produce the same hash.
|
|
976
|
+
* - Uses `JSON.stringify` internally with custom sorting for plain objects via `hashKey`.
|
|
977
|
+
* - Non-plain objects (arrays, Dates, etc.) and primitives are serialized naturally.
|
|
978
|
+
*
|
|
979
|
+
* @param {...unknown} args Values to include in the hash.
|
|
980
|
+
* @returns A stable string hash representing the input arguments.
|
|
981
|
+
* @example
|
|
982
|
+
* const userQuery = (id: number) => ['user', { id, timestamp: Date.now() }];
|
|
983
|
+
*
|
|
984
|
+
* const obj1 = { a: 1, b: 2 };
|
|
985
|
+
* const obj2 = { b: 2, a: 1 }; // Same keys/values, different order
|
|
986
|
+
*
|
|
987
|
+
* hash('posts', 10);
|
|
988
|
+
* // => '["posts",10]'
|
|
989
|
+
*
|
|
990
|
+
* hash('config', obj1);
|
|
991
|
+
* // => '["config",{"a":1,"b":2}]'
|
|
992
|
+
*
|
|
993
|
+
* hash('config', obj2);
|
|
994
|
+
* // => '["config",{"a":1,"b":2}]' (Same as above due to key sorting)
|
|
995
|
+
*
|
|
996
|
+
* hash(['todos', { status: 'done', owner: obj1 }]);
|
|
997
|
+
* // => '[["todos",{"owner":{"a":1,"b":2},"status":"done"}]]'
|
|
998
|
+
*
|
|
999
|
+
* // Be mindful of values JSON.stringify cannot handle (functions, undefined, Symbols)
|
|
1000
|
+
* // hash('a', undefined, function() {}) => '["a",null,null]'
|
|
1001
|
+
*/
|
|
1002
|
+
function hash(...args) {
|
|
1003
|
+
return hashKey(args);
|
|
1004
|
+
}
|
|
930
1005
|
function equalTransferCache(a, b) {
|
|
931
1006
|
if (!a && !b)
|
|
932
1007
|
return true;
|
|
@@ -993,8 +1068,8 @@ function equalParams(a, b) {
|
|
|
993
1068
|
return false;
|
|
994
1069
|
const aObj = a instanceof HttpParams ? paramToObject(a) : a;
|
|
995
1070
|
const bObj = b instanceof HttpParams ? paramToObject(b) : b;
|
|
996
|
-
const aKeys = keys(aObj);
|
|
997
|
-
const bKeys = keys(bObj);
|
|
1071
|
+
const aKeys = Object.keys(aObj);
|
|
1072
|
+
const bKeys = Object.keys(bObj);
|
|
998
1073
|
if (aKeys.length !== bKeys.length)
|
|
999
1074
|
return false;
|
|
1000
1075
|
return aKeys.every((key) => {
|
|
@@ -1018,8 +1093,8 @@ function equalHeaders(a, b) {
|
|
|
1018
1093
|
return false;
|
|
1019
1094
|
const aObj = a instanceof HttpHeaders ? headersToObject(a) : a;
|
|
1020
1095
|
const bObj = b instanceof HttpHeaders ? headersToObject(b) : b;
|
|
1021
|
-
const aKeys = keys(aObj);
|
|
1022
|
-
const bKeys = keys(bObj);
|
|
1096
|
+
const aKeys = Object.keys(aObj);
|
|
1097
|
+
const bKeys = Object.keys(bObj);
|
|
1023
1098
|
if (aKeys.length !== bKeys.length)
|
|
1024
1099
|
return false;
|
|
1025
1100
|
return aKeys.every((key) => {
|
|
@@ -1029,16 +1104,31 @@ function equalHeaders(a, b) {
|
|
|
1029
1104
|
return aObj[key] === bObj[key];
|
|
1030
1105
|
});
|
|
1031
1106
|
}
|
|
1107
|
+
function toHttpContextEntries(ctx) {
|
|
1108
|
+
if (!ctx)
|
|
1109
|
+
return [];
|
|
1110
|
+
if (ctx instanceof HttpContext) {
|
|
1111
|
+
const tokens = Array.from(ctx.keys());
|
|
1112
|
+
return tokens.map((key) => [key.toString(), ctx.get(key)]);
|
|
1113
|
+
}
|
|
1114
|
+
if (typeof ctx === 'object') {
|
|
1115
|
+
return Object.entries(ctx);
|
|
1116
|
+
}
|
|
1117
|
+
return [];
|
|
1118
|
+
}
|
|
1032
1119
|
function equalContext(a, b) {
|
|
1033
1120
|
if (!a && !b)
|
|
1034
1121
|
return true;
|
|
1035
1122
|
if (!a || !b)
|
|
1036
1123
|
return false;
|
|
1037
|
-
const
|
|
1038
|
-
const
|
|
1039
|
-
if (
|
|
1124
|
+
const aEntries = toHttpContextEntries(a);
|
|
1125
|
+
const bEntries = toHttpContextEntries(b);
|
|
1126
|
+
if (aEntries.length !== bEntries.length)
|
|
1040
1127
|
return false;
|
|
1041
|
-
|
|
1128
|
+
if (aEntries.length === 0)
|
|
1129
|
+
return true;
|
|
1130
|
+
const bMap = new Map(bEntries);
|
|
1131
|
+
return aEntries.every(([key, value]) => value === bMap.get(key));
|
|
1042
1132
|
}
|
|
1043
1133
|
function createEqualRequest(equalResult) {
|
|
1044
1134
|
const eqb = equalResult ?? equalBody;
|
|
@@ -1211,7 +1301,7 @@ function normalizeParams(params) {
|
|
|
1211
1301
|
if (params instanceof HttpParams)
|
|
1212
1302
|
return params.toString();
|
|
1213
1303
|
const paramMap = new Map();
|
|
1214
|
-
for (const [key, value] of entries(params)) {
|
|
1304
|
+
for (const [key, value] of Object.entries(params)) {
|
|
1215
1305
|
if (Array.isArray(value)) {
|
|
1216
1306
|
paramMap.set(key, value.map(encodeURIComponent).join(','));
|
|
1217
1307
|
}
|
|
@@ -1293,7 +1383,6 @@ function queryResource(request, options) {
|
|
|
1293
1383
|
: stableRequest;
|
|
1294
1384
|
let resource = toResourceObject(httpResource(cachedRequest, {
|
|
1295
1385
|
...options,
|
|
1296
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1297
1386
|
parse: options?.parse, // Not my favorite thing to do, but here it is completely safe.
|
|
1298
1387
|
}));
|
|
1299
1388
|
resource = catchValueError(resource, options?.defaultValue);
|