@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.
- package/cfg/frontend/tsconfig.json +67 -0
- package/dist/browser/adminService.d.ts +69 -0
- package/dist/browser/adminService.js +98 -0
- package/dist/browser/analytics.util.d.ts +12 -0
- package/dist/browser/analytics.util.js +59 -0
- package/dist/browser/i18n/fetchTranslationLoader.d.ts +13 -0
- package/dist/browser/i18n/fetchTranslationLoader.js +17 -0
- package/dist/browser/i18n/translation.service.d.ts +53 -0
- package/dist/browser/i18n/translation.service.js +61 -0
- package/dist/browser/imageFitter.d.ts +60 -0
- package/dist/browser/imageFitter.js +69 -0
- package/dist/browser/script.util.d.ts +14 -0
- package/dist/browser/script.util.js +50 -0
- package/dist/browser/topbar.d.ts +23 -0
- package/dist/browser/topbar.js +137 -0
- package/dist/decorators/memo.util.d.ts +2 -1
- package/dist/decorators/memo.util.js +8 -6
- package/dist/decorators/swarmSafe.decorator.d.ts +9 -0
- package/dist/decorators/swarmSafe.decorator.js +42 -0
- package/dist/deviceIdService.d.ts +65 -0
- package/dist/deviceIdService.js +109 -0
- package/dist/error/assert.d.ts +2 -1
- package/dist/error/assert.js +15 -13
- package/dist/error/error.util.js +9 -6
- package/dist/index.d.ts +9 -0
- package/dist/index.js +9 -0
- package/dist/nanoid.d.ts +7 -0
- package/dist/nanoid.js +61 -0
- package/dist/number/createDeterministicRandom.d.ts +6 -1
- package/dist/number/createDeterministicRandom.js +1 -2
- package/dist/string/hash.util.d.ts +1 -1
- package/dist/string/hash.util.js +1 -1
- package/dist/web.d.ts +6 -0
- package/dist/web.js +6 -0
- package/dist/zod/zod.util.d.ts +1 -1
- package/dist-esm/browser/adminService.js +94 -0
- package/dist-esm/browser/analytics.util.js +54 -0
- package/dist-esm/browser/i18n/fetchTranslationLoader.js +13 -0
- package/dist-esm/browser/i18n/translation.service.js +56 -0
- package/dist-esm/browser/imageFitter.js +65 -0
- package/dist-esm/browser/script.util.js +46 -0
- package/dist-esm/browser/topbar.js +134 -0
- package/dist-esm/decorators/memo.util.js +3 -1
- package/dist-esm/decorators/swarmSafe.decorator.js +38 -0
- package/dist-esm/deviceIdService.js +105 -0
- package/dist-esm/error/assert.js +3 -1
- package/dist-esm/error/error.util.js +4 -1
- package/dist-esm/index.js +9 -0
- package/dist-esm/nanoid.js +57 -0
- package/dist-esm/number/createDeterministicRandom.js +1 -2
- package/dist-esm/string/hash.util.js +1 -1
- package/dist-esm/web.js +6 -0
- package/package.json +2 -1
- package/src/browser/adminService.ts +157 -0
- package/src/browser/analytics.util.ts +68 -0
- package/src/browser/i18n/fetchTranslationLoader.ts +16 -0
- package/src/browser/i18n/translation.service.ts +102 -0
- package/src/browser/imageFitter.ts +128 -0
- package/src/browser/script.util.ts +52 -0
- package/src/browser/topbar.ts +147 -0
- package/src/datetime/localDate.ts +16 -0
- package/src/datetime/localTime.ts +39 -0
- package/src/decorators/debounce.ts +1 -0
- package/src/decorators/memo.util.ts +4 -1
- package/src/decorators/swarmSafe.decorator.ts +47 -0
- package/src/deviceIdService.ts +137 -0
- package/src/error/assert.ts +5 -11
- package/src/error/error.util.ts +4 -1
- package/src/index.ts +9 -0
- package/src/json-schema/jsonSchemaBuilder.ts +20 -0
- package/src/nanoid.ts +79 -0
- package/src/number/createDeterministicRandom.ts +7 -2
- package/src/semver.ts +2 -0
- package/src/string/hash.util.ts +1 -1
- package/src/web.ts +6 -0
- package/src/zod/zod.util.ts +1 -1
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
export interface FitImagesCfg {
|
|
2
|
+
/**
|
|
3
|
+
* Container of the images
|
|
4
|
+
*/
|
|
5
|
+
containerElement: HTMLElement
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Array of image metadatas (most notably: aspectRatio).
|
|
9
|
+
*/
|
|
10
|
+
images: FitImage[]
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Will be called on each layout change.
|
|
14
|
+
* Should be listened to to update the width/height of the images in your DOM.
|
|
15
|
+
*/
|
|
16
|
+
onChange: (images: FitImage[]) => any
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Max image height in pixels.
|
|
20
|
+
*
|
|
21
|
+
* @default 300
|
|
22
|
+
*/
|
|
23
|
+
maxHeight?: number
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Margin between images.
|
|
27
|
+
*
|
|
28
|
+
* @default 8
|
|
29
|
+
*/
|
|
30
|
+
margin?: number
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface FitImage {
|
|
34
|
+
src: string
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* width divided by height
|
|
38
|
+
*/
|
|
39
|
+
aspectRatio: number
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Calculated image width to fit the layout.
|
|
43
|
+
*/
|
|
44
|
+
fitWidth?: number
|
|
45
|
+
/**
|
|
46
|
+
* Calculated image height to fit the layout.
|
|
47
|
+
*/
|
|
48
|
+
fitHeight?: number
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Calculates the width/height of the images to fit in the layout.
|
|
53
|
+
*
|
|
54
|
+
* Currently does not mutate the cfg.images array, but DOES mutate individual images with .fitWidth, .fitHeight properties.
|
|
55
|
+
*
|
|
56
|
+
* @experimental
|
|
57
|
+
*/
|
|
58
|
+
export class ImageFitter {
|
|
59
|
+
constructor(cfg: FitImagesCfg) {
|
|
60
|
+
this.cfg = {
|
|
61
|
+
maxHeight: 300,
|
|
62
|
+
margin: 8,
|
|
63
|
+
...cfg,
|
|
64
|
+
}
|
|
65
|
+
this.resizeObserver = new ResizeObserver(entries => this.update(entries))
|
|
66
|
+
this.resizeObserver.observe(cfg.containerElement)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
cfg!: Required<FitImagesCfg>
|
|
70
|
+
resizeObserver: ResizeObserver
|
|
71
|
+
containerWidth = -1
|
|
72
|
+
|
|
73
|
+
stop(): void {
|
|
74
|
+
this.resizeObserver.disconnect()
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
private update(entries: ResizeObserverEntry[]): void {
|
|
78
|
+
const width = Math.floor(entries[0]!.contentRect.width)
|
|
79
|
+
if (width === this.containerWidth) return // we're only interested in width changes
|
|
80
|
+
this.containerWidth = width
|
|
81
|
+
|
|
82
|
+
console.log(`resize ${width}`)
|
|
83
|
+
this.doLayout(this.cfg.images)
|
|
84
|
+
this.cfg.onChange(this.cfg.images)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
private doLayout(imgs: readonly FitImage[]): void {
|
|
88
|
+
if (imgs.length === 0) return // nothing to do
|
|
89
|
+
const { maxHeight } = this.cfg
|
|
90
|
+
|
|
91
|
+
let imgNodes = imgs.slice(0)
|
|
92
|
+
|
|
93
|
+
w: while (imgNodes.length > 0) {
|
|
94
|
+
let slice: FitImage[]
|
|
95
|
+
let h: number
|
|
96
|
+
|
|
97
|
+
for (let i = 1; i <= imgNodes.length; i++) {
|
|
98
|
+
slice = imgNodes.slice(0, i)
|
|
99
|
+
h = this.getHeigth(slice)
|
|
100
|
+
|
|
101
|
+
if (h < maxHeight) {
|
|
102
|
+
this.setHeight(slice, h)
|
|
103
|
+
imgNodes = imgNodes.slice(i)
|
|
104
|
+
continue w
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
this.setHeight(slice!, Math.min(maxHeight, h!))
|
|
109
|
+
break
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
private getHeigth(images: readonly FitImage[]): number {
|
|
114
|
+
const width = this.containerWidth - images.length * this.cfg.margin
|
|
115
|
+
let r = 0
|
|
116
|
+
images.forEach(img => (r += img.aspectRatio))
|
|
117
|
+
|
|
118
|
+
return width / r // have to round down because Firefox will automatically roundup value with number of decimals > 3
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// mutates/sets images' fitWidth, fitHeight properties
|
|
122
|
+
private setHeight(images: readonly FitImage[], height: number): void {
|
|
123
|
+
images.forEach(img => {
|
|
124
|
+
img.fitWidth = Math.floor(height * img.aspectRatio)
|
|
125
|
+
img.fitHeight = Math.floor(height)
|
|
126
|
+
})
|
|
127
|
+
}
|
|
128
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { isServerSide } from '../env'
|
|
2
|
+
import { _objectAssign } from '../types'
|
|
3
|
+
|
|
4
|
+
export type LoadScriptOptions = Partial<HTMLScriptElement>
|
|
5
|
+
export type LoadCSSOptions = Partial<HTMLLinkElement>
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* opt.async defaults to `true`.
|
|
9
|
+
* No other options are set by default.
|
|
10
|
+
*/
|
|
11
|
+
export async function loadScript(src: string, opt?: LoadScriptOptions): Promise<void> {
|
|
12
|
+
if (isServerSide()) return
|
|
13
|
+
|
|
14
|
+
return await new Promise<void>((resolve, reject) => {
|
|
15
|
+
const s = _objectAssign(document.createElement('script'), {
|
|
16
|
+
src,
|
|
17
|
+
async: true,
|
|
18
|
+
...opt,
|
|
19
|
+
onload: resolve as any,
|
|
20
|
+
onerror: (_event, _source, _lineno, _colno, err) => {
|
|
21
|
+
reject(err || new Error(`loadScript failed: ${src}`))
|
|
22
|
+
},
|
|
23
|
+
})
|
|
24
|
+
document.head.append(s)
|
|
25
|
+
})
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Default options:
|
|
30
|
+
* rel: 'stylesheet'
|
|
31
|
+
*
|
|
32
|
+
* No other options are set by default.
|
|
33
|
+
*/
|
|
34
|
+
export async function loadCSS(href: string, opt?: LoadCSSOptions): Promise<void> {
|
|
35
|
+
if (isServerSide()) return
|
|
36
|
+
|
|
37
|
+
return await new Promise<void>((resolve, reject) => {
|
|
38
|
+
const link = _objectAssign(document.createElement('link'), {
|
|
39
|
+
href,
|
|
40
|
+
rel: 'stylesheet',
|
|
41
|
+
// type seems to be unnecessary: https://stackoverflow.com/a/5409146/4919972
|
|
42
|
+
// type: 'text/css',
|
|
43
|
+
...opt,
|
|
44
|
+
onload: resolve as any,
|
|
45
|
+
onerror: (_event, _source, _lineno, _colno, err) => {
|
|
46
|
+
reject(err || new Error(`loadCSS failed: ${href}`))
|
|
47
|
+
},
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
document.head.append(link)
|
|
51
|
+
})
|
|
52
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
// Modified version of topbar:
|
|
2
|
+
// http://buunguyen.github.io/topbar
|
|
3
|
+
/* eslint-disable */
|
|
4
|
+
|
|
5
|
+
export interface TopBarOptions {
|
|
6
|
+
/**
|
|
7
|
+
* @default true
|
|
8
|
+
*/
|
|
9
|
+
autoRun?: boolean
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @default 5
|
|
13
|
+
*/
|
|
14
|
+
barThickness?: number
|
|
15
|
+
|
|
16
|
+
barColors?: any
|
|
17
|
+
shadowColor?: any
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @default 10
|
|
21
|
+
*/
|
|
22
|
+
shadowBlur?: number
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const browser = typeof window !== 'undefined'
|
|
26
|
+
|
|
27
|
+
let canvas: any
|
|
28
|
+
let progressTimerId: any
|
|
29
|
+
let fadeTimerId: any
|
|
30
|
+
let currentProgress: any
|
|
31
|
+
let showing: any
|
|
32
|
+
const addEvent = (elem: any, type: any, handler: any) => {
|
|
33
|
+
if (elem.addEventListener) elem.addEventListener(type, handler, false)
|
|
34
|
+
else if (elem.attachEvent) elem.attachEvent('on' + type, handler)
|
|
35
|
+
else elem['on' + type] = handler
|
|
36
|
+
}
|
|
37
|
+
const options = {
|
|
38
|
+
autoRun: true,
|
|
39
|
+
barThickness: 5,
|
|
40
|
+
barColors: {
|
|
41
|
+
'0': 'rgba(26, 188, 156, .9)',
|
|
42
|
+
'.25': 'rgba(52, 152, 219, .9)',
|
|
43
|
+
'.50': 'rgba(241, 196, 15, .9)',
|
|
44
|
+
'.75': 'rgba(230, 126, 34, .9)',
|
|
45
|
+
'1.0': 'rgba(211, 84, 0, .9)',
|
|
46
|
+
},
|
|
47
|
+
shadowBlur: 10,
|
|
48
|
+
shadowColor: 'rgba(0, 0, 0, .6)',
|
|
49
|
+
}
|
|
50
|
+
const repaint = () => {
|
|
51
|
+
canvas.width = window.innerWidth
|
|
52
|
+
canvas.height = options.barThickness * 5 // need space for shadow
|
|
53
|
+
|
|
54
|
+
const ctx = canvas.getContext('2d')
|
|
55
|
+
ctx.shadowBlur = options.shadowBlur
|
|
56
|
+
ctx.shadowColor = options.shadowColor
|
|
57
|
+
|
|
58
|
+
const lineGradient = ctx.createLinearGradient(0, 0, canvas.width, 0)
|
|
59
|
+
for (const stop in options.barColors) {
|
|
60
|
+
// @ts-ignore
|
|
61
|
+
lineGradient.addColorStop(stop, options.barColors[stop])
|
|
62
|
+
}
|
|
63
|
+
ctx.lineWidth = options.barThickness
|
|
64
|
+
ctx.beginPath()
|
|
65
|
+
ctx.moveTo(0, options.barThickness / 2)
|
|
66
|
+
ctx.lineTo(Math.ceil(currentProgress * canvas.width), options.barThickness / 2)
|
|
67
|
+
ctx.strokeStyle = lineGradient
|
|
68
|
+
ctx.stroke()
|
|
69
|
+
}
|
|
70
|
+
const createCanvas = () => {
|
|
71
|
+
canvas = document.createElement('canvas')
|
|
72
|
+
const style = canvas.style
|
|
73
|
+
style.position = 'fixed'
|
|
74
|
+
style.top = style.left = style.right = style.margin = style.padding = 0
|
|
75
|
+
style.zIndex = 100001
|
|
76
|
+
style.display = 'none'
|
|
77
|
+
document.body.appendChild(canvas)
|
|
78
|
+
addEvent(window, 'resize', repaint)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export const topbar = {
|
|
82
|
+
config(opts: TopBarOptions) {
|
|
83
|
+
for (const key in opts) {
|
|
84
|
+
if (options.hasOwnProperty(key)) {
|
|
85
|
+
// @ts-ignore
|
|
86
|
+
options[key] = opts[key]
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
set(show: boolean, opts?: TopBarOptions) {
|
|
91
|
+
if (show) {
|
|
92
|
+
topbar.show(opts)
|
|
93
|
+
} else {
|
|
94
|
+
topbar.hide()
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
show(opts?: TopBarOptions) {
|
|
98
|
+
if (!browser) return // ssr protection
|
|
99
|
+
if (opts) topbar.config(opts)
|
|
100
|
+
if (showing) return
|
|
101
|
+
showing = true
|
|
102
|
+
if (fadeTimerId !== null) {
|
|
103
|
+
window.cancelAnimationFrame(fadeTimerId)
|
|
104
|
+
}
|
|
105
|
+
if (!canvas) createCanvas()
|
|
106
|
+
canvas.style.opacity = 1
|
|
107
|
+
canvas.style.display = 'block'
|
|
108
|
+
topbar.progress(0)
|
|
109
|
+
if (options.autoRun) {
|
|
110
|
+
;(function loop() {
|
|
111
|
+
progressTimerId = window.requestAnimationFrame(loop)
|
|
112
|
+
topbar.progress('+' + 0.05 * (1 - Math.sqrt(currentProgress)) ** 2)
|
|
113
|
+
})()
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
progress(to: number | string) {
|
|
117
|
+
if (!browser) return // ssr protection
|
|
118
|
+
if (typeof to === 'undefined') {
|
|
119
|
+
return currentProgress
|
|
120
|
+
}
|
|
121
|
+
if (typeof to === 'string') {
|
|
122
|
+
to = (to.indexOf('+') >= 0 || to.indexOf('-') >= 0 ? currentProgress : 0) + parseFloat(to)
|
|
123
|
+
}
|
|
124
|
+
currentProgress = (to as number) > 1 ? 1 : to
|
|
125
|
+
repaint()
|
|
126
|
+
return currentProgress
|
|
127
|
+
},
|
|
128
|
+
hide() {
|
|
129
|
+
if (!showing || !browser) return
|
|
130
|
+
showing = false
|
|
131
|
+
if (progressTimerId != null) {
|
|
132
|
+
window.cancelAnimationFrame(progressTimerId)
|
|
133
|
+
progressTimerId = null
|
|
134
|
+
}
|
|
135
|
+
;(function loop() {
|
|
136
|
+
if (topbar.progress('+.1') >= 1) {
|
|
137
|
+
canvas.style.opacity -= 0.05
|
|
138
|
+
if (canvas.style.opacity <= 0.05) {
|
|
139
|
+
canvas.style.display = 'none'
|
|
140
|
+
fadeTimerId = null
|
|
141
|
+
return
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
fadeTimerId = window.requestAnimationFrame(loop)
|
|
145
|
+
})()
|
|
146
|
+
},
|
|
147
|
+
}
|
|
@@ -57,9 +57,11 @@ export class LocalDate {
|
|
|
57
57
|
setYear(v: number): LocalDate {
|
|
58
58
|
return this.set('year', v)
|
|
59
59
|
}
|
|
60
|
+
|
|
60
61
|
setMonth(v: number): LocalDate {
|
|
61
62
|
return this.set('month', v)
|
|
62
63
|
}
|
|
64
|
+
|
|
63
65
|
setDay(v: number): LocalDate {
|
|
64
66
|
return this.set('day', v)
|
|
65
67
|
}
|
|
@@ -143,15 +145,19 @@ export class LocalDate {
|
|
|
143
145
|
isToday(): boolean {
|
|
144
146
|
return this.isSame(localDate.today())
|
|
145
147
|
}
|
|
148
|
+
|
|
146
149
|
isAfterToday(): boolean {
|
|
147
150
|
return this.isAfter(localDate.today())
|
|
148
151
|
}
|
|
152
|
+
|
|
149
153
|
isSameOrAfterToday(): boolean {
|
|
150
154
|
return this.isSameOrAfter(localDate.today())
|
|
151
155
|
}
|
|
156
|
+
|
|
152
157
|
isBeforeToday(): boolean {
|
|
153
158
|
return this.isBefore(localDate.today())
|
|
154
159
|
}
|
|
160
|
+
|
|
155
161
|
isSameOrBeforeToday(): boolean {
|
|
156
162
|
return this.isSameOrBefore(localDate.today())
|
|
157
163
|
}
|
|
@@ -159,12 +165,15 @@ export class LocalDate {
|
|
|
159
165
|
getAgeInYears(today?: LocalDateInput): number {
|
|
160
166
|
return this.getAgeIn('year', today)
|
|
161
167
|
}
|
|
168
|
+
|
|
162
169
|
getAgeInMonths(today?: LocalDateInput): number {
|
|
163
170
|
return this.getAgeIn('month', today)
|
|
164
171
|
}
|
|
172
|
+
|
|
165
173
|
getAgeInDays(today?: LocalDateInput): number {
|
|
166
174
|
return this.getAgeIn('day', today)
|
|
167
175
|
}
|
|
176
|
+
|
|
168
177
|
getAgeIn(unit: LocalDateUnit, today?: LocalDateInput): number {
|
|
169
178
|
return localDate.fromInput(today || new Date()).diff(this, unit)
|
|
170
179
|
}
|
|
@@ -264,24 +273,31 @@ export class LocalDate {
|
|
|
264
273
|
plusDays(num: number): LocalDate {
|
|
265
274
|
return this.plus(num, 'day')
|
|
266
275
|
}
|
|
276
|
+
|
|
267
277
|
plusWeeks(num: number): LocalDate {
|
|
268
278
|
return this.plus(num, 'week')
|
|
269
279
|
}
|
|
280
|
+
|
|
270
281
|
plusMonths(num: number): LocalDate {
|
|
271
282
|
return this.plus(num, 'month')
|
|
272
283
|
}
|
|
284
|
+
|
|
273
285
|
plusYears(num: number): LocalDate {
|
|
274
286
|
return this.plus(num, 'year')
|
|
275
287
|
}
|
|
288
|
+
|
|
276
289
|
minusDays(num: number): LocalDate {
|
|
277
290
|
return this.plus(-num, 'day')
|
|
278
291
|
}
|
|
292
|
+
|
|
279
293
|
minusWeeks(num: number): LocalDate {
|
|
280
294
|
return this.plus(-num, 'week')
|
|
281
295
|
}
|
|
296
|
+
|
|
282
297
|
minusMonths(num: number): LocalDate {
|
|
283
298
|
return this.plus(-num, 'month')
|
|
284
299
|
}
|
|
300
|
+
|
|
285
301
|
minusYears(num: number): LocalDate {
|
|
286
302
|
return this.plus(-num, 'year')
|
|
287
303
|
}
|
|
@@ -212,42 +212,55 @@ export class LocalTime {
|
|
|
212
212
|
get year(): number {
|
|
213
213
|
return this.$date.getFullYear()
|
|
214
214
|
}
|
|
215
|
+
|
|
215
216
|
setYear(v: number): LocalTime {
|
|
216
217
|
return this.set('year', v)
|
|
217
218
|
}
|
|
219
|
+
|
|
218
220
|
get month(): number {
|
|
219
221
|
return this.$date.getMonth() + 1
|
|
220
222
|
}
|
|
223
|
+
|
|
221
224
|
setMonth(v: number): LocalTime {
|
|
222
225
|
return this.set('month', v)
|
|
223
226
|
}
|
|
227
|
+
|
|
224
228
|
get week(): number {
|
|
225
229
|
return getWeek(this.$date)
|
|
226
230
|
}
|
|
231
|
+
|
|
227
232
|
setWeek(v: number): LocalTime {
|
|
228
233
|
return this.set('week', v)
|
|
229
234
|
}
|
|
235
|
+
|
|
230
236
|
get day(): number {
|
|
231
237
|
return this.$date.getDate()
|
|
232
238
|
}
|
|
239
|
+
|
|
233
240
|
setDay(v: number): LocalTime {
|
|
234
241
|
return this.set('day', v)
|
|
235
242
|
}
|
|
243
|
+
|
|
236
244
|
get hour(): number {
|
|
237
245
|
return this.$date.getHours()
|
|
238
246
|
}
|
|
247
|
+
|
|
239
248
|
setHour(v: number): LocalTime {
|
|
240
249
|
return this.set('hour', v)
|
|
241
250
|
}
|
|
251
|
+
|
|
242
252
|
get minute(): number {
|
|
243
253
|
return this.$date.getMinutes()
|
|
244
254
|
}
|
|
255
|
+
|
|
245
256
|
setMinute(v: number): LocalTime {
|
|
246
257
|
return this.set('minute', v)
|
|
247
258
|
}
|
|
259
|
+
|
|
248
260
|
get second(): number {
|
|
249
261
|
return this.$date.getSeconds()
|
|
250
262
|
}
|
|
263
|
+
|
|
251
264
|
setSecond(v: number): LocalTime {
|
|
252
265
|
return this.set('second', v)
|
|
253
266
|
}
|
|
@@ -258,6 +271,7 @@ export class LocalTime {
|
|
|
258
271
|
get dayOfWeek(): ISODayOfWeek {
|
|
259
272
|
return (this.$date.getDay() || 7) as ISODayOfWeek
|
|
260
273
|
}
|
|
274
|
+
|
|
261
275
|
setDayOfWeek(v: ISODayOfWeek): LocalTime {
|
|
262
276
|
_assert(VALID_DAYS_OF_WEEK.has(v), `Invalid dayOfWeek: ${v}`)
|
|
263
277
|
const dow = this.$date.getDay() || 7
|
|
@@ -292,42 +306,55 @@ export class LocalTime {
|
|
|
292
306
|
plusSeconds(num: number): LocalTime {
|
|
293
307
|
return this.plus(num, 'second')
|
|
294
308
|
}
|
|
309
|
+
|
|
295
310
|
plusMinutes(num: number): LocalTime {
|
|
296
311
|
return this.plus(num, 'minute')
|
|
297
312
|
}
|
|
313
|
+
|
|
298
314
|
plusHours(num: number): LocalTime {
|
|
299
315
|
return this.plus(num, 'hour')
|
|
300
316
|
}
|
|
317
|
+
|
|
301
318
|
plusDays(num: number): LocalTime {
|
|
302
319
|
return this.plus(num, 'day')
|
|
303
320
|
}
|
|
321
|
+
|
|
304
322
|
plusWeeks(num: number): LocalTime {
|
|
305
323
|
return this.plus(num, 'week')
|
|
306
324
|
}
|
|
325
|
+
|
|
307
326
|
plusMonths(num: number): LocalTime {
|
|
308
327
|
return this.plus(num, 'month')
|
|
309
328
|
}
|
|
329
|
+
|
|
310
330
|
plusYears(num: number): LocalTime {
|
|
311
331
|
return this.plus(num, 'year')
|
|
312
332
|
}
|
|
333
|
+
|
|
313
334
|
minusSeconds(num: number): LocalTime {
|
|
314
335
|
return this.plus(-num, 'second')
|
|
315
336
|
}
|
|
337
|
+
|
|
316
338
|
minusMinutes(num: number): LocalTime {
|
|
317
339
|
return this.plus(-num, 'minute')
|
|
318
340
|
}
|
|
341
|
+
|
|
319
342
|
minusHours(num: number): LocalTime {
|
|
320
343
|
return this.plus(-num, 'hour')
|
|
321
344
|
}
|
|
345
|
+
|
|
322
346
|
minusDays(num: number): LocalTime {
|
|
323
347
|
return this.plus(-num, 'day')
|
|
324
348
|
}
|
|
349
|
+
|
|
325
350
|
minusWeeks(num: number): LocalTime {
|
|
326
351
|
return this.plus(-num, 'week')
|
|
327
352
|
}
|
|
353
|
+
|
|
328
354
|
minusMonths(num: number): LocalTime {
|
|
329
355
|
return this.plus(-num, 'month')
|
|
330
356
|
}
|
|
357
|
+
|
|
331
358
|
minusYears(num: number): LocalTime {
|
|
332
359
|
return this.plus(-num, 'year')
|
|
333
360
|
}
|
|
@@ -452,20 +479,25 @@ export class LocalTime {
|
|
|
452
479
|
isSame(d: LocalTimeInput): boolean {
|
|
453
480
|
return this.compare(d) === 0
|
|
454
481
|
}
|
|
482
|
+
|
|
455
483
|
isBefore(d: LocalTimeInput, inclusive = false): boolean {
|
|
456
484
|
const r = this.compare(d)
|
|
457
485
|
return r === -1 || (r === 0 && inclusive)
|
|
458
486
|
}
|
|
487
|
+
|
|
459
488
|
isSameOrBefore(d: LocalTimeInput): boolean {
|
|
460
489
|
return this.compare(d) <= 0
|
|
461
490
|
}
|
|
491
|
+
|
|
462
492
|
isAfter(d: LocalTimeInput, inclusive = false): boolean {
|
|
463
493
|
const r = this.compare(d)
|
|
464
494
|
return r === 1 || (r === 0 && inclusive)
|
|
465
495
|
}
|
|
496
|
+
|
|
466
497
|
isSameOrAfter(d: LocalTimeInput): boolean {
|
|
467
498
|
return this.compare(d) >= 0
|
|
468
499
|
}
|
|
500
|
+
|
|
469
501
|
isBetween(min: LocalTimeInput, max: LocalTimeInput, incl: Inclusiveness = '[)'): boolean {
|
|
470
502
|
let r = this.compare(min)
|
|
471
503
|
// eslint-disable-next-line @typescript-eslint/prefer-string-starts-ends-with
|
|
@@ -518,21 +550,27 @@ export class LocalTime {
|
|
|
518
550
|
getAgeInYears(now?: LocalTimeInput): number {
|
|
519
551
|
return this.getAgeIn('year', now)
|
|
520
552
|
}
|
|
553
|
+
|
|
521
554
|
getAgeInMonths(now?: LocalTimeInput): number {
|
|
522
555
|
return this.getAgeIn('month', now)
|
|
523
556
|
}
|
|
557
|
+
|
|
524
558
|
getAgeInDays(now?: LocalTimeInput): number {
|
|
525
559
|
return this.getAgeIn('day', now)
|
|
526
560
|
}
|
|
561
|
+
|
|
527
562
|
getAgeInHours(now?: LocalTimeInput): number {
|
|
528
563
|
return this.getAgeIn('hour', now)
|
|
529
564
|
}
|
|
565
|
+
|
|
530
566
|
getAgeInMinutes(now?: LocalTimeInput): number {
|
|
531
567
|
return this.getAgeIn('minute', now)
|
|
532
568
|
}
|
|
569
|
+
|
|
533
570
|
getAgeInSeconds(now?: LocalTimeInput): number {
|
|
534
571
|
return this.getAgeIn('second', now)
|
|
535
572
|
}
|
|
573
|
+
|
|
536
574
|
getAgeIn(unit: LocalTimeUnit, now?: LocalTimeInput): number {
|
|
537
575
|
return localTime.fromInput(now ?? new Date()).diff(this, unit)
|
|
538
576
|
}
|
|
@@ -540,6 +578,7 @@ export class LocalTime {
|
|
|
540
578
|
isAfterNow(): boolean {
|
|
541
579
|
return this.$date.valueOf() > Date.now()
|
|
542
580
|
}
|
|
581
|
+
|
|
543
582
|
isBeforeNow(): boolean {
|
|
544
583
|
return this.$date.valueOf() < Date.now()
|
|
545
584
|
}
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import { _isPrimitive
|
|
1
|
+
import { _isPrimitive } from '../is.util'
|
|
2
|
+
import { pDelay } from '../promise/pDelay'
|
|
3
|
+
import type { UnixTimestampNumber } from '../types'
|
|
4
|
+
import { MISS } from '../types'
|
|
2
5
|
|
|
3
6
|
export type MemoSerializer = (args: any[]) => any
|
|
4
7
|
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { AnyObject } from '../types'
|
|
2
|
+
import { _getTargetMethodSignature } from './decorator.util'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Prevents "swarm" of async calls to the same method.
|
|
6
|
+
* Allows max 1 in-flight promise to exist.
|
|
7
|
+
* If more calls appear, while Promise is not resolved yet - same Promise is returned.
|
|
8
|
+
*
|
|
9
|
+
* Does not support `cacheKey`.
|
|
10
|
+
* So, the same Promise is returned, regardless of the arguments.
|
|
11
|
+
*/
|
|
12
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
13
|
+
export const _SwarmSafe = (): MethodDecorator => (target, key, descriptor) => {
|
|
14
|
+
if (typeof descriptor.value !== 'function') {
|
|
15
|
+
throw new TypeError('@_SwarmSafe can be applied only to methods')
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const originalFn = descriptor.value
|
|
19
|
+
const keyStr = String(key)
|
|
20
|
+
const methodSignature = _getTargetMethodSignature(target, keyStr)
|
|
21
|
+
const instanceCache = new Map<AnyObject, Promise<any>>()
|
|
22
|
+
|
|
23
|
+
console.log('SwarmSafe constructor called', { key, methodSignature })
|
|
24
|
+
|
|
25
|
+
// eslint-disable-next-line @typescript-eslint/promise-function-async
|
|
26
|
+
descriptor.value = function (this: typeof target, ...args: any[]): Promise<any> {
|
|
27
|
+
console.log('SwarmSafe method called', { key, methodSignature, args })
|
|
28
|
+
const ctx = this
|
|
29
|
+
|
|
30
|
+
let inFlightPromise = instanceCache.get(ctx)
|
|
31
|
+
if (inFlightPromise) {
|
|
32
|
+
console.log(`SwarmSafe: returning in-flight promise`)
|
|
33
|
+
return inFlightPromise
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
console.log(`SwarmSafe: first-time call, creating in-flight promise`)
|
|
37
|
+
|
|
38
|
+
inFlightPromise = originalFn.apply(ctx, args) as Promise<any>
|
|
39
|
+
instanceCache.set(ctx, inFlightPromise)
|
|
40
|
+
void inFlightPromise.finally(() => {
|
|
41
|
+
console.log(`SwarmSafe: in-flight promise resolved`)
|
|
42
|
+
instanceCache.delete(ctx)
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
return inFlightPromise
|
|
46
|
+
} as any
|
|
47
|
+
}
|