@xylabs/object 3.1.7 → 3.1.9

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/src/Base.ts CHANGED
@@ -1,8 +1,17 @@
1
+ import { assertEx } from '@xylabs/assert'
1
2
  import { Logger } from '@xylabs/logger'
2
3
 
3
4
  import { EmptyObject } from './EmptyObject'
4
5
  import { globallyUnique } from './globallyUnique'
5
6
 
7
+ const DEFAULT_HISTORY_INTERVAL = 1000 * 5
8
+ const DEFAULT_HISTORY_TIME = 60 * 60 * 1000
9
+ const MAX_GC_FREQUENCY = 1000 * 60
10
+ const MIN_GC_FREQUENCY = 1000
11
+ const MIN_HISTORY_INTERVAL = 1000
12
+
13
+ export type BaseClassName = Exclude<string, 'base-class-name-reserved-32546239486'>
14
+
6
15
  export type BaseParamsFields = {
7
16
  logger?: Logger
8
17
  }
@@ -10,22 +19,133 @@ export type BaseParamsFields = {
10
19
  export type BaseParams<TAdditionalParams extends EmptyObject | void = void> =
11
20
  TAdditionalParams extends EmptyObject ? BaseParamsFields & TAdditionalParams : BaseParamsFields
12
21
 
13
- export abstract class Base<TParams extends BaseParams | undefined = BaseParams> {
22
+ export abstract class Base<TParams extends BaseParams | void = void> {
14
23
  static defaultLogger?: Logger
24
+ static readonly globalInstances: Record<BaseClassName, WeakRef<Base>[]> = {}
25
+ static readonly globalInstancesCountHistory: Record<BaseClassName, number[]> = {}
15
26
  static readonly uniqueName = globallyUnique(this.name, this, 'xyo')
27
+ private static _historyInterval = DEFAULT_HISTORY_INTERVAL
28
+ private static _historyTime = DEFAULT_HISTORY_TIME
29
+ private static _historyTimeout?: ReturnType<typeof setTimeout>
30
+ private static _lastGC = 0
31
+ private static _maxGcFrequency = MAX_GC_FREQUENCY
32
+ private _params: TParams extends BaseParams ? TParams : BaseParams
16
33
 
17
- private _params: TParams
18
-
19
- constructor(params: TParams) {
20
- this._params = params
34
+ constructor(params?: TParams extends BaseParams ? TParams : BaseParams) {
35
+ this._params = params ?? ({} as TParams extends BaseParams ? TParams : BaseParams)
21
36
  params?.logger?.debug(`Base constructed [${Object(this).name}]`)
37
+ this.recordInstance()
38
+ }
39
+
40
+ static get historyInterval() {
41
+ return this._historyInterval
42
+ }
43
+
44
+ static set historyInterval(value: number) {
45
+ assertEx(value <= this.historyTime, () => `historyInterval [${value}] must be less than or equal to historyTime [${this.historyTime}]`)
46
+ this._historyInterval = Math.max(value, MIN_HISTORY_INTERVAL)
47
+ }
48
+
49
+ static get historyTime() {
50
+ return this._historyTime
51
+ }
52
+
53
+ static set historyTime(value: number) {
54
+ assertEx(value >= this.historyInterval, () => `historyTime [${value}] must be greater than or equal to historyInterval [${this.historyInterval}]`)
55
+ this._historyInterval = value
56
+ }
57
+
58
+ static get maxGcFrequency() {
59
+ return this._maxGcFrequency
60
+ }
61
+
62
+ static set maxGcFrequency(value: number) {
63
+ this._maxGcFrequency = Math.max(value, MIN_GC_FREQUENCY)
64
+ }
65
+
66
+ static get maxHistoryDepth() {
67
+ return Math.floor(this.historyTime / this.historyInterval)
22
68
  }
23
69
 
24
70
  get logger() {
25
71
  return this.params?.logger ?? Base.defaultLogger
26
72
  }
27
73
 
28
- get params() {
74
+ get params(): TParams extends BaseParams ? TParams : BaseParams {
29
75
  return this._params
30
76
  }
77
+
78
+ static gc(force?: boolean): void
79
+ static gc(className: string): void
80
+ static gc(classNameOrForce: string | boolean = false): void {
81
+ if (typeof classNameOrForce === 'string') {
82
+ this.gcClass(classNameOrForce)
83
+ } else {
84
+ if (classNameOrForce || Date.now() - this._lastGC > this._maxGcFrequency) {
85
+ this.gcAll()
86
+ }
87
+ }
88
+ }
89
+
90
+ static instanceCount(className: string): number {
91
+ return this.globalInstances[className]?.length ?? 0
92
+ }
93
+
94
+ static instanceCounts(): Record<BaseClassName, number> {
95
+ this.gc()
96
+ const result: Record<BaseClassName, number> = {}
97
+ Object.entries(this.globalInstances).map(([className, instances]) => (result[className] = instances.length))
98
+ return result
99
+ }
100
+
101
+ static startHistory(): void {
102
+ if (this._historyTimeout) {
103
+ this.stopHistory()
104
+ }
105
+
106
+ const timeoutHandler = () => {
107
+ if (this._historyTimeout) {
108
+ this.addToHistory()
109
+ this._historyTimeout = setTimeout(() => {
110
+ timeoutHandler
111
+ }, this.historyInterval)
112
+ }
113
+ }
114
+
115
+ this._historyTimeout = setTimeout(() => {
116
+ timeoutHandler
117
+ }, this.historyInterval)
118
+ }
119
+
120
+ static stopHistory(): void {
121
+ if (this._historyTimeout) {
122
+ clearTimeout(this._historyTimeout)
123
+ this._historyTimeout = undefined
124
+ }
125
+ }
126
+
127
+ private static addToHistory() {
128
+ const counts = this.instanceCounts()
129
+ for (const className of Object.keys(this.globalInstances)) {
130
+ this.globalInstancesCountHistory[className] = this.globalInstancesCountHistory[className]?.slice(-this.maxHistoryDepth) ?? []
131
+ this.globalInstancesCountHistory[className].push(counts[className])
132
+ }
133
+ }
134
+
135
+ private static gcAll() {
136
+ for (const className of Object.keys(this.globalInstances)) {
137
+ this.gcClass(className)
138
+ }
139
+ }
140
+
141
+ private static gcClass(className: BaseClassName) {
142
+ //remove all the weak refs that are now empty
143
+ this.globalInstances[className] = this.globalInstances[className]?.filter((ref) => ref.deref() !== null) ?? []
144
+ }
145
+
146
+ private recordInstance() {
147
+ const instanceArray = Base.globalInstances[this.constructor.name] ?? []
148
+ instanceArray.push(new WeakRef(this))
149
+ Base.globalInstances[this.constructor.name] = instanceArray
150
+ }
31
151
  }