@xylabs/object 3.1.7 → 3.1.8

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
  }
@@ -12,13 +21,50 @@ export type BaseParams<TAdditionalParams extends EmptyObject | void = void> =
12
21
 
13
22
  export abstract class Base<TParams extends BaseParams | undefined = BaseParams> {
14
23
  static defaultLogger?: Logger
24
+ static readonly globalInstances: Record<BaseClassName, WeakRef<Base<BaseParams | undefined>>[]> = {}
25
+ static readonly globalInstancesCountHistory: Record<BaseClassName, number[]> = {}
15
26
  static readonly uniqueName = globallyUnique(this.name, this, 'xyo')
16
-
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
17
32
  private _params: TParams
18
33
 
19
34
  constructor(params: TParams) {
20
35
  this._params = params
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() {
@@ -28,4 +74,78 @@ export abstract class Base<TParams extends BaseParams | undefined = BaseParams>
28
74
  get params() {
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
  }