@naturalcycles/js-lib 14.256.0 → 14.258.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.
Files changed (76) hide show
  1. package/cfg/frontend/tsconfig.json +67 -0
  2. package/dist/browser/adminService.d.ts +69 -0
  3. package/dist/browser/adminService.js +98 -0
  4. package/dist/browser/analytics.util.d.ts +12 -0
  5. package/dist/browser/analytics.util.js +59 -0
  6. package/dist/browser/i18n/fetchTranslationLoader.d.ts +13 -0
  7. package/dist/browser/i18n/fetchTranslationLoader.js +17 -0
  8. package/dist/browser/i18n/translation.service.d.ts +53 -0
  9. package/dist/browser/i18n/translation.service.js +61 -0
  10. package/dist/browser/imageFitter.d.ts +60 -0
  11. package/dist/browser/imageFitter.js +69 -0
  12. package/dist/browser/script.util.d.ts +14 -0
  13. package/dist/browser/script.util.js +50 -0
  14. package/dist/browser/topbar.d.ts +23 -0
  15. package/dist/browser/topbar.js +137 -0
  16. package/dist/decorators/memo.util.d.ts +2 -1
  17. package/dist/decorators/memo.util.js +8 -6
  18. package/dist/decorators/swarmSafe.decorator.d.ts +9 -0
  19. package/dist/decorators/swarmSafe.decorator.js +42 -0
  20. package/dist/deviceIdService.d.ts +65 -0
  21. package/dist/deviceIdService.js +109 -0
  22. package/dist/error/assert.d.ts +2 -1
  23. package/dist/error/assert.js +15 -13
  24. package/dist/error/error.util.js +9 -6
  25. package/dist/index.d.ts +9 -0
  26. package/dist/index.js +9 -0
  27. package/dist/nanoid.d.ts +7 -0
  28. package/dist/nanoid.js +61 -0
  29. package/dist/number/createDeterministicRandom.d.ts +6 -1
  30. package/dist/number/createDeterministicRandom.js +1 -2
  31. package/dist/string/hash.util.d.ts +1 -1
  32. package/dist/string/hash.util.js +1 -1
  33. package/dist/web.d.ts +6 -0
  34. package/dist/web.js +6 -0
  35. package/dist/zod/zod.util.d.ts +1 -1
  36. package/dist-esm/browser/adminService.js +94 -0
  37. package/dist-esm/browser/analytics.util.js +54 -0
  38. package/dist-esm/browser/i18n/fetchTranslationLoader.js +13 -0
  39. package/dist-esm/browser/i18n/translation.service.js +56 -0
  40. package/dist-esm/browser/imageFitter.js +65 -0
  41. package/dist-esm/browser/script.util.js +46 -0
  42. package/dist-esm/browser/topbar.js +134 -0
  43. package/dist-esm/decorators/memo.util.js +3 -1
  44. package/dist-esm/decorators/swarmSafe.decorator.js +38 -0
  45. package/dist-esm/deviceIdService.js +105 -0
  46. package/dist-esm/error/assert.js +3 -1
  47. package/dist-esm/error/error.util.js +4 -1
  48. package/dist-esm/index.js +9 -0
  49. package/dist-esm/nanoid.js +57 -0
  50. package/dist-esm/number/createDeterministicRandom.js +1 -2
  51. package/dist-esm/string/hash.util.js +1 -1
  52. package/dist-esm/web.js +6 -0
  53. package/package.json +2 -1
  54. package/src/browser/adminService.ts +157 -0
  55. package/src/browser/analytics.util.ts +68 -0
  56. package/src/browser/i18n/fetchTranslationLoader.ts +16 -0
  57. package/src/browser/i18n/translation.service.ts +102 -0
  58. package/src/browser/imageFitter.ts +128 -0
  59. package/src/browser/script.util.ts +52 -0
  60. package/src/browser/topbar.ts +147 -0
  61. package/src/datetime/localDate.ts +16 -0
  62. package/src/datetime/localTime.ts +39 -0
  63. package/src/decorators/debounce.ts +1 -0
  64. package/src/decorators/memo.util.ts +4 -1
  65. package/src/decorators/swarmSafe.decorator.ts +47 -0
  66. package/src/deviceIdService.ts +137 -0
  67. package/src/error/assert.ts +5 -11
  68. package/src/error/error.util.ts +4 -1
  69. package/src/index.ts +9 -0
  70. package/src/json-schema/jsonSchemaBuilder.ts +20 -0
  71. package/src/nanoid.ts +79 -0
  72. package/src/number/createDeterministicRandom.ts +7 -2
  73. package/src/semver.ts +2 -0
  74. package/src/string/hash.util.ts +1 -1
  75. package/src/web.ts +6 -0
  76. package/src/zod/zod.util.ts +1 -1
@@ -0,0 +1,137 @@
1
+ /// <reference lib="dom" preserve="true" />
2
+
3
+ import { isServerSide } from './env'
4
+ import { nanoidBrowser } from './nanoid'
5
+ import { hashCode } from './string/hash.util'
6
+
7
+ // This is in sync with the default length in Nanoid.
8
+ const deviceIdLength = 21
9
+
10
+ /**
11
+ * Service to generate, maintain, persist a stable "device id".
12
+ *
13
+ * It's called "device id" and not userId/visitorId, to indicate that it only identifies a device,
14
+ * and has nothing to do with user identification.
15
+ * User might be logged in or not.
16
+ * User id can be the same on multiple devices.
17
+ * DeviceId is unique per device, same User or not.
18
+ *
19
+ * Service provides methods to deterministically select fraction of devices.
20
+ * For example, select 10% of devices that visit the website to be tracked by Analytics
21
+ * (to reduce Analytics quota usage).
22
+ * DeviceId persistence will ensure that recurring visits from the same device will yield the same
23
+ * DeviceId, and same "selection assignment" (like an assignment in an AB test).
24
+ *
25
+ * @experimental
26
+ */
27
+ export class DeviceIdService {
28
+ constructor(cfg: DeviceIdServiceCfg = {}) {
29
+ this.cfg = {
30
+ localStorageKey: 'deviceId',
31
+ debug: false,
32
+ ...cfg,
33
+ }
34
+
35
+ this.init()
36
+ }
37
+
38
+ cfg: Required<DeviceIdServiceCfg>
39
+
40
+ /**
41
+ * `deviceId` is null only in anomalous cases, e.g when localStorage is not available (due to e.g "out of disk space" on device).
42
+ * In all other cases it should be defined and stable (persisted indefinitely between multiple visits).
43
+ *
44
+ * It is null if the service is run on the server side.
45
+ */
46
+ deviceId!: string | null
47
+
48
+ /**
49
+ * Selects this device based on "deterministic random selection", according to the defined `rate`.
50
+ * Rate is a floating number between 0 and 1.
51
+ * E.g rate of 0.1 means 10% chance of being selected.
52
+ *
53
+ * Selection is based on deviceId, which is generated random and persisted between visits.
54
+ * Persistence ensures that the selection (similar to an AB-test assignment) "sticks" to the device.
55
+ *
56
+ * If deviceId failed to be generated, e.g due to Device running out-of-space to save a string to localStorage,
57
+ * it will NOT be selected.
58
+ *
59
+ * @returns true if the device is selected.
60
+ */
61
+ select(rate: number): boolean {
62
+ if (!this.deviceId) {
63
+ this.debug(`deviceId is null, skipping selection`)
64
+ return false
65
+ }
66
+
67
+ const mod = Math.trunc(rate * 1000)
68
+ // console.log('hash: ', hashCode(this.deviceId)) // todo
69
+
70
+ return hashCode(this.deviceId) % 1000 < mod
71
+ }
72
+
73
+ /**
74
+ * Deletes the persisted deviceId.
75
+ * Keeps it in the service.
76
+ * To remove it from the service, assign deviceIdService.deviceId = null.
77
+ */
78
+ clearPersistence(): void {
79
+ try {
80
+ globalThis.localStorage.removeItem(this.cfg.localStorageKey)
81
+ } catch (err) {
82
+ console.log(err)
83
+ }
84
+ }
85
+
86
+ /**
87
+ * Generates a stable Device id if it wasn't previously generated on this device.
88
+ * Otherwise, reads a Device id from persistent storage.
89
+ */
90
+ private init(): void {
91
+ this.deviceId = null
92
+ if (isServerSide()) return
93
+
94
+ try {
95
+ this.deviceId = globalThis.localStorage.getItem(this.cfg.localStorageKey)
96
+ if (this.deviceId) this.debug(`loaded deviceId: ${this.deviceId}`)
97
+ } catch (err) {
98
+ console.log(err)
99
+ this.deviceId = null
100
+ }
101
+
102
+ if (this.deviceId && this.deviceId.length !== deviceIdLength) {
103
+ console.warn(
104
+ `[DeviceIdService] unexpected deviceIdLength (${this.deviceId.length}), will re-generate the id`,
105
+ { deviceId: this.deviceId },
106
+ )
107
+ this.deviceId = null
108
+ }
109
+
110
+ if (!this.deviceId) {
111
+ try {
112
+ this.deviceId = nanoidBrowser(deviceIdLength)
113
+ this.debug(`generated new deviceId: ${this.deviceId}`)
114
+ globalThis.localStorage.setItem(this.cfg.localStorageKey, this.deviceId)
115
+ } catch (err) {
116
+ console.log(err)
117
+ this.deviceId = null
118
+ }
119
+ }
120
+ }
121
+
122
+ private debug(...args: any[]): void {
123
+ if (this.cfg.debug) console.log('[DeviceIdService]', ...args)
124
+ }
125
+ }
126
+
127
+ export interface DeviceIdServiceCfg {
128
+ /**
129
+ * Default: deviceId
130
+ */
131
+ localStorageKey?: string
132
+
133
+ /**
134
+ * Set to true to enable debug logging.
135
+ */
136
+ debug?: boolean
137
+ }
@@ -1,14 +1,8 @@
1
- import {
2
- _deepEquals,
3
- _isBackendErrorResponseObject,
4
- _isErrorObject,
5
- _stringify,
6
- AssertionError,
7
- BackendErrorResponseObject,
8
- Class,
9
- ErrorData,
10
- ErrorObject,
11
- } from '..'
1
+ import { _deepEquals } from '../object/deepEquals'
2
+ import { _stringify } from '../string/stringify'
3
+ import type { Class } from '../typeFest'
4
+ import type { BackendErrorResponseObject, ErrorData, ErrorObject } from './error.model'
5
+ import { _isBackendErrorResponseObject, _isErrorObject, AssertionError } from './error.util'
12
6
 
13
7
  /**
14
8
  * Evaluates the `condition` (casts it to Boolean).
@@ -6,7 +6,10 @@ import type {
6
6
  ErrorObject,
7
7
  HttpRequestErrorData,
8
8
  } from '..'
9
- import { _jsonParseIfPossible, _stringify, _truncate, _truncateMiddle, isServerSide } from '..'
9
+ import { isServerSide } from '../env'
10
+ import { _jsonParseIfPossible } from '../string/json.util'
11
+ import { _truncate, _truncateMiddle } from '../string/string.util'
12
+ import { _stringify } from '../string/stringify'
10
13
 
11
14
  /**
12
15
  * Useful to ensure that error in `catch (err) { ... }`
package/src/index.ts CHANGED
@@ -2,6 +2,13 @@ export * from './abort'
2
2
  export * from './array/array.util'
3
3
  export * from './array/range'
4
4
  export * from './bot'
5
+ export * from './browser/adminService'
6
+ export * from './browser/analytics.util'
7
+ export * from './browser/i18n/fetchTranslationLoader'
8
+ export * from './browser/i18n/translation.service'
9
+ export * from './browser/imageFitter'
10
+ export * from './browser/script.util'
11
+ export * from './browser/topbar'
5
12
  export * from './datetime/dateInterval'
6
13
  export * from './datetime/localDate'
7
14
  export * from './datetime/localTime'
@@ -20,6 +27,7 @@ export * from './decorators/memoFnAsync'
20
27
  export * from './decorators/retry.decorator'
21
28
  export * from './decorators/timeout.decorator'
22
29
  export * from './define'
30
+ export * from './deviceIdService'
23
31
  export * from './enum.util'
24
32
  export * from './env'
25
33
  export * from './env/buildInfo'
@@ -45,6 +53,7 @@ export * from './log/commonLogger'
45
53
  export * from './math/math.util'
46
54
  export * from './math/sma'
47
55
  export * from './math/stack.util'
56
+ export * from './nanoid'
48
57
  export * from './number/createDeterministicRandom'
49
58
  export * from './number/number.util'
50
59
  export * from './object/deepEquals'
@@ -131,38 +131,47 @@ export class JsonSchemaAnyBuilder<T = unknown, SCHEMA_TYPE extends JsonSchema<T>
131
131
  Object.assign(this.schema, { $schema })
132
132
  return this
133
133
  }
134
+
134
135
  $schemaDraft7(): this {
135
136
  this.$schema('http://json-schema.org/draft-07/schema#')
136
137
  return this
137
138
  }
139
+
138
140
  $id($id: string): this {
139
141
  Object.assign(this.schema, { $id })
140
142
  return this
141
143
  }
144
+
142
145
  title(title: string): this {
143
146
  Object.assign(this.schema, { title })
144
147
  return this
145
148
  }
149
+
146
150
  description(description: string): this {
147
151
  Object.assign(this.schema, { description })
148
152
  return this
149
153
  }
154
+
150
155
  deprecated(deprecated = true): this {
151
156
  Object.assign(this.schema, { deprecated })
152
157
  return this
153
158
  }
159
+
154
160
  type(type: string): this {
155
161
  Object.assign(this.schema, { type })
156
162
  return this
157
163
  }
164
+
158
165
  default(v: any): this {
159
166
  Object.assign(this.schema, { default: v })
160
167
  return this
161
168
  }
169
+
162
170
  oneOf(schemas: JsonSchema[]): this {
163
171
  Object.assign(this.schema, { oneOf: schemas })
164
172
  return this
165
173
  }
174
+
166
175
  allOf(schemas: JsonSchema[]): this {
167
176
  Object.assign(this.schema, { allOf: schemas })
168
177
  return this
@@ -211,18 +220,22 @@ export class JsonSchemaNumberBuilder extends JsonSchemaAnyBuilder<number, JsonSc
211
220
  Object.assign(this.schema, { multipleOf })
212
221
  return this
213
222
  }
223
+
214
224
  min(minimum: number): this {
215
225
  Object.assign(this.schema, { minimum })
216
226
  return this
217
227
  }
228
+
218
229
  exclusiveMin(exclusiveMinimum: number): this {
219
230
  Object.assign(this.schema, { exclusiveMinimum })
220
231
  return this
221
232
  }
233
+
222
234
  max(maximum: number): this {
223
235
  Object.assign(this.schema, { maximum })
224
236
  return this
225
237
  }
238
+
226
239
  exclusiveMax(exclusiveMaximum: number): this {
227
240
  Object.assign(this.schema, { exclusiveMaximum })
228
241
  return this
@@ -264,14 +277,17 @@ export class JsonSchemaStringBuilder extends JsonSchemaAnyBuilder<string, JsonSc
264
277
  Object.assign(this.schema, { pattern })
265
278
  return this
266
279
  }
280
+
267
281
  min(minLength: number): this {
268
282
  Object.assign(this.schema, { minLength })
269
283
  return this
270
284
  }
285
+
271
286
  max(maxLength: number): this {
272
287
  Object.assign(this.schema, { maxLength })
273
288
  return this
274
289
  }
290
+
275
291
  length(minLength: number, maxLength: number): this {
276
292
  Object.assign(this.schema, { minLength, maxLength })
277
293
  return this
@@ -358,10 +374,12 @@ export class JsonSchemaObjectBuilder<T extends AnyObject> extends JsonSchemaAnyB
358
374
  Object.assign(this.schema, { minProperties })
359
375
  return this
360
376
  }
377
+
361
378
  maxProps(maxProperties: number): this {
362
379
  Object.assign(this.schema, { maxProperties })
363
380
  return this
364
381
  }
382
+
365
383
  additionalProps(additionalProperties: boolean): this {
366
384
  Object.assign(this.schema, { additionalProperties })
367
385
  return this
@@ -400,10 +418,12 @@ export class JsonSchemaArrayBuilder<ITEM> extends JsonSchemaAnyBuilder<
400
418
  Object.assign(this.schema, { minItems })
401
419
  return this
402
420
  }
421
+
403
422
  max(maxItems: number): this {
404
423
  Object.assign(this.schema, { maxItems })
405
424
  return this
406
425
  }
426
+
407
427
  unique(uniqueItems: number): this {
408
428
  Object.assign(this.schema, { uniqueItems })
409
429
  return this
package/src/nanoid.ts ADDED
@@ -0,0 +1,79 @@
1
+ // Vendored from https://github.com/ai/nanoid/blob/main/index.browser.js
2
+ // All credit to nanoid authors: https://github.com/ai/nanoid
3
+ // Reason for vendoring: (still) cannot import esm, and Nanoid went ESM-only since 4.0
4
+
5
+ /// <reference lib="dom" preserve="true" />
6
+
7
+ /* eslint-disable no-bitwise */
8
+
9
+ // "0-9a-zA-Z-_", same as base64url alphabet
10
+ const urlAlphabet = 'useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict'
11
+
12
+ /**
13
+ * Function that takes a length (defaults to 21) and generates a random string id of that length.
14
+ */
15
+ export type NanoidFunction = (length?: number) => string
16
+
17
+ type NanoidRandomFunction = (bytes: number) => Uint8Array
18
+
19
+ export function nanoidBrowser(length = 21): string {
20
+ let id = ''
21
+ const bytes = globalThis.crypto.getRandomValues(new Uint8Array(length))
22
+ while (length--) {
23
+ // Using the bitwise AND operator to "cap" the value of
24
+ // the random byte from 255 to 63, in that way we can make sure
25
+ // that the value will be a valid index for the "chars" string.
26
+ id += urlAlphabet[bytes[length]! & 63]
27
+ }
28
+ return id
29
+ }
30
+
31
+ const defaultRandomFunction: NanoidRandomFunction = (bytes: number) =>
32
+ globalThis.crypto.getRandomValues(new Uint8Array(bytes))
33
+
34
+ export function nanoidBrowserCustomAlphabet(alphabet: string, length = 21): NanoidFunction {
35
+ return customRandom(alphabet, length, defaultRandomFunction)
36
+ }
37
+
38
+ function customRandom(
39
+ alphabet: string,
40
+ defaultSize: number,
41
+ getRandom: NanoidRandomFunction,
42
+ ): NanoidFunction {
43
+ // First, a bitmask is necessary to generate the ID. The bitmask makes bytes
44
+ // values closer to the alphabet size. The bitmask calculates the closest
45
+ // `2^31 - 1` number, which exceeds the alphabet size.
46
+ // For example, the bitmask for the alphabet size 30 is 31 (00011111).
47
+ // `Math.clz32` is not used, because it is not available in browsers.
48
+ const mask = (2 << Math.log2(alphabet.length - 1)) - 1
49
+ // Though, the bitmask solution is not perfect since the bytes exceeding
50
+ // the alphabet size are refused. Therefore, to reliably generate the ID,
51
+ // the random bytes redundancy has to be satisfied.
52
+
53
+ // Note: every hardware random generator call is performance expensive,
54
+ // because the system call for entropy collection takes a lot of time.
55
+ // So, to avoid additional system calls, extra bytes are requested in advance.
56
+
57
+ // Next, a step determines how many random bytes to generate.
58
+ // The number of random bytes gets decided upon the ID size, mask,
59
+ // alphabet size, and magic number 1.6 (using 1.6 peaks at performance
60
+ // according to benchmarks).
61
+
62
+ // `-~f => Math.ceil(f)` if f is a float
63
+ // `-~i => i + 1` if i is an integer
64
+ const step = -~((1.6 * mask * defaultSize) / alphabet.length)
65
+
66
+ return (size = defaultSize) => {
67
+ let id = ''
68
+ while (true) {
69
+ const bytes = getRandom(step)
70
+ // A compact alternative for `for (var i = 0; i < step; i++)`.
71
+ let j = step
72
+ while (j--) {
73
+ // Adding `|| ''` refuses a random byte that exceeds the alphabet size.
74
+ id += alphabet[bytes[j]! & mask] || ''
75
+ if (id.length === size) return id
76
+ }
77
+ }
78
+ }
79
+ }
@@ -1,12 +1,17 @@
1
1
  /* eslint-disable no-bitwise */
2
2
 
3
+ /**
4
+ * Function that returns a random number between 0 and 1.
5
+ * Exactly same signature as Math.random function.
6
+ */
7
+ export type RandomFunction = () => number
8
+
3
9
  /**
4
10
  * Returns a "deterministic Math.random() function"
5
11
  *
6
12
  * Based on: https://gist.github.com/mathiasbynens/5670917
7
13
  */
8
- export function _createDeterministicRandom(): () => number {
9
- let seed = 0x2f6e2b1
14
+ export function _createDeterministicRandom(seed = 0x2f6e2b1): RandomFunction {
10
15
  return () => {
11
16
  // Robert Jenkins’ 32 bit integer hash function
12
17
  seed = (seed + 0x7ed55d16 + (seed << 12)) & 0xffffffff
package/src/semver.ts CHANGED
@@ -28,9 +28,11 @@ export class Semver {
28
28
  get major(): number {
29
29
  return this.tokens[0]
30
30
  }
31
+
31
32
  get minor(): number {
32
33
  return this.tokens[1]
33
34
  }
35
+
34
36
  get patch(): number {
35
37
  return this.tokens[2]
36
38
  }
@@ -11,7 +11,7 @@ const BASE64URL = BASE62 + '-_'
11
11
  *
12
12
  * 1. Performance
13
13
  * 2. For non-cryptographic use (where accidental collision is not the end-of-the-world)
14
- * 3. Compact size (32 bits max, versus 128 in md5; presented in less string json-safe characters)
14
+ * 3. Compact size (32 bits max, versus 128 in md5; presented in smaller number of string json-safe characters)
15
15
  *
16
16
  * Basically, these functions are as simple as they can be, but still "random enough" for
17
17
  * normal non-cryptographic use cases.
package/src/web.ts CHANGED
@@ -7,6 +7,12 @@ import { StringMap } from './types'
7
7
  * Implements WebStorage API by using in-memory storage.
8
8
  * Can be useful in SSR environment or unit tests.
9
9
  *
10
+ * This is how localStorage can be mocked in Node:
11
+ *
12
+ * Object.assign(globalThis, {
13
+ * localStorage: new InMemoryWebStorage(),
14
+ * })
15
+ *
10
16
  * @experimental
11
17
  */
12
18
  export class InMemoryWebStorage implements Storage {
@@ -1,4 +1,4 @@
1
- import { ZodError, ZodIssue, ZodSchema } from 'zod'
1
+ import { ZodError, type ZodIssue, type ZodSchema } from 'zod'
2
2
  import { _stringify } from '../string/stringify'
3
3
 
4
4
  export interface ZodErrorResult<T> {