@naturalcycles/js-lib 15.38.0 → 15.40.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/enum.util.d.ts +2 -1
- package/dist/enum.util.js +29 -0
- package/dist/http/fetcher.d.ts +6 -2
- package/dist/http/fetcher.js +24 -17
- package/dist/json-schema/jsonSchemaBuilder.d.ts +6 -18
- package/dist/json-schema/jsonSchemaBuilder.js +35 -31
- package/dist/json-schema/jsonSchemas.js +2 -2
- package/dist/nanoid.js +1 -1
- package/dist/number/createDeterministicRandom.js +1 -1
- package/dist/object/map2.d.ts +4 -0
- package/dist/object/map2.js +9 -0
- package/dist/object/set2.d.ts +5 -0
- package/dist/object/set2.js +10 -0
- package/dist/string/hash.util.js +2 -1
- package/dist/string/leven.js +2 -1
- package/dist/string/safeJsonStringify.js +9 -4
- package/package.json +1 -1
- package/src/enum.util.ts +33 -1
- package/src/http/fetcher.ts +27 -20
- package/src/json-schema/jsonSchemaBuilder.ts +59 -36
- package/src/json-schema/jsonSchemas.ts +2 -2
- package/src/nanoid.ts +1 -2
- package/src/number/createDeterministicRandom.ts +1 -2
- package/src/object/map2.ts +10 -0
- package/src/object/set2.ts +11 -0
- package/src/string/hash.util.ts +3 -1
- package/src/string/leven.ts +2 -1
- package/src/string/safeJsonStringify.ts +9 -4
package/dist/enum.util.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type { NumberEnum, StringEnum } from './types.js';
|
|
1
|
+
import type { AnyObject, NumberEnum, StringEnum } from './types.js';
|
|
2
|
+
export declare function getEnumType(en: AnyObject): 'StringEnum' | 'NumberEnum' | undefined;
|
|
2
3
|
/**
|
|
3
4
|
* Returns all String keys of a number-enum.
|
|
4
5
|
*/
|
package/dist/enum.util.js
CHANGED
|
@@ -1,3 +1,32 @@
|
|
|
1
|
+
export function getEnumType(en) {
|
|
2
|
+
/*
|
|
3
|
+
* enum Foo { A = 1, B = 2 }
|
|
4
|
+
* becomes
|
|
5
|
+
* { "1": "A", "2": "B", "A": 1, "B": 2}
|
|
6
|
+
*
|
|
7
|
+
* enum Foo { A = "V1", B = "V2" }
|
|
8
|
+
* becomes
|
|
9
|
+
* { "V1": "A", "V2": "B", "A": "V1", "B": "V2"}
|
|
10
|
+
*/
|
|
11
|
+
const entries = Object.entries(en);
|
|
12
|
+
if (!entries.length)
|
|
13
|
+
return;
|
|
14
|
+
const [, value] = entries.pop();
|
|
15
|
+
let isNumberEnum = typeof value === 'number';
|
|
16
|
+
let isStringEnum = typeof value === 'string';
|
|
17
|
+
for (const [key, value] of entries) {
|
|
18
|
+
const isValueNumber = typeof value === 'number';
|
|
19
|
+
const isValueString = typeof value === 'string';
|
|
20
|
+
isStringEnum &&= isValueString;
|
|
21
|
+
isNumberEnum &&= isValueNumber || String(en[value]) === key;
|
|
22
|
+
if (!isStringEnum && !isNumberEnum)
|
|
23
|
+
break;
|
|
24
|
+
}
|
|
25
|
+
if (isNumberEnum)
|
|
26
|
+
return 'NumberEnum';
|
|
27
|
+
if (isStringEnum)
|
|
28
|
+
return 'StringEnum';
|
|
29
|
+
}
|
|
1
30
|
/**
|
|
2
31
|
* Returns all String keys of a number-enum.
|
|
3
32
|
*/
|
package/dist/http/fetcher.d.ts
CHANGED
|
@@ -16,8 +16,12 @@ export declare class Fetcher {
|
|
|
16
16
|
*
|
|
17
17
|
* Version is to be incremented every time a difference in behaviour (or a bugfix) is done.
|
|
18
18
|
*/
|
|
19
|
-
static readonly VERSION
|
|
20
|
-
|
|
19
|
+
private static readonly VERSION;
|
|
20
|
+
/**
|
|
21
|
+
* userAgent is statically exposed as Fetcher.userAgent.
|
|
22
|
+
* It can be modified globally, and will be used (read) at the start of every request.
|
|
23
|
+
*/
|
|
24
|
+
static userAgent: string | undefined;
|
|
21
25
|
private constructor();
|
|
22
26
|
/**
|
|
23
27
|
* Add BeforeRequest hook at the end of the hooks list.
|
package/dist/http/fetcher.js
CHANGED
|
@@ -12,20 +12,6 @@ import { pTimeout } from '../promise/pTimeout.js';
|
|
|
12
12
|
import { _jsonParse, _jsonParseIfPossible } from '../string/json.util.js';
|
|
13
13
|
import { _stringify } from '../string/stringify.js';
|
|
14
14
|
import { HTTP_METHODS } from './http.model.js';
|
|
15
|
-
const acceptByResponseType = {
|
|
16
|
-
text: 'text/plain',
|
|
17
|
-
json: 'application/json',
|
|
18
|
-
void: '*/*',
|
|
19
|
-
readableStream: 'application/octet-stream',
|
|
20
|
-
arrayBuffer: 'application/octet-stream',
|
|
21
|
-
blob: 'application/octet-stream',
|
|
22
|
-
};
|
|
23
|
-
const defRetryOptions = {
|
|
24
|
-
count: 2,
|
|
25
|
-
timeout: 1000,
|
|
26
|
-
timeoutMax: 30_000,
|
|
27
|
-
timeoutMultiplier: 2,
|
|
28
|
-
};
|
|
29
15
|
/**
|
|
30
16
|
* Experimental wrapper around Fetch.
|
|
31
17
|
* Works in both Browser and Node, using `globalThis.fetch`.
|
|
@@ -37,7 +23,11 @@ export class Fetcher {
|
|
|
37
23
|
*
|
|
38
24
|
* Version is to be incremented every time a difference in behaviour (or a bugfix) is done.
|
|
39
25
|
*/
|
|
40
|
-
static VERSION =
|
|
26
|
+
static VERSION = 3;
|
|
27
|
+
/**
|
|
28
|
+
* userAgent is statically exposed as Fetcher.userAgent.
|
|
29
|
+
* It can be modified globally, and will be used (read) at the start of every request.
|
|
30
|
+
*/
|
|
41
31
|
static userAgent = isServerSide() ? `fetcher/${this.VERSION}` : undefined;
|
|
42
32
|
constructor(cfg = {}) {
|
|
43
33
|
if (typeof globalThis.fetch !== 'function') {
|
|
@@ -614,7 +604,7 @@ export class Fetcher {
|
|
|
614
604
|
logResponseBody: debug,
|
|
615
605
|
logWithBaseUrl: isServerSide(),
|
|
616
606
|
logWithSearchParams: true,
|
|
617
|
-
retry: { ...
|
|
607
|
+
retry: { ...defaultRetryOptions },
|
|
618
608
|
init: {
|
|
619
609
|
method: cfg.method || 'GET',
|
|
620
610
|
headers: _filterNullishValues({
|
|
@@ -659,7 +649,10 @@ export class Fetcher {
|
|
|
659
649
|
},
|
|
660
650
|
init: _merge({
|
|
661
651
|
...this.cfg.init,
|
|
662
|
-
headers: {
|
|
652
|
+
headers: {
|
|
653
|
+
...this.cfg.init.headers, // this avoids mutation
|
|
654
|
+
'user-agent': Fetcher.userAgent, // re-load it here, to support setting it globally post-fetcher-creation
|
|
655
|
+
},
|
|
663
656
|
method: opt.method || this.cfg.init.method,
|
|
664
657
|
credentials: opt.credentials || this.cfg.init.credentials,
|
|
665
658
|
redirect: opt.redirect || this.cfg.init.redirect || 'follow',
|
|
@@ -716,3 +709,17 @@ export class Fetcher {
|
|
|
716
709
|
export function getFetcher(cfg = {}) {
|
|
717
710
|
return Fetcher.create(cfg);
|
|
718
711
|
}
|
|
712
|
+
const acceptByResponseType = {
|
|
713
|
+
text: 'text/plain',
|
|
714
|
+
json: 'application/json',
|
|
715
|
+
void: '*/*',
|
|
716
|
+
readableStream: 'application/octet-stream',
|
|
717
|
+
arrayBuffer: 'application/octet-stream',
|
|
718
|
+
blob: 'application/octet-stream',
|
|
719
|
+
};
|
|
720
|
+
const defaultRetryOptions = {
|
|
721
|
+
count: 2,
|
|
722
|
+
timeout: 1000,
|
|
723
|
+
timeoutMax: 30_000,
|
|
724
|
+
timeoutMultiplier: 2,
|
|
725
|
+
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type AnyObject, type BaseDBEntity, type IsoDate, type IsoDateTime, type UnixTimestamp } from '../types.js';
|
|
1
|
+
import { type AnyObject, type BaseDBEntity, type IsoDate, type IsoDateTime, type NumberEnum, type StringEnum, type UnixTimestamp, type UnixTimestampMillis } from '../types.js';
|
|
2
2
|
import type { JsonSchema, JsonSchemaAllOf, JsonSchemaAny, JsonSchemaArray, JsonSchemaBoolean, JsonSchemaConst, JsonSchemaEnum, JsonSchemaNull, JsonSchemaNumber, JsonSchemaObject, JsonSchemaOneOf, JsonSchemaRef, JsonSchemaString, JsonSchemaTuple } from './jsonSchema.model.js';
|
|
3
3
|
export interface JsonSchemaBuilder<T = unknown> {
|
|
4
4
|
build: () => JsonSchema<T>;
|
|
@@ -12,24 +12,12 @@ export declare const j: {
|
|
|
12
12
|
const<T extends string | number | boolean | null>(value: T): JsonSchemaAnyBuilder<T, JsonSchemaConst<T>, false>;
|
|
13
13
|
null(): JsonSchemaAnyBuilder<null, JsonSchemaNull, false>;
|
|
14
14
|
ref<T = unknown>($ref: string): JsonSchemaAnyBuilder<T, JsonSchemaRef<T>, false>;
|
|
15
|
-
enum<T
|
|
15
|
+
enum<const T extends readonly (string | number | boolean | null)[] | StringEnum | NumberEnum>(input: T): JsonSchemaAnyBuilder<T extends readonly (infer U)[] ? U : T extends StringEnum ? T[keyof T] : T extends NumberEnum ? T[keyof T] : never, JsonSchemaEnum<any>, false>;
|
|
16
16
|
boolean(): JsonSchemaAnyBuilder<boolean, JsonSchemaBoolean, false>;
|
|
17
17
|
buffer(): JsonSchemaAnyBuilder<Buffer<ArrayBufferLike>, JsonSchemaAny<Buffer<ArrayBufferLike>>, false>;
|
|
18
18
|
number<T extends number = number>(): JsonSchemaNumberBuilder<T, false>;
|
|
19
19
|
integer<T extends number = number>(): JsonSchemaNumberBuilder<T, false>;
|
|
20
|
-
unixTimestamp(): JsonSchemaNumberBuilder<UnixTimestamp, false>;
|
|
21
|
-
unixTimestamp2000(): JsonSchemaNumberBuilder<UnixTimestamp, false>;
|
|
22
20
|
string<T extends string = string>(): JsonSchemaStringBuilder<T, false>;
|
|
23
|
-
jwt(): JsonSchemaStringBuilder<string, false>;
|
|
24
|
-
/**
|
|
25
|
-
* Accepts only the `YYYY-MM-DD` shape from all ISO 8601 variants.
|
|
26
|
-
*/
|
|
27
|
-
isoDate(): JsonSchemaStringBuilder<IsoDate, false>;
|
|
28
|
-
/**
|
|
29
|
-
* Accepts strings that start with the `YYYY-MM-DDTHH:MM:SS` shape
|
|
30
|
-
* and optionally end with either a `Z` or a `+/-hh:mm` timezone part.
|
|
31
|
-
*/
|
|
32
|
-
isoDateTime(): JsonSchemaStringBuilder<IsoDateTime, false>;
|
|
33
21
|
object: typeof object;
|
|
34
22
|
dbEntity<T extends AnyObject>(props: T): JsonSchemaObjectBuilder<BaseDBEntity & ({ [K in keyof T as T[K] extends JsonSchemaAnyBuilder<any, any, infer Opt extends boolean> ? Opt extends true ? never : K : never]: T[K] extends JsonSchemaAnyBuilder<infer U, any, any> ? U : never; } & { [K_1 in keyof T as T[K_1] extends JsonSchemaAnyBuilder<any, any, infer Opt extends boolean> ? Opt extends true ? K_1 : never : never]?: (T[K_1] extends JsonSchemaAnyBuilder<infer U, any, any> ? U : never) | undefined; } extends infer O ? { [K_2 in keyof O]: O[K_2]; } : never) extends infer O_1 ? { [K_3 in keyof O_1]: O_1[K_3]; } : never, false>;
|
|
35
23
|
rootObject<T extends AnyObject>(props: { [K in keyof T]: JsonSchemaAnyBuilder<T[K]>; }): JsonSchemaObjectBuilder<T, false>;
|
|
@@ -86,10 +74,10 @@ export declare class JsonSchemaNumberBuilder<T extends number = number, Opt exte
|
|
|
86
74
|
int64: () => this;
|
|
87
75
|
float: () => this;
|
|
88
76
|
double: () => this;
|
|
89
|
-
unixTimestamp: () =>
|
|
90
|
-
unixTimestamp2000: () =>
|
|
91
|
-
unixTimestampMillis: () =>
|
|
92
|
-
unixTimestampMillis2000: () =>
|
|
77
|
+
unixTimestamp: () => JsonSchemaNumberBuilder<UnixTimestamp>;
|
|
78
|
+
unixTimestamp2000: () => JsonSchemaNumberBuilder<UnixTimestamp>;
|
|
79
|
+
unixTimestampMillis: () => JsonSchemaNumberBuilder<UnixTimestampMillis>;
|
|
80
|
+
unixTimestampMillis2000: () => JsonSchemaNumberBuilder<UnixTimestampMillis>;
|
|
93
81
|
utcOffset: () => this;
|
|
94
82
|
utcOffsetHours: () => this;
|
|
95
83
|
branded<B extends number>(): JsonSchemaNumberBuilder<B>;
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { _uniq } from '../array/array.util.js';
|
|
2
|
+
import { _numberEnumValues, _stringEnumValues, getEnumType } from '../enum.util.js';
|
|
3
|
+
import { _assert } from '../error/assert.js';
|
|
2
4
|
import { _deepCopy } from '../object/object.util.js';
|
|
3
5
|
import { _sortObject } from '../object/sortObject.js';
|
|
4
6
|
import { JWT_REGEX, } from '../types.js';
|
|
@@ -27,8 +29,24 @@ export const j = {
|
|
|
27
29
|
$ref,
|
|
28
30
|
});
|
|
29
31
|
},
|
|
30
|
-
enum(
|
|
31
|
-
|
|
32
|
+
enum(input) {
|
|
33
|
+
let enumValues;
|
|
34
|
+
if (Array.isArray(input)) {
|
|
35
|
+
enumValues = input;
|
|
36
|
+
}
|
|
37
|
+
else if (typeof input === 'object') {
|
|
38
|
+
const enumType = getEnumType(input);
|
|
39
|
+
if (enumType === 'NumberEnum') {
|
|
40
|
+
enumValues = _numberEnumValues(input);
|
|
41
|
+
}
|
|
42
|
+
else if (enumType === 'StringEnum') {
|
|
43
|
+
enumValues = _stringEnumValues(input);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
_assert(enumValues, 'Unsupported enum input');
|
|
47
|
+
return new JsonSchemaAnyBuilder({
|
|
48
|
+
enum: enumValues,
|
|
49
|
+
});
|
|
32
50
|
},
|
|
33
51
|
boolean() {
|
|
34
52
|
return new JsonSchemaAnyBuilder({
|
|
@@ -47,41 +65,18 @@ export const j = {
|
|
|
47
65
|
integer() {
|
|
48
66
|
return new JsonSchemaNumberBuilder().integer();
|
|
49
67
|
},
|
|
50
|
-
unixTimestamp() {
|
|
51
|
-
return new JsonSchemaNumberBuilder().unixTimestamp();
|
|
52
|
-
},
|
|
53
|
-
unixTimestamp2000() {
|
|
54
|
-
return new JsonSchemaNumberBuilder().unixTimestamp2000();
|
|
55
|
-
},
|
|
56
68
|
// string types
|
|
57
69
|
string() {
|
|
58
70
|
return new JsonSchemaStringBuilder();
|
|
59
71
|
},
|
|
60
|
-
jwt() {
|
|
61
|
-
return new JsonSchemaStringBuilder().jwt();
|
|
62
|
-
},
|
|
63
|
-
/**
|
|
64
|
-
* Accepts only the `YYYY-MM-DD` shape from all ISO 8601 variants.
|
|
65
|
-
*/
|
|
66
|
-
isoDate() {
|
|
67
|
-
return new JsonSchemaStringBuilder().isoDate();
|
|
68
|
-
},
|
|
69
|
-
/**
|
|
70
|
-
* Accepts strings that start with the `YYYY-MM-DDTHH:MM:SS` shape
|
|
71
|
-
* and optionally end with either a `Z` or a `+/-hh:mm` timezone part.
|
|
72
|
-
*/
|
|
73
|
-
isoDateTime() {
|
|
74
|
-
return new JsonSchemaStringBuilder().isoDateTime();
|
|
75
|
-
},
|
|
76
|
-
// email: () => new JsonSchemaStringBuilder().email(),
|
|
77
72
|
// complex types
|
|
78
73
|
object,
|
|
79
74
|
dbEntity(props) {
|
|
80
75
|
return j
|
|
81
76
|
.object({
|
|
82
77
|
id: j.string(),
|
|
83
|
-
created: j.unixTimestamp2000(),
|
|
84
|
-
updated: j.unixTimestamp2000(),
|
|
78
|
+
created: j.integer().unixTimestamp2000(),
|
|
79
|
+
updated: j.integer().unixTimestamp2000(),
|
|
85
80
|
})
|
|
86
81
|
.extend(j.object(props));
|
|
87
82
|
},
|
|
@@ -226,10 +221,19 @@ export class JsonSchemaNumberBuilder extends JsonSchemaAnyBuilder {
|
|
|
226
221
|
int64 = () => this.format('int64');
|
|
227
222
|
float = () => this.format('float');
|
|
228
223
|
double = () => this.format('double');
|
|
229
|
-
unixTimestamp = () => this.format('unixTimestamp').description('UnixTimestamp');
|
|
230
|
-
unixTimestamp2000 = () => this.
|
|
231
|
-
|
|
232
|
-
|
|
224
|
+
unixTimestamp = () => this.integer().branded().format('unixTimestamp').description('UnixTimestamp');
|
|
225
|
+
unixTimestamp2000 = () => this.integer()
|
|
226
|
+
.branded()
|
|
227
|
+
.format('unixTimestamp2000')
|
|
228
|
+
.description('UnixTimestamp2000');
|
|
229
|
+
unixTimestampMillis = () => this.integer()
|
|
230
|
+
.branded()
|
|
231
|
+
.format('unixTimestampMillis')
|
|
232
|
+
.description('UnixTimestampMillis');
|
|
233
|
+
unixTimestampMillis2000 = () => this.integer()
|
|
234
|
+
.branded()
|
|
235
|
+
.format('unixTimestampMillis2000')
|
|
236
|
+
.description('UnixTimestampMillis2000');
|
|
233
237
|
utcOffset = () => this.format('utcOffset');
|
|
234
238
|
utcOffsetHours = () => this.format('utcOffsetHours');
|
|
235
239
|
branded() {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { j } from './jsonSchemaBuilder.js';
|
|
2
2
|
export const baseDBEntityJsonSchema = j.object({
|
|
3
3
|
id: j.string(),
|
|
4
|
-
created: j.unixTimestamp2000(),
|
|
5
|
-
updated: j.unixTimestamp2000(),
|
|
4
|
+
created: j.integer().unixTimestamp2000(),
|
|
5
|
+
updated: j.integer().unixTimestamp2000(),
|
|
6
6
|
});
|
package/dist/nanoid.js
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
// All credit to nanoid authors: https://github.com/ai/nanoid
|
|
3
3
|
// Reason for vendoring: (still) cannot import esm, and Nanoid went ESM-only since 4.0
|
|
4
4
|
/// <reference lib="dom" preserve="true" />
|
|
5
|
-
/* eslint-disable no-bitwise */
|
|
6
5
|
// "0-9a-zA-Z-_", same as base64url alphabet
|
|
7
6
|
const urlAlphabet = 'useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict';
|
|
7
|
+
// oxlint-disable no-bitwise -- NanoID uses bit operations to build compact IDs
|
|
8
8
|
export function nanoidBrowser(length = 21) {
|
|
9
9
|
let id = '';
|
|
10
10
|
const bytes = globalThis.crypto.getRandomValues(new Uint8Array(length));
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
/* eslint-disable no-bitwise */
|
|
2
1
|
/**
|
|
3
2
|
* Returns a "deterministic Math.random() function"
|
|
4
3
|
*
|
|
@@ -6,6 +5,7 @@
|
|
|
6
5
|
*/
|
|
7
6
|
export function _createDeterministicRandom(seed = 0x2f6e2b1) {
|
|
8
7
|
return () => {
|
|
8
|
+
// oxlint-disable no-bitwise
|
|
9
9
|
// Robert Jenkins’ 32 bit integer hash function
|
|
10
10
|
seed = (seed + 0x7ed55d16 + (seed << 12)) & 0xffffffff;
|
|
11
11
|
seed = (seed ^ 0xc761c23c ^ (seed >>> 19)) & 0xffffffff;
|
package/dist/object/map2.d.ts
CHANGED
|
@@ -10,6 +10,10 @@ export declare class Map2<K = any, V = any> extends Map<K, V> {
|
|
|
10
10
|
* Convenience way to create Map2 from object.
|
|
11
11
|
*/
|
|
12
12
|
static of<V>(obj: Record<any, V>): Map2<string, V>;
|
|
13
|
+
/**
|
|
14
|
+
* Allows to set multiple key-value pairs at once.
|
|
15
|
+
*/
|
|
16
|
+
setMany(obj: Record<any, V>): this;
|
|
13
17
|
toObject(): Record<string, V>;
|
|
14
18
|
toJSON(): Record<string, V>;
|
|
15
19
|
}
|
package/dist/object/map2.js
CHANGED
|
@@ -12,6 +12,15 @@ export class Map2 extends Map {
|
|
|
12
12
|
static of(obj) {
|
|
13
13
|
return new Map2(Object.entries(obj));
|
|
14
14
|
}
|
|
15
|
+
/**
|
|
16
|
+
* Allows to set multiple key-value pairs at once.
|
|
17
|
+
*/
|
|
18
|
+
setMany(obj) {
|
|
19
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
20
|
+
this.set(k, v);
|
|
21
|
+
}
|
|
22
|
+
return this;
|
|
23
|
+
}
|
|
15
24
|
toObject() {
|
|
16
25
|
return Object.fromEntries(this);
|
|
17
26
|
}
|
package/dist/object/set2.d.ts
CHANGED
|
@@ -6,6 +6,11 @@
|
|
|
6
6
|
* @experimental
|
|
7
7
|
*/
|
|
8
8
|
export declare class Set2<T = any> extends Set<T> {
|
|
9
|
+
/**
|
|
10
|
+
* Like .add(), but allows to add multiple items at once.
|
|
11
|
+
* Mutates the Set, but also returns it conveniently.
|
|
12
|
+
*/
|
|
13
|
+
addMany(items: Iterable<T>): this;
|
|
9
14
|
toArray(): T[];
|
|
10
15
|
toJSON(): T[];
|
|
11
16
|
}
|
package/dist/object/set2.js
CHANGED
|
@@ -6,6 +6,16 @@
|
|
|
6
6
|
* @experimental
|
|
7
7
|
*/
|
|
8
8
|
export class Set2 extends Set {
|
|
9
|
+
/**
|
|
10
|
+
* Like .add(), but allows to add multiple items at once.
|
|
11
|
+
* Mutates the Set, but also returns it conveniently.
|
|
12
|
+
*/
|
|
13
|
+
addMany(items) {
|
|
14
|
+
for (const item of items) {
|
|
15
|
+
this.add(item);
|
|
16
|
+
}
|
|
17
|
+
return this;
|
|
18
|
+
}
|
|
9
19
|
toArray() {
|
|
10
20
|
return [...this];
|
|
11
21
|
}
|
package/dist/string/hash.util.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// oxlint-disable no-bitwise -- hash implementations use bit-level operations for speed
|
|
1
2
|
const BASE62 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
|
2
3
|
// const BASE64 = BASE62 + '+/'
|
|
3
4
|
const BASE64URL = BASE62 + '-_';
|
|
@@ -48,7 +49,7 @@ export function hashCode(s) {
|
|
|
48
49
|
let i = 0;
|
|
49
50
|
const len = s.length;
|
|
50
51
|
while (i < len) {
|
|
51
|
-
// eslint-disable-next-line
|
|
52
|
+
// eslint-disable-next-line unicorn/prefer-math-trunc, unicorn/prefer-code-point
|
|
52
53
|
hash = ((hash << 5) - hash + s.charCodeAt(i++)) << 0;
|
|
53
54
|
}
|
|
54
55
|
return hash + 2147483647 + 1;
|
package/dist/string/leven.js
CHANGED
|
@@ -13,7 +13,6 @@ export function _safeJsonStringify(obj, replacer, spaces, cycleReplacer) {
|
|
|
13
13
|
return JSON.stringify(obj, serializer(replacer, cycleReplacer), spaces);
|
|
14
14
|
}
|
|
15
15
|
}
|
|
16
|
-
/* eslint-disable no-bitwise, no-implicit-coercion */
|
|
17
16
|
// oxlint-disable no-unused-expressions
|
|
18
17
|
function serializer(replacer, cycleReplacer) {
|
|
19
18
|
const stack = [];
|
|
@@ -26,9 +25,15 @@ function serializer(replacer, cycleReplacer) {
|
|
|
26
25
|
return function (key, value) {
|
|
27
26
|
if (stack.length > 0) {
|
|
28
27
|
const thisPos = stack.indexOf(this);
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
28
|
+
if (thisPos !== -1) {
|
|
29
|
+
stack.splice(thisPos + 1);
|
|
30
|
+
keys.splice(thisPos, Infinity, key);
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
stack.push(this);
|
|
34
|
+
keys.push(key);
|
|
35
|
+
}
|
|
36
|
+
if (stack.includes(value)) {
|
|
32
37
|
value = cycleReplacer.call(this, key, value);
|
|
33
38
|
}
|
|
34
39
|
}
|
package/package.json
CHANGED
package/src/enum.util.ts
CHANGED
|
@@ -1,4 +1,36 @@
|
|
|
1
|
-
import type { NumberEnum, StringEnum } from './types.js'
|
|
1
|
+
import type { AnyObject, NumberEnum, StringEnum } from './types.js'
|
|
2
|
+
|
|
3
|
+
export function getEnumType(en: AnyObject): 'StringEnum' | 'NumberEnum' | undefined {
|
|
4
|
+
/*
|
|
5
|
+
* enum Foo { A = 1, B = 2 }
|
|
6
|
+
* becomes
|
|
7
|
+
* { "1": "A", "2": "B", "A": 1, "B": 2}
|
|
8
|
+
*
|
|
9
|
+
* enum Foo { A = "V1", B = "V2" }
|
|
10
|
+
* becomes
|
|
11
|
+
* { "V1": "A", "V2": "B", "A": "V1", "B": "V2"}
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const entries = Object.entries(en)
|
|
15
|
+
if (!entries.length) return
|
|
16
|
+
|
|
17
|
+
const [, value] = entries.pop()!
|
|
18
|
+
|
|
19
|
+
let isNumberEnum = typeof value === 'number'
|
|
20
|
+
let isStringEnum = typeof value === 'string'
|
|
21
|
+
|
|
22
|
+
for (const [key, value] of entries) {
|
|
23
|
+
const isValueNumber = typeof value === 'number'
|
|
24
|
+
const isValueString = typeof value === 'string'
|
|
25
|
+
|
|
26
|
+
isStringEnum &&= isValueString
|
|
27
|
+
isNumberEnum &&= isValueNumber || String(en[value]) === key
|
|
28
|
+
if (!isStringEnum && !isNumberEnum) break
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (isNumberEnum) return 'NumberEnum'
|
|
32
|
+
if (isStringEnum) return 'StringEnum'
|
|
33
|
+
}
|
|
2
34
|
|
|
3
35
|
/**
|
|
4
36
|
* Returns all String keys of a number-enum.
|
package/src/http/fetcher.ts
CHANGED
|
@@ -56,22 +56,6 @@ import type {
|
|
|
56
56
|
import type { HttpStatusFamily } from './http.model.js'
|
|
57
57
|
import { HTTP_METHODS } from './http.model.js'
|
|
58
58
|
|
|
59
|
-
const acceptByResponseType: Record<FetcherResponseType, string> = {
|
|
60
|
-
text: 'text/plain',
|
|
61
|
-
json: 'application/json',
|
|
62
|
-
void: '*/*',
|
|
63
|
-
readableStream: 'application/octet-stream',
|
|
64
|
-
arrayBuffer: 'application/octet-stream',
|
|
65
|
-
blob: 'application/octet-stream',
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
const defRetryOptions: FetcherRetryOptions = {
|
|
69
|
-
count: 2,
|
|
70
|
-
timeout: 1000,
|
|
71
|
-
timeoutMax: 30_000,
|
|
72
|
-
timeoutMultiplier: 2,
|
|
73
|
-
}
|
|
74
|
-
|
|
75
59
|
/**
|
|
76
60
|
* Experimental wrapper around Fetch.
|
|
77
61
|
* Works in both Browser and Node, using `globalThis.fetch`.
|
|
@@ -83,8 +67,12 @@ export class Fetcher {
|
|
|
83
67
|
*
|
|
84
68
|
* Version is to be incremented every time a difference in behaviour (or a bugfix) is done.
|
|
85
69
|
*/
|
|
86
|
-
static readonly VERSION =
|
|
87
|
-
|
|
70
|
+
private static readonly VERSION = 3
|
|
71
|
+
/**
|
|
72
|
+
* userAgent is statically exposed as Fetcher.userAgent.
|
|
73
|
+
* It can be modified globally, and will be used (read) at the start of every request.
|
|
74
|
+
*/
|
|
75
|
+
static userAgent = isServerSide() ? `fetcher/${this.VERSION}` : undefined
|
|
88
76
|
|
|
89
77
|
private constructor(cfg: FetcherCfg & FetcherOptions = {}) {
|
|
90
78
|
if (typeof globalThis.fetch !== 'function') {
|
|
@@ -750,7 +738,7 @@ export class Fetcher {
|
|
|
750
738
|
logResponseBody: debug,
|
|
751
739
|
logWithBaseUrl: isServerSide(),
|
|
752
740
|
logWithSearchParams: true,
|
|
753
|
-
retry: { ...
|
|
741
|
+
retry: { ...defaultRetryOptions },
|
|
754
742
|
init: {
|
|
755
743
|
method: cfg.method || 'GET',
|
|
756
744
|
headers: _filterNullishValues({
|
|
@@ -801,7 +789,10 @@ export class Fetcher {
|
|
|
801
789
|
init: _merge(
|
|
802
790
|
{
|
|
803
791
|
...this.cfg.init,
|
|
804
|
-
headers: {
|
|
792
|
+
headers: {
|
|
793
|
+
...this.cfg.init.headers, // this avoids mutation
|
|
794
|
+
'user-agent': Fetcher.userAgent, // re-load it here, to support setting it globally post-fetcher-creation
|
|
795
|
+
},
|
|
805
796
|
method: opt.method || this.cfg.init.method,
|
|
806
797
|
credentials: opt.credentials || this.cfg.init.credentials,
|
|
807
798
|
redirect: opt.redirect || this.cfg.init.redirect || 'follow',
|
|
@@ -864,3 +855,19 @@ export class Fetcher {
|
|
|
864
855
|
export function getFetcher(cfg: FetcherCfg & FetcherOptions = {}): Fetcher {
|
|
865
856
|
return Fetcher.create(cfg)
|
|
866
857
|
}
|
|
858
|
+
|
|
859
|
+
const acceptByResponseType: Record<FetcherResponseType, string> = {
|
|
860
|
+
text: 'text/plain',
|
|
861
|
+
json: 'application/json',
|
|
862
|
+
void: '*/*',
|
|
863
|
+
readableStream: 'application/octet-stream',
|
|
864
|
+
arrayBuffer: 'application/octet-stream',
|
|
865
|
+
blob: 'application/octet-stream',
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
const defaultRetryOptions: FetcherRetryOptions = {
|
|
869
|
+
count: 2,
|
|
870
|
+
timeout: 1000,
|
|
871
|
+
timeoutMax: 30_000,
|
|
872
|
+
timeoutMultiplier: 2,
|
|
873
|
+
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { _uniq } from '../array/array.util.js'
|
|
2
|
+
import { _numberEnumValues, _stringEnumValues, getEnumType } from '../enum.util.js'
|
|
3
|
+
import { _assert } from '../error/assert.js'
|
|
2
4
|
import { _deepCopy } from '../object/object.util.js'
|
|
3
5
|
import { _sortObject } from '../object/sortObject.js'
|
|
4
6
|
import {
|
|
@@ -7,8 +9,10 @@ import {
|
|
|
7
9
|
type IsoDate,
|
|
8
10
|
type IsoDateTime,
|
|
9
11
|
JWT_REGEX,
|
|
10
|
-
type
|
|
12
|
+
type NumberEnum,
|
|
13
|
+
type StringEnum,
|
|
11
14
|
type UnixTimestamp,
|
|
15
|
+
type UnixTimestampMillis,
|
|
12
16
|
} from '../types.js'
|
|
13
17
|
import { JSON_SCHEMA_ORDER } from './jsonSchema.cnst.js'
|
|
14
18
|
import type {
|
|
@@ -58,9 +62,39 @@ export const j = {
|
|
|
58
62
|
$ref,
|
|
59
63
|
})
|
|
60
64
|
},
|
|
61
|
-
|
|
62
|
-
|
|
65
|
+
|
|
66
|
+
enum<const T extends readonly (string | number | boolean | null)[] | StringEnum | NumberEnum>(
|
|
67
|
+
input: T,
|
|
68
|
+
) {
|
|
69
|
+
let enumValues: readonly (string | number | boolean | null)[] | undefined
|
|
70
|
+
|
|
71
|
+
if (Array.isArray(input)) {
|
|
72
|
+
enumValues = input
|
|
73
|
+
} else if (typeof input === 'object') {
|
|
74
|
+
const enumType = getEnumType(input)
|
|
75
|
+
if (enumType === 'NumberEnum') {
|
|
76
|
+
enumValues = _numberEnumValues(input as NumberEnum)
|
|
77
|
+
} else if (enumType === 'StringEnum') {
|
|
78
|
+
enumValues = _stringEnumValues(input as StringEnum)
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
_assert(enumValues, 'Unsupported enum input')
|
|
83
|
+
|
|
84
|
+
return new JsonSchemaAnyBuilder<
|
|
85
|
+
T extends readonly (infer U)[]
|
|
86
|
+
? U
|
|
87
|
+
: T extends StringEnum
|
|
88
|
+
? T[keyof T]
|
|
89
|
+
: T extends NumberEnum
|
|
90
|
+
? T[keyof T]
|
|
91
|
+
: never,
|
|
92
|
+
JsonSchemaEnum<any>
|
|
93
|
+
>({
|
|
94
|
+
enum: enumValues as any[],
|
|
95
|
+
})
|
|
63
96
|
},
|
|
97
|
+
|
|
64
98
|
boolean() {
|
|
65
99
|
return new JsonSchemaAnyBuilder<boolean, JsonSchemaBoolean>({
|
|
66
100
|
type: 'boolean',
|
|
@@ -79,45 +113,20 @@ export const j = {
|
|
|
79
113
|
integer<T extends number = number>() {
|
|
80
114
|
return new JsonSchemaNumberBuilder<T>().integer()
|
|
81
115
|
},
|
|
82
|
-
unixTimestamp() {
|
|
83
|
-
return new JsonSchemaNumberBuilder<UnixTimestamp>().unixTimestamp()
|
|
84
|
-
},
|
|
85
|
-
unixTimestamp2000() {
|
|
86
|
-
return new JsonSchemaNumberBuilder<UnixTimestamp>().unixTimestamp2000()
|
|
87
|
-
},
|
|
88
116
|
|
|
89
117
|
// string types
|
|
90
118
|
string<T extends string = string>() {
|
|
91
119
|
return new JsonSchemaStringBuilder<T>()
|
|
92
120
|
},
|
|
93
|
-
jwt() {
|
|
94
|
-
return new JsonSchemaStringBuilder<JWTString>().jwt()
|
|
95
|
-
},
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* Accepts only the `YYYY-MM-DD` shape from all ISO 8601 variants.
|
|
99
|
-
*/
|
|
100
|
-
isoDate() {
|
|
101
|
-
return new JsonSchemaStringBuilder<IsoDate>().isoDate()
|
|
102
|
-
},
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* Accepts strings that start with the `YYYY-MM-DDTHH:MM:SS` shape
|
|
106
|
-
* and optionally end with either a `Z` or a `+/-hh:mm` timezone part.
|
|
107
|
-
*/
|
|
108
|
-
isoDateTime() {
|
|
109
|
-
return new JsonSchemaStringBuilder<IsoDateTime>().isoDateTime()
|
|
110
|
-
},
|
|
111
121
|
|
|
112
|
-
// email: () => new JsonSchemaStringBuilder().email(),
|
|
113
122
|
// complex types
|
|
114
123
|
object,
|
|
115
124
|
dbEntity<T extends AnyObject>(props: T) {
|
|
116
125
|
return j
|
|
117
126
|
.object<BaseDBEntity>({
|
|
118
127
|
id: j.string(),
|
|
119
|
-
created: j.unixTimestamp2000(),
|
|
120
|
-
updated: j.unixTimestamp2000(),
|
|
128
|
+
created: j.integer().unixTimestamp2000(),
|
|
129
|
+
updated: j.integer().unixTimestamp2000(),
|
|
121
130
|
})
|
|
122
131
|
.extend(j.object(props))
|
|
123
132
|
},
|
|
@@ -305,13 +314,27 @@ export class JsonSchemaNumberBuilder<
|
|
|
305
314
|
int64 = (): this => this.format('int64')
|
|
306
315
|
float = (): this => this.format('float')
|
|
307
316
|
double = (): this => this.format('double')
|
|
308
|
-
unixTimestamp = (): this => this.format('unixTimestamp').description('UnixTimestamp')
|
|
309
|
-
unixTimestamp2000 = (): this => this.format('unixTimestamp2000').description('UnixTimestamp2000')
|
|
310
|
-
unixTimestampMillis = (): this =>
|
|
311
|
-
this.format('unixTimestampMillis').description('UnixTimestampMillis')
|
|
312
317
|
|
|
313
|
-
|
|
314
|
-
this.format('
|
|
318
|
+
unixTimestamp = (): JsonSchemaNumberBuilder<UnixTimestamp> =>
|
|
319
|
+
this.integer().branded<UnixTimestamp>().format('unixTimestamp').description('UnixTimestamp')
|
|
320
|
+
|
|
321
|
+
unixTimestamp2000 = (): JsonSchemaNumberBuilder<UnixTimestamp> =>
|
|
322
|
+
this.integer()
|
|
323
|
+
.branded<UnixTimestamp>()
|
|
324
|
+
.format('unixTimestamp2000')
|
|
325
|
+
.description('UnixTimestamp2000')
|
|
326
|
+
|
|
327
|
+
unixTimestampMillis = (): JsonSchemaNumberBuilder<UnixTimestampMillis> =>
|
|
328
|
+
this.integer()
|
|
329
|
+
.branded<UnixTimestampMillis>()
|
|
330
|
+
.format('unixTimestampMillis')
|
|
331
|
+
.description('UnixTimestampMillis')
|
|
332
|
+
|
|
333
|
+
unixTimestampMillis2000 = (): JsonSchemaNumberBuilder<UnixTimestampMillis> =>
|
|
334
|
+
this.integer()
|
|
335
|
+
.branded<UnixTimestampMillis>()
|
|
336
|
+
.format('unixTimestampMillis2000')
|
|
337
|
+
.description('UnixTimestampMillis2000')
|
|
315
338
|
|
|
316
339
|
utcOffset = (): this => this.format('utcOffset')
|
|
317
340
|
utcOffsetHours = (): this => this.format('utcOffsetHours')
|
|
@@ -3,6 +3,6 @@ import { j } from './jsonSchemaBuilder.js'
|
|
|
3
3
|
|
|
4
4
|
export const baseDBEntityJsonSchema = j.object<BaseDBEntity>({
|
|
5
5
|
id: j.string(),
|
|
6
|
-
created: j.unixTimestamp2000(),
|
|
7
|
-
updated: j.unixTimestamp2000(),
|
|
6
|
+
created: j.integer().unixTimestamp2000(),
|
|
7
|
+
updated: j.integer().unixTimestamp2000(),
|
|
8
8
|
})
|
package/src/nanoid.ts
CHANGED
|
@@ -4,8 +4,6 @@
|
|
|
4
4
|
|
|
5
5
|
/// <reference lib="dom" preserve="true" />
|
|
6
6
|
|
|
7
|
-
/* eslint-disable no-bitwise */
|
|
8
|
-
|
|
9
7
|
// "0-9a-zA-Z-_", same as base64url alphabet
|
|
10
8
|
const urlAlphabet = 'useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict'
|
|
11
9
|
|
|
@@ -16,6 +14,7 @@ export type NanoidFunction = (length?: number) => string
|
|
|
16
14
|
|
|
17
15
|
type NanoidRandomFunction = (bytes: number) => Uint8Array
|
|
18
16
|
|
|
17
|
+
// oxlint-disable no-bitwise -- NanoID uses bit operations to build compact IDs
|
|
19
18
|
export function nanoidBrowser(length = 21): string {
|
|
20
19
|
let id = ''
|
|
21
20
|
const bytes = globalThis.crypto.getRandomValues(new Uint8Array(length))
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
/* eslint-disable no-bitwise */
|
|
2
|
-
|
|
3
1
|
/**
|
|
4
2
|
* Function that returns a random number between 0 and 1.
|
|
5
3
|
* Exactly same signature as Math.random function.
|
|
@@ -13,6 +11,7 @@ export type RandomFunction = () => number
|
|
|
13
11
|
*/
|
|
14
12
|
export function _createDeterministicRandom(seed = 0x2f6e2b1): RandomFunction {
|
|
15
13
|
return () => {
|
|
14
|
+
// oxlint-disable no-bitwise
|
|
16
15
|
// Robert Jenkins’ 32 bit integer hash function
|
|
17
16
|
seed = (seed + 0x7ed55d16 + (seed << 12)) & 0xffffffff
|
|
18
17
|
seed = (seed ^ 0xc761c23c ^ (seed >>> 19)) & 0xffffffff
|
package/src/object/map2.ts
CHANGED
|
@@ -13,6 +13,16 @@ export class Map2<K = any, V = any> extends Map<K, V> {
|
|
|
13
13
|
return new Map2(Object.entries(obj))
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
/**
|
|
17
|
+
* Allows to set multiple key-value pairs at once.
|
|
18
|
+
*/
|
|
19
|
+
setMany(obj: Record<any, V>): this {
|
|
20
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
21
|
+
this.set(k as K, v)
|
|
22
|
+
}
|
|
23
|
+
return this
|
|
24
|
+
}
|
|
25
|
+
|
|
16
26
|
toObject(): Record<string, V> {
|
|
17
27
|
return Object.fromEntries(this)
|
|
18
28
|
}
|
package/src/object/set2.ts
CHANGED
|
@@ -6,6 +6,17 @@
|
|
|
6
6
|
* @experimental
|
|
7
7
|
*/
|
|
8
8
|
export class Set2<T = any> extends Set<T> {
|
|
9
|
+
/**
|
|
10
|
+
* Like .add(), but allows to add multiple items at once.
|
|
11
|
+
* Mutates the Set, but also returns it conveniently.
|
|
12
|
+
*/
|
|
13
|
+
addMany(items: Iterable<T>): this {
|
|
14
|
+
for (const item of items) {
|
|
15
|
+
this.add(item)
|
|
16
|
+
}
|
|
17
|
+
return this
|
|
18
|
+
}
|
|
19
|
+
|
|
9
20
|
toArray(): T[] {
|
|
10
21
|
return [...this]
|
|
11
22
|
}
|
package/src/string/hash.util.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { Integer } from '../types.js'
|
|
2
2
|
|
|
3
|
+
// oxlint-disable no-bitwise -- hash implementations use bit-level operations for speed
|
|
4
|
+
|
|
3
5
|
const BASE62 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
|
|
4
6
|
// const BASE64 = BASE62 + '+/'
|
|
5
7
|
const BASE64URL = BASE62 + '-_'
|
|
@@ -54,7 +56,7 @@ export function hashCode(s: string): Integer {
|
|
|
54
56
|
let i = 0
|
|
55
57
|
const len = s.length
|
|
56
58
|
while (i < len) {
|
|
57
|
-
// eslint-disable-next-line
|
|
59
|
+
// eslint-disable-next-line unicorn/prefer-math-trunc, unicorn/prefer-code-point
|
|
58
60
|
hash = ((hash << 5) - hash + s.charCodeAt(i++)) << 0
|
|
59
61
|
}
|
|
60
62
|
return hash + 2147483647 + 1
|
package/src/string/leven.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
const array: number[] = []
|
|
2
2
|
const characterCodeCache: number[] = []
|
|
3
3
|
|
|
4
|
-
/* eslint-disable unicorn/prefer-code-point
|
|
4
|
+
/* eslint-disable unicorn/prefer-code-point */
|
|
5
|
+
// oxlint-disable no-bitwise
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Modified version of: https://github.com/sindresorhus/leven/
|
|
@@ -20,7 +20,6 @@ export function _safeJsonStringify(
|
|
|
20
20
|
}
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
/* eslint-disable no-bitwise, no-implicit-coercion */
|
|
24
23
|
// oxlint-disable no-unused-expressions
|
|
25
24
|
function serializer(replacer?: Reviver, cycleReplacer?: Reviver): Reviver {
|
|
26
25
|
const stack: any[] = []
|
|
@@ -34,9 +33,15 @@ function serializer(replacer?: Reviver, cycleReplacer?: Reviver): Reviver {
|
|
|
34
33
|
return function (key, value) {
|
|
35
34
|
if (stack.length > 0) {
|
|
36
35
|
const thisPos = stack.indexOf(this)
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
36
|
+
if (thisPos !== -1) {
|
|
37
|
+
stack.splice(thisPos + 1)
|
|
38
|
+
keys.splice(thisPos, Infinity, key)
|
|
39
|
+
} else {
|
|
40
|
+
stack.push(this)
|
|
41
|
+
keys.push(key)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (stack.includes(value)) {
|
|
40
45
|
value = cycleReplacer.call(this, key, value)
|
|
41
46
|
}
|
|
42
47
|
} else {
|