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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 chrisitopherus
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,319 @@
1
+ <div align="center">
2
+ <br />
3
+ <a href="https://github.com/chrisitopherus/tyneq">
4
+ <img src="./docs/public/logo.svg" alt="Tyneq" width="180" height="180" />
5
+ </a>
6
+ <h1>tyneq</h1>
7
+ <p><strong>Lazy query pipelines for TypeScript.</strong></p>
8
+
9
+ <p>
10
+ <a href="https://www.npmjs.com/package/tyneq">
11
+ <img src="https://img.shields.io/npm/v/tyneq?style=flat-square&color=0ea5e9" alt="npm version" />
12
+ </a>
13
+ <img src="https://img.shields.io/badge/TypeScript-5.x-3178c6?style=flat-square&logo=typescript&logoColor=white" alt="TypeScript" />
14
+ <img src="https://img.shields.io/badge/dependencies-zero-22c55e?style=flat-square" alt="zero dependencies" />
15
+ <a href="./LICENSE">
16
+ <img src="https://img.shields.io/badge/license-MIT-a78bfa?style=flat-square" alt="MIT license" />
17
+ </a>
18
+ </p>
19
+
20
+ <p>
21
+ <a href="https://chrisitopherus.github.io/tyneq/guide/">Guide</a>
22
+ &nbsp;&middot;&nbsp;
23
+ <a href="https://chrisitopherus.github.io/tyneq/api/">API Reference</a>
24
+ &nbsp;&middot;&nbsp;
25
+ <a href="https://github.com/chrisitopherus/tyneq/issues">Issues</a>
26
+ </p>
27
+ <br />
28
+ </div>
29
+
30
+ ---
31
+
32
+ ```ts
33
+ import { Tyneq } from "tyneq";
34
+
35
+ const topScorers = Tyneq
36
+ .from([
37
+ { name: "Ada", team: "core", score: 84 },
38
+ { name: "Linus", team: "infra", score: 92 },
39
+ { name: "Grace", team: "core", score: 97 },
40
+ ])
41
+ .where((p) => p.team === "core")
42
+ .orderByDescending((p) => p.score)
43
+ .select((p) => `${p.name} (${p.score})`)
44
+ .toArray();
45
+ // ["Grace (97)", "Ada (84)"]
46
+ ```
47
+
48
+ Nothing runs until `.toArray()`. Every operator is deferred, fully typed, and the same query can be re-evaluated as many times as you want.
49
+
50
+ ---
51
+
52
+ ## What is this?
53
+
54
+ Tyneq is a LINQ-style query pipeline library for TypeScript. You compose operators on a sequence, nothing executes until you call a terminal, and you can reuse the same query without rebuilding it.
55
+
56
+ It is not a thin wrapper around `Array.prototype`. It is a pipeline engine with a deliberate execution model, a real query plan system, and a plugin API that lets you ship custom operators as standalone packages.
57
+
58
+ ---
59
+
60
+ ## Why Tyneq?
61
+
62
+ ### Sequences, not cursors
63
+
64
+ Most iterator libraries give you a one-shot cursor. Once you consume it, it is gone. Tyneq sequences are re-iterable by default - call any terminal as many times as you want, each gets independent state.
65
+
66
+ ```ts
67
+ const active = Tyneq.from(users)
68
+ .where((u) => u.active)
69
+ .orderByDescending((u) => u.score);
70
+
71
+ active.count(); // 3
72
+ active.first().name; // "Grace"
73
+ active.select((u) => u.email).toArray(); // ["g@...", "l@...", "a@..."]
74
+ ```
75
+
76
+ No re-wrapping. No rebuilding. Same query, three independent evaluations.
77
+
78
+ ### You always know what is happening
79
+
80
+ Every operator is explicitly **streaming** (O(1) memory, one element at a time) or **buffering** (reads the full source once, then serves from a buffer). There is no hidden materialization and no guessing about when data gets copied.
81
+
82
+ ```ts
83
+ Tyneq.from(largeDataset)
84
+ .where((x) => x.active) // streaming
85
+ .orderBy((x) => x.score) // buffering - reads all matching, sorts once
86
+ .take(10) // streaming - stops after 10
87
+ .toArray(); // terminal - kicks everything off
88
+ ```
89
+
90
+ ### Query plans you can actually use
91
+
92
+ Every sequence carries a live description of its pipeline. Print it, walk it with a visitor, rewrite it with a transformer, or compile it back into an executable sequence.
93
+
94
+ ```ts
95
+ import { QueryPlanPrinter, QueryPlanCompiler, QueryPlanOptimizer, tyneqQueryNode } from "tyneq";
96
+
97
+ const seq = Tyneq.from(data)
98
+ .where((x) => x > 0)
99
+ .where((x) => x < 100)
100
+ .select((x) => x * 2);
101
+
102
+ // Print the plan
103
+ console.log(QueryPlanPrinter.print(seq[tyneqQueryNode]!));
104
+ // from([...])
105
+ // -> where(<fn>)
106
+ // -> where(<fn>)
107
+ // -> select(<fn>)
108
+
109
+ // Compile with optimization - fuses the two where nodes
110
+ const compiler = new QueryPlanCompiler([new QueryPlanOptimizer()]);
111
+ compiler.compile(seq[tyneqQueryNode]!).toArray();
112
+ ```
113
+
114
+ The compiler turns plans into executable sequences. Store a pipeline as metadata, optimize it, swap the data source, replay it. Pipelines become data.
115
+
116
+ ### Extensible to the core
117
+
118
+ Custom operators look and behave exactly like built-ins. They get registered at import time, appear on every sequence, and show up in query plans.
119
+
120
+ ```ts
121
+ import { createGeneratorOperator } from "tyneq";
122
+
123
+ createGeneratorOperator({
124
+ name: "repeatEach",
125
+ category: "streaming",
126
+ *generator(source: Iterable<unknown>, times: number) {
127
+ for (const item of source) {
128
+ for (let i = 0; i < times; i++) yield item;
129
+ }
130
+ },
131
+ validate(times) {
132
+ if (times < 1) throw new RangeError("times must be >= 1");
133
+ },
134
+ });
135
+
136
+ declare module "tyneq" {
137
+ interface TyneqSequence<T> {
138
+ repeatEach(times: number): TyneqSequence<T>;
139
+ }
140
+ }
141
+
142
+ Tyneq.from([1, 2, 3]).repeatEach(2).toArray();
143
+ // [1, 1, 2, 2, 3, 3]
144
+ ```
145
+
146
+ Two registration styles: functional (generators, factories, terminals) and class-based (decorators with full lifecycle). Ship it as a package - consumers import once and every sequence gains the operator.
147
+
148
+ ---
149
+
150
+ ## Quick comparison
151
+
152
+ | | Tyneq | Typical iterator lib |
153
+ |---|---|---|
154
+ | Deferred execution | yes | yes |
155
+ | Re-iterable sequences | yes | often no |
156
+ | Explicit streaming vs. buffering | yes | usually implicit |
157
+ | Multi-key ordering (`thenBy`) | yes | varies |
158
+ | Joins and group joins | yes | rare |
159
+ | Built-in memoization | yes | rare |
160
+ | Custom operator plugin API | yes | rare |
161
+ | Query plan + compiler | yes | very rare |
162
+ | Zero runtime dependencies | yes | varies |
163
+
164
+ ---
165
+
166
+ ## Install
167
+
168
+ ```bash
169
+ npm install tyneq
170
+ ```
171
+
172
+ TypeScript 5.x with `"strictNullChecks": true`. No `@types` package needed.
173
+
174
+ ---
175
+
176
+ ## Quick tour
177
+
178
+ ```ts
179
+ import { Tyneq } from "tyneq";
180
+
181
+ // Wrap any iterable
182
+ Tyneq.from([1, 2, 3]);
183
+ Tyneq.from(new Set(["a", "b"]));
184
+ Tyneq.range(1, 5); // [1, 2, 3, 4, 5]
185
+ Tyneq.empty<number>();
186
+
187
+ // Compose operators - nothing runs yet
188
+ const query = Tyneq.range(1, 1_000_000)
189
+ .where((n) => n % 2 === 0)
190
+ .select((n) => n * n)
191
+ .take(5);
192
+
193
+ // Execute with a terminal
194
+ query.toArray(); // [4, 16, 36, 64, 100]
195
+ query.count(); // 5 - same query, independent traversal
196
+ query.first(); // 4
197
+
198
+ // Standard iteration works too
199
+ for (const n of query) console.log(n);
200
+ const arr = [...query];
201
+ ```
202
+
203
+ Multi-key sorting, grouping, joins - all built in:
204
+
205
+ ```ts
206
+ Tyneq.from(employees)
207
+ .where((e) => e.department === "engineering")
208
+ .orderBy((e) => e.level)
209
+ .thenByDescending((e) => e.yearsAtCompany)
210
+ .groupBy(
211
+ (e) => e.team,
212
+ (e) => e.name,
213
+ (team, members) => ({ team, members: members.toArray() })
214
+ )
215
+ .toArray();
216
+ ```
217
+
218
+ ---
219
+
220
+ ## Operators
221
+
222
+ 80+ operators across three categories.
223
+
224
+ ### Streaming (O(1) memory)
225
+
226
+ `select` `where` `take` `takeWhile` `takeUntil` `skip` `skipWhile` `skipLast` `skipUntil` `slice` `selectMany` `flatten` `append` `prepend` `concat` `zip` `scan` `pairwise` `window` `chunk` `split` `repeat` `defaultIfEmpty` `populate` `ofType` `tap` `tapIf` `throttle` `pipe`
227
+
228
+ ### Buffering (reads full source once)
229
+
230
+ `orderBy` `orderByDescending` `thenBy` `thenByDescending` `groupBy` `distinct` `distinctBy` `reverse` `shuffle` `union` `unionBy` `intersect` `intersectBy` `except` `exceptBy` `join` `groupJoin` `backsert` `memoize` `permutations`
231
+
232
+ ### Terminal (executes the pipeline)
233
+
234
+ `toArray` `toSet` `toMap` `toRecord` `toAsync` `first` `firstOrDefault` `last` `lastOrDefault` `single` `singleOrDefault` `elementAt` `elementAtOrDefault` `count` `countBy` `sum` `average` `min` `max` `minBy` `maxBy` `minMax` `aggregate` `any` `all` `contains` `indexOf` `sequenceEqual` `startsWith` `endsWith` `isNullOrEmpty` `consume`
235
+
236
+ ---
237
+
238
+ ## Query plan
239
+
240
+ Every sequence carries a query plan tree. Access it, print it, walk it, transform it, or compile it back into an executable sequence.
241
+
242
+ ```ts
243
+ import { QueryPlanPrinter, tyneqQueryNode } from "tyneq";
244
+
245
+ const seq = Tyneq.from([1, 2, 3])
246
+ .where((x) => x > 1)
247
+ .select((x) => x * 2)
248
+ .take(5);
249
+
250
+ console.log(QueryPlanPrinter.print(seq[tyneqQueryNode]!));
251
+ // from([1, 2, 3])
252
+ // -> where(<fn>)
253
+ // -> select(<fn>)
254
+ // -> take(5)
255
+ ```
256
+
257
+ The `QueryPlanCompiler` takes any plan node and produces a fully executable sequence. Pass a `source` option to run the same pipeline against different data without rebuilding it:
258
+
259
+ ```ts
260
+ const plan = Tyneq.from(data).where((x) => x > 0).select((x) => x * 2)[tyneqQueryNode]!;
261
+ const compiler = new QueryPlanCompiler();
262
+
263
+ compiler.compile(plan, { source: datasetA }).toArray();
264
+ compiler.compile(plan, { source: datasetB }).toArray();
265
+ ```
266
+
267
+ Store pipelines as metadata, optimize them, replay them on any source. See the [Query Plan guide](https://chrisitopherus.github.io/tyneq/guide/query-plan) for the full picture.
268
+
269
+ ---
270
+
271
+ ## Docs
272
+
273
+ **[chrisitopherus.github.io/tyneq](https://chrisitopherus.github.io/tyneq/)**
274
+
275
+ | | |
276
+ |---|---|
277
+ | [Getting Started](https://chrisitopherus.github.io/tyneq/guide/getting-started) | Install, first query, sources, re-iteration |
278
+ | [Core Concepts](https://chrisitopherus.github.io/tyneq/guide/concepts) | Execution model, streaming vs. buffering, memoization |
279
+ | [Operators](https://chrisitopherus.github.io/tyneq/guide/operators) | All 80+ operators with examples |
280
+ | [Custom Operators](https://chrisitopherus.github.io/tyneq/guide/extensibility) | Functional API and decorators |
281
+ | [Plugin Internals](https://chrisitopherus.github.io/tyneq/guide/plugin-internals) | Registry, custom enumerators, utility helpers |
282
+ | [Query Plan](https://chrisitopherus.github.io/tyneq/guide/query-plan) | Plan access, printing, walking, transforming, compiling |
283
+ | [Best Practices](https://chrisitopherus.github.io/tyneq/guide/best-practices) | Patterns, pitfalls, and performance guidance |
284
+ | [API Reference](https://chrisitopherus.github.io/tyneq/api/) | Full generated API docs |
285
+
286
+ ---
287
+
288
+ ## Stability
289
+
290
+ Tyneq follows [Semantic Versioning](https://semver.org/). The public API contract covers:
291
+
292
+ - All methods on `TyneqSequence`, `TyneqOrderedSequence`, and `TyneqCachedSequence`
293
+ - All symbols exported from the `tyneq`, `tyneq/plugin`, and `tyneq/utility` subpaths
294
+ - The `Tyneq` static factory class
295
+
296
+ Internal classes (`TyneqEnumerableBase`, `TyneqEnumerableCore`, and anything tagged `@internal`) are **not** part of the contract and may change between minor versions.
297
+
298
+ ---
299
+
300
+ ## Contributing
301
+
302
+ ```bash
303
+ git clone https://github.com/chrisitopherus/tyneq
304
+ npm install
305
+ npm run build # compile CJS + ESM + types
306
+ npm test # run test suite
307
+ npm run lint # check style
308
+ npm run docs:dev # local docs site
309
+ ```
310
+
311
+ See the [Contributing guide](https://chrisitopherus.github.io/tyneq/guide/contributing) for the full workflow, including how to add operators, run the test suite, and submit a PR.
312
+
313
+ Bug reports and feature requests: [github.com/chrisitopherus/tyneq/issues](https://github.com/chrisitopherus/tyneq/issues)
314
+
315
+ ---
316
+
317
+ ## License
318
+
319
+ [MIT](./LICENSE) Copyright (c) 2026 [chrisitopherus](https://github.com/chrisitopherus)