| 1 | import { assert, describe, it } from "@effect/vitest" |
| 2 | import { Effect, Hash, HashMap, Option, Result, TxHashMap } from "effect" |
| 3 | |
| 4 | describe("TxHashMap", () => { |
| 5 | describe("constructors", () => { |
| 6 | it.effect("empty", () => |
| 7 | Effect.tx(Effect.gen(function*() { |
| 8 | const txMap = yield* TxHashMap.empty<string, number>() |
| 9 | const isEmpty = yield* TxHashMap.isEmpty(txMap) |
| 10 | const size = yield* TxHashMap.size(txMap) |
| 11 | |
| 12 | assert.strictEqual(isEmpty, true) |
| 13 | assert.strictEqual(size, 0) |
| 14 | }))) |
| 15 | |
| 16 | it.effect("make", () => |
| 17 | Effect.tx(Effect.gen(function*() { |
| 18 | const txMap = yield* TxHashMap.make(["a", 1], ["b", 2], ["c", 3]) |
| 19 | const size = yield* TxHashMap.size(txMap) |
| 20 | const a = yield* TxHashMap.get(txMap, "a") |
| 21 | const b = yield* TxHashMap.get(txMap, "b") |
| 22 | const c = yield* TxHashMap.get(txMap, "c") |
| 23 | |
| 24 | assert.strictEqual(size, 3) |
| 25 | assert.deepStrictEqual(a, Option.some(1)) |
| 26 | assert.deepStrictEqual(b, Option.some(2)) |
| 27 | assert.deepStrictEqual(c, Option.some(3)) |
| 28 | }))) |
| 29 | |
| 30 | it.effect("fromIterable", () => |
| 31 | Effect.tx(Effect.gen(function*() { |
| 32 | const entries = [["a", 1], ["b", 2], ["c", 3]] as const |
| 33 | const txMap = yield* TxHashMap.fromIterable(entries) |
| 34 | const size = yield* TxHashMap.size(txMap) |
| 35 | const a = yield* TxHashMap.get(txMap, "a") |
| 36 | const b = yield* TxHashMap.get(txMap, "b") |
| 37 | const c = yield* TxHashMap.get(txMap, "c") |
| 38 | |
| 39 | assert.strictEqual(size, 3) |
| 40 | assert.deepStrictEqual(a, Option.some(1)) |
| 41 | assert.deepStrictEqual(b, Option.some(2)) |
| 42 | assert.deepStrictEqual(c, Option.some(3)) |
| 43 | }))) |
| 44 | }) |
| 45 | |
| 46 | describe("basic operations", () => { |
| 47 | it.effect("get - existing key", () => |
| 48 | Effect.tx(Effect.gen(function*() { |
| 49 | const txMap = yield* TxHashMap.make(["a", 1], ["b", 2]) |
| 50 | const a = yield* TxHashMap.get(txMap, "a") |
| 51 | const b = yield* TxHashMap.get(txMap, "b") |
| 52 | |
| 53 | assert.deepStrictEqual(a, Option.some(1)) |
| 54 | assert.deepStrictEqual(b, Option.some(2)) |
| 55 | }))) |
| 56 | |
| 57 | it.effect("get - non-existing key", () => |
| 58 | Effect.tx(Effect.gen(function*() { |
| 59 | const txMap = yield* TxHashMap.make(["a", 1], ["b", 2]) |
| 60 | const c = yield* TxHashMap.get(txMap, "c") |
| 61 | |
| 62 | assert.deepStrictEqual(c, Option.none()) |
| 63 | }))) |
| 64 | |
| 65 | it.effect("has - existing key", () => |
| 66 | Effect.tx(Effect.gen(function*() { |
| 67 | const txMap = yield* TxHashMap.make(["a", 1], ["b", 2]) |
| 68 | const hasA = yield* TxHashMap.has(txMap, "a") |
| 69 | const hasB = yield* TxHashMap.has(txMap, "b") |
| 70 | |
| 71 | assert.strictEqual(hasA, true) |
| 72 | assert.strictEqual(hasB, true) |
| 73 | }))) |
| 74 | |
| 75 | it.effect("has - non-existing key", () => |
| 76 | Effect.tx(Effect.gen(function*() { |
| 77 | const txMap = yield* TxHashMap.make(["a", 1], ["b", 2]) |
| 78 | const hasC = yield* TxHashMap.has(txMap, "c") |
| 79 | |
| 80 | assert.strictEqual(hasC, false) |
| 81 | }))) |
| 82 | |
| 83 | it.effect("set - new key", () => |
| 84 | Effect.tx(Effect.gen(function*() { |
| 85 | const txMap = yield* TxHashMap.make(["a", 1], ["b", 2]) |
| 86 | yield* TxHashMap.set(txMap, "c", 3) |
| 87 | |
| 88 | const size = yield* TxHashMap.size(txMap) |
| 89 | const c = yield* TxHashMap.get(txMap, "c") |
| 90 | |
| 91 | assert.strictEqual(size, 3) |
| 92 | assert.deepStrictEqual(c, Option.some(3)) |
| 93 | }))) |
| 94 | |
| 95 | it.effect("set - existing key", () => |
| 96 | Effect.tx(Effect.gen(function*() { |
| 97 | const txMap = yield* TxHashMap.make(["a", 1], ["b", 2]) |
| 98 | yield* TxHashMap.set(txMap, "a", 10) |
| 99 | |
| 100 | const size = yield* TxHashMap.size(txMap) |
| 101 | const a = yield* TxHashMap.get(txMap, "a") |
| 102 | |
| 103 | assert.strictEqual(size, 2) // size unchanged |
| 104 | assert.deepStrictEqual(a, Option.some(10)) |
| 105 | }))) |
| 106 | |
| 107 | it.effect("remove - existing key", () => |
| 108 | Effect.tx(Effect.gen(function*() { |
| 109 | const txMap = yield* TxHashMap.make(["a", 1], ["b", 2], ["c", 3]) |
| 110 | const removed = yield* TxHashMap.remove(txMap, "b") |
| 111 | |
| 112 | const size = yield* TxHashMap.size(txMap) |
| 113 | const hasB = yield* TxHashMap.has(txMap, "b") |
| 114 | |
| 115 | assert.strictEqual(removed, true) |
| 116 | assert.strictEqual(size, 2) |
| 117 | assert.strictEqual(hasB, false) |
| 118 | }))) |
| 119 | |
| 120 | it.effect("remove - non-existing key", () => |
| 121 | Effect.tx(Effect.gen(function*() { |
| 122 | const txMap = yield* TxHashMap.make(["a", 1], ["b", 2]) |
| 123 | const removed = yield* TxHashMap.remove(txMap, "c") |
| 124 | |
| 125 | const size = yield* TxHashMap.size(txMap) |
| 126 | |
| 127 | assert.strictEqual(removed, false) |
| 128 | assert.strictEqual(size, 2) |
| 129 | }))) |
| 130 | |
| 131 | it.effect("clear", () => |
| 132 | Effect.tx(Effect.gen(function*() { |
| 133 | const txMap = yield* TxHashMap.make(["a", 1], ["b", 2], ["c", 3]) |
| 134 | yield* TxHashMap.clear(txMap) |
| 135 | |
| 136 | const size = yield* TxHashMap.size(txMap) |
| 137 | const isEmpty = yield* TxHashMap.isEmpty(txMap) |
| 138 | |
| 139 | assert.strictEqual(size, 0) |
| 140 | assert.strictEqual(isEmpty, true) |
| 141 | }))) |
| 142 | }) |
| 143 | |
| 144 | describe("query operations", () => { |
| 145 | it.effect("size", () => |
| 146 | Effect.tx(Effect.gen(function*() { |
| 147 | const empty = yield* TxHashMap.empty<string, number>() |
| 148 | const small = yield* TxHashMap.make(["a", 1]) |
| 149 | const large = yield* TxHashMap.make(["a", 1], ["b", 2], ["c", 3], ["d", 4]) |
| 150 | |
| 151 | assert.strictEqual(yield* TxHashMap.size(empty), 0) |
| 152 | assert.strictEqual(yield* TxHashMap.size(small), 1) |
| 153 | assert.strictEqual(yield* TxHashMap.size(large), 4) |
| 154 | }))) |
| 155 | |
| 156 | it.effect("isEmpty", () => |
| 157 | Effect.tx(Effect.gen(function*() { |
| 158 | const empty = yield* TxHashMap.empty<string, number>() |
| 159 | const nonEmpty = yield* TxHashMap.make(["a", 1]) |
| 160 | |
| 161 | assert.strictEqual(yield* TxHashMap.isEmpty(empty), true) |
| 162 | assert.strictEqual(yield* TxHashMap.isEmpty(nonEmpty), false) |
| 163 | }))) |
| 164 | |
| 165 | it.effect("isNonEmpty", () => |
| 166 | Effect.tx(Effect.gen(function*() { |
| 167 | const empty = yield* TxHashMap.empty<string, number>() |
| 168 | const nonEmpty = yield* TxHashMap.make(["a", 1]) |
| 169 | |
| 170 | assert.strictEqual(yield* TxHashMap.isNonEmpty(empty), false) |
| 171 | assert.strictEqual(yield* TxHashMap.isNonEmpty(nonEmpty), true) |
| 172 | }))) |
| 173 | }) |
| 174 | |
| 175 | describe("advanced operations", () => { |
| 176 | it.effect("modify - existing key", () => |
| 177 | Effect.tx(Effect.gen(function*() { |
| 178 | const txMap = yield* TxHashMap.make(["counter", 5]) |
| 179 | const oldValue = yield* TxHashMap.modify(txMap, "counter", (n) => n * 2) |
| 180 | const newValue = yield* TxHashMap.get(txMap, "counter") |
| 181 | |
| 182 | assert.deepStrictEqual(oldValue, Option.some(5)) |
| 183 | assert.deepStrictEqual(newValue, Option.some(10)) |
| 184 | }))) |
| 185 | |
| 186 | it.effect("modify - non-existing key", () => |
| 187 | Effect.tx(Effect.gen(function*() { |
| 188 | const txMap = yield* TxHashMap.empty<string, number>() |
| 189 | const oldValue = yield* TxHashMap.modify(txMap, "counter", (n) => n * 2) |
| 190 | const size = yield* TxHashMap.size(txMap) |
| 191 | |
| 192 | assert.deepStrictEqual(oldValue, Option.none()) |
| 193 | assert.strictEqual(size, 0) |
| 194 | }))) |
| 195 | |
| 196 | it.effect("modifyAt - insert new value", () => |
| 197 | Effect.tx(Effect.gen(function*() { |
| 198 | const txMap = yield* TxHashMap.make(["a", 1]) |
| 199 | yield* TxHashMap.modifyAt(txMap, "b", () => Option.some(2)) |
| 200 | |
| 201 | const size = yield* TxHashMap.size(txMap) |
| 202 | const b = yield* TxHashMap.get(txMap, "b") |
| 203 | |
| 204 | assert.strictEqual(size, 2) |
| 205 | assert.deepStrictEqual(b, Option.some(2)) |
| 206 | }))) |
| 207 | |
| 208 | it.effect("modifyAt - remove existing value", () => |
| 209 | Effect.tx(Effect.gen(function*() { |
| 210 | const txMap = yield* TxHashMap.make(["a", 1], ["b", 2]) |
| 211 | yield* TxHashMap.modifyAt(txMap, "a", () => Option.none()) |
| 212 | |
| 213 | const size = yield* TxHashMap.size(txMap) |
| 214 | const hasA = yield* TxHashMap.has(txMap, "a") |
| 215 | |
| 216 | assert.strictEqual(size, 1) |
| 217 | assert.strictEqual(hasA, false) |
| 218 | }))) |
| 219 | |
| 220 | it.effect("keys", () => |
| 221 | Effect.tx(Effect.gen(function*() { |
| 222 | const txMap = yield* TxHashMap.make(["a", 1], ["b", 2], ["c", 3]) |
| 223 | const keys = yield* TxHashMap.keys(txMap) |
| 224 | |
| 225 | assert.deepStrictEqual(keys.sort(), ["a", "b", "c"]) |
| 226 | }))) |
| 227 | |
| 228 | it.effect("values", () => |
| 229 | Effect.tx(Effect.gen(function*() { |
| 230 | const txMap = yield* TxHashMap.make(["a", 1], ["b", 2], ["c", 3]) |
| 231 | const values = yield* TxHashMap.values(txMap) |
| 232 | |
| 233 | assert.deepStrictEqual(values.sort(), [1, 2, 3]) |
| 234 | }))) |
| 235 | |
| 236 | it.effect("entries", () => |
| 237 | Effect.tx(Effect.gen(function*() { |
| 238 | const txMap = yield* TxHashMap.make(["a", 1], ["b", 2]) |
| 239 | const entries = yield* TxHashMap.entries(txMap) |
| 240 | |
| 241 | assert.deepStrictEqual(entries.sort(), [["a", 1], ["b", 2]]) |
| 242 | }))) |
| 243 | |
| 244 | it.effect("snapshot", () => |
| 245 | Effect.tx(Effect.gen(function*() { |
| 246 | const txMap = yield* TxHashMap.make(["a", 1], ["b", 2]) |
| 247 | const snapshot = yield* TxHashMap.snapshot(txMap) |
| 248 | |
| 249 | // Modify the TxHashMap after taking snapshot |
| 250 | yield* TxHashMap.set(txMap, "c", 3) |
| 251 | |
| 252 | // Snapshot should be unchanged |
| 253 | assert.strictEqual(HashMap.size(snapshot), 2) |
| 254 | assert.deepStrictEqual(HashMap.get(snapshot, "a"), Option.some(1)) |
| 255 | assert.deepStrictEqual(HashMap.get(snapshot, "b"), Option.some(2)) |
| 256 | assert.deepStrictEqual(HashMap.get(snapshot, "c"), Option.none()) |
| 257 | |
| 258 | // Original should be modified |
| 259 | assert.strictEqual(yield* TxHashMap.size(txMap), 3) |
| 260 | }))) |
| 261 | }) |
| 262 | |
| 263 | describe("bulk operations", () => { |
| 264 | it.effect("union", () => |
| 265 | Effect.tx(Effect.gen(function*() { |
| 266 | const txMap = yield* TxHashMap.make(["a", 1], ["b", 2]) |
| 267 | const other = HashMap.make(["b", 20], ["c", 3]) // "b" should be overwritten |
| 268 | |
| 269 | yield* TxHashMap.union(txMap, other) |
| 270 | |
| 271 | const size = yield* TxHashMap.size(txMap) |
| 272 | const a = yield* TxHashMap.get(txMap, "a") |
| 273 | const b = yield* TxHashMap.get(txMap, "b") |
| 274 | const c = yield* TxHashMap.get(txMap, "c") |
| 275 | |
| 276 | assert.strictEqual(size, 3) |
| 277 | assert.deepStrictEqual(a, Option.some(1)) |
| 278 | assert.deepStrictEqual(b, Option.some(20)) // overwritten |
| 279 | assert.deepStrictEqual(c, Option.some(3)) |
| 280 | }))) |
| 281 | |
| 282 | it.effect("removeMany", () => |
| 283 | Effect.tx(Effect.gen(function*() { |
| 284 | const txMap = yield* TxHashMap.make(["a", 1], ["b", 2], ["c", 3], ["d", 4]) |
| 285 | yield* TxHashMap.removeMany(txMap, ["b", "d"]) |
| 286 | |
| 287 | const size = yield* TxHashMap.size(txMap) |
| 288 | const hasA = yield* TxHashMap.has(txMap, "a") |
| 289 | const hasB = yield* TxHashMap.has(txMap, "b") |
| 290 | const hasC = yield* TxHashMap.has(txMap, "c") |
| 291 | const hasD = yield* TxHashMap.has(txMap, "d") |
| 292 | |
| 293 | assert.strictEqual(size, 2) |
| 294 | assert.strictEqual(hasA, true) |
| 295 | assert.strictEqual(hasB, false) |
| 296 | assert.strictEqual(hasC, true) |
| 297 | assert.strictEqual(hasD, false) |
| 298 | }))) |
| 299 | |
| 300 | it.effect("setMany", () => |
| 301 | Effect.tx(Effect.gen(function*() { |
| 302 | const txMap = yield* TxHashMap.make(["a", 1], ["b", 2]) |
| 303 | const newEntries = [["c", 3], ["d", 4], ["a", 10]] as const // "a" should be overwritten |
| 304 | |
| 305 | yield* TxHashMap.setMany(txMap, newEntries) |
| 306 | |
| 307 | const size = yield* TxHashMap.size(txMap) |
| 308 | const a = yield* TxHashMap.get(txMap, "a") |
| 309 | const b = yield* TxHashMap.get(txMap, "b") |
| 310 | const c = yield* TxHashMap.get(txMap, "c") |
| 311 | const d = yield* TxHashMap.get(txMap, "d") |
| 312 | |
| 313 | assert.strictEqual(size, 4) |
| 314 | assert.deepStrictEqual(a, Option.some(10)) // overwritten |
| 315 | assert.deepStrictEqual(b, Option.some(2)) // preserved |
| 316 | assert.deepStrictEqual(c, Option.some(3)) // new |
| 317 | assert.deepStrictEqual(d, Option.some(4)) // new |
| 318 | }))) |
| 319 | }) |
| 320 | |
| 321 | describe("transactional semantics", () => { |
| 322 | it.effect("single operations are automatically transactional", () => |
| 323 | Effect.tx(Effect.gen(function*() { |
| 324 | const txMap = yield* TxHashMap.make(["counter", 0]) |
| 325 | |
| 326 | // These operations should be individually atomic |
| 327 | yield* TxHashMap.set(txMap, "counter", 1) |
| 328 | yield* TxHashMap.modify(txMap, "counter", (n) => n + 1) |
| 329 | |
| 330 | const result = yield* TxHashMap.get(txMap, "counter") |
| 331 | assert.deepStrictEqual(result, Option.some(2)) |
| 332 | }))) |
| 333 | |
| 334 | it.effect("multi-step operations commit together", () => |
| 335 | Effect.tx(Effect.gen(function*() { |
| 336 | const txMap = yield* TxHashMap.make(["a", 1], ["b", 2]) |
| 337 | |
| 338 | // Multi-step operations |
| 339 | const currentA = yield* TxHashMap.get(txMap, "a") |
| 340 | if (Option.isSome(currentA)) { |
| 341 | yield* TxHashMap.set(txMap, "a", currentA.value * 10) |
| 342 | yield* TxHashMap.remove(txMap, "b") |
| 343 | yield* TxHashMap.set(txMap, "c", 3) |
| 344 | } |
| 345 | |
| 346 | const a = yield* TxHashMap.get(txMap, "a") |
| 347 | const hasB = yield* TxHashMap.has(txMap, "b") |
| 348 | const c = yield* TxHashMap.get(txMap, "c") |
| 349 | const size = yield* TxHashMap.size(txMap) |
| 350 | |
| 351 | assert.deepStrictEqual(a, Option.some(10)) |
| 352 | assert.strictEqual(hasB, false) |
| 353 | assert.deepStrictEqual(c, Option.some(3)) |
| 354 | assert.strictEqual(size, 2) |
| 355 | }))) |
| 356 | }) |
| 357 | |
| 358 | describe("pipe syntax", () => { |
| 359 | it.effect("supports data-last operations", () => |
| 360 | Effect.tx(Effect.gen(function*() { |
| 361 | const txMap = yield* TxHashMap.empty<string, number>() |
| 362 | |
| 363 | // Test data-last pipe operations |
| 364 | yield* Effect.flatMap(TxHashMap.set(txMap, "a", 1), () => Effect.void) |
| 365 | yield* Effect.flatMap(TxHashMap.set(txMap, "b", 2), () => Effect.void) |
| 366 | |
| 367 | const result = yield* TxHashMap.get(txMap, "a") |
| 368 | const size = yield* TxHashMap.size(txMap) |
| 369 | |
| 370 | assert.deepStrictEqual(result, Option.some(1)) |
| 371 | assert.strictEqual(size, 2) |
| 372 | }))) |
| 373 | }) |
| 374 | |
| 375 | describe("Phase 1: Essential Functions", () => { |
| 376 | it.effect("isTxHashMap should correctly identify TxHashMap instances", () => |
| 377 | Effect.tx(Effect.gen(function*() { |
| 378 | const txMap = yield* TxHashMap.make(["key", "value"]) |
| 379 | |
| 380 | // Positive cases |
| 381 | assert.strictEqual(TxHashMap.isTxHashMap(txMap), true) |
| 382 | |
| 383 | // Negative cases |
| 384 | assert.strictEqual(TxHashMap.isTxHashMap({}), false) |
| 385 | assert.strictEqual(TxHashMap.isTxHashMap(null), false) |
| 386 | assert.strictEqual(TxHashMap.isTxHashMap(undefined), false) |
| 387 | assert.strictEqual(TxHashMap.isTxHashMap("string"), false) |
| 388 | assert.strictEqual(TxHashMap.isTxHashMap(42), false) |
| 389 | assert.strictEqual(TxHashMap.isTxHashMap([]), false) |
| 390 | assert.strictEqual(TxHashMap.isTxHashMap(new Map()), false) |
| 391 | }))) |
| 392 | |
| 393 | it.effect("getHash should lookup values using custom hash", () => |
| 394 | Effect.tx(Effect.gen(function*() { |
| 395 | const txMap = yield* TxHashMap.make( |
| 396 | ["user123", { name: "Alice", role: "admin" }], |
| 397 | ["user456", { name: "Bob", role: "user" }] |
| 398 | ) |
| 399 | |
| 400 | const userId = "user123" |
| 401 | const hash = Hash.string(userId) |
| 402 | |
| 403 | // Test data-first |
| 404 | const user1 = yield* TxHashMap.getHash(txMap, userId, hash) |
| 405 | assert.deepStrictEqual(user1, Option.some({ name: "Alice", role: "admin" })) |
| 406 | |
| 407 | // Test data-last |
| 408 | const user2 = yield* txMap.pipe(TxHashMap.getHash(userId, hash)) |
| 409 | assert.deepStrictEqual(user2, Option.some({ name: "Alice", role: "admin" })) |
| 410 | |
| 411 | // Test non-existent key |
| 412 | const notFound = yield* TxHashMap.getHash(txMap, "user999", Hash.string("user999")) |
| 413 | assert.deepStrictEqual(notFound, Option.none()) |
| 414 | |
| 415 | // Test with wrong hash (may not find the value due to hash mismatch) |
| 416 | const wrongHash = yield* TxHashMap.getHash(txMap, userId, Hash.string("different")) |
| 417 | assert.deepStrictEqual(wrongHash, Option.none()) |
| 418 | }))) |
| 419 | |
| 420 | it.effect("hasHash should check existence using custom hash", () => |
| 421 | Effect.tx(Effect.gen(function*() { |
| 422 | const txMap = yield* TxHashMap.make( |
| 423 | ["admin", { read: true, write: true }], |
| 424 | ["user", { read: true, write: false }] |
| 425 | ) |
| 426 | |
| 427 | const role = "admin" |
| 428 | const hash = Hash.string(role) |
| 429 | |
| 430 | // Test data-first |
| 431 | const hasAdmin1 = yield* TxHashMap.hasHash(txMap, role, hash) |
| 432 | assert.strictEqual(hasAdmin1, true) |
| 433 | |
| 434 | // Test data-last |
| 435 | const hasAdmin2 = yield* txMap.pipe(TxHashMap.hasHash(role, hash)) |
| 436 | assert.strictEqual(hasAdmin2, true) |
| 437 | |
| 438 | // Test non-existent key |
| 439 | const hasGuest = yield* TxHashMap.hasHash(txMap, "guest", Hash.string("guest")) |
| 440 | assert.strictEqual(hasGuest, false) |
| 441 | |
| 442 | // Test with empty map |
| 443 | const emptyMap = yield* TxHashMap.empty<string, { read: boolean }>() |
| 444 | const hasInEmpty = yield* TxHashMap.hasHash(emptyMap, "admin", hash) |
| 445 | assert.strictEqual(hasInEmpty, false) |
| 446 | }))) |
| 447 | |
| 448 | it.effect("getHash and hasHash should work together", () => |
| 449 | Effect.tx(Effect.gen(function*() { |
| 450 | const txMap = yield* TxHashMap.make( |
| 451 | ["session_abc", { userId: "user1", active: true }], |
| 452 | ["session_def", { userId: "user2", active: false }] |
| 453 | ) |
| 454 | |
| 455 | const sessionId = "session_abc" |
| 456 | const hash = Hash.string(sessionId) |
| 457 | |
| 458 | // Check existence first |
| 459 | const exists = yield* TxHashMap.hasHash(txMap, sessionId, hash) |
| 460 | assert.strictEqual(exists, true) |
| 461 | |
| 462 | // Then get the value |
| 463 | const session = yield* TxHashMap.getHash(txMap, sessionId, hash) |
| 464 | assert.deepStrictEqual(session, Option.some({ userId: "user1", active: true })) |
| 465 | |
| 466 | // Test non-existent session |
| 467 | const invalidSessionId = "session_xyz" |
| 468 | const invalidHash = Hash.string(invalidSessionId) |
| 469 | |
| 470 | const invalidExists = yield* TxHashMap.hasHash(txMap, invalidSessionId, invalidHash) |
| 471 | assert.strictEqual(invalidExists, false) |
| 472 | |
| 473 | const invalidSession = yield* TxHashMap.getHash(txMap, invalidSessionId, invalidHash) |
| 474 | assert.deepStrictEqual(invalidSession, Option.none()) |
| 475 | }))) |
| 476 | |
| 477 | it.effect("hash functions should work correctly", () => |
| 478 | Effect.tx(Effect.gen(function*() { |
| 479 | const txMap = yield* TxHashMap.make(["key1", "value1"]) |
| 480 | const hash = Hash.string("key2") |
| 481 | |
| 482 | // Add a new key-value pair |
| 483 | yield* TxHashMap.set(txMap, "key2", "value2") |
| 484 | |
| 485 | // Check with hash functions |
| 486 | const exists = yield* TxHashMap.hasHash(txMap, "key2", hash) |
| 487 | assert.strictEqual(exists, true) |
| 488 | |
| 489 | const value = yield* TxHashMap.getHash(txMap, "key2", hash) |
| 490 | assert.deepStrictEqual(value, Option.some("value2")) |
| 491 | |
| 492 | // Verify changes persisted after transaction |
| 493 | const finalExists = yield* TxHashMap.hasHash(txMap, "key2", hash) |
| 494 | assert.strictEqual(finalExists, true) |
| 495 | }))) |
| 496 | }) |
| 497 | |
| 498 | describe("Phase 2: Functional Programming Operations", () => { |
| 499 | it.effect("map should transform values while preserving keys", () => |
| 500 | Effect.tx(Effect.gen(function*() { |
| 501 | const txMap = yield* TxHashMap.make( |
| 502 | ["user1", { name: "Alice", age: 25 }], |
| 503 | ["user2", { name: "Bob", age: 30 }], |
| 504 | ["user3", { name: "Charlie", age: 35 }] |
| 505 | ) |
| 506 | |
| 507 | // Test data-first: extract names |
| 508 | const nameMap = yield* TxHashMap.map(txMap, (user) => user.name) |
| 509 | |
| 510 | const alice = yield* TxHashMap.get(nameMap, "user1") |
| 511 | const bob = yield* TxHashMap.get(nameMap, "user2") |
| 512 | const charlie = yield* TxHashMap.get(nameMap, "user3") |
| 513 | |
| 514 | assert.deepStrictEqual(alice, Option.some("Alice")) |
| 515 | assert.deepStrictEqual(bob, Option.some("Bob")) |
| 516 | assert.deepStrictEqual(charlie, Option.some("Charlie")) |
| 517 | |
| 518 | // Test data-last: increment ages |
| 519 | const ageMap = yield* txMap.pipe(TxHashMap.map((user, key) => ({ ...user, age: user.age + 1, key }))) |
| 520 | |
| 521 | const updatedAlice = yield* TxHashMap.get(ageMap, "user1") |
| 522 | assert.deepStrictEqual(updatedAlice, Option.some({ name: "Alice", age: 26, key: "user1" })) |
| 523 | }))) |
| 524 | |
| 525 | it.effect("filter should keep only matching entries", () => |
| 526 | Effect.tx(Effect.gen(function*() { |
| 527 | const txMap = yield* TxHashMap.make( |
| 528 | ["apple", { price: 1.50, category: "fruit" }], |
| 529 | ["carrot", { price: 0.80, category: "vegetable" }], |
| 530 | ["banana", { price: 2.00, category: "fruit" }], |
| 531 | ["lettuce", { price: 1.20, category: "vegetable" }] |
| 532 | ) |
| 533 | |
| 534 | // Test data-first: filter fruits |
| 535 | const fruitMap = yield* TxHashMap.filter(txMap, (item) => item.category === "fruit") |
| 536 | |
| 537 | const size = yield* TxHashMap.size(fruitMap) |
| 538 | const hasApple = yield* TxHashMap.has(fruitMap, "apple") |
| 539 | const hasBanana = yield* TxHashMap.has(fruitMap, "banana") |
| 540 | const hasCarrot = yield* TxHashMap.has(fruitMap, "carrot") |
| 541 | |
| 542 | assert.strictEqual(size, 2) |
| 543 | assert.strictEqual(hasApple, true) |
| 544 | assert.strictEqual(hasBanana, true) |
| 545 | assert.strictEqual(hasCarrot, false) |
| 546 | |
| 547 | // Test data-last: filter expensive items |
| 548 | const expensiveMap = yield* txMap.pipe(TxHashMap.filter((item) => item.price > 1.50)) |
| 549 | const expensiveSize = yield* TxHashMap.size(expensiveMap) |
| 550 | assert.strictEqual(expensiveSize, 1) // only banana |
| 551 | }))) |
| 552 | |
| 553 | it.effect("reduce should fold over all entries", () => |
| 554 | Effect.tx(Effect.gen(function*() { |
| 555 | const txMap = yield* TxHashMap.make( |
| 556 | ["item1", 10], |
| 557 | ["item2", 20], |
| 558 | ["item3", 30] |
| 559 | ) |
| 560 | |
| 561 | // Test data-first: sum all values |
| 562 | const sum = yield* TxHashMap.reduce(txMap, 0, (acc, value) => acc + value) |
| 563 | assert.strictEqual(sum, 60) |
| 564 | |
| 565 | // Test data-last: count entries with key info |
| 566 | const info = yield* txMap.pipe( |
| 567 | TxHashMap.reduce({ count: 0, keys: [] as Array<string> }, (acc, _, key) => ({ |
| 568 | count: acc.count + 1, |
| 569 | keys: [...acc.keys, key] |
| 570 | })) |
| 571 | ) |
| 572 | |
| 573 | assert.strictEqual(info.count, 3) |
| 574 | assert.deepStrictEqual(info.keys.sort(), ["item1", "item2", "item3"]) |
| 575 | |
| 576 | // Test with empty map |
| 577 | const emptyMap = yield* TxHashMap.empty<string, number>() |
| 578 | const emptySum = yield* TxHashMap.reduce(emptyMap, 0, (acc, value) => acc + value) |
| 579 | assert.strictEqual(emptySum, 0) |
| 580 | }))) |
| 581 | |
| 582 | it.effect("filterMap should filter and transform in one operation", () => |
| 583 | Effect.tx(Effect.gen(function*() { |
| 584 | const txMap = yield* TxHashMap.make( |
| 585 | ["valid1", "10"], |
| 586 | ["invalid", "not-a-number"], |
| 587 | ["valid2", "20"], |
| 588 | ["empty", ""], |
| 589 | ["valid3", "30"] |
| 590 | ) |
| 591 | |
| 592 | // Test data-first: parse numbers, filter out invalid ones |
| 593 | const numberMap = yield* TxHashMap.filterMap(txMap, (value) => { |
| 594 | const num = parseInt(value, 10) |
| 595 | return isNaN(num) ? Result.failVoid : Result.succeed(num) |
| 596 | }) |
| 597 | |
| 598 | const size = yield* TxHashMap.size(numberMap) |
| 599 | const val1 = yield* TxHashMap.get(numberMap, "valid1") |
| 600 | const val2 = yield* TxHashMap.get(numberMap, "valid2") |
| 601 | const val3 = yield* TxHashMap.get(numberMap, "valid3") |
| 602 | const invalid = yield* TxHashMap.get(numberMap, "invalid") |
| 603 | |
| 604 | assert.strictEqual(size, 3) |
| 605 | assert.deepStrictEqual(val1, Option.some(10)) |
| 606 | assert.deepStrictEqual(val2, Option.some(20)) |
| 607 | assert.deepStrictEqual(val3, Option.some(30)) |
| 608 | assert.deepStrictEqual(invalid, Option.none()) |
| 609 | |
| 610 | // Test data-last: extract emails from user objects |
| 611 | const userMap = yield* TxHashMap.make( |
| 612 | ["user1", { name: "Alice", email: "alice@example.com" }], |
| 613 | ["user2", { name: "Bob" }], // no email |
| 614 | ["user3", { name: "Charlie", email: "charlie@example.com" }] |
| 615 | ) |
| 616 | |
| 617 | const emailMap = yield* userMap.pipe( |
| 618 | TxHashMap.filterMap( |
| 619 | (user, key) => "email" in user ? Result.succeed(`${key}:${user.email}`) : Result.failVoid |
| 620 | ) |
| 621 | ) |
| 622 | |
| 623 | const emailSize = yield* TxHashMap.size(emailMap) |
| 624 | const user1Email = yield* TxHashMap.get(emailMap, "user1") |
| 625 | const user2Email = yield* TxHashMap.get(emailMap, "user2") |
| 626 | const user3Email = yield* TxHashMap.get(emailMap, "user3") |
| 627 | |
| 628 | assert.strictEqual(emailSize, 2) |
| 629 | assert.deepStrictEqual(user1Email, Option.some("user1:alice@example.com")) |
| 630 | assert.deepStrictEqual(user2Email, Option.none()) |
| 631 | assert.deepStrictEqual(user3Email, Option.some("user3:charlie@example.com")) |
| 632 | }))) |
| 633 | |
| 634 | it.effect("hasBy should check if any entry matches predicate", () => |
| 635 | Effect.tx(Effect.gen(function*() { |
| 636 | const txMap = yield* TxHashMap.make( |
| 637 | ["admin", { role: "admin", active: true }], |
| 638 | ["user1", { role: "user", active: true }], |
| 639 | ["user2", { role: "user", active: false }] |
| 640 | ) |
| 641 | |
| 642 | // Test data-first: check for admin users |
| 643 | const hasAdmin = yield* TxHashMap.hasBy(txMap, (user) => user.role === "admin") |
| 644 | assert.strictEqual(hasAdmin, true) |
| 645 | |
| 646 | const hasGuest = yield* TxHashMap.hasBy(txMap, (user) => user.role === "guest") |
| 647 | assert.strictEqual(hasGuest, false) |
| 648 | |
| 649 | // Test data-last: check for inactive users |
| 650 | const hasInactive = yield* txMap.pipe(TxHashMap.hasBy((user) => !user.active)) |
| 651 | assert.strictEqual(hasInactive, true) |
| 652 | |
| 653 | // Test with empty map |
| 654 | const emptyMap = yield* TxHashMap.empty<string, { role: string }>() |
| 655 | const hasAny = yield* TxHashMap.hasBy(emptyMap, () => true) |
| 656 | assert.strictEqual(hasAny, false) |
| 657 | }))) |
| 658 | |
| 659 | it.effect("findFirst should return first matching entry", () => |
| 660 | Effect.tx(Effect.gen(function*() { |
| 661 | const txMap = yield* TxHashMap.make( |
| 662 | ["session1", { userId: "user1", loginTime: "09:00" }], |
| 663 | ["session2", { userId: "user2", loginTime: "10:00" }], |
| 664 | ["session3", { userId: "user1", loginTime: "11:00" }] |
| 665 | ) |
| 666 | |
| 667 | // Test data-first: find user1's first session |
| 668 | const user1Session = yield* TxHashMap.findFirst(txMap, (session) => session.userId === "user1") |
| 669 | assert.deepStrictEqual( |
| 670 | user1Session, |
| 671 | Option.some<[string, { userId: string; loginTime: string }]>([ |
| 672 | "session3", |
| 673 | { userId: "user1", loginTime: "11:00" } |
| 674 | ]) |
| 675 | ) |
| 676 | |
| 677 | // Test data-last: find session not belonging to user1 or user2 |
| 678 | const otherSession = yield* txMap.pipe( |
| 679 | TxHashMap.findFirst((session) => session.userId !== "user1" && session.userId !== "user2") |
| 680 | ) |
| 681 | assert.deepStrictEqual(otherSession, Option.none()) |
| 682 | |
| 683 | // Test with empty map |
| 684 | const emptyMap = yield* TxHashMap.empty<string, { userId: string }>() |
| 685 | const notFound = yield* TxHashMap.findFirst(emptyMap, () => true) |
| 686 | assert.deepStrictEqual(notFound, Option.none()) |
| 687 | }))) |
| 688 | |
| 689 | it.effect("some should check if any entry matches predicate", () => |
| 690 | Effect.tx(Effect.gen(function*() { |
| 691 | const txMap = yield* TxHashMap.make( |
| 692 | ["order1", { status: "pending", amount: 100 }], |
| 693 | ["order2", { status: "completed", amount: 250 }], |
| 694 | ["order3", { status: "pending", amount: 50 }] |
| 695 | ) |
| 696 | |
| 697 | // Test data-first: check if any order is completed |
| 698 | const hasCompleted = yield* TxHashMap.some(txMap, (order) => order.status === "completed") |
| 699 | assert.strictEqual(hasCompleted, true) |
| 700 | |
| 701 | const hasCancelled = yield* TxHashMap.some(txMap, (order) => order.status === "cancelled") |
| 702 | assert.strictEqual(hasCancelled, false) |
| 703 | |
| 704 | // Test data-last: check if any order exceeds amount |
| 705 | const hasLargeOrder = yield* txMap.pipe(TxHashMap.some((order) => order.amount > 200)) |
| 706 | assert.strictEqual(hasLargeOrder, true) |
| 707 | |
| 708 | const hasVeryLargeOrder = yield* txMap.pipe(TxHashMap.some((order) => order.amount > 1000)) |
| 709 | assert.strictEqual(hasVeryLargeOrder, false) |
| 710 | |
| 711 | // Test with empty map |
| 712 | const emptyMap = yield* TxHashMap.empty<string, { status: string }>() |
| 713 | const emptyResult = yield* TxHashMap.some(emptyMap, () => true) |
| 714 | assert.strictEqual(emptyResult, false) |
| 715 | }))) |
| 716 | |
| 717 | it.effect("every should check if all entries match predicate", () => |
| 718 | Effect.tx(Effect.gen(function*() { |
| 719 | const txMap = yield* TxHashMap.make( |
| 720 | ["task1", { completed: true, priority: "high" }], |
| 721 | ["task2", { completed: true, priority: "medium" }], |
| 722 | ["task3", { completed: true, priority: "low" }] |
| 723 | ) |
| 724 | |
| 725 | // Test data-first: check if all tasks are completed |
| 726 | const allCompleted = yield* TxHashMap.every(txMap, (task) => task.completed) |
| 727 | assert.strictEqual(allCompleted, true) |
| 728 | |
| 729 | const allHighPriority = yield* TxHashMap.every(txMap, (task) => task.priority === "high") |
| 730 | assert.strictEqual(allHighPriority, false) |
| 731 | |
| 732 | // Add an incomplete task |
| 733 | yield* TxHashMap.set(txMap, "task4", { completed: false, priority: "high" }) |
| 734 | |
| 735 | // Test data-last: now not all tasks are completed |
| 736 | const stillAllCompleted = yield* txMap.pipe(TxHashMap.every((task) => task.completed)) |
| 737 | assert.strictEqual(stillAllCompleted, false) |
| 738 | |
| 739 | // Test that all tasks have a priority |
| 740 | const allHavePriority = yield* txMap.pipe(TxHashMap.every((task) => Boolean(task.priority))) |
| 741 | assert.strictEqual(allHavePriority, true) |
| 742 | |
| 743 | // Test with empty map (vacuous truth) |
| 744 | const emptyMap = yield* TxHashMap.empty<string, { completed: boolean }>() |
| 745 | const emptyResult = yield* TxHashMap.every(emptyMap, () => false) |
| 746 | assert.strictEqual(emptyResult, true) // empty set satisfies all predicates |
| 747 | }))) |
| 748 | |
| 749 | it.effect("functional operations should work correctly", () => |
| 750 | Effect.tx(Effect.gen(function*() { |
| 751 | const txMap = yield* TxHashMap.make( |
| 752 | ["item1", { price: 10, discount: 0 }], |
| 753 | ["item2", { price: 20, discount: 0 }], |
| 754 | ["item3", { price: 30, discount: 0 }] |
| 755 | ) |
| 756 | |
| 757 | // Apply bulk discount |
| 758 | const discountedMap = yield* TxHashMap.map(txMap, (item) => ({ |
| 759 | ...item, |
| 760 | discount: 0.1, |
| 761 | discountedPrice: item.price * 0.9 |
| 762 | })) |
| 763 | |
| 764 | // Filter expensive items |
| 765 | const expensiveItems = yield* TxHashMap.filter(discountedMap, (item) => item.price > 15) |
| 766 | |
| 767 | // Calculate total savings |
| 768 | const totalSavings = yield* TxHashMap.reduce( |
| 769 | expensiveItems, |
| 770 | 0, |
| 771 | (acc, item) => acc + (item.price - item.discountedPrice) |
| 772 | ) |
| 773 | |
| 774 | // Verify operations worked correctly |
| 775 | const expensiveSize = yield* TxHashMap.size(expensiveItems) |
| 776 | assert.strictEqual(expensiveSize, 2) // item2 and item3 |
| 777 | |
| 778 | assert.strictEqual(totalSavings, 5) // (20 + 30) * 0.1 = 5 |
| 779 | |
| 780 | // Check that all expensive items have discount |
| 781 | const allDiscounted = yield* TxHashMap.every(expensiveItems, (item) => item.discount > 0) |
| 782 | assert.strictEqual(allDiscounted, true) |
| 783 | }))) |
| 784 | }) |
| 785 | |
| 786 | describe("Phase 3: Specialized Operations", () => { |
| 787 | it.effect("forEach should execute side effects for each entry", () => |
| 788 | Effect.tx(Effect.gen(function*() { |
| 789 | const txMap = yield* TxHashMap.make( |
| 790 | ["user1", { name: "Alice", status: "active" }], |
| 791 | ["user2", { name: "Bob", status: "inactive" }], |
| 792 | ["user3", { name: "Charlie", status: "active" }] |
| 793 | ) |
| 794 | |
| 795 | // Collect side effects |
| 796 | const processed: Array<{ user: string; name: string; status: string }> = [] |
| 797 | |
| 798 | // Test data-first: process all users |
| 799 | yield* TxHashMap.forEach(txMap, (userData, userId) => |
| 800 | Effect.sync(() => { |
| 801 | processed.push({ user: userId, name: userData.name, status: userData.status }) |
| 802 | })) |
| 803 | |
| 804 | assert.strictEqual(processed.length, 3) |
| 805 | assert.strictEqual(processed.filter((p) => p.status === "active").length, 2) |
| 806 | |
| 807 | // Test data-last: process only active users |
| 808 | const activeUsers: Array<string> = [] |
| 809 | yield* txMap.pipe( |
| 810 | TxHashMap.forEach((userData, userId) => |
| 811 | userData.status === "active" |
| 812 | ? Effect.sync(() => activeUsers.push(userId)) |
| 813 | : Effect.void |
| 814 | ) |
| 815 | ) |
| 816 | |
| 817 | assert.strictEqual(activeUsers.length, 2) |
| 818 | assert.deepStrictEqual(activeUsers.sort(), ["user1", "user3"]) |
| 819 | }))) |
| 820 | |
| 821 | it.effect("flatMap should transform and flatten TxHashMaps", () => |
| 822 | Effect.tx(Effect.gen(function*() { |
| 823 | const departments = yield* TxHashMap.make( |
| 824 | ["eng", ["alice", "bob"]], |
| 825 | ["sales", ["charlie"]] |
| 826 | ) |
| 827 | |
| 828 | // Transform departments into individual employee records |
| 829 | const employees = yield* TxHashMap.flatMap( |
| 830 | departments, |
| 831 | (members, dept) => |
| 832 | Effect.gen(function*() { |
| 833 | const empMap = yield* TxHashMap.empty<string, { department: string; role: string }>() |
| 834 | for (let i = 0; i < members.length; i++) { |
| 835 | const member = members[i] |
| 836 | const role = i === 0 ? "lead" : "member" |
| 837 | yield* TxHashMap.set(empMap, member, { department: dept, role }) |
| 838 | } |
| 839 | return empMap |
| 840 | }) |
| 841 | ) |
| 842 | |
| 843 | const size = yield* TxHashMap.size(employees) |
| 844 | assert.strictEqual(size, 3) |
| 845 | |
| 846 | const alice = yield* TxHashMap.get(employees, "alice") |
| 847 | assert.deepStrictEqual(alice, Option.some({ department: "eng", role: "lead" })) |
| 848 | |
| 849 | const bob = yield* TxHashMap.get(employees, "bob") |
| 850 | assert.deepStrictEqual(bob, Option.some({ department: "eng", role: "member" })) |
| 851 | |
| 852 | const charlie = yield* TxHashMap.get(employees, "charlie") |
| 853 | assert.deepStrictEqual(charlie, Option.some({ department: "sales", role: "lead" })) |
| 854 | }))) |
| 855 | |
| 856 | it.effect("compact should remove None values and unwrap Some values", () => |
| 857 | Effect.tx(Effect.gen(function*() { |
| 858 | const optionalData = yield* TxHashMap.make<string, Option.Option<{ value: number }>>( |
| 859 | ["valid1", Option.some({ value: 10 })], |
| 860 | ["invalid1", Option.none()], |
| 861 | ["valid2", Option.some({ value: 20 })], |
| 862 | ["invalid2", Option.none()], |
| 863 | ["valid3", Option.some({ value: 30 })] |
| 864 | ) |
| 865 | |
| 866 | // Compact to remove None values |
| 867 | const compacted = yield* TxHashMap.compact(optionalData) |
| 868 | |
| 869 | const size = yield* TxHashMap.size(compacted) |
| 870 | assert.strictEqual(size, 3) |
| 871 | |
| 872 | const valid1 = yield* TxHashMap.get(compacted, "valid1") |
| 873 | assert.deepStrictEqual(valid1, Option.some({ value: 10 })) |
| 874 | |
| 875 | const valid2 = yield* TxHashMap.get(compacted, "valid2") |
| 876 | assert.deepStrictEqual(valid2, Option.some({ value: 20 })) |
| 877 | |
| 878 | const valid3 = yield* TxHashMap.get(compacted, "valid3") |
| 879 | assert.deepStrictEqual(valid3, Option.some({ value: 30 })) |
| 880 | |
| 881 | const invalid1 = yield* TxHashMap.get(compacted, "invalid1") |
| 882 | assert.deepStrictEqual(invalid1, Option.none()) |
| 883 | |
| 884 | const invalid2 = yield* TxHashMap.get(compacted, "invalid2") |
| 885 | assert.deepStrictEqual(invalid2, Option.none()) |
| 886 | }))) |
| 887 | |
| 888 | it.effect("toEntries should return all entries as array", () => |
| 889 | Effect.tx(Effect.gen(function*() { |
| 890 | const txMap = yield* TxHashMap.make( |
| 891 | ["a", 1], |
| 892 | ["b", 2], |
| 893 | ["c", 3] |
| 894 | ) |
| 895 | |
| 896 | const entriesArray = yield* TxHashMap.toEntries(txMap) |
| 897 | assert.strictEqual(entriesArray.length, 3) |
| 898 | |
| 899 | // Sort for consistent comparison |
| 900 | const sortedEntries = entriesArray.sort(([a], [b]) => a.localeCompare(b)) |
| 901 | assert.deepStrictEqual(sortedEntries, [["a", 1], ["b", 2], ["c", 3]]) |
| 902 | |
| 903 | // Verify it's the same as regular entries |
| 904 | const regularEntries = yield* TxHashMap.entries(txMap) |
| 905 | const sortedRegular = regularEntries.sort(([a], [b]) => a.localeCompare(b)) |
| 906 | assert.deepStrictEqual(sortedEntries, sortedRegular) |
| 907 | }))) |
| 908 | |
| 909 | it.effect("toValues should return all values as array", () => |
| 910 | Effect.tx(Effect.gen(function*() { |
| 911 | const txMap = yield* TxHashMap.make( |
| 912 | ["x", { price: 100 }], |
| 913 | ["y", { price: 200 }], |
| 914 | ["z", { price: 300 }] |
| 915 | ) |
| 916 | |
| 917 | const valuesArray = yield* TxHashMap.toValues(txMap) |
| 918 | assert.strictEqual(valuesArray.length, 3) |
| 919 | |
| 920 | // Sort for consistent comparison |
| 921 | const sortedValues = valuesArray.sort((a, b) => a.price - b.price) |
| 922 | assert.deepStrictEqual(sortedValues, [{ price: 100 }, { price: 200 }, { price: 300 }]) |
| 923 | |
| 924 | // Verify it's the same as regular values |
| 925 | const regularValues = yield* TxHashMap.values(txMap) |
| 926 | const sortedRegular = regularValues.sort((a, b) => a.price - b.price) |
| 927 | assert.deepStrictEqual(sortedValues, sortedRegular) |
| 928 | }))) |
| 929 | |
| 930 | it.effect("specialized operations should work correctly", () => |
| 931 | Effect.tx(Effect.gen(function*() { |
| 932 | const txMap = yield* TxHashMap.make<string, Option.Option<number>>( |
| 933 | ["a", Option.some(1)], |
| 934 | ["b", Option.none()], |
| 935 | ["c", Option.some(3)] |
| 936 | ) |
| 937 | |
| 938 | // Add more data |
| 939 | yield* TxHashMap.set(txMap, "d", Option.some(4)) |
| 940 | yield* TxHashMap.set(txMap, "e", Option.none()) |
| 941 | |
| 942 | // Compact the map |
| 943 | const compacted = yield* TxHashMap.compact(txMap) |
| 944 | const compactedSize = yield* TxHashMap.size(compacted) |
| 945 | assert.strictEqual(compactedSize, 3) // a, c, d |
| 946 | |
| 947 | // Use forEach for side effects |
| 948 | const sum = { value: 0 } |
| 949 | yield* TxHashMap.forEach(compacted, (value) => |
| 950 | Effect.sync(() => { |
| 951 | sum.value += value |
| 952 | })) |
| 953 | assert.strictEqual(sum.value, 8) // 1 + 3 + 4 |
| 954 | |
| 955 | // Test toValues and toEntries |
| 956 | const values = yield* TxHashMap.toValues(compacted) |
| 957 | const entries = yield* TxHashMap.toEntries(compacted) |
| 958 | |
| 959 | assert.strictEqual(values.length, 3) |
| 960 | assert.strictEqual(entries.length, 3) |
| 961 | }))) |
| 962 | }) |
| 963 | }) |