@kingsy/objectloader2 2.25.7

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 (256) hide show
  1. package/.tshy/build.json +8 -0
  2. package/.tshy/commonjs.json +17 -0
  3. package/.tshy/esm.json +16 -0
  4. package/dist/commonjs/helpers/aggregateQueue.d.ts +8 -0
  5. package/dist/commonjs/helpers/aggregateQueue.d.ts.map +1 -0
  6. package/dist/commonjs/helpers/aggregateQueue.js +19 -0
  7. package/dist/commonjs/helpers/aggregateQueue.js.map +1 -0
  8. package/dist/commonjs/helpers/asyncGeneratorQueue.d.ts +8 -0
  9. package/dist/commonjs/helpers/asyncGeneratorQueue.d.ts.map +1 -0
  10. package/dist/commonjs/helpers/asyncGeneratorQueue.js +35 -0
  11. package/dist/commonjs/helpers/asyncGeneratorQueue.js.map +1 -0
  12. package/dist/commonjs/helpers/batchedPool.d.ts +12 -0
  13. package/dist/commonjs/helpers/batchedPool.d.ts.map +1 -0
  14. package/dist/commonjs/helpers/batchedPool.js +45 -0
  15. package/dist/commonjs/helpers/batchedPool.js.map +1 -0
  16. package/dist/commonjs/helpers/batchingQueue.d.ts +14 -0
  17. package/dist/commonjs/helpers/batchingQueue.d.ts.map +1 -0
  18. package/dist/commonjs/helpers/batchingQueue.js +77 -0
  19. package/dist/commonjs/helpers/batchingQueue.js.map +1 -0
  20. package/dist/commonjs/helpers/bufferQueue.d.ts +7 -0
  21. package/dist/commonjs/helpers/bufferQueue.d.ts.map +1 -0
  22. package/dist/commonjs/helpers/bufferQueue.js +13 -0
  23. package/dist/commonjs/helpers/bufferQueue.js.map +1 -0
  24. package/dist/commonjs/helpers/cachePump.d.ts +22 -0
  25. package/dist/commonjs/helpers/cachePump.d.ts.map +1 -0
  26. package/dist/commonjs/helpers/cachePump.js +86 -0
  27. package/dist/commonjs/helpers/cachePump.js.map +1 -0
  28. package/dist/commonjs/helpers/cacheReader.d.ts +14 -0
  29. package/dist/commonjs/helpers/cacheReader.d.ts.map +1 -0
  30. package/dist/commonjs/helpers/cacheReader.js +58 -0
  31. package/dist/commonjs/helpers/cacheReader.js.map +1 -0
  32. package/dist/commonjs/helpers/defermentManager.d.ts +28 -0
  33. package/dist/commonjs/helpers/defermentManager.d.ts.map +1 -0
  34. package/dist/commonjs/helpers/defermentManager.js +150 -0
  35. package/dist/commonjs/helpers/defermentManager.js.map +1 -0
  36. package/dist/commonjs/helpers/deferredBase.d.ts +19 -0
  37. package/dist/commonjs/helpers/deferredBase.d.ts.map +1 -0
  38. package/dist/commonjs/helpers/deferredBase.js +51 -0
  39. package/dist/commonjs/helpers/deferredBase.js.map +1 -0
  40. package/dist/commonjs/helpers/keyedQueue.d.ts +11 -0
  41. package/dist/commonjs/helpers/keyedQueue.d.ts.map +1 -0
  42. package/dist/commonjs/helpers/keyedQueue.js +41 -0
  43. package/dist/commonjs/helpers/keyedQueue.js.map +1 -0
  44. package/dist/commonjs/helpers/memoryPump.d.ts +15 -0
  45. package/dist/commonjs/helpers/memoryPump.d.ts.map +1 -0
  46. package/dist/commonjs/helpers/memoryPump.js +34 -0
  47. package/dist/commonjs/helpers/memoryPump.js.map +1 -0
  48. package/dist/commonjs/helpers/pump.d.ts +8 -0
  49. package/dist/commonjs/helpers/pump.d.ts.map +1 -0
  50. package/dist/commonjs/helpers/pump.js +3 -0
  51. package/dist/commonjs/helpers/pump.js.map +1 -0
  52. package/dist/commonjs/helpers/queue.d.ts +4 -0
  53. package/dist/commonjs/helpers/queue.d.ts.map +1 -0
  54. package/dist/commonjs/helpers/queue.js +3 -0
  55. package/dist/commonjs/helpers/queue.js.map +1 -0
  56. package/dist/commonjs/index.d.ts +3 -0
  57. package/dist/commonjs/index.d.ts.map +1 -0
  58. package/dist/commonjs/index.js +8 -0
  59. package/dist/commonjs/index.js.map +1 -0
  60. package/dist/commonjs/operations/databases/indexedDatabase.d.ts +27 -0
  61. package/dist/commonjs/operations/databases/indexedDatabase.d.ts.map +1 -0
  62. package/dist/commonjs/operations/databases/indexedDatabase.js +98 -0
  63. package/dist/commonjs/operations/databases/indexedDatabase.js.map +1 -0
  64. package/dist/commonjs/operations/databases/memoryDatabase.d.ts +13 -0
  65. package/dist/commonjs/operations/databases/memoryDatabase.d.ts.map +1 -0
  66. package/dist/commonjs/operations/databases/memoryDatabase.js +33 -0
  67. package/dist/commonjs/operations/databases/memoryDatabase.js.map +1 -0
  68. package/dist/commonjs/operations/downloaders/memoryDownloader.d.ts +16 -0
  69. package/dist/commonjs/operations/downloaders/memoryDownloader.d.ts.map +1 -0
  70. package/dist/commonjs/operations/downloaders/memoryDownloader.js +35 -0
  71. package/dist/commonjs/operations/downloaders/memoryDownloader.js.map +1 -0
  72. package/dist/commonjs/operations/downloaders/serverDownloader.d.ts +32 -0
  73. package/dist/commonjs/operations/downloaders/serverDownloader.d.ts.map +1 -0
  74. package/dist/commonjs/operations/downloaders/serverDownloader.js +169 -0
  75. package/dist/commonjs/operations/downloaders/serverDownloader.js.map +1 -0
  76. package/dist/commonjs/operations/interfaces.d.ts +19 -0
  77. package/dist/commonjs/operations/interfaces.d.ts.map +1 -0
  78. package/dist/commonjs/operations/interfaces.js +3 -0
  79. package/dist/commonjs/operations/interfaces.js.map +1 -0
  80. package/dist/commonjs/operations/objectLoader2.d.ts +16 -0
  81. package/dist/commonjs/operations/objectLoader2.d.ts.map +1 -0
  82. package/dist/commonjs/operations/objectLoader2.js +101 -0
  83. package/dist/commonjs/operations/objectLoader2.js.map +1 -0
  84. package/dist/commonjs/operations/objectLoader2Factory.d.ts +25 -0
  85. package/dist/commonjs/operations/objectLoader2Factory.d.ts.map +1 -0
  86. package/dist/commonjs/operations/objectLoader2Factory.js +68 -0
  87. package/dist/commonjs/operations/objectLoader2Factory.js.map +1 -0
  88. package/dist/commonjs/operations/options.d.ts +26 -0
  89. package/dist/commonjs/operations/options.d.ts.map +1 -0
  90. package/dist/commonjs/operations/options.js +3 -0
  91. package/dist/commonjs/operations/options.js.map +1 -0
  92. package/dist/commonjs/operations/traverser.d.ts +19 -0
  93. package/dist/commonjs/operations/traverser.d.ts.map +1 -0
  94. package/dist/commonjs/operations/traverser.js +96 -0
  95. package/dist/commonjs/operations/traverser.js.map +1 -0
  96. package/dist/commonjs/package.json +3 -0
  97. package/dist/commonjs/types/errors.d.ts +21 -0
  98. package/dist/commonjs/types/errors.d.ts.map +1 -0
  99. package/dist/commonjs/types/errors.js +28 -0
  100. package/dist/commonjs/types/errors.js.map +1 -0
  101. package/dist/commonjs/types/types.d.ts +25 -0
  102. package/dist/commonjs/types/types.d.ts.map +1 -0
  103. package/dist/commonjs/types/types.js +39 -0
  104. package/dist/commonjs/types/types.js.map +1 -0
  105. package/dist/esm/helpers/aggregateQueue.d.ts +8 -0
  106. package/dist/esm/helpers/aggregateQueue.d.ts.map +1 -0
  107. package/dist/esm/helpers/aggregateQueue.js +16 -0
  108. package/dist/esm/helpers/aggregateQueue.js.map +1 -0
  109. package/dist/esm/helpers/asyncGeneratorQueue.d.ts +8 -0
  110. package/dist/esm/helpers/asyncGeneratorQueue.d.ts.map +1 -0
  111. package/dist/esm/helpers/asyncGeneratorQueue.js +32 -0
  112. package/dist/esm/helpers/asyncGeneratorQueue.js.map +1 -0
  113. package/dist/esm/helpers/batchedPool.d.ts +12 -0
  114. package/dist/esm/helpers/batchedPool.d.ts.map +1 -0
  115. package/dist/esm/helpers/batchedPool.js +42 -0
  116. package/dist/esm/helpers/batchedPool.js.map +1 -0
  117. package/dist/esm/helpers/batchingQueue.d.ts +14 -0
  118. package/dist/esm/helpers/batchingQueue.d.ts.map +1 -0
  119. package/dist/esm/helpers/batchingQueue.js +71 -0
  120. package/dist/esm/helpers/batchingQueue.js.map +1 -0
  121. package/dist/esm/helpers/bufferQueue.d.ts +7 -0
  122. package/dist/esm/helpers/bufferQueue.d.ts.map +1 -0
  123. package/dist/esm/helpers/bufferQueue.js +10 -0
  124. package/dist/esm/helpers/bufferQueue.js.map +1 -0
  125. package/dist/esm/helpers/cachePump.d.ts +22 -0
  126. package/dist/esm/helpers/cachePump.d.ts.map +1 -0
  127. package/dist/esm/helpers/cachePump.js +79 -0
  128. package/dist/esm/helpers/cachePump.js.map +1 -0
  129. package/dist/esm/helpers/cacheReader.d.ts +14 -0
  130. package/dist/esm/helpers/cacheReader.d.ts.map +1 -0
  131. package/dist/esm/helpers/cacheReader.js +51 -0
  132. package/dist/esm/helpers/cacheReader.js.map +1 -0
  133. package/dist/esm/helpers/defermentManager.d.ts +28 -0
  134. package/dist/esm/helpers/defermentManager.d.ts.map +1 -0
  135. package/dist/esm/helpers/defermentManager.js +146 -0
  136. package/dist/esm/helpers/defermentManager.js.map +1 -0
  137. package/dist/esm/helpers/deferredBase.d.ts +19 -0
  138. package/dist/esm/helpers/deferredBase.d.ts.map +1 -0
  139. package/dist/esm/helpers/deferredBase.js +47 -0
  140. package/dist/esm/helpers/deferredBase.js.map +1 -0
  141. package/dist/esm/helpers/keyedQueue.d.ts +11 -0
  142. package/dist/esm/helpers/keyedQueue.d.ts.map +1 -0
  143. package/dist/esm/helpers/keyedQueue.js +38 -0
  144. package/dist/esm/helpers/keyedQueue.js.map +1 -0
  145. package/dist/esm/helpers/memoryPump.d.ts +15 -0
  146. package/dist/esm/helpers/memoryPump.d.ts.map +1 -0
  147. package/dist/esm/helpers/memoryPump.js +30 -0
  148. package/dist/esm/helpers/memoryPump.js.map +1 -0
  149. package/dist/esm/helpers/pump.d.ts +8 -0
  150. package/dist/esm/helpers/pump.d.ts.map +1 -0
  151. package/dist/esm/helpers/pump.js +2 -0
  152. package/dist/esm/helpers/pump.js.map +1 -0
  153. package/dist/esm/helpers/queue.d.ts +4 -0
  154. package/dist/esm/helpers/queue.d.ts.map +1 -0
  155. package/dist/esm/helpers/queue.js +2 -0
  156. package/dist/esm/helpers/queue.js.map +1 -0
  157. package/dist/esm/index.d.ts +3 -0
  158. package/dist/esm/index.d.ts.map +1 -0
  159. package/dist/esm/index.js +3 -0
  160. package/dist/esm/index.js.map +1 -0
  161. package/dist/esm/operations/databases/indexedDatabase.d.ts +27 -0
  162. package/dist/esm/operations/databases/indexedDatabase.d.ts.map +1 -0
  163. package/dist/esm/operations/databases/indexedDatabase.js +93 -0
  164. package/dist/esm/operations/databases/indexedDatabase.js.map +1 -0
  165. package/dist/esm/operations/databases/memoryDatabase.d.ts +13 -0
  166. package/dist/esm/operations/databases/memoryDatabase.d.ts.map +1 -0
  167. package/dist/esm/operations/databases/memoryDatabase.js +29 -0
  168. package/dist/esm/operations/databases/memoryDatabase.js.map +1 -0
  169. package/dist/esm/operations/downloaders/memoryDownloader.d.ts +16 -0
  170. package/dist/esm/operations/downloaders/memoryDownloader.d.ts.map +1 -0
  171. package/dist/esm/operations/downloaders/memoryDownloader.js +31 -0
  172. package/dist/esm/operations/downloaders/memoryDownloader.js.map +1 -0
  173. package/dist/esm/operations/downloaders/serverDownloader.d.ts +32 -0
  174. package/dist/esm/operations/downloaders/serverDownloader.d.ts.map +1 -0
  175. package/dist/esm/operations/downloaders/serverDownloader.js +163 -0
  176. package/dist/esm/operations/downloaders/serverDownloader.js.map +1 -0
  177. package/dist/esm/operations/interfaces.d.ts +19 -0
  178. package/dist/esm/operations/interfaces.d.ts.map +1 -0
  179. package/dist/esm/operations/interfaces.js +2 -0
  180. package/dist/esm/operations/interfaces.js.map +1 -0
  181. package/dist/esm/operations/objectLoader2.d.ts +16 -0
  182. package/dist/esm/operations/objectLoader2.d.ts.map +1 -0
  183. package/dist/esm/operations/objectLoader2.js +94 -0
  184. package/dist/esm/operations/objectLoader2.js.map +1 -0
  185. package/dist/esm/operations/objectLoader2Factory.d.ts +25 -0
  186. package/dist/esm/operations/objectLoader2Factory.d.ts.map +1 -0
  187. package/dist/esm/operations/objectLoader2Factory.js +61 -0
  188. package/dist/esm/operations/objectLoader2Factory.js.map +1 -0
  189. package/dist/esm/operations/options.d.ts +26 -0
  190. package/dist/esm/operations/options.d.ts.map +1 -0
  191. package/dist/esm/operations/options.js +2 -0
  192. package/dist/esm/operations/options.js.map +1 -0
  193. package/dist/esm/operations/traverser.d.ts +19 -0
  194. package/dist/esm/operations/traverser.d.ts.map +1 -0
  195. package/dist/esm/operations/traverser.js +93 -0
  196. package/dist/esm/operations/traverser.js.map +1 -0
  197. package/dist/esm/package.json +3 -0
  198. package/dist/esm/types/errors.d.ts +21 -0
  199. package/dist/esm/types/errors.d.ts.map +1 -0
  200. package/dist/esm/types/errors.js +23 -0
  201. package/dist/esm/types/errors.js.map +1 -0
  202. package/dist/esm/types/types.d.ts +25 -0
  203. package/dist/esm/types/types.d.ts.map +1 -0
  204. package/dist/esm/types/types.js +33 -0
  205. package/dist/esm/types/types.js.map +1 -0
  206. package/eslint.config.mjs +60 -0
  207. package/package.json +76 -0
  208. package/readme.md +42 -0
  209. package/src/helpers/__snapshots__/cachePump.spec.ts.snap +31 -0
  210. package/src/helpers/__snapshots__/cacheReader.spec.ts.snap +8 -0
  211. package/src/helpers/__snapshots__/defermentManager.spec.ts.snap +8 -0
  212. package/src/helpers/aggregateQueue.ts +20 -0
  213. package/src/helpers/asyncGeneratorQueue.ts +35 -0
  214. package/src/helpers/batchedPool.ts +56 -0
  215. package/src/helpers/batchingQueue.ts +85 -0
  216. package/src/helpers/bufferQueue.ts +12 -0
  217. package/src/helpers/cachePump.disposal.spec.ts +51 -0
  218. package/src/helpers/cachePump.spec.ts +104 -0
  219. package/src/helpers/cachePump.ts +107 -0
  220. package/src/helpers/cacheReader.spec.ts +35 -0
  221. package/src/helpers/cacheReader.ts +64 -0
  222. package/src/helpers/defermentManager.defermentTotals.spec.ts +53 -0
  223. package/src/helpers/defermentManager.disposal.spec.ts +28 -0
  224. package/src/helpers/defermentManager.spec.ts +37 -0
  225. package/src/helpers/defermentManager.ts +160 -0
  226. package/src/helpers/deferredBase.ts +55 -0
  227. package/src/helpers/keyedQueue.ts +45 -0
  228. package/src/helpers/memoryPump.ts +40 -0
  229. package/src/helpers/pump.ts +8 -0
  230. package/src/helpers/queue.ts +3 -0
  231. package/src/index.ts +2 -0
  232. package/src/operations/__snapshots__/objectLoader2.spec.ts.snap +149 -0
  233. package/src/operations/__snapshots__/traverser.spec.ts.snap +45 -0
  234. package/src/operations/databases/__snapshots__/indexedDatabase.spec.ts.snap +18 -0
  235. package/src/operations/databases/indexedDatabase.spec.ts +33 -0
  236. package/src/operations/databases/indexedDatabase.ts +126 -0
  237. package/src/operations/databases/memoryDatabase.spec.ts +48 -0
  238. package/src/operations/databases/memoryDatabase.ts +35 -0
  239. package/src/operations/downloaders/__snapshots__/memoryDownloader.spec.ts.snap +12 -0
  240. package/src/operations/downloaders/__snapshots__/serverDownloader.spec.ts.snap +92 -0
  241. package/src/operations/downloaders/memoryDownloader.spec.ts +49 -0
  242. package/src/operations/downloaders/memoryDownloader.ts +39 -0
  243. package/src/operations/downloaders/serverDownloader.spec.ts +161 -0
  244. package/src/operations/downloaders/serverDownloader.ts +217 -0
  245. package/src/operations/interfaces.ts +18 -0
  246. package/src/operations/objectLoader2.spec.ts +229 -0
  247. package/src/operations/objectLoader2.ts +119 -0
  248. package/src/operations/objectLoader2Factory.ts +78 -0
  249. package/src/operations/options.ts +29 -0
  250. package/src/operations/traverser.spec.ts +58 -0
  251. package/src/operations/traverser.ts +112 -0
  252. package/src/test/e2e.spec.ts +43 -0
  253. package/src/types/errors.ts +25 -0
  254. package/src/types/types.ts +71 -0
  255. package/tsconfig.json +103 -0
  256. package/vitest.config.ts +3 -0
@@ -0,0 +1,85 @@
1
+ import KeyedQueue from './keyedQueue.js'
2
+
3
+ export default class BatchingQueue<T> {
4
+ #queue: KeyedQueue<string, T> = new KeyedQueue<string, T>()
5
+ #batchSize: number
6
+ #processFunction: (batch: T[]) => Promise<void>
7
+
8
+ #baseInterval: number
9
+ #minInterval: number
10
+ #maxInterval: number
11
+
12
+ #processingLoop: Promise<void>
13
+ #disposed = false
14
+
15
+ constructor(params: {
16
+ batchSize: number
17
+ maxWaitTime?: number
18
+ processFunction: (batch: T[]) => Promise<void>
19
+ }) {
20
+ this.#batchSize = params.batchSize
21
+ this.#baseInterval = Math.min(params.maxWaitTime ?? 200, 200) // Initial batch time (ms)
22
+ this.#minInterval = Math.min(params.maxWaitTime ?? 100, 100) // Minimum batch time
23
+ this.#maxInterval = Math.min(params.maxWaitTime ?? 3000, 3000) // Maximum batch time
24
+ this.#processFunction = params.processFunction
25
+ this.#processingLoop = this.#loop()
26
+ }
27
+
28
+ async disposeAsync(): Promise<void> {
29
+ this.#disposed = true
30
+ await this.#processingLoop
31
+ }
32
+
33
+ add(key: string, item: T): void {
34
+ this.#queue.enqueue(key, item)
35
+ }
36
+
37
+ get(id: string): T | undefined {
38
+ return this.#queue.get(id)
39
+ }
40
+
41
+ count(): number {
42
+ return this.#queue.size
43
+ }
44
+
45
+ isDisposed(): boolean {
46
+ return this.#disposed
47
+ }
48
+
49
+ #getBatch(batchSize: number): T[] {
50
+ return this.#queue.spliceValues(0, Math.min(batchSize, this.#queue.size))
51
+ }
52
+
53
+ async #loop(): Promise<void> {
54
+ let interval = this.#baseInterval
55
+ while (!this.#disposed || this.#queue.size > 0) {
56
+ const startTime = performance.now()
57
+ if (this.#queue.size > 0) {
58
+ const batch = this.#getBatch(this.#batchSize)
59
+ //console.log('running with queue size of ' + this.#queue.length)
60
+ await this.#processFunction(batch)
61
+ }
62
+ if (this.#queue.size < this.#batchSize / 2) {
63
+ //refigure interval
64
+ const endTime = performance.now()
65
+ const duration = endTime - startTime
66
+ if (duration > interval) {
67
+ interval = Math.min(interval * 1.5, this.#maxInterval) // Increase if slow or empty
68
+ } else {
69
+ interval = Math.max(interval * 0.8, this.#minInterval) // Decrease if fast
70
+ }
71
+ /*console.log(
72
+ 'queue is waiting ' +
73
+ interval / 1000 +
74
+ ' with queue size of ' +
75
+ this.#queue.length
76
+ )*/
77
+ await this.#delay(interval)
78
+ }
79
+ }
80
+ }
81
+
82
+ #delay(ms: number): Promise<void> {
83
+ return new Promise((resolve) => setTimeout(resolve, ms))
84
+ }
85
+ }
@@ -0,0 +1,12 @@
1
+ import Queue from './queue.js'
2
+
3
+ export default class BufferQueue<T> implements Queue<T> {
4
+ #buffer: T[] = []
5
+ add(value: T): void {
6
+ this.#buffer.push(value)
7
+ }
8
+
9
+ values(): T[] {
10
+ return this.#buffer
11
+ }
12
+ }
@@ -0,0 +1,51 @@
1
+ import { describe, expect, test } from 'vitest'
2
+ import { CachePump } from './cachePump.js'
3
+ import { Database } from '../operations/interfaces.js'
4
+ import AsyncGeneratorQueue from './asyncGeneratorQueue.js'
5
+ import { Item } from '../types/types.js'
6
+ import { DefermentManager } from './defermentManager.js'
7
+
8
+ const makeDatabase = (): Database =>
9
+ ({
10
+ cacheSaveBatch: async (): Promise<void> => {},
11
+ getAll: async (): Promise<(Item | undefined)[]> => Promise.resolve([]),
12
+ getItem: async (): Promise<Item | undefined> => Promise.resolve(undefined),
13
+ disposeAsync: async (): Promise<void> => {}
14
+ } as unknown as Database)
15
+ const makeGathered = (): AsyncGeneratorQueue<Item> =>
16
+ ({
17
+ add: () => {},
18
+ async *consume() {}
19
+ } as unknown as AsyncGeneratorQueue<Item>)
20
+ const makeDeferments = (): DefermentManager =>
21
+ ({
22
+ undefer: () => {}
23
+ } as unknown as DefermentManager)
24
+ describe('CachePump disposal', () => {
25
+ test('disposeAsync is idempotent and always resolves', async () => {
26
+ const pump = new CachePump(makeDatabase(), makeGathered(), makeDeferments(), {
27
+ maxCacheWriteSize: 2,
28
+ maxCacheBatchWriteWait: 100,
29
+ maxCacheBatchReadWait: 1,
30
+ maxWriteQueueSize: 2,
31
+ maxCacheReadSize: 2
32
+ })
33
+ await pump.disposeAsync()
34
+ await expect(pump.disposeAsync()).resolves.toBeUndefined()
35
+ })
36
+
37
+ test('should not throw on add after dispose if writeQueue was never created', async () => {
38
+ const pump = new CachePump(makeDatabase(), makeGathered(), makeDeferments(), {
39
+ maxCacheWriteSize: 2,
40
+ maxCacheBatchWriteWait: 100,
41
+ maxCacheBatchReadWait: 1,
42
+ maxWriteQueueSize: 2,
43
+ maxCacheReadSize: 2
44
+ })
45
+ await pump.disposeAsync()
46
+ // Should not throw, but will not add anything
47
+ expect(() =>
48
+ pump.add({ baseId: 'a', base: { id: 'b', kingsy_type: 'type' } })
49
+ ).not.toThrow()
50
+ })
51
+ })
@@ -0,0 +1,104 @@
1
+ import { describe, expect, test } from 'vitest'
2
+ import { CachePump } from './cachePump.js'
3
+ import { Base, Item } from '../types/types.js'
4
+ import BufferQueue from './bufferQueue.js'
5
+ import AsyncGeneratorQueue from './asyncGeneratorQueue.js'
6
+ import { DefermentManager } from './defermentManager.js'
7
+ import { MemoryDatabase } from '../operations/databases/memoryDatabase.js'
8
+ import { Database } from '../operations/interfaces.js'
9
+
10
+ describe('CachePump testing', () => {
11
+ test('write two items to queue use pumpItems that are NOT found', async () => {
12
+ const i1: Item = { baseId: 'id1', base: { id: 'id', kingsy_type: 'type' } }
13
+ const i2: Item = { baseId: 'id2', base: { id: 'id', kingsy_type: 'type' } }
14
+
15
+ const gathered = new AsyncGeneratorQueue<Item>()
16
+ const deferments = new DefermentManager({ maxSizeInMb: 1, ttlms: 1 })
17
+ const cachePump = new CachePump(new MemoryDatabase({}), gathered, deferments, {
18
+ maxCacheReadSize: 1,
19
+ maxCacheWriteSize: 1,
20
+ maxCacheBatchWriteWait: 1,
21
+ maxCacheBatchReadWait: 1,
22
+ maxWriteQueueSize: 1
23
+ })
24
+
25
+ const foundItems = new BufferQueue<Item>()
26
+ const notFoundItems = new BufferQueue<string>()
27
+
28
+ await cachePump.pumpItems({
29
+ ids: [i1.baseId, i2.baseId],
30
+ foundItems,
31
+ notFoundItems
32
+ })
33
+
34
+ expect(foundItems.values()).toMatchSnapshot()
35
+ expect(notFoundItems.values()).toMatchSnapshot()
36
+ await cachePump.disposeAsync()
37
+ })
38
+
39
+ test('write two items to queue use pumpItems that are found', async () => {
40
+ const i1: Item = { baseId: 'id1', base: { id: 'id', kingsy_type: 'type' } }
41
+ const i2: Item = { baseId: 'id2', base: { id: 'id', kingsy_type: 'type' } }
42
+
43
+ const db = new Map<string, Base>()
44
+ db.set(i1.baseId, i1.base)
45
+ db.set(i2.baseId, i2.base)
46
+
47
+ const gathered = new AsyncGeneratorQueue<Item>()
48
+ const deferments = new DefermentManager({ maxSizeInMb: 1, ttlms: 1 })
49
+ const cachePump = new CachePump(
50
+ new MemoryDatabase({ items: db }),
51
+ gathered,
52
+ deferments,
53
+ {
54
+ maxCacheReadSize: 1,
55
+ maxCacheWriteSize: 1,
56
+ maxCacheBatchWriteWait: 1,
57
+ maxCacheBatchReadWait: 1,
58
+ maxWriteQueueSize: 1
59
+ }
60
+ )
61
+
62
+ const foundItems = new BufferQueue<Item>()
63
+ const notFoundItems = new BufferQueue<string>()
64
+
65
+ await cachePump.pumpItems({
66
+ ids: [i1.baseId, i2.baseId],
67
+ foundItems,
68
+ notFoundItems
69
+ })
70
+
71
+ expect(foundItems.values()).toMatchSnapshot()
72
+ expect(notFoundItems.values()).toMatchSnapshot()
73
+ await cachePump.disposeAsync()
74
+ })
75
+
76
+ test('can dispose while waiting and not wait', async () => {
77
+ const i1: Item = { baseId: 'id1', base: { id: 'id', kingsy_type: 'type' } }
78
+ const i2: Item = { baseId: 'id2', base: { id: 'id', kingsy_type: 'type' } }
79
+
80
+ const db: Database = {
81
+ getAll: async () => Promise.resolve([]),
82
+ disposeAsync: async (): Promise<void> => {}
83
+ } as unknown as Database
84
+ const gathered = new AsyncGeneratorQueue<Item>()
85
+ const deferments = new DefermentManager({ maxSizeInMb: 1, ttlms: 1 })
86
+ const cachePump = new CachePump(db, gathered, deferments, {
87
+ maxCacheReadSize: 1,
88
+ maxCacheWriteSize: 1,
89
+ maxCacheBatchWriteWait: 1,
90
+ maxCacheBatchReadWait: 1,
91
+ maxWriteQueueSize: 1
92
+ })
93
+
94
+ const foundItems = new BufferQueue<Item>()
95
+ const notFoundItems = new BufferQueue<string>()
96
+
97
+ await cachePump.disposeAsync()
98
+ await cachePump.pumpItems({
99
+ ids: [i1.baseId, i2.baseId],
100
+ foundItems,
101
+ notFoundItems
102
+ })
103
+ })
104
+ })
@@ -0,0 +1,107 @@
1
+ import { TIME } from '@kingsy/shared'
2
+ import { Database } from '../operations/interfaces.js'
3
+ import { CacheOptions } from '../operations/options.js'
4
+ import { CustomLogger, Item } from '../types/types.js'
5
+ import BatchingQueue from './batchingQueue.js'
6
+ import Queue from './queue.js'
7
+ import { Downloader } from '../operations/interfaces.js'
8
+ import { DefermentManager } from './defermentManager.js'
9
+ import AsyncGeneratorQueue from './asyncGeneratorQueue.js'
10
+ import { Pump } from './pump.js'
11
+
12
+ export class CachePump implements Pump {
13
+ #writeQueue: BatchingQueue<Item> | undefined
14
+ #database: Database
15
+ #logger: CustomLogger
16
+ #deferments: DefermentManager
17
+
18
+ #gathered: AsyncGeneratorQueue<Item>
19
+
20
+ #options: CacheOptions
21
+
22
+ #disposed = false
23
+
24
+ constructor(
25
+ database: Database,
26
+ gathered: AsyncGeneratorQueue<Item>,
27
+ deferments: DefermentManager,
28
+ options: CacheOptions
29
+ ) {
30
+ this.#database = database
31
+ this.#gathered = gathered
32
+ this.#deferments = deferments
33
+ this.#options = options
34
+ this.#logger = options.logger || ((): void => {})
35
+ }
36
+
37
+ add(item: Item): void {
38
+ if (!this.#writeQueue) {
39
+ this.#writeQueue = new BatchingQueue({
40
+ batchSize: this.#options.maxCacheWriteSize,
41
+ maxWaitTime: this.#options.maxCacheBatchWriteWait,
42
+ processFunction: (batch: Item[]): Promise<void> =>
43
+ this.#database.cacheSaveBatch({ batch })
44
+ })
45
+ }
46
+ this.#writeQueue.add(item.baseId, item)
47
+ }
48
+
49
+ async disposeAsync(): Promise<void> {
50
+ await this.#writeQueue?.disposeAsync()
51
+ await this.#database.disposeAsync()
52
+ this.#disposed = true
53
+ }
54
+
55
+ get isDisposed(): boolean {
56
+ return this.#disposed
57
+ }
58
+
59
+ async pumpItems(params: {
60
+ ids: string[]
61
+ foundItems: Queue<Item>
62
+ notFoundItems: Queue<string>
63
+ }): Promise<void> {
64
+ const { ids, foundItems, notFoundItems } = params
65
+ const maxCacheReadSize = this.#options.maxCacheReadSize
66
+
67
+ for (let i = 0; i < ids.length; ) {
68
+ if (this.isDisposed) break
69
+ if ((this.#writeQueue?.count() ?? 0) > this.#options.maxWriteQueueSize) {
70
+ this.#logger(
71
+ 'pausing reads (# in write queue: ' + this.#writeQueue?.count() + ')'
72
+ )
73
+ await new Promise((resolve) => setTimeout(resolve, TIME.second)) // Pause for 1 second, protects against out of memory
74
+ continue
75
+ }
76
+ const batch = ids.slice(i, i + maxCacheReadSize)
77
+ const cachedData = await this.#database.getAll(batch)
78
+ for (let i = 0; i < cachedData.length; i++) {
79
+ if (cachedData[i]) {
80
+ foundItems.add(cachedData[i]!)
81
+ } else {
82
+ notFoundItems.add(batch[i])
83
+ }
84
+ }
85
+ i += maxCacheReadSize
86
+ }
87
+ }
88
+
89
+ async *gather(ids: string[], downloader: Downloader): AsyncGenerator<Item> {
90
+ const total = ids.length
91
+ const pumpPromise = this.pumpItems({
92
+ ids,
93
+ foundItems: this.#gathered,
94
+ notFoundItems: downloader
95
+ })
96
+ let count = 0
97
+ for await (const item of this.#gathered.consume()) {
98
+ this.#deferments.undefer(item)
99
+ yield item
100
+ count++
101
+ if (count >= total) {
102
+ this.#gathered.dispose()
103
+ }
104
+ }
105
+ await pumpPromise
106
+ }
107
+ }
@@ -0,0 +1,35 @@
1
+ import { describe, expect, test } from 'vitest'
2
+ import { Base, Item } from '../types/types.js'
3
+ import { DefermentManager } from './defermentManager.js'
4
+ import { CacheReader } from './cacheReader.js'
5
+ import { MemoryDatabase } from '../operations/databases/memoryDatabase.js'
6
+
7
+ describe('CacheReader testing', () => {
8
+ test('deferred getObject', async () => {
9
+ const i1: Item = { baseId: 'id1', base: { id: 'id', kingsy_type: 'type' } }
10
+
11
+ const deferments = new DefermentManager({ maxSizeInMb: 1, ttlms: 1 })
12
+ const cacheReader = new CacheReader(
13
+ new MemoryDatabase({
14
+ items: new Map<string, Base>([[i1.baseId, i1.base]])
15
+ }),
16
+ deferments,
17
+ {
18
+ maxCacheReadSize: 1,
19
+ maxCacheWriteSize: 1,
20
+ maxCacheBatchWriteWait: 1,
21
+ maxCacheBatchReadWait: 1,
22
+ maxWriteQueueSize: 1
23
+ }
24
+ )
25
+
26
+ const objPromise = cacheReader.getObject({
27
+ id: i1.baseId
28
+ })
29
+ deferments.undefer(i1)
30
+ const base = await objPromise
31
+
32
+ expect(base).toMatchSnapshot()
33
+ await cacheReader.disposeAsync()
34
+ })
35
+ })
@@ -0,0 +1,64 @@
1
+ import { Database } from '../operations/interfaces.js'
2
+ import { CacheOptions } from '../operations/options.js'
3
+ import { Base, CustomLogger, Item } from '../types/types.js'
4
+ import BatchingQueue from './batchingQueue.js'
5
+ import { DefermentManager } from './defermentManager.js'
6
+
7
+ export class CacheReader {
8
+ #database: Database
9
+ #defermentManager: DefermentManager
10
+ #logger: CustomLogger
11
+ #options: CacheOptions
12
+ #readQueue: BatchingQueue<string> | undefined
13
+
14
+ constructor(
15
+ database: Database,
16
+ defermentManager: DefermentManager,
17
+ options: CacheOptions
18
+ ) {
19
+ this.#database = database
20
+ this.#defermentManager = defermentManager
21
+ this.#options = options
22
+ this.#logger = options.logger || ((): void => {})
23
+ }
24
+
25
+ async getObject(params: { id: string }): Promise<Base> {
26
+ if (!this.#defermentManager.isDeferred(params.id)) {
27
+ this.#getItem(params.id)
28
+ }
29
+ return await this.#defermentManager.defer({ id: params.id })
30
+ }
31
+
32
+ #getItem(id: string): void {
33
+ if (!this.#readQueue) {
34
+ this.#readQueue = new BatchingQueue({
35
+ batchSize: this.#options.maxCacheReadSize,
36
+ maxWaitTime: this.#options.maxCacheBatchReadWait,
37
+ processFunction: this.#processBatch
38
+ })
39
+ }
40
+ if (!this.#readQueue.get(id)) {
41
+ this.#readQueue.add(id, id)
42
+ }
43
+ }
44
+
45
+ async getAll(keys: string[]): Promise<(Item | undefined)[]> {
46
+ return this.#database.getAll(keys)
47
+ }
48
+
49
+ #processBatch = async (batch: string[]): Promise<void> => {
50
+ const items = await this.#database.getAll(batch)
51
+ for (let i = 0; i < items.length; i++) {
52
+ if (items[i]) {
53
+ this.#defermentManager.undefer(items[i]!)
54
+ } else {
55
+ //this is okay!
56
+ //this.#logger(`Item ${batch[i]} not found in cache`)
57
+ }
58
+ }
59
+ }
60
+
61
+ async disposeAsync(): Promise<void> {
62
+ await this.#readQueue?.disposeAsync()
63
+ }
64
+ }
@@ -0,0 +1,53 @@
1
+ import { describe, test, expect, beforeEach } from 'vitest'
2
+ import { DefermentManager } from './defermentManager.js'
3
+ import { DefermentManagerOptions } from '../operations/options.js'
4
+ import { Base, Item } from '../types/types.js'
5
+
6
+ const makeItem = (id: string, size = 1): Item => ({
7
+ baseId: id,
8
+ base: { foo: 'bar' } as unknown as Base,
9
+ size
10
+ })
11
+
12
+ describe('DefermentManager totalDefermentRequests', () => {
13
+ let manager: DefermentManager
14
+ let options: DefermentManagerOptions
15
+
16
+ beforeEach(() => {
17
+ options = { maxSizeInMb: 1, ttlms: 1000, logger: (): void => {} }
18
+ manager = new DefermentManager(options)
19
+ })
20
+
21
+ test('tracks deferment requests for each id', () => {
22
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
23
+ manager.defer({ id: 'a' })
24
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
25
+ manager.defer({ id: 'a' })
26
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
27
+ manager.defer({ id: 'b' })
28
+ // @ts-expect-error: access private for test
29
+ expect(manager.totalDefermentRequests.get('a')).toBe(2)
30
+ // @ts-expect-error: access private for test
31
+ expect(manager.totalDefermentRequests.get('b')).toBe(1)
32
+ })
33
+
34
+ test('increments and does not reset on undefer', () => {
35
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
36
+ manager.defer({ id: 'x' })
37
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
38
+ manager.defer({ id: 'x' })
39
+ manager.undefer(makeItem('x'))
40
+ // @ts-expect-error: access private for test
41
+ expect(manager.totalDefermentRequests.get('x')).toBe(2)
42
+ // @ts-expect-error: access private for test
43
+ const deferredBase = manager.deferments.get('x')
44
+ expect(deferredBase).toBeDefined()
45
+ expect(deferredBase?.getId()).toBe('x')
46
+ })
47
+
48
+ test('does not increment for undefer only', () => {
49
+ manager.undefer(makeItem('y'))
50
+ // @ts-expect-error: access private for test
51
+ expect(manager.totalDefermentRequests.get('y')).toBeUndefined()
52
+ })
53
+ })
@@ -0,0 +1,28 @@
1
+ import { describe, it, expect } from 'vitest'
2
+ import { DefermentManager } from './defermentManager.js'
3
+ import { DefermentManagerOptions } from '../operations/options.js'
4
+ import { Item } from '../types/types.js'
5
+
6
+ describe('DefermentManager disposal', () => {
7
+ const options: DefermentManagerOptions = { ttlms: 10, maxSizeInMb: 1 }
8
+ const makeItem = (id: string): Item => ({
9
+ baseId: id,
10
+ base: { id, kingsy_type: 'test' }
11
+ })
12
+
13
+ it('should throw on get/defer/undefer after dispose', async () => {
14
+ const manager = new DefermentManager(options)
15
+ manager.dispose()
16
+ expect(() => manager.get('a')).toThrow('DefermentManager is disposed')
17
+ expect(() => manager.undefer(makeItem('a'))).toThrow('DefermentManager is disposed')
18
+ await expect(manager.defer({ id: 'a' })).rejects.toThrow(
19
+ 'DefermentManager is disposed'
20
+ )
21
+ })
22
+
23
+ it('dispose is idempotent', () => {
24
+ const manager = new DefermentManager(options)
25
+ manager.dispose()
26
+ expect(() => manager.dispose()).not.toThrow()
27
+ })
28
+ })
@@ -0,0 +1,37 @@
1
+ /* eslint-disable @typescript-eslint/no-unsafe-member-access */
2
+ /* eslint-disable @typescript-eslint/no-explicit-any */
3
+ import { describe, expect, test } from 'vitest'
4
+ import { DefermentManager } from './defermentManager.js'
5
+
6
+ describe('deferments', () => {
7
+ test('defer one', async () => {
8
+ const deferments = new DefermentManager({ maxSizeInMb: 1, ttlms: 1 })
9
+ const x = deferments.defer({ id: 'id' })
10
+ expect(x).toBeInstanceOf(Promise)
11
+ deferments.undefer({ baseId: 'id', base: { id: 'id', kingsy_type: 'type' } })
12
+ const b = await x
13
+ expect(b).toMatchSnapshot()
14
+ })
15
+
16
+ test('expireAt timeout', async () => {
17
+ const now = 1
18
+ const deferments = new DefermentManager({ maxSizeInMb: 1, ttlms: 1 })
19
+ deferments['now'] = (): number => now
20
+ const x = deferments.defer({ id: 'id' })
21
+ expect(x).toBeInstanceOf(Promise)
22
+ const d = deferments.get('id')
23
+ expect(d).toBeDefined()
24
+ expect(d?.getId()).toBe('id')
25
+ expect((d as any).expiresAt).toBe(2)
26
+ expect((d as any).ttl).toBe(1)
27
+ expect((d as any).item).toBeUndefined()
28
+ expect(d?.isExpired(1)).toBe(false)
29
+ deferments.undefer({ baseId: 'id', base: { id: 'id', kingsy_type: 'type' } })
30
+ await x
31
+ expect((d as any).expiresAt).toBe(2)
32
+ expect((d as any).ttl).toBe(1)
33
+ expect((d as any).item).toBeDefined()
34
+ expect(d?.isExpired(1)).toBe(false)
35
+ expect(d?.isExpired(3)).toBe(true)
36
+ })
37
+ })