ts-data-forge 1.0.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 (143) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +534 -0
  3. package/package.json +101 -0
  4. package/src/array/array-utils-creation.test.mts +443 -0
  5. package/src/array/array-utils-modification.test.mts +197 -0
  6. package/src/array/array-utils-overload-type-error.test.mts +149 -0
  7. package/src/array/array-utils-reducing-value.test.mts +425 -0
  8. package/src/array/array-utils-search.test.mts +169 -0
  9. package/src/array/array-utils-set-op.test.mts +335 -0
  10. package/src/array/array-utils-slice-clamped.test.mts +113 -0
  11. package/src/array/array-utils-slicing.test.mts +316 -0
  12. package/src/array/array-utils-transformation.test.mts +790 -0
  13. package/src/array/array-utils-validation.test.mts +492 -0
  14. package/src/array/array-utils.mts +4000 -0
  15. package/src/array/array.test.mts +146 -0
  16. package/src/array/index.mts +2 -0
  17. package/src/array/tuple-utils.mts +519 -0
  18. package/src/array/tuple-utils.test.mts +518 -0
  19. package/src/collections/imap-mapped.mts +801 -0
  20. package/src/collections/imap-mapped.test.mts +860 -0
  21. package/src/collections/imap.mts +651 -0
  22. package/src/collections/imap.test.mts +932 -0
  23. package/src/collections/index.mts +6 -0
  24. package/src/collections/iset-mapped.mts +889 -0
  25. package/src/collections/iset-mapped.test.mts +1187 -0
  26. package/src/collections/iset.mts +682 -0
  27. package/src/collections/iset.test.mts +1084 -0
  28. package/src/collections/queue.mts +390 -0
  29. package/src/collections/queue.test.mts +282 -0
  30. package/src/collections/stack.mts +423 -0
  31. package/src/collections/stack.test.mts +225 -0
  32. package/src/expect-type.mts +206 -0
  33. package/src/functional/index.mts +4 -0
  34. package/src/functional/match.mts +300 -0
  35. package/src/functional/match.test.mts +177 -0
  36. package/src/functional/optional.mts +733 -0
  37. package/src/functional/optional.test.mts +619 -0
  38. package/src/functional/pipe.mts +212 -0
  39. package/src/functional/pipe.test.mts +85 -0
  40. package/src/functional/result.mts +1134 -0
  41. package/src/functional/result.test.mts +777 -0
  42. package/src/globals.d.mts +38 -0
  43. package/src/guard/has-key.mts +119 -0
  44. package/src/guard/has-key.test.mts +219 -0
  45. package/src/guard/index.mts +7 -0
  46. package/src/guard/is-non-empty-string.mts +108 -0
  47. package/src/guard/is-non-empty-string.test.mts +91 -0
  48. package/src/guard/is-non-null-object.mts +106 -0
  49. package/src/guard/is-non-null-object.test.mts +90 -0
  50. package/src/guard/is-primitive.mts +165 -0
  51. package/src/guard/is-primitive.test.mts +102 -0
  52. package/src/guard/is-record.mts +153 -0
  53. package/src/guard/is-record.test.mts +112 -0
  54. package/src/guard/is-type.mts +450 -0
  55. package/src/guard/is-type.test.mts +496 -0
  56. package/src/guard/key-is-in.mts +163 -0
  57. package/src/guard/key-is-in.test.mts +19 -0
  58. package/src/index.mts +10 -0
  59. package/src/iterator/index.mts +1 -0
  60. package/src/iterator/range.mts +120 -0
  61. package/src/iterator/range.test.mts +33 -0
  62. package/src/json/index.mts +1 -0
  63. package/src/json/json.mts +711 -0
  64. package/src/json/json.test.mts +628 -0
  65. package/src/number/branded-types/finite-number.mts +354 -0
  66. package/src/number/branded-types/finite-number.test.mts +135 -0
  67. package/src/number/branded-types/index.mts +26 -0
  68. package/src/number/branded-types/int.mts +278 -0
  69. package/src/number/branded-types/int.test.mts +140 -0
  70. package/src/number/branded-types/int16.mts +192 -0
  71. package/src/number/branded-types/int16.test.mts +170 -0
  72. package/src/number/branded-types/int32.mts +193 -0
  73. package/src/number/branded-types/int32.test.mts +170 -0
  74. package/src/number/branded-types/non-negative-finite-number.mts +223 -0
  75. package/src/number/branded-types/non-negative-finite-number.test.mts +188 -0
  76. package/src/number/branded-types/non-negative-int16.mts +187 -0
  77. package/src/number/branded-types/non-negative-int16.test.mts +201 -0
  78. package/src/number/branded-types/non-negative-int32.mts +187 -0
  79. package/src/number/branded-types/non-negative-int32.test.mts +204 -0
  80. package/src/number/branded-types/non-zero-finite-number.mts +229 -0
  81. package/src/number/branded-types/non-zero-finite-number.test.mts +198 -0
  82. package/src/number/branded-types/non-zero-int.mts +167 -0
  83. package/src/number/branded-types/non-zero-int.test.mts +177 -0
  84. package/src/number/branded-types/non-zero-int16.mts +196 -0
  85. package/src/number/branded-types/non-zero-int16.test.mts +195 -0
  86. package/src/number/branded-types/non-zero-int32.mts +196 -0
  87. package/src/number/branded-types/non-zero-int32.test.mts +197 -0
  88. package/src/number/branded-types/non-zero-safe-int.mts +196 -0
  89. package/src/number/branded-types/non-zero-safe-int.test.mts +232 -0
  90. package/src/number/branded-types/non-zero-uint16.mts +189 -0
  91. package/src/number/branded-types/non-zero-uint16.test.mts +199 -0
  92. package/src/number/branded-types/non-zero-uint32.mts +189 -0
  93. package/src/number/branded-types/non-zero-uint32.test.mts +199 -0
  94. package/src/number/branded-types/positive-finite-number.mts +241 -0
  95. package/src/number/branded-types/positive-finite-number.test.mts +204 -0
  96. package/src/number/branded-types/positive-int.mts +304 -0
  97. package/src/number/branded-types/positive-int.test.mts +176 -0
  98. package/src/number/branded-types/positive-int16.mts +188 -0
  99. package/src/number/branded-types/positive-int16.test.mts +197 -0
  100. package/src/number/branded-types/positive-int32.mts +188 -0
  101. package/src/number/branded-types/positive-int32.test.mts +197 -0
  102. package/src/number/branded-types/positive-safe-int.mts +187 -0
  103. package/src/number/branded-types/positive-safe-int.test.mts +210 -0
  104. package/src/number/branded-types/positive-uint16.mts +188 -0
  105. package/src/number/branded-types/positive-uint16.test.mts +203 -0
  106. package/src/number/branded-types/positive-uint32.mts +188 -0
  107. package/src/number/branded-types/positive-uint32.test.mts +203 -0
  108. package/src/number/branded-types/safe-int.mts +291 -0
  109. package/src/number/branded-types/safe-int.test.mts +170 -0
  110. package/src/number/branded-types/safe-uint.mts +187 -0
  111. package/src/number/branded-types/safe-uint.test.mts +176 -0
  112. package/src/number/branded-types/uint.mts +179 -0
  113. package/src/number/branded-types/uint.test.mts +158 -0
  114. package/src/number/branded-types/uint16.mts +186 -0
  115. package/src/number/branded-types/uint16.test.mts +170 -0
  116. package/src/number/branded-types/uint32.mts +218 -0
  117. package/src/number/branded-types/uint32.test.mts +170 -0
  118. package/src/number/enum/index.mts +2 -0
  119. package/src/number/enum/int8.mts +344 -0
  120. package/src/number/enum/int8.test.mts +180 -0
  121. package/src/number/enum/uint8.mts +293 -0
  122. package/src/number/enum/uint8.test.mts +164 -0
  123. package/src/number/index.mts +4 -0
  124. package/src/number/num.mts +604 -0
  125. package/src/number/num.test.mts +242 -0
  126. package/src/number/refined-number-utils.mts +566 -0
  127. package/src/object/index.mts +1 -0
  128. package/src/object/object.mts +447 -0
  129. package/src/object/object.test.mts +124 -0
  130. package/src/others/cast-mutable.mts +113 -0
  131. package/src/others/cast-readonly.mts +192 -0
  132. package/src/others/cast-readonly.test.mts +89 -0
  133. package/src/others/if-then.mts +98 -0
  134. package/src/others/if-then.test.mts +75 -0
  135. package/src/others/index.mts +7 -0
  136. package/src/others/map-nullable.mts +172 -0
  137. package/src/others/map-nullable.test.mts +297 -0
  138. package/src/others/memoize-function.mts +196 -0
  139. package/src/others/memoize-function.test.mts +168 -0
  140. package/src/others/tuple.mts +160 -0
  141. package/src/others/tuple.test.mts +11 -0
  142. package/src/others/unknown-to-string.mts +215 -0
  143. package/src/others/unknown-to-string.test.mts +114 -0
@@ -0,0 +1,390 @@
1
+ import { Arr } from '../array/index.mjs';
2
+ import { Optional } from '../functional/index.mjs';
3
+ import { asUint32 } from '../number/index.mjs';
4
+ import { castMutable } from '../others/index.mjs';
5
+
6
+ /**
7
+ * Interface for a high-performance queue with FIFO (First-In, First-Out) behavior.
8
+ *
9
+ * This interface defines a mutable queue data structure where elements are added to the back
10
+ * and removed from the front, maintaining the order in which they were added. The implementation
11
+ * uses a circular buffer for optimal performance, providing O(1) operations for both enqueue
12
+ * and dequeue operations.
13
+ *
14
+ * **FIFO Behavior:**
15
+ * - **enqueue**: Adds elements to the back of the queue
16
+ * - **dequeue**: Removes and returns elements from the front of the queue
17
+ * - Elements are processed in the exact order they were added
18
+ *
19
+ * **Performance Characteristics:**
20
+ * - enqueue: O(1) amortized (O(n) when buffer needs resizing)
21
+ * - dequeue: O(1) always
22
+ * - size/isEmpty: O(1) always
23
+ * - Memory efficient with automatic garbage collection of removed elements
24
+ *
25
+ * **Use Cases:**
26
+ * - Task scheduling and job queues
27
+ * - Breadth-first search algorithms
28
+ * - Event processing systems
29
+ * - Producer-consumer patterns
30
+ * - Buffer management for streaming data
31
+ *
32
+ * @template T The type of elements stored in the queue.
33
+ *
34
+ * @example
35
+ * ```typescript
36
+ * import { createQueue, Queue } from './queue';
37
+ *
38
+ * // Example 1: Basic FIFO operations
39
+ * const messageQueue: Queue<string> = createQueue<string>();
40
+ *
41
+ * messageQueue.enqueue("first message"); // Add to back
42
+ * messageQueue.enqueue("second message"); // Add to back
43
+ * messageQueue.enqueue("third message"); // Add to back
44
+ *
45
+ * console.log(messageQueue.size); // Output: 3
46
+ *
47
+ * // Process messages in FIFO order
48
+ * console.log(messageQueue.dequeue().unwrap()); // "first message" (first in, first out)
49
+ * console.log(messageQueue.dequeue().unwrap()); // "second message"
50
+ * console.log(messageQueue.size); // Output: 1
51
+ *
52
+ * // Example 2: Task processing system
53
+ * type Task = { id: number; priority: string; action: () => void };
54
+ * const taskQueue: Queue<Task> = createQueue<Task>();
55
+ *
56
+ * taskQueue.enqueue({ id: 1, priority: "high", action: () => console.log("Task 1") });
57
+ * taskQueue.enqueue({ id: 2, priority: "low", action: () => console.log("Task 2") });
58
+ *
59
+ * // Process tasks in order
60
+ * while (!taskQueue.isEmpty) {
61
+ * const task = taskQueue.dequeue().unwrap();
62
+ * console.log(`Processing task ${task.id} with ${task.priority} priority`);
63
+ * task.action();
64
+ * }
65
+ * ```
66
+ */
67
+ export type Queue<T> = Readonly<{
68
+ /** Checks if the queue is empty. */
69
+ isEmpty: boolean;
70
+
71
+ /** The number of elements in the queue. */
72
+ size: SizeType.Arr;
73
+
74
+ /**
75
+ * Removes and returns the element at the front of the queue.
76
+ * @returns The element at the front of the queue, or `Optional.none` if the queue is empty.
77
+ */
78
+ dequeue: () => Optional<T>;
79
+
80
+ /**
81
+ * Adds an element to the back of the queue.
82
+ * @param value The element to add.
83
+ */
84
+ enqueue: (value: T) => void;
85
+ }>;
86
+
87
+ /**
88
+ * Class implementation for a queue with FIFO (First-In, First-Out) behavior using a circular buffer.
89
+ * This implementation provides O(1) enqueue and dequeue operations by using a fixed-size buffer
90
+ * with head and tail pointers that wrap around when they reach the buffer boundary.
91
+ *
92
+ * The circular buffer automatically resizes when it becomes full, ensuring that the queue
93
+ * can grow to accommodate any number of elements while maintaining efficient operations.
94
+ *
95
+ * @template T The type of elements in the queue.
96
+ * @implements Queue
97
+ */
98
+ class QueueClass<T> implements Queue<T> {
99
+ /** @internal Circular buffer to store queue elements. */
100
+ #buffer: (T | undefined)[];
101
+
102
+ /** @internal Index of the first element (front of queue). */
103
+ #head: number;
104
+
105
+ /** @internal Index where the next element will be added (back of queue). */
106
+ #tail: number;
107
+
108
+ /** @internal Current number of elements in the queue. */
109
+ #mut_size: number;
110
+
111
+ /** @internal Current capacity of the buffer. */
112
+ #capacity: number;
113
+
114
+ /** @internal Initial capacity for new queues. */
115
+ static readonly #INITIAL_CAPACITY = 8;
116
+
117
+ /**
118
+ * Constructs a new QueueClass instance.
119
+ * @param initialValues Optional initial values to populate the queue.
120
+ */
121
+ constructor(initialValues: readonly T[] = []) {
122
+ const initialCapacity = asUint32(
123
+ Math.max(QueueClass.#INITIAL_CAPACITY, initialValues.length * 2),
124
+ );
125
+
126
+ this.#buffer = castMutable(
127
+ Arr.create<T | undefined>(initialCapacity, undefined),
128
+ );
129
+ this.#head = 0;
130
+ this.#tail = 0;
131
+ this.#mut_size = 0;
132
+ this.#capacity = initialCapacity;
133
+
134
+ // Add initial values
135
+ for (const value of initialValues) {
136
+ this.enqueue(value);
137
+ }
138
+ }
139
+
140
+ /** @inheritdoc */
141
+ get isEmpty(): boolean {
142
+ return this.#mut_size === 0;
143
+ }
144
+
145
+ /** @inheritdoc */
146
+ get size(): SizeType.Arr {
147
+ return asUint32(this.#mut_size);
148
+ }
149
+
150
+ /**
151
+ * Removes and returns the element at the front of the queue (FIFO).
152
+ *
153
+ * This operation removes the element that was added earliest (first-in) and returns it.
154
+ * If the queue is empty, returns `Optional.none`. The operation is guaranteed to be O(1)
155
+ * and does not require any array shifting or copying.
156
+ *
157
+ * **Time Complexity:** O(1) - constant time operation
158
+ * **Space Complexity:** O(1) - no additional memory allocation
159
+ *
160
+ * @returns An Optional containing the removed element, or `Optional.none` if the queue is empty.
161
+ *
162
+ * @example
163
+ * ```typescript
164
+ * const queue = createQueue<string>();
165
+ *
166
+ * // Add some elements
167
+ * queue.enqueue("first");
168
+ * queue.enqueue("second");
169
+ * queue.enqueue("third");
170
+ *
171
+ * // Remove elements in FIFO order
172
+ * const first = queue.dequeue();
173
+ * if (first.isSome) {
174
+ * console.log(first.value); // "first"
175
+ * }
176
+ *
177
+ * const second = queue.dequeue().unwrap(); // "second"
178
+ * console.log(queue.size); // 1
179
+ *
180
+ * // Safe handling of empty queue
181
+ * const emptyQueue = createQueue<number>();
182
+ * const result = emptyQueue.dequeue();
183
+ * if (result.isNone) {
184
+ * console.log("Queue is empty");
185
+ * }
186
+ * ```
187
+ */
188
+ dequeue(): Optional<T> {
189
+ if (this.isEmpty) {
190
+ return Optional.none;
191
+ }
192
+
193
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
194
+ const element = this.#buffer[this.#head]!;
195
+ this.#buffer[this.#head] = undefined; // Clear reference for garbage collection
196
+ this.#head = (this.#head + 1) % this.#capacity;
197
+ this.#mut_size -= 1;
198
+
199
+ return Optional.some(element);
200
+ }
201
+
202
+ /**
203
+ * Adds an element to the back of the queue (FIFO).
204
+ *
205
+ * This operation adds the element to the end of the queue, where it will be the last
206
+ * to be dequeued (first-in, first-out ordering). The operation is amortized O(1),
207
+ * meaning it's O(1) for most operations with occasional O(n) when the buffer needs resizing.
208
+ *
209
+ * **Time Complexity:** O(1) amortized - O(n) only when buffer resize is needed
210
+ * **Space Complexity:** O(1) - constant additional memory per element
211
+ *
212
+ * **Buffer Resizing:** When the internal buffer becomes full, it automatically doubles
213
+ * in size and reorganizes elements to maintain the circular buffer structure.
214
+ *
215
+ * @param value The element to add to the back of the queue.
216
+ *
217
+ * @example
218
+ * ```typescript
219
+ * const taskQueue = createQueue<string>();
220
+ *
221
+ * // Add tasks in order of arrival
222
+ * taskQueue.enqueue("Process order #1001"); // O(1)
223
+ * taskQueue.enqueue("Send notification"); // O(1)
224
+ * taskQueue.enqueue("Update inventory"); // O(1)
225
+ *
226
+ * console.log(taskQueue.size); // 3
227
+ *
228
+ * // Tasks will be processed in the order they were added
229
+ * while (!taskQueue.isEmpty) {
230
+ * const task = taskQueue.dequeue().unwrap();
231
+ * console.log(`Executing: ${task}`);
232
+ * }
233
+ *
234
+ * // High-volume enqueueing (demonstrates amortized O(1) performance)
235
+ * const dataQueue = createQueue<number>();
236
+ *
237
+ * for (let i = 0; i < 1000000; i++) {
238
+ * dataQueue.enqueue(i); // Each operation is O(1) amortized
239
+ * }
240
+ *
241
+ * console.log(dataQueue.size); // 1000000
242
+ * ```
243
+ */
244
+ enqueue(value: T): void {
245
+ // Resize if buffer is full
246
+ if (this.#mut_size === this.#capacity) {
247
+ this.#resize();
248
+ }
249
+
250
+ this.#buffer[this.#tail] = value;
251
+ this.#tail = (this.#tail + 1) % this.#capacity;
252
+ this.#mut_size += 1;
253
+ }
254
+
255
+ /**
256
+ * @internal
257
+ * Resizes the circular buffer when it becomes full.
258
+ * Doubles the capacity and reorganizes elements to maintain queue order.
259
+ */
260
+ #resize(): void {
261
+ const newCapacity = asUint32(this.#capacity * 2);
262
+ const newBuffer = castMutable(
263
+ Arr.create<T | undefined>(newCapacity, undefined),
264
+ );
265
+
266
+ // Copy elements in order from head to tail
267
+ for (let i = 0; i < this.#mut_size; i++) {
268
+ const sourceIndex = (this.#head + i) % this.#capacity;
269
+ newBuffer[i] = this.#buffer[sourceIndex];
270
+ }
271
+
272
+ this.#buffer = newBuffer;
273
+ this.#head = 0;
274
+ this.#tail = this.#mut_size;
275
+ this.#capacity = newCapacity;
276
+ }
277
+ }
278
+
279
+ /**
280
+ * Creates a new Queue instance with FIFO (First-In, First-Out) behavior using a high-performance circular buffer.
281
+ *
282
+ * This factory function creates an optimized queue implementation that maintains excellent performance
283
+ * characteristics for both enqueue and dequeue operations. The underlying circular buffer automatically
284
+ * resizes to accommodate growing workloads while providing predictable O(1) operations.
285
+ *
286
+ * **Implementation Features:**
287
+ * - **O(1) enqueue operations** (amortized - occasionally O(n) when resizing)
288
+ * - **O(1) dequeue operations** (always)
289
+ * - **Automatic buffer resizing** - starts at 8 elements, doubles when full
290
+ * - **Memory efficient** - garbage collects removed elements immediately
291
+ * - **Circular buffer design** - eliminates need for array shifting operations
292
+ *
293
+ * **Performance Benefits:**
294
+ * - No array copying during normal operations
295
+ * - Minimal memory allocation overhead
296
+ * - Predictable performance under high load
297
+ * - Efficient memory usage with automatic cleanup
298
+ *
299
+ * @template T The type of elements stored in the queue.
300
+ * @param initialValues Optional array of initial elements to populate the queue.
301
+ * Elements will be dequeued in the same order they appear in the array.
302
+ * If provided, the initial buffer capacity will be at least twice the array length.
303
+ * @returns A new Queue instance optimized for high-performance FIFO operations.
304
+ *
305
+ * @example
306
+ * ```typescript
307
+ * import { createQueue } from './queue';
308
+ *
309
+ * // Example 1: Basic FIFO workflow
310
+ * const requestQueue = createQueue<string>();
311
+ *
312
+ * // Add requests to the queue
313
+ * requestQueue.enqueue("GET /api/users"); // O(1)
314
+ * requestQueue.enqueue("POST /api/orders"); // O(1)
315
+ * requestQueue.enqueue("DELETE /api/cache"); // O(1)
316
+ *
317
+ * // Process requests in order
318
+ * while (!requestQueue.isEmpty) {
319
+ * const request = requestQueue.dequeue().unwrap(); // O(1)
320
+ * console.log(`Processing: ${request}`);
321
+ * }
322
+ * // Output:
323
+ * // Processing: GET /api/users
324
+ * // Processing: POST /api/orders
325
+ * // Processing: DELETE /api/cache
326
+ *
327
+ * // Example 2: High-throughput event processing
328
+ * type Event = { timestamp: number; type: string; data: any };
329
+ * const eventQueue = createQueue<Event>();
330
+ *
331
+ * // Simulate high-volume event ingestion
332
+ * for (let i = 0; i < 10000; i++) {
333
+ * eventQueue.enqueue({
334
+ * timestamp: Date.now(),
335
+ * type: `event-${i % 5}`,
336
+ * data: { value: i }
337
+ * }); // Each enqueue is O(1) amortized
338
+ * }
339
+ *
340
+ * // Process events efficiently
341
+ * let processedCount = 0;
342
+ * while (!eventQueue.isEmpty) {
343
+ * const event = eventQueue.dequeue().unwrap(); // O(1)
344
+ * // Process event...
345
+ * processedCount++;
346
+ * }
347
+ * console.log(`Processed ${processedCount} events`); // 10000
348
+ *
349
+ * // Example 3: Queue with pre-populated data
350
+ * const priorityTasks = createQueue<string>([
351
+ * "Initialize system",
352
+ * "Load configuration",
353
+ * "Start services",
354
+ * "Begin processing"
355
+ * ]);
356
+ *
357
+ * console.log(priorityTasks.size); // Output: 4
358
+ *
359
+ * // Execute tasks in initialization order
360
+ * while (!priorityTasks.isEmpty) {
361
+ * const task = priorityTasks.dequeue().unwrap();
362
+ * console.log(`Executing: ${task}`);
363
+ * }
364
+ *
365
+ * // Example 4: Producer-Consumer pattern
366
+ * const workQueue = createQueue<() => Promise<void>>();
367
+ *
368
+ * // Producer: Add work items
369
+ * const addWork = (workFn: () => Promise<void>) => {
370
+ * workQueue.enqueue(workFn);
371
+ * };
372
+ *
373
+ * // Consumer: Process work items
374
+ * const processWork = async () => {
375
+ * while (!workQueue.isEmpty) {
376
+ * const workItem = workQueue.dequeue().unwrap();
377
+ * await workItem();
378
+ * }
379
+ * };
380
+ *
381
+ * // Add some work
382
+ * addWork(async () => console.log("Work item 1"));
383
+ * addWork(async () => console.log("Work item 2"));
384
+ *
385
+ * // Process the work
386
+ * await processWork();
387
+ * ```
388
+ */
389
+ export const createQueue = <T,>(initialValues?: readonly T[]): Queue<T> =>
390
+ new QueueClass<T>(initialValues);
@@ -0,0 +1,282 @@
1
+ import { Arr } from '../array/index.mjs';
2
+ import { Optional } from '../functional/index.mjs';
3
+ import { asUint32 } from '../number/index.mjs';
4
+ import { createQueue, type Queue } from './queue.mjs';
5
+
6
+ describe('Queue', () => {
7
+ describe('initialization', () => {
8
+ test('should be empty if initialized without values', () => {
9
+ const q = createQueue();
10
+ expect(q.isEmpty).toBe(true);
11
+ expect(q.size).toBe(0);
12
+ });
13
+
14
+ test('should not be empty if initialized with values', () => {
15
+ const q = createQueue([1, 2, 3]);
16
+ expect(q.isEmpty).toBe(false);
17
+ expect(q.size).toBe(3);
18
+ });
19
+ });
20
+
21
+ describe('enqueue', () => {
22
+ let q: Queue<number>;
23
+
24
+ beforeEach(() => {
25
+ q = createQueue();
26
+ });
27
+
28
+ test('should increase size and not be empty after enqueueing to an empty queue', () => {
29
+ q.enqueue(1);
30
+ expect(q.isEmpty).toBe(false);
31
+ expect(q.size).toBe(1);
32
+ });
33
+
34
+ test('should increase size when enqueueing to a non-empty queue', () => {
35
+ q.enqueue(1);
36
+ q.enqueue(2);
37
+ expect(q.size).toBe(2);
38
+ });
39
+ });
40
+
41
+ describe('dequeue', () => {
42
+ test('should return Optional.none and size should be 0 when dequeuing from an empty queue', () => {
43
+ const q = createQueue<number>();
44
+ const result = q.dequeue();
45
+ expect(Optional.isNone(result)).toBe(true);
46
+ expect(q.isEmpty).toBe(true);
47
+ expect(q.size).toBe(0);
48
+ });
49
+
50
+ test('should decrease size and return the dequeued element for a non-empty queue', () => {
51
+ const q = createQueue([1, 2, 3]); // FIFO: elements are in order [1, 2, 3]
52
+ const initialSize = q.size;
53
+
54
+ const result1 = q.dequeue(); // Dequeues 1 (first element)
55
+ expect(Optional.isSome(result1)).toBe(true);
56
+ if (Optional.isSome(result1)) {
57
+ expect(result1.value).toBe(1);
58
+ }
59
+ expect(q.size).toBe(initialSize - 1);
60
+
61
+ const result2 = q.dequeue(); // Dequeues 2
62
+ expect(Optional.isSome(result2)).toBe(true);
63
+ if (Optional.isSome(result2)) {
64
+ expect(result2.value).toBe(2);
65
+ }
66
+ expect(q.size).toBe(initialSize - 2);
67
+ });
68
+
69
+ test('should become empty after dequeuing all elements', () => {
70
+ const q = createQueue([1, 2]); // Internal: [1, 2]
71
+ q.dequeue(); // Dequeues 1
72
+ q.dequeue(); // Dequeues 2
73
+ expect(q.isEmpty).toBe(true);
74
+ expect(q.size).toBe(0);
75
+ const result = q.dequeue(); // Dequeue from empty
76
+ expect(Optional.isNone(result)).toBe(true);
77
+ });
78
+ });
79
+
80
+ describe('FIFO behavior', () => {
81
+ test('elements should be dequeued in first-in, first-out order', () => {
82
+ const q = createQueue<number>();
83
+ q.enqueue(1); // internal: [1]
84
+ q.enqueue(2); // internal: [1, 2]
85
+ q.enqueue(3); // internal: [1, 2, 3]
86
+
87
+ expect(q.size).toBe(3);
88
+
89
+ let result = q.dequeue(); // Dequeues 1 (first in)
90
+ expect(Optional.isSome(result) && result.value === 1).toBe(true);
91
+ expect(q.size).toBe(2);
92
+
93
+ result = q.dequeue(); // Dequeues 2
94
+ expect(Optional.isSome(result) && result.value === 2).toBe(true);
95
+ expect(q.size).toBe(1);
96
+
97
+ result = q.dequeue(); // Dequeues 3
98
+ expect(Optional.isSome(result) && result.value === 3).toBe(true);
99
+ expect(q.size).toBe(0);
100
+ expect(q.isEmpty).toBe(true);
101
+
102
+ result = q.dequeue();
103
+ expect(Optional.isNone(result)).toBe(true);
104
+ });
105
+
106
+ test('initial values are dequeued in the same order (FIFO)', () => {
107
+ const q = createQueue([1, 2, 3]); // Internal: [1, 2, 3]
108
+ expect(q.size).toBe(3);
109
+
110
+ let result = q.dequeue(); // Dequeues 1 (first element)
111
+ expect(Optional.isSome(result) && result.value === 1).toBe(true);
112
+
113
+ result = q.dequeue(); // Dequeues 2
114
+ expect(Optional.isSome(result) && result.value === 2).toBe(true);
115
+
116
+ result = q.dequeue(); // Dequeues 3
117
+ expect(Optional.isSome(result) && result.value === 3).toBe(true);
118
+
119
+ expect(q.isEmpty).toBe(true);
120
+ });
121
+
122
+ test('mixed enqueue and dequeue operations maintain FIFO order', () => {
123
+ const q = createQueue<string>();
124
+
125
+ q.enqueue('A');
126
+ q.enqueue('B');
127
+
128
+ let result = q.dequeue();
129
+ expect(Optional.isSome(result) && result.value === 'A').toBe(true);
130
+
131
+ q.enqueue('C');
132
+ q.enqueue('D');
133
+
134
+ result = q.dequeue();
135
+ expect(Optional.isSome(result) && result.value === 'B').toBe(true);
136
+
137
+ result = q.dequeue();
138
+ expect(Optional.isSome(result) && result.value === 'C').toBe(true);
139
+
140
+ result = q.dequeue();
141
+ expect(Optional.isSome(result) && result.value === 'D').toBe(true);
142
+
143
+ expect(q.isEmpty).toBe(true);
144
+ });
145
+ });
146
+
147
+ describe('Circular buffer behavior', () => {
148
+ test('should handle buffer wraparound correctly', () => {
149
+ const q = createQueue<number>();
150
+
151
+ // Fill and partially empty the queue to create wraparound conditions
152
+ for (let i = 1; i <= 5; i++) {
153
+ q.enqueue(i);
154
+ }
155
+
156
+ // Remove first 3 elements
157
+ expect(Optional.unwrap(q.dequeue())).toBe(1);
158
+ expect(Optional.unwrap(q.dequeue())).toBe(2);
159
+ expect(Optional.unwrap(q.dequeue())).toBe(3);
160
+
161
+ // Add more elements (this should wrap around in the buffer)
162
+ q.enqueue(6);
163
+ q.enqueue(7);
164
+ q.enqueue(8);
165
+
166
+ // Verify FIFO order is maintained
167
+ expect(Optional.unwrap(q.dequeue())).toBe(4);
168
+ expect(Optional.unwrap(q.dequeue())).toBe(5);
169
+ expect(Optional.unwrap(q.dequeue())).toBe(6);
170
+ expect(Optional.unwrap(q.dequeue())).toBe(7);
171
+ expect(Optional.unwrap(q.dequeue())).toBe(8);
172
+
173
+ expect(q.isEmpty).toBe(true);
174
+ });
175
+
176
+ test('should automatically resize when buffer becomes full', () => {
177
+ const q = createQueue<number>();
178
+
179
+ // Add more elements than initial capacity (8) to trigger resize
180
+ for (let i = 1; i <= 20; i++) {
181
+ q.enqueue(i);
182
+ }
183
+
184
+ expect(q.size).toBe(20);
185
+
186
+ // Verify all elements can be dequeued in correct order
187
+ for (let i = 1; i <= 20; i++) {
188
+ const result = q.dequeue();
189
+ expect(Optional.isSome(result) && result.value === i).toBe(true);
190
+ }
191
+
192
+ expect(q.isEmpty).toBe(true);
193
+ });
194
+
195
+ test('should handle multiple resize operations', () => {
196
+ const q = createQueue<number>();
197
+
198
+ // Add many elements to trigger multiple resizes
199
+ for (let i = 1; i <= 100; i++) {
200
+ q.enqueue(i);
201
+ }
202
+
203
+ expect(q.size).toBe(100);
204
+
205
+ // Remove half
206
+ for (let i = 1; i <= 50; i++) {
207
+ expect(Optional.unwrap(q.dequeue())).toBe(i);
208
+ }
209
+
210
+ // Add more to trigger another resize
211
+ for (let i = 101; i <= 150; i++) {
212
+ q.enqueue(i);
213
+ }
214
+
215
+ expect(q.size).toBe(100); // 50 remaining + 50 new
216
+
217
+ // Verify correct order
218
+ for (let i = 51; i <= 100; i++) {
219
+ expect(Optional.unwrap(q.dequeue())).toBe(i);
220
+ }
221
+ for (let i = 101; i <= 150; i++) {
222
+ expect(Optional.unwrap(q.dequeue())).toBe(i);
223
+ }
224
+
225
+ expect(q.isEmpty).toBe(true);
226
+ });
227
+
228
+ test('should handle edge case of single element operations', () => {
229
+ const q = createQueue<string>();
230
+
231
+ // Test single element enqueue/dequeue cycles
232
+ for (let i = 0; i < 10; i++) {
233
+ q.enqueue(`item-${i}`);
234
+ expect(q.size).toBe(1);
235
+
236
+ const result = q.dequeue();
237
+ expect(Optional.isSome(result) && result.value === `item-${i}`).toBe(
238
+ true,
239
+ );
240
+ expect(q.isEmpty).toBe(true);
241
+ }
242
+ });
243
+
244
+ test('should handle large initial values array', () => {
245
+ const largeArray = Arr.range(1, asUint32(51));
246
+ const q = createQueue(largeArray);
247
+
248
+ expect(q.size).toBe(50);
249
+
250
+ // Verify all elements are in correct order
251
+ for (let i = 1; i <= 50; i++) {
252
+ expect(Optional.unwrap(q.dequeue())).toBe(i);
253
+ }
254
+
255
+ expect(q.isEmpty).toBe(true);
256
+ });
257
+
258
+ test('should properly clean up references for garbage collection', () => {
259
+ const q = createQueue<{ id: number }>();
260
+ const objects = Arr.seq(10).map((i) => ({ id: i }));
261
+
262
+ // Add all objects
263
+ objects.forEach((obj) => {
264
+ q.enqueue(obj);
265
+ });
266
+
267
+ // Remove first 5 objects
268
+ for (let i = 0; i < 5; i++) {
269
+ const result = q.dequeue();
270
+ expect(Optional.isSome(result) && result.value.id === i).toBe(true);
271
+ }
272
+
273
+ // Remaining objects should still be accessible
274
+ for (let i = 5; i < 10; i++) {
275
+ const result = q.dequeue();
276
+ expect(Optional.isSome(result) && result.value.id === i).toBe(true);
277
+ }
278
+
279
+ expect(q.isEmpty).toBe(true);
280
+ });
281
+ });
282
+ });