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