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,423 @@
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 stack with LIFO (Last-In, First-Out) behavior.
8
+ *
9
+ * This interface defines a mutable stack data structure where elements are added to and removed
10
+ * from the top, following the Last-In, First-Out principle. The implementation uses a dynamic
11
+ * array for optimal performance, providing O(1) operations for both push and pop operations.
12
+ *
13
+ * **LIFO Behavior:**
14
+ * - **push**: Adds elements to the top of the stack
15
+ * - **pop**: Removes and returns elements from the top of the stack
16
+ * - The last element added is the first element to be removed
17
+ *
18
+ * **Performance Characteristics:**
19
+ * - push: O(1) amortized (O(n) when buffer needs resizing)
20
+ * - pop: O(1) always
21
+ * - size/isEmpty: O(1) always
22
+ * - Memory efficient with automatic garbage collection of removed elements
23
+ *
24
+ * **Use Cases:**
25
+ * - Function call management and recursion
26
+ * - Undo/redo functionality
27
+ * - Expression evaluation and parsing
28
+ * - Depth-first search algorithms
29
+ * - Backtracking algorithms
30
+ * - Browser history management
31
+ *
32
+ * @template T The type of elements stored in the stack.
33
+ *
34
+ * @example
35
+ * ```typescript
36
+ * import { createStack, Stack } from './stack';
37
+ *
38
+ * // Example 1: Basic LIFO operations
39
+ * const operationStack: Stack<string> = createStack<string>();
40
+ *
41
+ * operationStack.push("operation1"); // Add to top
42
+ * operationStack.push("operation2"); // Add to top
43
+ * operationStack.push("operation3"); // Add to top
44
+ *
45
+ * console.log(operationStack.size); // Output: 3
46
+ *
47
+ * // Process operations in LIFO order
48
+ * console.log(operationStack.pop().unwrap()); // "operation3" (last in, first out)
49
+ * console.log(operationStack.pop().unwrap()); // "operation2"
50
+ * console.log(operationStack.size); // Output: 1
51
+ *
52
+ * // Example 2: Undo functionality
53
+ * type Action = { type: string; data: any; timestamp: number };
54
+ * const undoStack: Stack<Action> = createStack<Action>();
55
+ *
56
+ * undoStack.push({ type: "delete", data: { id: 123 }, timestamp: Date.now() });
57
+ * undoStack.push({ type: "edit", data: { field: "name", oldValue: "old" }, timestamp: Date.now() });
58
+ *
59
+ * // Undo last action
60
+ * if (!undoStack.isEmpty) {
61
+ * const lastAction = undoStack.pop().unwrap();
62
+ * console.log(`Undoing: ${lastAction.type}`);
63
+ * }
64
+ * ```
65
+ */
66
+ export type Stack<T> = Readonly<{
67
+ /** Checks if the stack is empty. */
68
+ isEmpty: boolean;
69
+
70
+ /** The number of elements in the stack. */
71
+ size: SizeType.Arr;
72
+
73
+ /**
74
+ * Removes and returns the element at the top of the stack.
75
+ * @returns The element at the top of the stack, or `Optional.none` if the stack is empty.
76
+ */
77
+ pop: () => Optional<T>;
78
+
79
+ /**
80
+ * Adds an element to the top of the stack.
81
+ * @param value The element to add.
82
+ */
83
+ push: (value: T) => void;
84
+ }>;
85
+
86
+ /**
87
+ * Class implementation for a stack with LIFO (Last-In, First-Out) behavior using a dynamic array.
88
+ * This implementation provides O(1) amortized push and O(1) pop operations by using a resizable buffer
89
+ * that grows as needed.
90
+ *
91
+ * The underlying array automatically resizes when it becomes full, ensuring that the stack
92
+ * can grow to accommodate any number of elements while maintaining efficient operations.
93
+ *
94
+ * @template T The type of elements in the stack.
95
+ * @implements Stack
96
+ */
97
+ class StackClass<T> implements Stack<T> {
98
+ /** @internal Dynamic array to store stack elements. */
99
+ #buffer: (T | undefined)[];
100
+
101
+ /** @internal Current number of elements in the stack. */
102
+ #mut_size: number;
103
+
104
+ /** @internal Current capacity of the buffer. */
105
+ #capacity: number;
106
+
107
+ /** @internal Initial capacity for new stacks. */
108
+ static readonly #INITIAL_CAPACITY = 8;
109
+
110
+ /**
111
+ * Constructs a new StackClass instance.
112
+ * @param initialValues Optional initial values to populate the stack.
113
+ */
114
+ constructor(initialValues: readonly T[] = []) {
115
+ const initialCapacity = asUint32(
116
+ Math.max(StackClass.#INITIAL_CAPACITY, initialValues.length * 2),
117
+ );
118
+
119
+ this.#buffer = castMutable(
120
+ Arr.create<T | undefined>(initialCapacity, undefined),
121
+ );
122
+ this.#mut_size = 0;
123
+ this.#capacity = initialCapacity;
124
+
125
+ // Add initial values
126
+ for (const value of initialValues) {
127
+ this.push(value);
128
+ }
129
+ }
130
+
131
+ /** @inheritdoc */
132
+ get isEmpty(): boolean {
133
+ return this.#mut_size === 0;
134
+ }
135
+
136
+ /** @inheritdoc */
137
+ get size(): SizeType.Arr {
138
+ return asUint32(this.#mut_size);
139
+ }
140
+
141
+ /**
142
+ * Removes and returns the element at the top of the stack (LIFO).
143
+ *
144
+ * This operation removes the element that was added most recently (last-in) and returns it.
145
+ * If the stack is empty, returns `Optional.none`. The operation is guaranteed to be O(1)
146
+ * and does not require any array resizing or memory reallocation.
147
+ *
148
+ * **Time Complexity:** O(1) - constant time operation
149
+ * **Space Complexity:** O(1) - no additional memory allocation
150
+ *
151
+ * @returns An Optional containing the removed element, or `Optional.none` if the stack is empty.
152
+ *
153
+ * @example
154
+ * ```typescript
155
+ * const stack = createStack<string>();
156
+ *
157
+ * // Add some elements
158
+ * stack.push("bottom");
159
+ * stack.push("middle");
160
+ * stack.push("top");
161
+ *
162
+ * // Remove elements in LIFO order
163
+ * const top = stack.pop();
164
+ * if (top.isSome) {
165
+ * console.log(top.value); // "top" (last pushed, first popped)
166
+ * }
167
+ *
168
+ * const middle = stack.pop().unwrap(); // "middle"
169
+ * console.log(stack.size); // 1
170
+ *
171
+ * // Safe handling of empty stack
172
+ * const emptyStack = createStack<number>();
173
+ * const result = emptyStack.pop();
174
+ * if (result.isNone) {
175
+ * console.log("Stack is empty");
176
+ * }
177
+ *
178
+ * // Typical usage in algorithms
179
+ * const pathStack = createStack<string>();
180
+ * pathStack.push("/home");
181
+ * pathStack.push("/users");
182
+ * pathStack.push("/documents");
183
+ *
184
+ * // Backtrack one level
185
+ * const currentDir = pathStack.pop().unwrap(); // "/documents"
186
+ * const parentDir = pathStack.pop().unwrap(); // "/users"
187
+ * ```
188
+ */
189
+ pop(): Optional<T> {
190
+ if (this.isEmpty) {
191
+ return Optional.none;
192
+ }
193
+
194
+ this.#mut_size -= 1;
195
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
196
+ const element = this.#buffer[this.#mut_size]!;
197
+ this.#buffer[this.#mut_size] = undefined; // Clear reference for garbage collection
198
+
199
+ return Optional.some(element);
200
+ }
201
+
202
+ /**
203
+ * Adds an element to the top of the stack (LIFO).
204
+ *
205
+ * This operation adds the element to the top of the stack, where it will be the first
206
+ * to be popped (last-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 copies existing elements to maintain the stack structure.
214
+ *
215
+ * @param value The element to add to the top of the stack.
216
+ *
217
+ * @example
218
+ * ```typescript
219
+ * const actionStack = createStack<string>();
220
+ *
221
+ * // Add actions in chronological order
222
+ * actionStack.push("open file"); // O(1)
223
+ * actionStack.push("edit content"); // O(1)
224
+ * actionStack.push("save file"); // O(1)
225
+ *
226
+ * console.log(actionStack.size); // 3
227
+ *
228
+ * // Actions will be undone in reverse order (LIFO)
229
+ * while (!actionStack.isEmpty) {
230
+ * const action = actionStack.pop().unwrap();
231
+ * console.log(`Undoing: ${action}`);
232
+ * }
233
+ * // Output:
234
+ * // Undoing: save file
235
+ * // Undoing: edit content
236
+ * // Undoing: open file
237
+ *
238
+ * // High-volume pushing (demonstrates amortized O(1) performance)
239
+ * const dataStack = createStack<number>();
240
+ *
241
+ * for (let i = 0; i < 1000000; i++) {
242
+ * dataStack.push(i); // Each operation is O(1) amortized
243
+ * }
244
+ *
245
+ * console.log(dataStack.size); // 1000000
246
+ *
247
+ * // Function call stack simulation
248
+ * type StackFrame = { function: string; variables: Record<string, any> };
249
+ * const callStack = createStack<StackFrame>();
250
+ *
251
+ * callStack.push({ function: "main", variables: { argc: 1, argv: ["program"] } });
252
+ * callStack.push({ function: "process", variables: { data: [1, 2, 3] } });
253
+ * callStack.push({ function: "validate", variables: { input: "test" } });
254
+ *
255
+ * // Current function context is at the top
256
+ * const currentFrame = callStack.pop().unwrap();
257
+ * console.log(`Current function: ${currentFrame.function}`);
258
+ * ```
259
+ */
260
+ push(value: T): void {
261
+ // Resize if buffer is full
262
+ if (this.#mut_size === this.#capacity) {
263
+ this.#resize();
264
+ }
265
+
266
+ this.#buffer[this.#mut_size] = value;
267
+ this.#mut_size += 1;
268
+ }
269
+
270
+ /**
271
+ * @internal
272
+ * Resizes the buffer when it becomes full.
273
+ * Doubles the capacity while preserving all elements.
274
+ */
275
+ #resize(): void {
276
+ const newCapacity = asUint32(this.#capacity * 2);
277
+ const newBuffer = castMutable(
278
+ Arr.create<T | undefined>(newCapacity, undefined),
279
+ );
280
+
281
+ // Copy existing elements
282
+ for (let i = 0; i < this.#mut_size; i++) {
283
+ newBuffer[i] = this.#buffer[i];
284
+ }
285
+
286
+ this.#buffer = newBuffer;
287
+ this.#capacity = newCapacity;
288
+ }
289
+ }
290
+
291
+ /**
292
+ * Creates a new Stack instance with LIFO (Last-In, First-Out) behavior using a high-performance dynamic array.
293
+ *
294
+ * This factory function creates an optimized stack implementation that maintains excellent performance
295
+ * characteristics for both push and pop operations. The underlying dynamic array automatically
296
+ * resizes to accommodate growing workloads while providing predictable O(1) operations.
297
+ *
298
+ * **Implementation Features:**
299
+ * - **O(1) push operations** (amortized - occasionally O(n) when resizing)
300
+ * - **O(1) pop operations** (always)
301
+ * - **Automatic buffer resizing** - starts at 8 elements, doubles when full
302
+ * - **Memory efficient** - garbage collects removed elements immediately
303
+ * - **Dynamic array design** - eliminates need for complex memory management
304
+ *
305
+ * **Performance Benefits:**
306
+ * - No array shifting required for stack operations
307
+ * - Minimal memory allocation overhead
308
+ * - Predictable performance under high load
309
+ * - Efficient memory usage with automatic cleanup
310
+ *
311
+ * @template T The type of elements stored in the stack.
312
+ * @param initialValues Optional array of initial elements to populate the stack.
313
+ * Elements will be popped in reverse order of how they appear in the array
314
+ * (last array element will be popped first).
315
+ * If provided, the initial buffer capacity will be at least twice the array length.
316
+ * @returns A new Stack instance optimized for high-performance LIFO operations.
317
+ *
318
+ * @example
319
+ * ```typescript
320
+ * import { createStack } from './stack';
321
+ *
322
+ * // Example 1: Function call simulation
323
+ * type FunctionCall = { name: string; args: any[]; context: any };
324
+ * const callStack = createStack<FunctionCall>();
325
+ *
326
+ * // Simulate function calls (push onto stack)
327
+ * callStack.push({ name: "main", args: [], context: {} }); // O(1)
328
+ * callStack.push({ name: "processData", args: [data], context: {} }); // O(1)
329
+ * callStack.push({ name: "validateInput", args: [input], context: {} }); // O(1)
330
+ *
331
+ * // Simulate function returns (pop from stack)
332
+ * while (!callStack.isEmpty) {
333
+ * const call = callStack.pop().unwrap(); // O(1)
334
+ * console.log(`Returning from: ${call.name}`);
335
+ * }
336
+ * // Output:
337
+ * // Returning from: validateInput
338
+ * // Returning from: processData
339
+ * // Returning from: main
340
+ *
341
+ * // Example 2: Expression evaluation with operator precedence
342
+ * const operatorStack = createStack<string>();
343
+ * const operandStack = createStack<number>();
344
+ *
345
+ * // Simulate parsing "3 + 4 * 2"
346
+ * operandStack.push(3);
347
+ * operatorStack.push("+");
348
+ * operandStack.push(4);
349
+ * operatorStack.push("*"); // Higher precedence
350
+ * operandStack.push(2);
351
+ *
352
+ * // Process higher precedence first (LIFO)
353
+ * const op = operatorStack.pop().unwrap(); // "*"
354
+ * const b = operandStack.pop().unwrap(); // 2
355
+ * const a = operandStack.pop().unwrap(); // 4
356
+ * operandStack.push(a * b); // Push result: 8
357
+ *
358
+ * // Example 3: Undo/Redo functionality
359
+ * type EditAction = {
360
+ * type: 'insert' | 'delete' | 'modify';
361
+ * position: number;
362
+ * oldValue: string;
363
+ * newValue: string;
364
+ * };
365
+ *
366
+ * const undoStack = createStack<EditAction>();
367
+ * const redoStack = createStack<EditAction>();
368
+ *
369
+ * // Perform edits (push to undo stack)
370
+ * const edit1: EditAction = { type: 'insert', position: 0, oldValue: '', newValue: 'Hello' };
371
+ * const edit2: EditAction = { type: 'insert', position: 5, oldValue: '', newValue: ' World' };
372
+ *
373
+ * undoStack.push(edit1);
374
+ * undoStack.push(edit2);
375
+ *
376
+ * // Undo last edit
377
+ * if (!undoStack.isEmpty) {
378
+ * const lastEdit = undoStack.pop().unwrap();
379
+ * redoStack.push(lastEdit);
380
+ * console.log(`Undid: ${lastEdit.type} at position ${lastEdit.position}`);
381
+ * }
382
+ *
383
+ * // Example 4: High-throughput data processing
384
+ * const processingStack = createStack<number>();
385
+ *
386
+ * // Add large amount of data (demonstrates amortized O(1) performance)
387
+ * for (let i = 0; i < 100000; i++) {
388
+ * processingStack.push(i); // Each push is O(1) amortized
389
+ * }
390
+ *
391
+ * // Process data in LIFO order
392
+ * let processedCount = 0;
393
+ * while (!processingStack.isEmpty) {
394
+ * const value = processingStack.pop().unwrap(); // O(1)
395
+ * // Process value...
396
+ * processedCount++;
397
+ * }
398
+ * console.log(`Processed ${processedCount} items`); // 100000
399
+ *
400
+ * // Example 5: Stack with pre-populated data
401
+ * const historyStack = createStack<string>([
402
+ * "page1.html",
403
+ * "page2.html",
404
+ * "page3.html",
405
+ * "page4.html"
406
+ * ]);
407
+ *
408
+ * console.log(historyStack.size); // Output: 4
409
+ *
410
+ * // Navigate back through history (LIFO order)
411
+ * while (!historyStack.isEmpty) {
412
+ * const page = historyStack.pop().unwrap();
413
+ * console.log(`Going back to: ${page}`);
414
+ * }
415
+ * // Output:
416
+ * // Going back to: page4.html (last added, first removed)
417
+ * // Going back to: page3.html
418
+ * // Going back to: page2.html
419
+ * // Going back to: page1.html
420
+ * ```
421
+ */
422
+ export const createStack = <T,>(initialValues?: readonly T[]): Stack<T> =>
423
+ new StackClass<T>(initialValues);
@@ -0,0 +1,225 @@
1
+ import { expectType } from '../expect-type.mjs';
2
+ import { Optional } from '../functional/index.mjs';
3
+ import { createStack, type Stack } from './stack.mjs';
4
+
5
+ describe('Stack', () => {
6
+ test('should have correct type definitions', () => {
7
+ const stack = createStack<number>();
8
+
9
+ expectType<typeof stack, Stack<number>>('=');
10
+ expectType<typeof stack.isEmpty, boolean>('=');
11
+ expectType<typeof stack.size, SizeType.Arr>('=');
12
+ expectType<typeof stack.pop, () => Optional<number>>('=');
13
+ expectType<typeof stack.push, (value: number) => void>('=');
14
+
15
+ // Verify the type checking works at runtime too
16
+ expect(typeof stack.isEmpty).toBe('boolean');
17
+ expect(typeof stack.size).toBe('number');
18
+ expect(typeof stack.pop).toBe('function');
19
+ expect(typeof stack.push).toBe('function');
20
+ });
21
+
22
+ test('should be empty when created without initial values', () => {
23
+ const stack = createStack<string>();
24
+
25
+ expect(stack.isEmpty).toBe(true);
26
+ expect(stack.size).toBe(0);
27
+ });
28
+
29
+ test('should create with initial values', () => {
30
+ const stack = createStack<number>([1, 2, 3]);
31
+
32
+ expect(stack.isEmpty).toBe(false);
33
+ expect(stack.size).toBe(3);
34
+
35
+ // Last element should be on top (LIFO)
36
+ expect(Optional.unwrap(stack.pop())).toBe(3);
37
+ expect(Optional.unwrap(stack.pop())).toBe(2);
38
+ expect(Optional.unwrap(stack.pop())).toBe(1);
39
+ expect(stack.isEmpty).toBe(true);
40
+ });
41
+
42
+ test('should implement LIFO behavior correctly', () => {
43
+ const stack = createStack<string>();
44
+
45
+ stack.push('first');
46
+ stack.push('second');
47
+ stack.push('third');
48
+
49
+ expect(stack.size).toBe(3);
50
+ expect(stack.isEmpty).toBe(false);
51
+
52
+ // LIFO: Last In, First Out
53
+ expect(Optional.unwrap(stack.pop())).toBe('third');
54
+ expect(Optional.unwrap(stack.pop())).toBe('second');
55
+ expect(Optional.unwrap(stack.pop())).toBe('first');
56
+
57
+ expect(stack.isEmpty).toBe(true);
58
+ expect(stack.size).toBe(0);
59
+ });
60
+
61
+ test('should return none when popping from empty stack', () => {
62
+ const stack = createStack<number>();
63
+
64
+ const result = stack.pop();
65
+ expect(Optional.isNone(result)).toBe(true);
66
+ expect(stack.isEmpty).toBe(true);
67
+ expect(stack.size).toBe(0);
68
+ });
69
+
70
+ test('should maintain size correctly during operations', () => {
71
+ const stack = createStack<number>();
72
+
73
+ expect(stack.size).toBe(0);
74
+
75
+ stack.push(1);
76
+ expect(stack.size).toBe(1);
77
+
78
+ stack.push(2);
79
+ expect(stack.size).toBe(2);
80
+
81
+ stack.push(3);
82
+ expect(stack.size).toBe(3);
83
+
84
+ Optional.unwrap(stack.pop());
85
+ expect(stack.size).toBe(2);
86
+
87
+ Optional.unwrap(stack.pop());
88
+ expect(stack.size).toBe(1);
89
+
90
+ Optional.unwrap(stack.pop());
91
+ expect(stack.size).toBe(0);
92
+ expect(stack.isEmpty).toBe(true);
93
+ });
94
+
95
+ test('should handle mixed push and pop operations', () => {
96
+ const stack = createStack<string>();
97
+
98
+ stack.push('a');
99
+ stack.push('b');
100
+ expect(Optional.unwrap(stack.pop())).toBe('b');
101
+
102
+ stack.push('c');
103
+ stack.push('d');
104
+ expect(Optional.unwrap(stack.pop())).toBe('d');
105
+ expect(Optional.unwrap(stack.pop())).toBe('c');
106
+ expect(Optional.unwrap(stack.pop())).toBe('a');
107
+
108
+ expect(stack.isEmpty).toBe(true);
109
+ });
110
+
111
+ test('should work with object types', () => {
112
+ type Item = Readonly<{ id: number; name: string }>;
113
+ const stack = createStack<Item>();
114
+
115
+ const item1: Item = { id: 1, name: 'first' };
116
+ const item2: Item = { id: 2, name: 'second' };
117
+
118
+ stack.push(item1);
119
+ stack.push(item2);
120
+
121
+ expect(Optional.unwrap(stack.pop())).toStrictEqual(item2);
122
+ expect(Optional.unwrap(stack.pop())).toStrictEqual(item1);
123
+ expect(stack.isEmpty).toBe(true);
124
+ });
125
+
126
+ test('should handle large number of operations efficiently', () => {
127
+ const stack = createStack<number>();
128
+ const n = 10000;
129
+
130
+ // Push many elements
131
+ for (let i = 0; i < n; i++) {
132
+ stack.push(i);
133
+ }
134
+
135
+ expect(stack.size).toBe(n);
136
+ expect(stack.isEmpty).toBe(false);
137
+
138
+ // Pop all elements and verify LIFO order
139
+ for (let i = n - 1; i >= 0; i--) {
140
+ const result = stack.pop();
141
+ expect(Optional.isSome(result)).toBe(true);
142
+ expect(Optional.unwrap(result)).toBe(i);
143
+ }
144
+
145
+ expect(stack.isEmpty).toBe(true);
146
+ expect(stack.size).toBe(0);
147
+ });
148
+
149
+ test('should not mutate initial values array', () => {
150
+ const initialValues = [1, 2, 3];
151
+ const originalLength = initialValues.length;
152
+
153
+ const stack = createStack(initialValues);
154
+
155
+ // Modify stack
156
+ stack.push(4);
157
+ Optional.unwrap(stack.pop());
158
+
159
+ // Original array should be unchanged
160
+ expect(initialValues.length).toBe(originalLength);
161
+ expect(initialValues).toStrictEqual([1, 2, 3]);
162
+ });
163
+
164
+ test('should work with undefined and null values', () => {
165
+ const stack = createStack<string | null | undefined>();
166
+
167
+ stack.push('value');
168
+ stack.push(null);
169
+ stack.push(undefined);
170
+ stack.push('another');
171
+
172
+ expect(Optional.unwrap(stack.pop())).toBe('another');
173
+ expect(Optional.unwrap(stack.pop())).toBe(undefined);
174
+ expect(Optional.unwrap(stack.pop())).toBe(null);
175
+ expect(Optional.unwrap(stack.pop())).toBe('value');
176
+ expect(stack.isEmpty).toBe(true);
177
+ });
178
+
179
+ test('should maintain performance characteristics', () => {
180
+ const stack = createStack<number>();
181
+
182
+ // Test that operations are efficient even with many elements
183
+ const startTime = performance.now();
184
+
185
+ // Push operations should be O(1) amortized
186
+ for (let i = 0; i < 1000; i++) {
187
+ stack.push(i);
188
+ }
189
+
190
+ // Pop operations should be O(1)
191
+ while (!stack.isEmpty) {
192
+ stack.pop();
193
+ }
194
+
195
+ const endTime = performance.now();
196
+ const duration = endTime - startTime;
197
+
198
+ // Should complete in reasonable time (much less than 100ms for 1000 operations)
199
+ expect(duration).toBeLessThan(100);
200
+ });
201
+
202
+ test('should handle alternating push/pop operations', () => {
203
+ const stack = createStack<number>();
204
+
205
+ for (let i = 0; i < 100; i++) {
206
+ stack.push(i);
207
+ if (i % 2 === 1) {
208
+ // Pop every other time
209
+ const result = stack.pop();
210
+ expect(Optional.unwrap(result)).toBe(i);
211
+ }
212
+ }
213
+
214
+ // Should have 50 elements remaining (0, 2, 4, ..., 98)
215
+ expect(stack.size).toBe(50);
216
+
217
+ // Verify elements are in correct LIFO order
218
+ for (let i = 98; i >= 0; i -= 2) {
219
+ const result = stack.pop();
220
+ expect(Optional.unwrap(result)).toBe(i);
221
+ }
222
+
223
+ expect(stack.isEmpty).toBe(true);
224
+ });
225
+ });