@naturalcycles/js-lib 15.86.2 → 15.88.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/analytics/analytics.model.d.ts +76 -0
- package/dist/analytics/analytics.model.js +26 -0
- package/dist/analytics/index.d.ts +1 -0
- package/dist/analytics/index.js +1 -0
- package/dist/datetime/localDate.js +5 -1
- package/dist/enum.util.d.ts +77 -1
- package/dist/enum.util.js +65 -0
- package/dist/number/number.util.js +5 -1
- package/dist/qr/qr.js +5 -2
- package/dist/semver.js +5 -1
- package/dist/string/leven.js +7 -8
- package/dist/types.d.ts +14 -0
- package/dist/ua/ua.d.ts +1 -1
- package/dist/ua/ua.js +1 -2
- package/package.json +3 -1
- package/src/analytics/analytics.model.ts +100 -0
- package/src/analytics/index.ts +1 -0
- package/src/datetime/localDate.ts +3 -1
- package/src/enum.util.ts +124 -1
- package/src/number/number.util.ts +3 -1
- package/src/qr/qr.ts +3 -2
- package/src/semver.ts +3 -1
- package/src/string/leven.ts +6 -8
- package/src/types.ts +15 -0
- package/src/ua/ua.ts +1 -2
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import type { AnyObject, Branded, StringMap, UnixTimestampMillis } from '../types.js';
|
|
2
|
+
/** Identity an event is attributed to: a generated device id, or an app-provided user id. */
|
|
3
|
+
export type AnalyticsDistinctId = Branded<string, 'AnalyticsDistinctId'>;
|
|
4
|
+
/** Emitted when the identity changes, naming the anonymous id the events so far belong to. */
|
|
5
|
+
export declare const ANALYTICS_IDENTIFY_EVENT_NAME = "identify";
|
|
6
|
+
/** Cap applied by `truncateAnalyticsProperty`. Event properties are passed through as given. */
|
|
7
|
+
export declare const MAX_ANALYTICS_PROPERTY_LENGTH = 255;
|
|
8
|
+
/**
|
|
9
|
+
* Caps a string property at the limit. Counts code points, because a plain
|
|
10
|
+
* slice(0, N) counts UTF-16 code units and can cut a surrogate pair in half.
|
|
11
|
+
*/
|
|
12
|
+
export declare function truncateAnalyticsProperty(value: string): string;
|
|
13
|
+
/**
|
|
14
|
+
* POST body of the ingestion endpoint - a client-batched analytics event envelope.
|
|
15
|
+
*
|
|
16
|
+
* Sent as a JSON string with `text/plain` content-type,
|
|
17
|
+
* to keep the request CORS-simple (no preflight).
|
|
18
|
+
*/
|
|
19
|
+
export interface AnalyticsBatchInput {
|
|
20
|
+
/**
|
|
21
|
+
* Client-side unix timestamp (ms) of when this request was sent.
|
|
22
|
+
* Kept as envelope timing metadata. It must not be used to adjust `event.ts`,
|
|
23
|
+
* because it changes between delivery attempts.
|
|
24
|
+
*/
|
|
25
|
+
sentAt: UnixTimestampMillis;
|
|
26
|
+
/**
|
|
27
|
+
* Client that sends the events. Allow-listed by the destination.
|
|
28
|
+
*/
|
|
29
|
+
clientId: number;
|
|
30
|
+
/**
|
|
31
|
+
* @deprecated Use `AnalyticsClientEvent.userId`, which is stamped when the event is tracked
|
|
32
|
+
* rather than when the batch is sent. Still sent for clients that predate the per-event id.
|
|
33
|
+
*/
|
|
34
|
+
userId: AnalyticsDistinctId;
|
|
35
|
+
events: AnalyticsClientEvent[];
|
|
36
|
+
}
|
|
37
|
+
export interface AnalyticsClientEvent {
|
|
38
|
+
/**
|
|
39
|
+
* Unique event id, used for deduplication. Alphanumeric-only, so it can be
|
|
40
|
+
* forwarded as a dedupe key without conversion.
|
|
41
|
+
*/
|
|
42
|
+
id: string;
|
|
43
|
+
/**
|
|
44
|
+
* Event name, e.g `WebClick`.
|
|
45
|
+
*/
|
|
46
|
+
name: string;
|
|
47
|
+
/**
|
|
48
|
+
* Client-side unix timestamp (ms) of when the event was tracked (not when it was sent).
|
|
49
|
+
* Stable across delivery attempts, because a deduplication key may include it.
|
|
50
|
+
*/
|
|
51
|
+
ts: UnixTimestampMillis;
|
|
52
|
+
/**
|
|
53
|
+
* Event properties.
|
|
54
|
+
*/
|
|
55
|
+
props?: AnyObject;
|
|
56
|
+
/**
|
|
57
|
+
* Identity this event was tracked under
|
|
58
|
+
* TODO: make required
|
|
59
|
+
*/
|
|
60
|
+
userId?: AnalyticsDistinctId;
|
|
61
|
+
}
|
|
62
|
+
/** Canonical utm parameters. */
|
|
63
|
+
export declare const CANONICAL_UTM_PARAMS: string[];
|
|
64
|
+
/** Keyed by `initial_<canonical utm param>`, to avoid permanently saving non-standard ones. */
|
|
65
|
+
export type FirstTouchUtms = StringMap<string | null>;
|
|
66
|
+
/**
|
|
67
|
+
* Properties that will be "set once" on a user profile. Meaning they are not changed by subsequent
|
|
68
|
+
* saves on the user profile. Keyed by `initial_referrer` and the first-touch utms.
|
|
69
|
+
*/
|
|
70
|
+
export type SetOnceUserProperties = StringMap<string | null>;
|
|
71
|
+
/** Body of the first-touch endpoint, posted once per pageload. */
|
|
72
|
+
export interface FirstTouchInput {
|
|
73
|
+
clientId: number;
|
|
74
|
+
userId: AnalyticsDistinctId;
|
|
75
|
+
props: SetOnceUserProperties;
|
|
76
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/** Emitted when the identity changes, naming the anonymous id the events so far belong to. */
|
|
2
|
+
export const ANALYTICS_IDENTIFY_EVENT_NAME = 'identify';
|
|
3
|
+
/** Cap applied by `truncateAnalyticsProperty`. Event properties are passed through as given. */
|
|
4
|
+
export const MAX_ANALYTICS_PROPERTY_LENGTH = 255;
|
|
5
|
+
/**
|
|
6
|
+
* Caps a string property at the limit. Counts code points, because a plain
|
|
7
|
+
* slice(0, N) counts UTF-16 code units and can cut a surrogate pair in half.
|
|
8
|
+
*/
|
|
9
|
+
export function truncateAnalyticsProperty(value) {
|
|
10
|
+
if (value.length <= MAX_ANALYTICS_PROPERTY_LENGTH)
|
|
11
|
+
return value;
|
|
12
|
+
return Array.from(value).slice(0, MAX_ANALYTICS_PROPERTY_LENGTH).join('');
|
|
13
|
+
}
|
|
14
|
+
/** Canonical utm parameters. */
|
|
15
|
+
export const CANONICAL_UTM_PARAMS = [
|
|
16
|
+
'utm_source',
|
|
17
|
+
'utm_medium',
|
|
18
|
+
'utm_campaign',
|
|
19
|
+
'utm_content',
|
|
20
|
+
'utm_term',
|
|
21
|
+
'utm_id',
|
|
22
|
+
'utm_source_platform',
|
|
23
|
+
'utm_campaign_id',
|
|
24
|
+
'utm_creative_format',
|
|
25
|
+
'utm_marketing_tactic',
|
|
26
|
+
];
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './analytics.model.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './analytics.model.js';
|
|
@@ -21,7 +21,11 @@ export class LocalDate {
|
|
|
21
21
|
this.day = day;
|
|
22
22
|
}
|
|
23
23
|
get(unit) {
|
|
24
|
-
|
|
24
|
+
if (unit === 'year')
|
|
25
|
+
return this.year;
|
|
26
|
+
if (unit === 'month')
|
|
27
|
+
return this.month;
|
|
28
|
+
return this.day;
|
|
25
29
|
}
|
|
26
30
|
set(unit, v, opt = {}) {
|
|
27
31
|
const t = opt.mutate ? this : this.clone();
|
package/dist/enum.util.d.ts
CHANGED
|
@@ -1,4 +1,32 @@
|
|
|
1
|
-
import type { AnyObject, NumberEnum, StringEnum } from './types.js';
|
|
1
|
+
import type { AnyObject, Enum, NumberEnum, StringEnum } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Creates a runtime Enum object. A replacement for a native TypeScript enum,
|
|
4
|
+
* which is not supported by type-stripping runtimes (e.g `node --experimental-strip-types`).
|
|
5
|
+
*
|
|
6
|
+
* Declare the type with the same name as the const, to mimic how native enums merge value and type.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* export const Color = _enum({ RED: 'red', GREEN: 'green' })
|
|
10
|
+
* export type Color = typeof Color.type // 'red' | 'green'
|
|
11
|
+
*
|
|
12
|
+
* Color.RED // 'red'
|
|
13
|
+
* Color.keys // ['RED', 'GREEN']
|
|
14
|
+
* Color.values // ['red', 'green']
|
|
15
|
+
* Color.entries // [['RED', 'red'], ['GREEN', 'green']]
|
|
16
|
+
* Color.is(x) // type-guard for 'red' | 'green'
|
|
17
|
+
* Color.keyOf('red') // 'RED'
|
|
18
|
+
* JSON.stringify(Color) // '{"RED":"red","GREEN":"green"}', no reverse-mapping, no helper leakage
|
|
19
|
+
*
|
|
20
|
+
* The result doesn't satisfy `StringEnum`/`NumberEnum`, so the `_stringEnum*`/`_numberEnum*`
|
|
21
|
+
* functions don't accept it. Use the helpers on the returned object instead.
|
|
22
|
+
*
|
|
23
|
+
* Nothing is frozen, immutability is expressed through `readonly` types only.
|
|
24
|
+
*
|
|
25
|
+
* Throws if the input contains a key that collides with one of the helper names.
|
|
26
|
+
*
|
|
27
|
+
* @experimental
|
|
28
|
+
*/
|
|
29
|
+
export declare function _enum<const T extends Record<string, string> | Record<string, number>>(obj: T): EnumObject<T>;
|
|
2
30
|
export declare function getEnumType(en: AnyObject): 'StringEnum' | 'NumberEnum' | undefined;
|
|
3
31
|
/**
|
|
4
32
|
* Returns all String keys of a number-enum.
|
|
@@ -91,3 +119,51 @@ export declare function _numberEnumKeyOrUndefined<T extends NumberEnum>(en: T, v
|
|
|
91
119
|
export declare function _numberEnumKey<T extends NumberEnum>(en: T, v: T[keyof T] | undefined | null): keyof T;
|
|
92
120
|
export declare function _stringEnumKeyOrUndefined<T extends StringEnum>(en: T, v: string | undefined | null): keyof T | undefined;
|
|
93
121
|
export declare function _stringEnumKey<T extends StringEnum>(en: T, v: string | undefined | null): keyof T;
|
|
122
|
+
/**
|
|
123
|
+
* @experimental
|
|
124
|
+
*/
|
|
125
|
+
export type EnumObject<T> = Readonly<T> & EnumHelpers<T>;
|
|
126
|
+
/**
|
|
127
|
+
* Helpers that `_enum()` hangs off the returned object.
|
|
128
|
+
* Defined as non-enumerable, so that `Object.keys/values/entries`, `JSON.stringify`
|
|
129
|
+
* and `for..in` still only see the actual Enum members.
|
|
130
|
+
*
|
|
131
|
+
* `keys`/`values`/`entries` are precomputed, and keep the literal types
|
|
132
|
+
* that the `Object.*` equivalents would widen to `string`.
|
|
133
|
+
*
|
|
134
|
+
* @experimental
|
|
135
|
+
*/
|
|
136
|
+
interface EnumHelpers<T> {
|
|
137
|
+
/**
|
|
138
|
+
* Type-only carrier for the union of all values, so that the Enum's type can be declared
|
|
139
|
+
* without a second import: `export type Color = typeof Color.type`.
|
|
140
|
+
*
|
|
141
|
+
* It has no runtime counterpart, reading it as a value returns undefined.
|
|
142
|
+
*/
|
|
143
|
+
readonly type: Enum<T>;
|
|
144
|
+
readonly keys: readonly (keyof T)[];
|
|
145
|
+
readonly values: readonly Enum<T>[];
|
|
146
|
+
readonly entries: EnumEntries<T>;
|
|
147
|
+
/**
|
|
148
|
+
* Type-guard. O(1), backed by a Map.
|
|
149
|
+
*/
|
|
150
|
+
readonly is: (v: unknown) => v is Enum<T>;
|
|
151
|
+
/**
|
|
152
|
+
* Reverse lookup, the counterpart of `Color.RED`. O(1). Throws if the value is not a member.
|
|
153
|
+
*/
|
|
154
|
+
readonly keyOf: (v: Enum<T>) => keyof T;
|
|
155
|
+
/**
|
|
156
|
+
* Like `keyOf`, but takes untrusted input and returns undefined instead of throwing.
|
|
157
|
+
*/
|
|
158
|
+
readonly keyOfOrUndefined: (v: unknown) => keyof T | undefined;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* The mapped type (rather than `readonly [keyof T, Enum<T>][]`) keeps each key paired
|
|
162
|
+
* with its own value, instead of allowing any key/value combination.
|
|
163
|
+
*
|
|
164
|
+
* @experimental
|
|
165
|
+
*/
|
|
166
|
+
type EnumEntries<T> = readonly {
|
|
167
|
+
[K in keyof T]: readonly [k: K, v: T[K]];
|
|
168
|
+
}[keyof T][];
|
|
169
|
+
export {};
|
package/dist/enum.util.js
CHANGED
|
@@ -1,3 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Creates a runtime Enum object. A replacement for a native TypeScript enum,
|
|
3
|
+
* which is not supported by type-stripping runtimes (e.g `node --experimental-strip-types`).
|
|
4
|
+
*
|
|
5
|
+
* Declare the type with the same name as the const, to mimic how native enums merge value and type.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* export const Color = _enum({ RED: 'red', GREEN: 'green' })
|
|
9
|
+
* export type Color = typeof Color.type // 'red' | 'green'
|
|
10
|
+
*
|
|
11
|
+
* Color.RED // 'red'
|
|
12
|
+
* Color.keys // ['RED', 'GREEN']
|
|
13
|
+
* Color.values // ['red', 'green']
|
|
14
|
+
* Color.entries // [['RED', 'red'], ['GREEN', 'green']]
|
|
15
|
+
* Color.is(x) // type-guard for 'red' | 'green'
|
|
16
|
+
* Color.keyOf('red') // 'RED'
|
|
17
|
+
* JSON.stringify(Color) // '{"RED":"red","GREEN":"green"}', no reverse-mapping, no helper leakage
|
|
18
|
+
*
|
|
19
|
+
* The result doesn't satisfy `StringEnum`/`NumberEnum`, so the `_stringEnum*`/`_numberEnum*`
|
|
20
|
+
* functions don't accept it. Use the helpers on the returned object instead.
|
|
21
|
+
*
|
|
22
|
+
* Nothing is frozen, immutability is expressed through `readonly` types only.
|
|
23
|
+
*
|
|
24
|
+
* Throws if the input contains a key that collides with one of the helper names.
|
|
25
|
+
*
|
|
26
|
+
* @experimental
|
|
27
|
+
*/
|
|
28
|
+
export function _enum(obj) {
|
|
29
|
+
if (RESERVED_KEYS.some(k => Object.hasOwn(obj, k))) {
|
|
30
|
+
throw new Error(`_enum keys must not be named ${RESERVED_KEYS.join(', ')}, as they collide with the helpers`);
|
|
31
|
+
}
|
|
32
|
+
const rawEntries = Object.entries(obj);
|
|
33
|
+
const keys = Object.keys(obj);
|
|
34
|
+
const values = Object.values(obj);
|
|
35
|
+
const entries = rawEntries;
|
|
36
|
+
// Backs both `is` and `keyOf`. On duplicate values the last key wins, like a native number enum.
|
|
37
|
+
const keyByValue = new Map(rawEntries.map(([k, v]) => [v, k]));
|
|
38
|
+
return Object.defineProperties({ ...obj }, {
|
|
39
|
+
keys: { value: keys },
|
|
40
|
+
values: { value: values },
|
|
41
|
+
entries: { value: entries },
|
|
42
|
+
is: { value: (v) => keyByValue.has(v) },
|
|
43
|
+
keyOf: {
|
|
44
|
+
value: (v) => {
|
|
45
|
+
const key = keyByValue.get(v);
|
|
46
|
+
if (key === undefined)
|
|
47
|
+
throw new Error(`_enum keyOf not found for: ${String(v)}`);
|
|
48
|
+
return key;
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
keyOfOrUndefined: { value: (v) => keyByValue.get(v) },
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Keys that `_enum()` reserves for its helpers, and therefore rejects as Enum member names.
|
|
56
|
+
*/
|
|
57
|
+
const RESERVED_KEYS = [
|
|
58
|
+
'type',
|
|
59
|
+
'keys',
|
|
60
|
+
'values',
|
|
61
|
+
'entries',
|
|
62
|
+
'is',
|
|
63
|
+
'keyOf',
|
|
64
|
+
'keyOfOrUndefined',
|
|
65
|
+
];
|
|
1
66
|
export function getEnumType(en) {
|
|
2
67
|
/*
|
|
3
68
|
* enum Foo { A = 1, B = 2 }
|
|
@@ -40,7 +40,11 @@ export function _isBetween(x, min, max, incl) {
|
|
|
40
40
|
return true;
|
|
41
41
|
}
|
|
42
42
|
export function _clamp(x, minIncl, maxIncl) {
|
|
43
|
-
|
|
43
|
+
if (x <= minIncl)
|
|
44
|
+
return minIncl;
|
|
45
|
+
if (x >= maxIncl)
|
|
46
|
+
return maxIncl;
|
|
47
|
+
return x;
|
|
44
48
|
}
|
|
45
49
|
/**
|
|
46
50
|
* Same as .toFixed(), but conveniently casts the output to Number.
|
package/dist/qr/qr.js
CHANGED
|
@@ -560,8 +560,11 @@ function getLengthInBits(mode, type) {
|
|
|
560
560
|
const bits = LENGTH_BITS[mode];
|
|
561
561
|
if (!bits)
|
|
562
562
|
throw new Error(`qr: bad mode: ${mode}`);
|
|
563
|
-
|
|
564
|
-
|
|
563
|
+
if (type < 10)
|
|
564
|
+
return bits[0];
|
|
565
|
+
if (type < 27)
|
|
566
|
+
return bits[1];
|
|
567
|
+
return bits[2];
|
|
565
568
|
}
|
|
566
569
|
// --- Reed-Solomon polynomials over GF(256) ---------------------------------
|
|
567
570
|
/** Drop leading zeros, then right-pad with `shift` zeros. */
|
package/dist/semver.js
CHANGED
package/dist/string/leven.js
CHANGED
|
@@ -63,14 +63,13 @@ export function _leven(first, second, limit) {
|
|
|
63
63
|
for (index = 0; index < firstLength; index++) {
|
|
64
64
|
temporary2 = bCharacterCode === characterCodeCache[index] ? temporary : temporary + 1;
|
|
65
65
|
temporary = array[index];
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
: temporary2;
|
|
66
|
+
if (temporary > result) {
|
|
67
|
+
result = temporary2 > result ? result + 1 : temporary2;
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
result = temporary2 > temporary ? temporary + 1 : temporary2;
|
|
71
|
+
}
|
|
72
|
+
array[index] = result;
|
|
74
73
|
}
|
|
75
74
|
}
|
|
76
75
|
return result;
|
package/dist/types.d.ts
CHANGED
|
@@ -28,6 +28,20 @@ export type AnyObject = Record<string, any>;
|
|
|
28
28
|
export type AnyEnum = NumberEnum;
|
|
29
29
|
export type NumberEnum = Record<string, number | string>;
|
|
30
30
|
export type StringEnum = Record<string, string>;
|
|
31
|
+
/**
|
|
32
|
+
* Union of all values of an Enum-like object: both native TypeScript enums and `_enum()` objects.
|
|
33
|
+
*
|
|
34
|
+
* For an `_enum()` object prefer `typeof Color.type`, which needs no import.
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* enum Color { RED = 'red', GREEN = 'green' }
|
|
38
|
+
* type ColorValue = Enum<typeof Color> // 'red' | 'green'
|
|
39
|
+
*
|
|
40
|
+
* @experimental
|
|
41
|
+
*/
|
|
42
|
+
export type Enum<T> = T extends {
|
|
43
|
+
values: readonly (infer V)[];
|
|
44
|
+
} ? V : T[keyof T];
|
|
31
45
|
export type CreatedUpdated = {
|
|
32
46
|
created: UnixTimestamp;
|
|
33
47
|
updated: UnixTimestamp;
|
package/dist/ua/ua.d.ts
CHANGED
|
@@ -152,7 +152,7 @@ export declare class UAParser {
|
|
|
152
152
|
getOS(): UAOS;
|
|
153
153
|
getResult(): UAResult;
|
|
154
154
|
getUA(): string;
|
|
155
|
-
setUA(ua
|
|
155
|
+
setUA(ua?: string | undefined): this;
|
|
156
156
|
/**
|
|
157
157
|
* True if the OS is iOS.
|
|
158
158
|
* Note: desktop-mode iPads send a Mac-like UA and are not detected as iOS
|
package/dist/ua/ua.js
CHANGED
|
@@ -146,8 +146,7 @@ export class UAParser {
|
|
|
146
146
|
getUA() {
|
|
147
147
|
return this.ua;
|
|
148
148
|
}
|
|
149
|
-
setUA(ua) {
|
|
150
|
-
ua ||= '';
|
|
149
|
+
setUA(ua = '') {
|
|
151
150
|
this.ua = ua.length > UA_MAX_LENGTH ? ua.replace(/^\s\s*/, '').slice(0, UA_MAX_LENGTH) : ua;
|
|
152
151
|
this.cachedBrowser = this.cachedOs = this.cachedDevice = undefined;
|
|
153
152
|
return this;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@naturalcycles/js-lib",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "15.
|
|
4
|
+
"version": "15.88.0",
|
|
5
5
|
"dependencies": {
|
|
6
6
|
"tslib": "^2"
|
|
7
7
|
},
|
|
@@ -22,6 +22,8 @@
|
|
|
22
22
|
},
|
|
23
23
|
"exports": {
|
|
24
24
|
".": "./dist/index.js",
|
|
25
|
+
"./analytics": "./dist/analytics/index.js",
|
|
26
|
+
"./analytics/*.js": "./dist/analytics/*.js",
|
|
25
27
|
"./array": "./dist/array/index.js",
|
|
26
28
|
"./array/*.js": "./dist/array/*.js",
|
|
27
29
|
"./browser": "./dist/browser/index.js",
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import type { AnyObject, Branded, StringMap, UnixTimestampMillis } from '../types.js'
|
|
2
|
+
|
|
3
|
+
/** Identity an event is attributed to: a generated device id, or an app-provided user id. */
|
|
4
|
+
export type AnalyticsDistinctId = Branded<string, 'AnalyticsDistinctId'>
|
|
5
|
+
|
|
6
|
+
/** Emitted when the identity changes, naming the anonymous id the events so far belong to. */
|
|
7
|
+
export const ANALYTICS_IDENTIFY_EVENT_NAME = 'identify'
|
|
8
|
+
|
|
9
|
+
/** Cap applied by `truncateAnalyticsProperty`. Event properties are passed through as given. */
|
|
10
|
+
export const MAX_ANALYTICS_PROPERTY_LENGTH = 255
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Caps a string property at the limit. Counts code points, because a plain
|
|
14
|
+
* slice(0, N) counts UTF-16 code units and can cut a surrogate pair in half.
|
|
15
|
+
*/
|
|
16
|
+
export function truncateAnalyticsProperty(value: string): string {
|
|
17
|
+
if (value.length <= MAX_ANALYTICS_PROPERTY_LENGTH) return value
|
|
18
|
+
return Array.from(value).slice(0, MAX_ANALYTICS_PROPERTY_LENGTH).join('')
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* POST body of the ingestion endpoint - a client-batched analytics event envelope.
|
|
23
|
+
*
|
|
24
|
+
* Sent as a JSON string with `text/plain` content-type,
|
|
25
|
+
* to keep the request CORS-simple (no preflight).
|
|
26
|
+
*/
|
|
27
|
+
export interface AnalyticsBatchInput {
|
|
28
|
+
/**
|
|
29
|
+
* Client-side unix timestamp (ms) of when this request was sent.
|
|
30
|
+
* Kept as envelope timing metadata. It must not be used to adjust `event.ts`,
|
|
31
|
+
* because it changes between delivery attempts.
|
|
32
|
+
*/
|
|
33
|
+
sentAt: UnixTimestampMillis
|
|
34
|
+
/**
|
|
35
|
+
* Client that sends the events. Allow-listed by the destination.
|
|
36
|
+
*/
|
|
37
|
+
clientId: number
|
|
38
|
+
/**
|
|
39
|
+
* @deprecated Use `AnalyticsClientEvent.userId`, which is stamped when the event is tracked
|
|
40
|
+
* rather than when the batch is sent. Still sent for clients that predate the per-event id.
|
|
41
|
+
*/
|
|
42
|
+
userId: AnalyticsDistinctId
|
|
43
|
+
events: AnalyticsClientEvent[]
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface AnalyticsClientEvent {
|
|
47
|
+
/**
|
|
48
|
+
* Unique event id, used for deduplication. Alphanumeric-only, so it can be
|
|
49
|
+
* forwarded as a dedupe key without conversion.
|
|
50
|
+
*/
|
|
51
|
+
id: string
|
|
52
|
+
/**
|
|
53
|
+
* Event name, e.g `WebClick`.
|
|
54
|
+
*/
|
|
55
|
+
name: string
|
|
56
|
+
/**
|
|
57
|
+
* Client-side unix timestamp (ms) of when the event was tracked (not when it was sent).
|
|
58
|
+
* Stable across delivery attempts, because a deduplication key may include it.
|
|
59
|
+
*/
|
|
60
|
+
ts: UnixTimestampMillis
|
|
61
|
+
/**
|
|
62
|
+
* Event properties.
|
|
63
|
+
*/
|
|
64
|
+
props?: AnyObject
|
|
65
|
+
/**
|
|
66
|
+
* Identity this event was tracked under
|
|
67
|
+
* TODO: make required
|
|
68
|
+
*/
|
|
69
|
+
userId?: AnalyticsDistinctId
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Canonical utm parameters. */
|
|
73
|
+
export const CANONICAL_UTM_PARAMS = [
|
|
74
|
+
'utm_source',
|
|
75
|
+
'utm_medium',
|
|
76
|
+
'utm_campaign',
|
|
77
|
+
'utm_content',
|
|
78
|
+
'utm_term',
|
|
79
|
+
'utm_id',
|
|
80
|
+
'utm_source_platform',
|
|
81
|
+
'utm_campaign_id',
|
|
82
|
+
'utm_creative_format',
|
|
83
|
+
'utm_marketing_tactic',
|
|
84
|
+
]
|
|
85
|
+
|
|
86
|
+
/** Keyed by `initial_<canonical utm param>`, to avoid permanently saving non-standard ones. */
|
|
87
|
+
export type FirstTouchUtms = StringMap<string | null>
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Properties that will be "set once" on a user profile. Meaning they are not changed by subsequent
|
|
91
|
+
* saves on the user profile. Keyed by `initial_referrer` and the first-touch utms.
|
|
92
|
+
*/
|
|
93
|
+
export type SetOnceUserProperties = StringMap<string | null>
|
|
94
|
+
|
|
95
|
+
/** Body of the first-touch endpoint, posted once per pageload. */
|
|
96
|
+
export interface FirstTouchInput {
|
|
97
|
+
clientId: number
|
|
98
|
+
userId: AnalyticsDistinctId
|
|
99
|
+
props: SetOnceUserProperties
|
|
100
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './analytics.model.js'
|
|
@@ -40,7 +40,9 @@ export class LocalDate {
|
|
|
40
40
|
) {}
|
|
41
41
|
|
|
42
42
|
get(unit: LocalDateUnitStrict): number {
|
|
43
|
-
|
|
43
|
+
if (unit === 'year') return this.year
|
|
44
|
+
if (unit === 'month') return this.month
|
|
45
|
+
return this.day
|
|
44
46
|
}
|
|
45
47
|
|
|
46
48
|
set(unit: LocalDateUnitStrict, v: number, opt: MutateOptions = {}): LocalDate {
|
package/src/enum.util.ts
CHANGED
|
@@ -1,4 +1,79 @@
|
|
|
1
|
-
import type { AnyObject, NumberEnum, StringEnum } from './types.js'
|
|
1
|
+
import type { AnyObject, Enum, NumberEnum, StringEnum } from './types.js'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Creates a runtime Enum object. A replacement for a native TypeScript enum,
|
|
5
|
+
* which is not supported by type-stripping runtimes (e.g `node --experimental-strip-types`).
|
|
6
|
+
*
|
|
7
|
+
* Declare the type with the same name as the const, to mimic how native enums merge value and type.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* export const Color = _enum({ RED: 'red', GREEN: 'green' })
|
|
11
|
+
* export type Color = typeof Color.type // 'red' | 'green'
|
|
12
|
+
*
|
|
13
|
+
* Color.RED // 'red'
|
|
14
|
+
* Color.keys // ['RED', 'GREEN']
|
|
15
|
+
* Color.values // ['red', 'green']
|
|
16
|
+
* Color.entries // [['RED', 'red'], ['GREEN', 'green']]
|
|
17
|
+
* Color.is(x) // type-guard for 'red' | 'green'
|
|
18
|
+
* Color.keyOf('red') // 'RED'
|
|
19
|
+
* JSON.stringify(Color) // '{"RED":"red","GREEN":"green"}', no reverse-mapping, no helper leakage
|
|
20
|
+
*
|
|
21
|
+
* The result doesn't satisfy `StringEnum`/`NumberEnum`, so the `_stringEnum*`/`_numberEnum*`
|
|
22
|
+
* functions don't accept it. Use the helpers on the returned object instead.
|
|
23
|
+
*
|
|
24
|
+
* Nothing is frozen, immutability is expressed through `readonly` types only.
|
|
25
|
+
*
|
|
26
|
+
* Throws if the input contains a key that collides with one of the helper names.
|
|
27
|
+
*
|
|
28
|
+
* @experimental
|
|
29
|
+
*/
|
|
30
|
+
export function _enum<const T extends Record<string, string> | Record<string, number>>(
|
|
31
|
+
obj: T,
|
|
32
|
+
): EnumObject<T> {
|
|
33
|
+
if (RESERVED_KEYS.some(k => Object.hasOwn(obj, k))) {
|
|
34
|
+
throw new Error(
|
|
35
|
+
`_enum keys must not be named ${RESERVED_KEYS.join(', ')}, as they collide with the helpers`,
|
|
36
|
+
)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const rawEntries = Object.entries(obj)
|
|
40
|
+
const keys = Object.keys(obj) as (keyof T)[]
|
|
41
|
+
const values = Object.values(obj) as Enum<T>[]
|
|
42
|
+
const entries = rawEntries as unknown as EnumEntries<T>
|
|
43
|
+
// Backs both `is` and `keyOf`. On duplicate values the last key wins, like a native number enum.
|
|
44
|
+
const keyByValue = new Map<unknown, keyof T>(rawEntries.map(([k, v]) => [v, k]))
|
|
45
|
+
|
|
46
|
+
return Object.defineProperties(
|
|
47
|
+
{ ...obj },
|
|
48
|
+
{
|
|
49
|
+
keys: { value: keys },
|
|
50
|
+
values: { value: values },
|
|
51
|
+
entries: { value: entries },
|
|
52
|
+
is: { value: (v: unknown): v is Enum<T> => keyByValue.has(v) },
|
|
53
|
+
keyOf: {
|
|
54
|
+
value: (v: Enum<T>): keyof T => {
|
|
55
|
+
const key = keyByValue.get(v)
|
|
56
|
+
if (key === undefined) throw new Error(`_enum keyOf not found for: ${String(v)}`)
|
|
57
|
+
return key
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
keyOfOrUndefined: { value: (v: unknown): keyof T | undefined => keyByValue.get(v) },
|
|
61
|
+
},
|
|
62
|
+
) as EnumObject<T>
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Keys that `_enum()` reserves for its helpers, and therefore rejects as Enum member names.
|
|
67
|
+
*/
|
|
68
|
+
const RESERVED_KEYS = [
|
|
69
|
+
'type',
|
|
70
|
+
'keys',
|
|
71
|
+
'values',
|
|
72
|
+
'entries',
|
|
73
|
+
'is',
|
|
74
|
+
'keyOf',
|
|
75
|
+
'keyOfOrUndefined',
|
|
76
|
+
] as const
|
|
2
77
|
|
|
3
78
|
export function getEnumType(en: AnyObject): 'StringEnum' | 'NumberEnum' | undefined {
|
|
4
79
|
/*
|
|
@@ -226,3 +301,51 @@ export function _stringEnumKey<T extends StringEnum>(en: T, v: string | undefine
|
|
|
226
301
|
if (!r) throw new Error(`_stringEnumKey not found for: ${v}`)
|
|
227
302
|
return r
|
|
228
303
|
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* @experimental
|
|
307
|
+
*/
|
|
308
|
+
export type EnumObject<T> = Readonly<T> & EnumHelpers<T>
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Helpers that `_enum()` hangs off the returned object.
|
|
312
|
+
* Defined as non-enumerable, so that `Object.keys/values/entries`, `JSON.stringify`
|
|
313
|
+
* and `for..in` still only see the actual Enum members.
|
|
314
|
+
*
|
|
315
|
+
* `keys`/`values`/`entries` are precomputed, and keep the literal types
|
|
316
|
+
* that the `Object.*` equivalents would widen to `string`.
|
|
317
|
+
*
|
|
318
|
+
* @experimental
|
|
319
|
+
*/
|
|
320
|
+
interface EnumHelpers<T> {
|
|
321
|
+
/**
|
|
322
|
+
* Type-only carrier for the union of all values, so that the Enum's type can be declared
|
|
323
|
+
* without a second import: `export type Color = typeof Color.type`.
|
|
324
|
+
*
|
|
325
|
+
* It has no runtime counterpart, reading it as a value returns undefined.
|
|
326
|
+
*/
|
|
327
|
+
readonly type: Enum<T>
|
|
328
|
+
readonly keys: readonly (keyof T)[]
|
|
329
|
+
readonly values: readonly Enum<T>[]
|
|
330
|
+
readonly entries: EnumEntries<T>
|
|
331
|
+
/**
|
|
332
|
+
* Type-guard. O(1), backed by a Map.
|
|
333
|
+
*/
|
|
334
|
+
readonly is: (v: unknown) => v is Enum<T>
|
|
335
|
+
/**
|
|
336
|
+
* Reverse lookup, the counterpart of `Color.RED`. O(1). Throws if the value is not a member.
|
|
337
|
+
*/
|
|
338
|
+
readonly keyOf: (v: Enum<T>) => keyof T
|
|
339
|
+
/**
|
|
340
|
+
* Like `keyOf`, but takes untrusted input and returns undefined instead of throwing.
|
|
341
|
+
*/
|
|
342
|
+
readonly keyOfOrUndefined: (v: unknown) => keyof T | undefined
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* The mapped type (rather than `readonly [keyof T, Enum<T>][]`) keeps each key paired
|
|
347
|
+
* with its own value, instead of allowing any key/value combination.
|
|
348
|
+
*
|
|
349
|
+
* @experimental
|
|
350
|
+
*/
|
|
351
|
+
type EnumEntries<T> = readonly { [K in keyof T]: readonly [k: K, v: T[K]] }[keyof T][]
|
|
@@ -50,7 +50,9 @@ export function _isBetween<T extends number | string>(
|
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
export function _clamp(x: number, minIncl: number, maxIncl: number): number {
|
|
53
|
-
|
|
53
|
+
if (x <= minIncl) return minIncl
|
|
54
|
+
if (x >= maxIncl) return maxIncl
|
|
55
|
+
return x
|
|
54
56
|
}
|
|
55
57
|
|
|
56
58
|
/**
|
package/src/qr/qr.ts
CHANGED
|
@@ -684,8 +684,9 @@ function getLengthInBits(mode: number, type: number): number {
|
|
|
684
684
|
if (type < 1 || type > 40) throw new Error(`qr: bad type number: ${type}`)
|
|
685
685
|
const bits = LENGTH_BITS[mode]
|
|
686
686
|
if (!bits) throw new Error(`qr: bad mode: ${mode}`)
|
|
687
|
-
|
|
688
|
-
return bits[
|
|
687
|
+
if (type < 10) return bits[0]
|
|
688
|
+
if (type < 27) return bits[1]
|
|
689
|
+
return bits[2]
|
|
689
690
|
}
|
|
690
691
|
|
|
691
692
|
// --- Reed-Solomon polynomials over GF(256) ---------------------------------
|
package/src/semver.ts
CHANGED
|
@@ -171,5 +171,7 @@ export function _quickSemverCompare(a: string, b: string): -1 | 0 | 1 {
|
|
|
171
171
|
const s2 = _range(3)
|
|
172
172
|
.map(i => (t2[i] || '').padStart(5))
|
|
173
173
|
.join('')
|
|
174
|
-
|
|
174
|
+
if (s1 < s2) return -1
|
|
175
|
+
if (s1 > s2) return 1
|
|
176
|
+
return 0
|
|
175
177
|
}
|
package/src/string/leven.ts
CHANGED
|
@@ -76,14 +76,12 @@ export function _leven(first: string, second: string, limit?: number): number {
|
|
|
76
76
|
temporary2 = bCharacterCode === characterCodeCache[index] ? temporary : temporary + 1
|
|
77
77
|
temporary = array[index]!
|
|
78
78
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
? temporary + 1
|
|
86
|
-
: temporary2
|
|
79
|
+
if (temporary > result) {
|
|
80
|
+
result = temporary2 > result ? result + 1 : temporary2
|
|
81
|
+
} else {
|
|
82
|
+
result = temporary2 > temporary ? temporary + 1 : temporary2
|
|
83
|
+
}
|
|
84
|
+
array[index] = result
|
|
87
85
|
}
|
|
88
86
|
}
|
|
89
87
|
|
package/src/types.ts
CHANGED
|
@@ -34,6 +34,21 @@ export type AnyEnum = NumberEnum
|
|
|
34
34
|
export type NumberEnum = Record<string, number | string>
|
|
35
35
|
export type StringEnum = Record<string, string>
|
|
36
36
|
|
|
37
|
+
/**
|
|
38
|
+
* Union of all values of an Enum-like object: both native TypeScript enums and `_enum()` objects.
|
|
39
|
+
*
|
|
40
|
+
* For an `_enum()` object prefer `typeof Color.type`, which needs no import.
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* enum Color { RED = 'red', GREEN = 'green' }
|
|
44
|
+
* type ColorValue = Enum<typeof Color> // 'red' | 'green'
|
|
45
|
+
*
|
|
46
|
+
* @experimental
|
|
47
|
+
*/
|
|
48
|
+
// The first branch is for `_enum()` objects, whose helpers `T[keyof T]` would otherwise pull into
|
|
49
|
+
// the union. Native enums never match it, since their values are `string | number`, never an array.
|
|
50
|
+
export type Enum<T> = T extends { values: readonly (infer V)[] } ? V : T[keyof T]
|
|
51
|
+
|
|
37
52
|
// oxlint-disable-next-line @typescript-eslint/consistent-type-definitions
|
|
38
53
|
export type CreatedUpdated = {
|
|
39
54
|
created: UnixTimestamp
|
package/src/ua/ua.ts
CHANGED
|
@@ -266,8 +266,7 @@ export class UAParser {
|
|
|
266
266
|
return this.ua
|
|
267
267
|
}
|
|
268
268
|
|
|
269
|
-
setUA(ua: string | undefined): this {
|
|
270
|
-
ua ||= ''
|
|
269
|
+
setUA(ua: string | undefined = ''): this {
|
|
271
270
|
this.ua = ua.length > UA_MAX_LENGTH ? ua.replace(/^\s\s*/, '').slice(0, UA_MAX_LENGTH) : ua
|
|
272
271
|
this.cachedBrowser = this.cachedOs = this.cachedDevice = undefined
|
|
273
272
|
return this
|