tyneq 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 (45) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +319 -0
  3. package/dist/Lazy.cjs +762 -0
  4. package/dist/Lazy.cjs.map +1 -0
  5. package/dist/Lazy.js +691 -0
  6. package/dist/Lazy.js.map +1 -0
  7. package/dist/TyneqCachedTerminalOperator.cjs +4950 -0
  8. package/dist/TyneqCachedTerminalOperator.cjs.map +1 -0
  9. package/dist/TyneqCachedTerminalOperator.d.cts +724 -0
  10. package/dist/TyneqCachedTerminalOperator.d.cts.map +1 -0
  11. package/dist/TyneqCachedTerminalOperator.d.ts +724 -0
  12. package/dist/TyneqCachedTerminalOperator.d.ts.map +1 -0
  13. package/dist/TyneqCachedTerminalOperator.js +4741 -0
  14. package/dist/TyneqCachedTerminalOperator.js.map +1 -0
  15. package/dist/ValidationBuilder.cjs +80 -0
  16. package/dist/ValidationBuilder.cjs.map +1 -0
  17. package/dist/ValidationBuilder.d.cts +319 -0
  18. package/dist/ValidationBuilder.d.cts.map +1 -0
  19. package/dist/ValidationBuilder.d.ts +319 -0
  20. package/dist/ValidationBuilder.d.ts.map +1 -0
  21. package/dist/ValidationBuilder.js +69 -0
  22. package/dist/ValidationBuilder.js.map +1 -0
  23. package/dist/core.d.cts +1393 -0
  24. package/dist/core.d.cts.map +1 -0
  25. package/dist/core.d.ts +1393 -0
  26. package/dist/core.d.ts.map +1 -0
  27. package/dist/index.cjs +863 -0
  28. package/dist/index.cjs.map +1 -0
  29. package/dist/index.d.cts +1038 -0
  30. package/dist/index.d.cts.map +1 -0
  31. package/dist/index.d.ts +1038 -0
  32. package/dist/index.d.ts.map +1 -0
  33. package/dist/index.js +809 -0
  34. package/dist/index.js.map +1 -0
  35. package/dist/plugin/index.cjs +24 -0
  36. package/dist/plugin/index.d.cts +89 -0
  37. package/dist/plugin/index.d.cts.map +1 -0
  38. package/dist/plugin/index.d.ts +89 -0
  39. package/dist/plugin/index.d.ts.map +1 -0
  40. package/dist/plugin/index.js +2 -0
  41. package/dist/utility/index.cjs +9 -0
  42. package/dist/utility/index.d.cts +2 -0
  43. package/dist/utility/index.d.ts +2 -0
  44. package/dist/utility/index.js +3 -0
  45. package/package.json +96 -0
package/dist/index.cjs ADDED
@@ -0,0 +1,863 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_TyneqCachedTerminalOperator = require("./TyneqCachedTerminalOperator.cjs");
3
+ const require_Lazy = require("./Lazy.cjs");
4
+ const require_ValidationBuilder = require("./ValidationBuilder.cjs");
5
+ //#region src/core/generators/range.ts
6
+ var RangeEnumerator = class extends require_TyneqCachedTerminalOperator.TyneqBaseEnumerator {
7
+ current;
8
+ end;
9
+ constructor(start, end) {
10
+ super();
11
+ this.current = start;
12
+ this.end = end;
13
+ }
14
+ handleNext() {
15
+ if (this.current > this.end) return this.done();
16
+ return this.yield(this.current++);
17
+ }
18
+ };
19
+ //#endregion
20
+ //#region src/core/generators/random.ts
21
+ var RandomEnumerator = class extends require_TyneqCachedTerminalOperator.TyneqBaseEnumerator {
22
+ count;
23
+ randomizer;
24
+ yieldedCount = 0;
25
+ constructor(count, randomizer) {
26
+ super();
27
+ this.count = count;
28
+ this.randomizer = randomizer;
29
+ }
30
+ handleNext() {
31
+ if (this.yieldedCount >= this.count) return this.done();
32
+ this.yieldedCount++;
33
+ return this.yield(this.randomizer());
34
+ }
35
+ };
36
+ //#endregion
37
+ //#region src/core/EnumerableAdapter.ts
38
+ /**
39
+ * Wraps a native `Iterable<T>` as a Tyneq {@link Enumerable}.
40
+ *
41
+ * @remarks
42
+ * Used by `Tyneq.from()` to adapt arrays, sets, generators, and any other iterable.
43
+ * Each call to `getEnumerator()` delegates to the underlying `[Symbol.iterator]()`.
44
+ *
45
+ * @internal
46
+ */
47
+ var EnumerableAdapter = class {
48
+ iterable;
49
+ constructor(iterable) {
50
+ require_Lazy.ArgumentUtility.checkNotOptional({ iterable });
51
+ require_Lazy.ArgumentUtility.checkIterable({ iterable });
52
+ this.iterable = iterable;
53
+ }
54
+ [Symbol.iterator]() {
55
+ return this.getEnumerator();
56
+ }
57
+ getEnumerator() {
58
+ return this.iterable[Symbol.iterator]();
59
+ }
60
+ };
61
+ //#endregion
62
+ //#region src/core/generators/repeat.ts
63
+ /**
64
+ * Yields a single value a fixed number of times.
65
+ *
66
+ * @group Operators
67
+ * @category Streaming
68
+ * @internal
69
+ */
70
+ var RepeatEnumerator = class extends require_TyneqCachedTerminalOperator.TyneqBaseEnumerator {
71
+ value;
72
+ count;
73
+ yieldedCount = 0;
74
+ constructor(value, count) {
75
+ super();
76
+ this.count = count;
77
+ this.value = value;
78
+ }
79
+ handleNext() {
80
+ if (this.yieldedCount >= this.count) return this.done();
81
+ this.yieldedCount++;
82
+ return this.yieldedCount === this.count ? this.doneWithYield(this.value) : this.yield(this.value);
83
+ }
84
+ };
85
+ //#endregion
86
+ //#region src/core/generators/generate.ts
87
+ /**
88
+ * Yields a sequence produced by repeatedly applying a selector to the previous element.
89
+ *
90
+ * @group Operators
91
+ * @category Streaming
92
+ * @internal
93
+ */
94
+ var GenerateEnumerator = class extends require_TyneqCachedTerminalOperator.TyneqBaseEnumerator {
95
+ nextSelector;
96
+ count;
97
+ value;
98
+ yieldedCount = 0;
99
+ index = 0;
100
+ constructor(seed, next, count) {
101
+ super();
102
+ this.value = seed;
103
+ this.nextSelector = next;
104
+ this.count = count ?? Infinity;
105
+ }
106
+ handleNext() {
107
+ if (this.yieldedCount >= this.count) return this.done();
108
+ const result = this.nextSelector(this.value, this.index++);
109
+ this.value = result;
110
+ this.yieldedCount++;
111
+ return this.yield(result);
112
+ }
113
+ };
114
+ //#endregion
115
+ //#region src/core/generators/sourceConcat.ts
116
+ /**
117
+ * Yields all elements from each source iterable in order.
118
+ *
119
+ * @group Operators
120
+ * @category Streaming
121
+ * @internal
122
+ */
123
+ var SourceConcatEnumerator = class extends require_TyneqCachedTerminalOperator.TyneqBaseEnumerator {
124
+ sources;
125
+ currentSourceIndex = 0;
126
+ currentIterator = null;
127
+ constructor(...sources) {
128
+ super();
129
+ this.sources = sources;
130
+ }
131
+ disposeAdditional() {
132
+ try {
133
+ this.currentIterator?.return?.();
134
+ } catch {}
135
+ this.currentIterator = null;
136
+ }
137
+ handleNext() {
138
+ while (this.currentSourceIndex < this.sources.length) {
139
+ if (this.currentIterator === null) this.currentIterator = this.sources[this.currentSourceIndex][Symbol.iterator]();
140
+ const result = this.currentIterator.next();
141
+ if (!result.done) return this.yield(result.value);
142
+ this.currentIterator = null;
143
+ this.currentSourceIndex++;
144
+ }
145
+ return this.done();
146
+ }
147
+ };
148
+ //#endregion
149
+ //#region src/core/tyneq.ts
150
+ /**
151
+ * Entry point for creating Tyneq sequences.
152
+ *
153
+ * @example
154
+ * ```ts
155
+ * import { Tyneq } from "tyneq";
156
+ *
157
+ * const sum = Tyneq.from([1, 2, 3, 4, 5])
158
+ * .where((x) => x % 2 === 0)
159
+ * .sum((x) => x); // -> 6
160
+ * ```
161
+ *
162
+ * @group Classes
163
+ */
164
+ var Tyneq = class Tyneq {
165
+ /**
166
+ * Creates a lazy sequence from any `Iterable<T>` (arrays, sets, generators, etc.).
167
+ *
168
+ * @throws {ArgumentNullError} When `source` is null or undefined.
169
+ * @throws {ArgumentTypeError} When `source` is not iterable.
170
+ */
171
+ @require_TyneqCachedTerminalOperator.source({ source: "internal" }) static from(source) {
172
+ require_Lazy.ArgumentUtility.checkNotOptional({ source });
173
+ require_Lazy.ArgumentUtility.checkIterable({ source });
174
+ const sourceKind = Tyneq.resolveSourceKind(source);
175
+ return new require_TyneqCachedTerminalOperator.TyneqEnumerable(new EnumerableAdapter(source), new require_TyneqCachedTerminalOperator.QueryNode("from", [source], null, "source", sourceKind));
176
+ }
177
+ static resolveSourceKind(source) {
178
+ if (Array.isArray(source)) return "array";
179
+ if (source instanceof Set) return "set";
180
+ if (source instanceof Map) return "map";
181
+ if (typeof source === "string") return "string";
182
+ return "other";
183
+ }
184
+ /**
185
+ * Creates a sequence of `count` elements produced by calling `randomizer` once per element.
186
+ *
187
+ * @remarks
188
+ * Returns an empty sequence when `count === 0`.
189
+ *
190
+ * @throws {ArgumentOutOfRangeError} When `count` is negative.
191
+ * @throws {ArgumentNullError} When `randomizer` is null or undefined.
192
+ */
193
+ @require_TyneqCachedTerminalOperator.source({ source: "internal" }) static random(count, randomizer) {
194
+ require_Lazy.ArgumentUtility.checkNonNegative({ count });
195
+ require_Lazy.ArgumentUtility.checkInteger({ count });
196
+ require_Lazy.ArgumentUtility.checkNotOptional({ randomizer });
197
+ return new require_TyneqCachedTerminalOperator.TyneqEnumerable({ getEnumerator: () => new RandomEnumerator(count, randomizer) }, new require_TyneqCachedTerminalOperator.QueryNode("random", [count, randomizer], null, "source"));
198
+ }
199
+ /**
200
+ * Returns `true` if `source` is `null`, `undefined`, or an iterable whose first element is `null` or `undefined`.
201
+ */
202
+ static isNullOrEmpty(source) {
203
+ if (source === null || source === void 0) return true;
204
+ return this.from(source).isNullOrEmpty();
205
+ }
206
+ /**
207
+ * Creates a sequence of `count` integers starting from `start`.
208
+ *
209
+ * @remarks
210
+ * Returns an empty sequence when `count === 0`.
211
+ *
212
+ * @example
213
+ * ```ts
214
+ * Tyneq.range(1, 5).toArray(); // -> [1, 2, 3, 4, 5]
215
+ * ```
216
+ *
217
+ * @throws {ArgumentOutOfRangeError} When `count` is negative.
218
+ * @throws {ArgumentError} When `count` is not an integer.
219
+ */
220
+ @require_TyneqCachedTerminalOperator.source({ source: "internal" }) static range(start, count) {
221
+ require_Lazy.ArgumentUtility.checkNonNegative({ count });
222
+ require_Lazy.ArgumentUtility.checkInteger({ count });
223
+ const end = start + count - 1;
224
+ return new require_TyneqCachedTerminalOperator.TyneqEnumerable({ getEnumerator: () => new RangeEnumerator(start, end) }, new require_TyneqCachedTerminalOperator.QueryNode("range", [start, count], null, "source"));
225
+ }
226
+ /** Returns an empty sequence with zero elements. */
227
+ @require_TyneqCachedTerminalOperator.source({ source: "internal" }) static empty() {
228
+ return new require_TyneqCachedTerminalOperator.TyneqEnumerable(new EnumerableAdapter([]), new require_TyneqCachedTerminalOperator.QueryNode("empty", [], null, "source"));
229
+ }
230
+ /**
231
+ * Creates a sequence that yields `value` exactly `count` times.
232
+ *
233
+ * @example
234
+ * ```ts
235
+ * Tyneq.repeat("x", 3).toArray(); // -> ["x", "x", "x"]
236
+ * ```
237
+ *
238
+ * @throws {ArgumentOutOfRangeError} When `count` is negative.
239
+ * @throws {ArgumentError} When `count` is not an integer.
240
+ */
241
+ @require_TyneqCachedTerminalOperator.source({ source: "internal" }) static repeat(value, count) {
242
+ require_Lazy.ArgumentUtility.checkNonNegative({ count });
243
+ require_Lazy.ArgumentUtility.checkInteger({ count });
244
+ return new require_TyneqCachedTerminalOperator.TyneqEnumerable({ getEnumerator: () => new RepeatEnumerator(value, count) }, new require_TyneqCachedTerminalOperator.QueryNode("repeat", [value, count], null, "source"));
245
+ }
246
+ /**
247
+ * Creates a sequence by repeatedly applying `next` to produce each element from the previous one.
248
+ *
249
+ * @remarks
250
+ * The selector receives `(currentValue, index)`. Each call's return value becomes the input
251
+ * for the next call. Omit `count` for an infinite sequence; pair with `take` to bound it.
252
+ *
253
+ * @example
254
+ * ```ts
255
+ * Tyneq.generate(1, (x) => x * 2, 4).toArray(); // -> [2, 4, 8, 16]
256
+ * ```
257
+ *
258
+ * @throws {ArgumentNullError} When `next` is null or undefined.
259
+ * @throws {ArgumentOutOfRangeError} When `count` is negative.
260
+ * @throws {ArgumentError} When `count` is not an integer.
261
+ */
262
+ @require_TyneqCachedTerminalOperator.source({ source: "internal" }) static generate(seed, next, count) {
263
+ require_Lazy.ArgumentUtility.checkNotOptional({ next });
264
+ if (count !== void 0) {
265
+ require_Lazy.ArgumentUtility.checkNonNegative({ count });
266
+ require_Lazy.ArgumentUtility.checkInteger({ count });
267
+ }
268
+ return new require_TyneqCachedTerminalOperator.TyneqEnumerable({ getEnumerator: () => new GenerateEnumerator(seed, next, count) }, new require_TyneqCachedTerminalOperator.QueryNode("generate", [
269
+ seed,
270
+ next,
271
+ count
272
+ ], null, "source"));
273
+ }
274
+ /**
275
+ * Creates a sequence that yields all elements from each source in order.
276
+ *
277
+ * @remarks
278
+ * Returns an empty sequence when called with no arguments.
279
+ *
280
+ * @example
281
+ * ```ts
282
+ * Tyneq.concat([1, 2], [3, 4], [5]).toArray(); // -> [1, 2, 3, 4, 5]
283
+ * ```
284
+ *
285
+ * @throws {ArgumentNullError} When any `source` is null or undefined.
286
+ * @throws {ArgumentTypeError} When any `source` is not iterable.
287
+ */
288
+ @require_TyneqCachedTerminalOperator.source({ source: "internal" }) static concat(...sources) {
289
+ for (const source of sources) {
290
+ require_Lazy.ArgumentUtility.checkNotOptional({ source });
291
+ require_Lazy.ArgumentUtility.checkIterable({ source });
292
+ }
293
+ return new require_TyneqCachedTerminalOperator.TyneqEnumerable({ getEnumerator: () => new SourceConcatEnumerator(...sources) }, new require_TyneqCachedTerminalOperator.QueryNode("concat", sources, null, "source"));
294
+ }
295
+ /**
296
+ * Pairs each element with its zero-based index.
297
+ *
298
+ * @remarks
299
+ * Each iteration produces independent index counters - safe to re-enumerate.
300
+ *
301
+ * @example
302
+ * ```ts
303
+ * Tyneq.enumerate(["a", "b", "c"]).toArray();
304
+ * // -> [[0, "a"], [1, "b"], [2, "c"]]
305
+ * ```
306
+ *
307
+ * @throws {ArgumentNullError} When `source` is null or undefined.
308
+ * @throws {ArgumentTypeError} When `source` is not iterable.
309
+ */
310
+ static enumerate(source) {
311
+ require_Lazy.ArgumentUtility.checkNotOptional({ source });
312
+ require_Lazy.ArgumentUtility.checkIterable({ source });
313
+ return this.from({ *[Symbol.iterator]() {
314
+ let index = 0;
315
+ for (const item of source) yield [index++, item];
316
+ } });
317
+ }
318
+ };
319
+ //#endregion
320
+ //#region src/core/errors/KeyNotFoundError.ts
321
+ /**
322
+ * Thrown when a lookup is performed with a key that does not exist in the collection.
323
+ *
324
+ * @example
325
+ * ```ts
326
+ * try { seq.toMap((x) => x.id).get(999); }
327
+ * catch (e) { if (e instanceof KeyNotFoundError) { ... } }
328
+ * ```
329
+ *
330
+ * @see {@link TyneqError}
331
+ * @group Errors
332
+ */
333
+ var KeyNotFoundError = class extends require_Lazy.TyneqError {
334
+ constructor(message = "The given key was not present in the dictionary.", inner) {
335
+ super(message, { inner });
336
+ }
337
+ };
338
+ //#endregion
339
+ //#region src/core/errors/NotSupportedError.ts
340
+ /**
341
+ * Thrown when a requested operation is not supported.
342
+ *
343
+ * @example
344
+ * ```ts
345
+ * try { iterator.throw?.(new Error()); }
346
+ * catch (e) { if (e instanceof NotSupportedError) { ... } }
347
+ * ```
348
+ *
349
+ * @see {@link TyneqError}
350
+ * @group Errors
351
+ */
352
+ var NotSupportedError = class extends require_Lazy.TyneqError {
353
+ constructor(message = "The requested operation is not supported.", inner) {
354
+ super(message, { inner });
355
+ }
356
+ };
357
+ //#endregion
358
+ //#region src/core/errors/CompilerError.ts
359
+ /**
360
+ * Thrown when the query plan compiler encounters a structural or semantic error
361
+ * while compiling a query plan into an executable sequence.
362
+ *
363
+ * @example
364
+ * ```ts
365
+ * try { compiler.compile(plan); }
366
+ * catch (e) {
367
+ * if (e instanceof CompilerError) {
368
+ * console.log(e.operatorName, e.phase, e.message);
369
+ * }
370
+ * }
371
+ * ```
372
+ *
373
+ * @see {@link TyneqError}
374
+ * @group Errors
375
+ */
376
+ var CompilerError = class extends require_Lazy.TyneqError {
377
+ /** The name of the operator being compiled when the error occurred, if known. */
378
+ operatorName;
379
+ /**
380
+ * The compilation phase in which the error occurred.
381
+ * - `"transform"` - during query plan transformation (pre-compile)
382
+ * - `"source"` - while compiling a source node
383
+ * - `"operator"` - while applying an operator node
384
+ */
385
+ phase;
386
+ constructor(message, phase, operatorName, inner) {
387
+ super(message, { inner });
388
+ this.phase = phase;
389
+ this.operatorName = operatorName;
390
+ }
391
+ };
392
+ //#endregion
393
+ //#region src/queryplan/QueryPlanPrinter.ts
394
+ /**
395
+ * Converts a query plan tree into a human-readable multi-line string.
396
+ *
397
+ * Implements `QueryPlanVisitor<string>`. The output lists operators from source to
398
+ * terminal, one per line, with indentation showing the pipeline depth.
399
+ *
400
+ * @example
401
+ * ```ts
402
+ * import { QueryPlanPrinter } from "tyneq/queryplan";
403
+ *
404
+ * const seq = Tyneq.range(1, 10).where(x => x % 2 === 0).select(x => x * x);
405
+ * console.log(QueryPlanPrinter.print(seq[tyneqQueryNode]!));
406
+ * // range(1, 10)
407
+ * // -> where(<fn>)
408
+ * // -> select(<fn>)
409
+ * ```
410
+ *
411
+ * @group QueryPlan
412
+ */
413
+ var QueryPlanPrinter = class QueryPlanPrinter {
414
+ indent;
415
+ arrow;
416
+ maxInlineArrayItems;
417
+ constructor(options = {}) {
418
+ this.indent = options.indent ?? " ";
419
+ this.arrow = options.arrow ?? "->";
420
+ this.maxInlineArrayItems = options.maxInlineArrayItems ?? 3;
421
+ }
422
+ /** Renders the full query plan rooted at `node` as a multi-line string. */
423
+ visit(node) {
424
+ return this.buildPlan(node);
425
+ }
426
+ /** Convenience static: creates a printer with `options` and calls `visit(node)`. */
427
+ static print(node, options) {
428
+ return new QueryPlanPrinter(options).visit(node);
429
+ }
430
+ /**
431
+ * Formats a single operator argument for display.
432
+ * Override to customise how arguments appear in printed plans.
433
+ */
434
+ formatArg(arg) {
435
+ if (typeof arg === "function") return "<fn>";
436
+ if (arg === null) return "null";
437
+ if (arg === void 0) return "undefined";
438
+ if (typeof arg === "string") return `"${arg}"`;
439
+ if (Array.isArray(arg)) {
440
+ if (arg.length === 0) return "[]";
441
+ if (arg.length <= this.maxInlineArrayItems) return `[${arg.map((a) => this.formatArg(a)).join(", ")}]`;
442
+ return `[...${arg.length} items]`;
443
+ }
444
+ if (typeof arg === "object") return "{...}";
445
+ return String(arg);
446
+ }
447
+ /**
448
+ * Formats one line of the plan output.
449
+ * Override to customise indentation or arrow style beyond what `QueryPlanPrinterOptions` allows.
450
+ */
451
+ formatLine(name, argStr, isRoot) {
452
+ return `${isRoot ? "" : `${this.indent}${this.arrow} `}${name}(${argStr})`;
453
+ }
454
+ buildPlan(node) {
455
+ return this.collectNodes(node).map((n, index) => {
456
+ const argStr = n.args.map((a) => this.formatArg(a)).join(", ");
457
+ return this.formatLine(n.operatorName, argStr, index === 0);
458
+ }).join("\n");
459
+ }
460
+ collectNodes(node) {
461
+ const nodes = [];
462
+ let current = node;
463
+ while (current !== null) {
464
+ nodes.push(current);
465
+ current = current.source;
466
+ }
467
+ nodes.reverse();
468
+ return nodes;
469
+ }
470
+ };
471
+ //#endregion
472
+ //#region src/queryplan/QueryPlanWalker.ts
473
+ /**
474
+ * Concrete base class for side-effect query plan visitors.
475
+ *
476
+ * @remarks
477
+ * Traverses the node chain calling {@link QueryPlanWalker.visitNode} once per node.
478
+ * The traversal direction defaults to `"source-to-terminal"` (bottom-up: `from` before
479
+ * `where` before `select`) and can be changed to `"terminal-to-source"` (top-down) via
480
+ * the options object.
481
+ *
482
+ * ### Usage patterns
483
+ *
484
+ * **Direct instantiation with a callback** - no subclass needed for simple traversals:
485
+ * ```ts
486
+ * const names: string[] = [];
487
+ * new QueryPlanWalker({ callback: node => names.push(node.operatorName) }).visit(plan);
488
+ * ```
489
+ *
490
+ * **Subclass** - for stateful walkers that accumulate results across nodes:
491
+ * ```ts
492
+ * class NodeCounter extends QueryPlanWalker {
493
+ * public count = 0;
494
+ * protected override visitNode(_node: QueryPlanNode): void { this.count++; }
495
+ * }
496
+ *
497
+ * const counter = new NodeCounter();
498
+ * counter.visit(seq[tyneqQueryNode]!);
499
+ * console.log(counter.count); // number of operators in the pipeline
500
+ * ```
501
+ *
502
+ * **Top-down traversal:**
503
+ * ```ts
504
+ * new QueryPlanWalker({
505
+ * callback: node => console.log(node.operatorName),
506
+ * direction: "terminal-to-source",
507
+ * }).visit(plan);
508
+ * ```
509
+ *
510
+ * **Subclass with direction override** - pass options to `super`:
511
+ * ```ts
512
+ * class ReverseCollector extends QueryPlanWalker {
513
+ * public readonly names: string[] = [];
514
+ * public constructor() { super({ direction: "terminal-to-source" }); }
515
+ * protected override visitNode(node: QueryPlanNode): void {
516
+ * this.names.push(node.operatorName);
517
+ * }
518
+ * }
519
+ * ```
520
+ *
521
+ * @group QueryPlan
522
+ */
523
+ var QueryPlanWalker = class {
524
+ callback;
525
+ direction;
526
+ /**
527
+ * @param options - Optional configuration for the walker.
528
+ */
529
+ constructor(options) {
530
+ this.callback = options?.callback;
531
+ this.direction = options?.direction ?? "source-to-terminal";
532
+ }
533
+ /**
534
+ * Walks the full chain rooted at `node`, calling {@link visitNode} for each node
535
+ * in the configured direction.
536
+ */
537
+ visit(node) {
538
+ if (this.direction === "source-to-terminal") {
539
+ if (node.source !== null) this.visit(node.source);
540
+ this.visitNode(node);
541
+ } else {
542
+ this.visitNode(node);
543
+ if (node.source !== null) this.visit(node.source);
544
+ }
545
+ }
546
+ /**
547
+ * Called once per node during traversal.
548
+ *
549
+ * @remarks
550
+ * The default implementation fires the callback passed via options (if any).
551
+ * Override this method in a subclass to provide custom behaviour. Subclasses that
552
+ * override this method decide whether to call `super.visitNode(node)` to also fire
553
+ * the options callback.
554
+ *
555
+ * @param node - The current node being visited.
556
+ */
557
+ visitNode(node) {
558
+ this.callback?.(node);
559
+ }
560
+ };
561
+ //#endregion
562
+ //#region src/queryplan/QueryPlanTransformer.ts
563
+ /**
564
+ * Base class for immutable query plan rewriting.
565
+ *
566
+ * @remarks
567
+ * Recursively rebuilds the node chain, calling {@link QueryPlanTransformer.transformNode}
568
+ * once per node. The default implementation is an **identity transform** - every node is
569
+ * reconstructed with the same data, producing a structurally equivalent copy.
570
+ *
571
+ * Subclasses override `transformNode` to intercept specific operators. Three rewrite
572
+ * patterns are possible:
573
+ *
574
+ * - **Rewrite a node** - return a new `QueryNode` with different `operatorName` or `args`
575
+ * - **Remove a node** - return `source` directly, skipping this node
576
+ * - **Collapse two nodes into one** - use `source` as the new node's `source` (fusing
577
+ * the current node with its already-transformed predecessor)
578
+ *
579
+ * @example
580
+ * ```ts
581
+ * // Rename all 'where' nodes to 'filter' in the plan view
582
+ * class RenameWhere extends QueryPlanTransformer {
583
+ * protected override transformNode(node: QueryPlanNode, source: QueryPlanNode | null): QueryPlanNode {
584
+ * if (node.operatorName === "where") {
585
+ * return new QueryNode("filter", node.args, source, node.category);
586
+ * }
587
+ * return super.transformNode(node, source);
588
+ * }
589
+ * }
590
+ * ```
591
+ *
592
+ * @group QueryPlan
593
+ */
594
+ var QueryPlanTransformer = class {
595
+ /**
596
+ * Transforms the full chain rooted at `node` and returns the new root node.
597
+ *
598
+ * @remarks
599
+ * Processes source-first (bottom-up): the source chain is fully transformed before
600
+ * `transformNode` is called for the current node. This means `source` passed to
601
+ * `transformNode` is always already the transformed predecessor.
602
+ */
603
+ visit(node) {
604
+ const transformedSource = node.source !== null ? this.visit(node.source) : null;
605
+ return this.transformNode(node, transformedSource);
606
+ }
607
+ /**
608
+ * Transforms a single node. Override to intercept specific operators.
609
+ *
610
+ * @remarks
611
+ * The default implementation reconstructs the node with identical data (identity transform).
612
+ * `source` is the already-transformed predecessor - use it as the `source` of any returned
613
+ * node to preserve chain continuity.
614
+ *
615
+ * @param node - The original node (unmodified).
616
+ * @param source - The transformed predecessor, or `null` for source nodes.
617
+ */
618
+ transformNode(node, source) {
619
+ return new require_TyneqCachedTerminalOperator.QueryNode(node.operatorName, node.args, source, node.category, node.sourceKind);
620
+ }
621
+ };
622
+ //#endregion
623
+ //#region src/queryplan/QueryPlanOptimizer.ts
624
+ /**
625
+ * A built-in {@link QueryPlanTransformer} that fuses redundant consecutive operators.
626
+ *
627
+ * @remarks
628
+ * **This optimizer rewrites the query plan tree only - it does not affect execution of the
629
+ * original sequence.** The returned `QueryPlanNode` reflects what an optimized pipeline would
630
+ * look like; the live sequence that produced the original plan is unchanged.
631
+ *
632
+ * **Fusion is only semantics-preserving for pure, side-effect-free functions.**
633
+ * If a predicate or projection has side effects (e.g. logging, mutation), fusing two nodes into
634
+ * one changes when and how many times those effects fire. For example, in a fused `where`, the
635
+ * second predicate is never called for items that fail the first - any mutation inside the second
636
+ * predicate is skipped for those items. Do not use this optimizer on pipelines with impure
637
+ * predicates or projections.
638
+ *
639
+ * ### Fusions applied
640
+ *
641
+ * | Pattern | Result |
642
+ * |---------|--------|
643
+ * | `where(a) -> where(b)` | `where(x => a(x) && b(x))` |
644
+ * | `select(a) -> select(b)` | `select(x => b(a(x)))` |
645
+ *
646
+ * Additional fusions can be added by subclassing and overriding
647
+ * {@link QueryPlanTransformer.transformNode}.
648
+ *
649
+ * @example
650
+ * ```ts
651
+ * import { Tyneq, tyneqQueryNode, QueryPlanOptimizer, QueryPlanPrinter } from "tyneq";
652
+ *
653
+ * const seq = Tyneq.from([1, 2, 3])
654
+ * .where(x => x > 1)
655
+ * .where(x => x < 3)
656
+ * .select(x => x * 2)
657
+ * .select(x => x + 1);
658
+ *
659
+ * const original = seq[tyneqQueryNode]!;
660
+ * const optimized = new QueryPlanOptimizer().visit(original);
661
+ *
662
+ * console.log(QueryPlanPrinter.print(original));
663
+ * // from([...])
664
+ * // -> where(<fn>)
665
+ * // -> where(<fn>)
666
+ * // -> select(<fn>)
667
+ * // -> select(<fn>)
668
+ *
669
+ * console.log(QueryPlanPrinter.print(optimized));
670
+ * // from([...])
671
+ * // -> where(<fn>)
672
+ * // -> select(<fn>)
673
+ * ```
674
+ *
675
+ * @group QueryPlan
676
+ */
677
+ var QueryPlanOptimizer = class extends QueryPlanTransformer {
678
+ transformNode(node, source) {
679
+ if (node.operatorName === "where" && source?.operatorName === "where") return this.fuseWhere(node, source);
680
+ if (node.operatorName === "select" && source?.operatorName === "select") return this.fuseSelect(node, source);
681
+ return super.transformNode(node, source);
682
+ }
683
+ /**
684
+ * @remarks Fusion is only semantics-preserving for pure, side-effect-free predicates.
685
+ */
686
+ fuseWhere(node, source) {
687
+ const predA = source.args[0];
688
+ const predB = node.args[0];
689
+ const fused = (x) => predA(x) && predB(x);
690
+ return new require_TyneqCachedTerminalOperator.QueryNode("where", [fused], source.source, "streaming");
691
+ }
692
+ /**
693
+ * @remarks Fusion is only semantics-preserving for pure, side-effect-free projections.
694
+ */
695
+ fuseSelect(node, source) {
696
+ const projA = source.args[0];
697
+ const projB = node.args[0];
698
+ const fused = (x) => projB(projA(x));
699
+ return new require_TyneqCachedTerminalOperator.QueryNode("select", [fused], source.source, "streaming");
700
+ }
701
+ };
702
+ //#endregion
703
+ //#region src/queryplan/compiler/QueryPlanCompiler.ts
704
+ /**
705
+ * Compiles a query plan tree into an executable `TyneqSequence`.
706
+ *
707
+ * @remarks
708
+ * Walks the `QueryPlanNode` chain from source to terminal, reconstructing each operator by
709
+ * looking it up in the `OperatorRegistry` and applying it to the compiled source.
710
+ * An optional list of `QueryPlanTransformer` instances runs before compilation, allowing
711
+ * optimization or rewriting of the plan.
712
+ *
713
+ * @example
714
+ * ```ts
715
+ * import { Tyneq, tyneqQueryNode, QueryPlanCompiler, QueryPlanOptimizer } from "tyneq";
716
+ *
717
+ * const seq = Tyneq.from([1, 2, 3]).where(x => x > 1).select(x => x * 2);
718
+ * const compiler = new QueryPlanCompiler([new QueryPlanOptimizer()]);
719
+ * const result = compiler.compile(seq[tyneqQueryNode]!);
720
+ * result.toArray(); // -> [4, 6]
721
+ * ```
722
+ *
723
+ * @group QueryPlan
724
+ */
725
+ var QueryPlanCompiler = class {
726
+ transformers;
727
+ constructor(transformers = []) {
728
+ this.transformers = [...transformers];
729
+ }
730
+ /**
731
+ * Compile a query plan into an executable sequence.
732
+ *
733
+ * Runs all registered transformers before compiling. Use {@link compileRaw} to skip
734
+ * the transform phase when the plan is already optimised.
735
+ *
736
+ * @param node - The root node of the query plan to compile.
737
+ * @param options - Optional compile-time overrides. Use `options.source` to supply a
738
+ * different data source than the one stored in the plan.
739
+ * @returns The compiled sequence typed as `TyneqSequence<T>` by default.
740
+ * If you know the plan ends in an operator that returns a subtype (e.g. `orderBy` ->
741
+ * `TyneqOrderedSequence`, `memoize` -> `TyneqCachedSequence`), supply `TResult` explicitly:
742
+ * `compiler.compile<number, TyneqOrderedSequence<number>>(node)`.
743
+ */
744
+ compile(node, options) {
745
+ if (node === null || node === void 0) throw new CompilerError("compile() received a null or undefined query plan node. Ensure the sequence was created via Tyneq.from(), Tyneq.range(), or another source operator before compiling.", "source");
746
+ const transformedNode = this.transform(node);
747
+ return this.compileNode(transformedNode, options);
748
+ }
749
+ /**
750
+ * Compile a pre-optimised query plan into an executable sequence, skipping the
751
+ * transform phase.
752
+ *
753
+ * @remarks
754
+ * Use this overload when you have already run transformers externally (or deliberately
755
+ * want to bypass them) and want to compile the node as-is. Equivalent to constructing
756
+ * a `QueryPlanCompiler` with no transformers and calling `compile()`.
757
+ *
758
+ * @param node - The root node of the already-transformed query plan to compile.
759
+ * @param options - Optional compile-time overrides. Use `options.source` to supply a
760
+ * different data source than the one stored in the plan.
761
+ */
762
+ compileRaw(node, options) {
763
+ if (node === null || node === void 0) throw new CompilerError("compileRaw() received a null or undefined query plan node. Ensure the sequence was created via Tyneq.from(), Tyneq.range(), or another source operator before compiling.", "source");
764
+ return this.compileNode(node, options);
765
+ }
766
+ transform(node) {
767
+ let transformedNode = node;
768
+ for (const transformer of this.transformers) try {
769
+ transformedNode = transformer.visit(transformedNode);
770
+ } catch (e) {
771
+ throw new CompilerError(`Transformer "${transformer.constructor.name}" threw during transformation.`, "transform", void 0, e instanceof Error ? e : void 0);
772
+ }
773
+ return transformedNode;
774
+ }
775
+ compileNode(node, options) {
776
+ if (node.category === "source") return this.compileSource(node, options);
777
+ if (node.source === null) throw new CompilerError("Operator node is missing a source node. Every operator node must have a source.", "operator", node.operatorName);
778
+ return this.applyOperator(this.compileNode(node.source, options), node);
779
+ }
780
+ compileSource(node, options) {
781
+ const entry = require_TyneqCachedTerminalOperator.OperatorRegistry.getSource(node.operatorName);
782
+ if (!entry) throw new CompilerError(`Unknown source operator "${node.operatorName}". Register it via OperatorRegistry.registerSource() before compiling.`, "source", node.operatorName);
783
+ const args = options?.source !== void 0 ? [options.source, ...node.args.slice(1)] : [...node.args];
784
+ return entry.impl.apply(null, args);
785
+ }
786
+ applyOperator(source, node) {
787
+ const entry = this.findOperatorEntry(node.operatorName, source);
788
+ if (!entry) {
789
+ const sourceType = source !== null && source !== void 0 ? Object.getPrototypeOf(source)?.constructor?.name ?? typeof source : "null";
790
+ throw new CompilerError(require_TyneqCachedTerminalOperator.OperatorRegistry.hasOperator(node.operatorName) ? `Operator "${node.operatorName}" is not registered for sequence type ${sourceType}. Ensure the source sequence is of the correct type for this operator.` : `Operator "${node.operatorName}" is not registered. Register it via @operator, createOperator, or createGeneratorOperator before compiling.`, "operator", node.operatorName);
791
+ }
792
+ return entry.impl.apply(source, [...node.args]);
793
+ }
794
+ findOperatorEntry(operatorName, source) {
795
+ if (source === null || source === void 0) return;
796
+ let proto = Object.getPrototypeOf(source);
797
+ while (proto !== null) {
798
+ const ctor = proto.constructor;
799
+ if (ctor !== void 0) {
800
+ const entry = require_TyneqCachedTerminalOperator.OperatorRegistry.getOperator(operatorName, ctor);
801
+ if (entry !== void 0) return entry;
802
+ }
803
+ proto = Object.getPrototypeOf(proto);
804
+ }
805
+ }
806
+ };
807
+ //#endregion
808
+ exports.ArgumentError = require_Lazy.ArgumentError;
809
+ exports.ArgumentNullError = require_Lazy.ArgumentNullError;
810
+ exports.ArgumentOutOfRangeError = require_Lazy.ArgumentOutOfRangeError;
811
+ exports.ArgumentTypeError = require_Lazy.ArgumentTypeError;
812
+ exports.ArgumentUtility = require_Lazy.ArgumentUtility;
813
+ exports.CompilerError = CompilerError;
814
+ exports.InvalidOperationError = require_TyneqCachedTerminalOperator.InvalidOperationError;
815
+ exports.KeyNotFoundError = KeyNotFoundError;
816
+ exports.Lazy = require_Lazy.Lazy;
817
+ exports.NotSupportedError = NotSupportedError;
818
+ exports.OperatorMetadata = require_TyneqCachedTerminalOperator.OperatorMetadata;
819
+ exports.OperatorRegistry = require_TyneqCachedTerminalOperator.OperatorRegistry;
820
+ exports.PluginError = require_TyneqCachedTerminalOperator.PluginError;
821
+ exports.QueryNode = require_TyneqCachedTerminalOperator.QueryNode;
822
+ exports.QueryPlanCompiler = QueryPlanCompiler;
823
+ exports.QueryPlanOptimizer = QueryPlanOptimizer;
824
+ exports.QueryPlanPrinter = QueryPlanPrinter;
825
+ exports.QueryPlanTransformer = QueryPlanTransformer;
826
+ exports.QueryPlanWalker = QueryPlanWalker;
827
+ exports.ReflectionContext = require_Lazy.ReflectionContext;
828
+ exports.ReflectionError = require_Lazy.ReflectionError;
829
+ exports.RegistryError = require_TyneqCachedTerminalOperator.RegistryError;
830
+ exports.SequenceContainsNoElementsError = require_TyneqCachedTerminalOperator.SequenceContainsNoElementsError;
831
+ exports.Tyneq = Tyneq;
832
+ exports.TyneqBaseEnumerator = require_TyneqCachedTerminalOperator.TyneqBaseEnumerator;
833
+ exports.TyneqCachedEnumerable = require_TyneqCachedTerminalOperator.TyneqCachedEnumerable;
834
+ exports.TyneqCachedEnumerator = require_TyneqCachedTerminalOperator.TyneqCachedEnumerator;
835
+ exports.TyneqCachedTerminalOperator = require_TyneqCachedTerminalOperator.TyneqCachedTerminalOperator;
836
+ exports.TyneqComparer = require_TyneqCachedTerminalOperator.TyneqComparer;
837
+ exports.TyneqEnumerator = require_TyneqCachedTerminalOperator.TyneqEnumerator;
838
+ exports.TyneqError = require_Lazy.TyneqError;
839
+ exports.TyneqOrderedEnumerable = require_TyneqCachedTerminalOperator.TyneqOrderedEnumerable;
840
+ exports.TyneqOrderedEnumerator = require_TyneqCachedTerminalOperator.TyneqOrderedEnumerator;
841
+ exports.TyneqOrderedTerminalOperator = require_TyneqCachedTerminalOperator.TyneqOrderedTerminalOperator;
842
+ exports.TyneqTerminalOperator = require_TyneqCachedTerminalOperator.TyneqTerminalOperator;
843
+ exports.TypeGuardUtility = require_Lazy.TypeGuardUtility;
844
+ exports.ValidationBuilder = require_ValidationBuilder.ValidationBuilder;
845
+ exports.ValidationError = require_ValidationBuilder.ValidationError;
846
+ exports.cachedOperator = require_TyneqCachedTerminalOperator.cachedOperator;
847
+ exports.cachedTerminal = require_TyneqCachedTerminalOperator.cachedTerminal;
848
+ exports.createCachedOperator = require_TyneqCachedTerminalOperator.createCachedOperator;
849
+ exports.createCachedTerminalOperator = require_TyneqCachedTerminalOperator.createCachedTerminalOperator;
850
+ exports.createGeneratorOperator = require_TyneqCachedTerminalOperator.createGeneratorOperator;
851
+ exports.createOperator = require_TyneqCachedTerminalOperator.createOperator;
852
+ exports.createOrderedOperator = require_TyneqCachedTerminalOperator.createOrderedOperator;
853
+ exports.createOrderedTerminalOperator = require_TyneqCachedTerminalOperator.createOrderedTerminalOperator;
854
+ exports.createTerminalOperator = require_TyneqCachedTerminalOperator.createTerminalOperator;
855
+ exports.isSourceNode = require_TyneqCachedTerminalOperator.isSourceNode;
856
+ exports.operator = require_TyneqCachedTerminalOperator.operator;
857
+ exports.orderedOperator = require_TyneqCachedTerminalOperator.orderedOperator;
858
+ exports.orderedTerminal = require_TyneqCachedTerminalOperator.orderedTerminal;
859
+ exports.reflect = require_Lazy.reflect;
860
+ exports.terminal = require_TyneqCachedTerminalOperator.terminal;
861
+ exports.tyneqQueryNode = require_TyneqCachedTerminalOperator.tyneqQueryNode;
862
+
863
+ //# sourceMappingURL=index.cjs.map