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.
- package/LICENSE +21 -0
- package/README.md +319 -0
- package/dist/Lazy.cjs +762 -0
- package/dist/Lazy.cjs.map +1 -0
- package/dist/Lazy.js +691 -0
- package/dist/Lazy.js.map +1 -0
- package/dist/TyneqCachedTerminalOperator.cjs +4950 -0
- package/dist/TyneqCachedTerminalOperator.cjs.map +1 -0
- package/dist/TyneqCachedTerminalOperator.d.cts +724 -0
- package/dist/TyneqCachedTerminalOperator.d.cts.map +1 -0
- package/dist/TyneqCachedTerminalOperator.d.ts +724 -0
- package/dist/TyneqCachedTerminalOperator.d.ts.map +1 -0
- package/dist/TyneqCachedTerminalOperator.js +4741 -0
- package/dist/TyneqCachedTerminalOperator.js.map +1 -0
- package/dist/ValidationBuilder.cjs +80 -0
- package/dist/ValidationBuilder.cjs.map +1 -0
- package/dist/ValidationBuilder.d.cts +319 -0
- package/dist/ValidationBuilder.d.cts.map +1 -0
- package/dist/ValidationBuilder.d.ts +319 -0
- package/dist/ValidationBuilder.d.ts.map +1 -0
- package/dist/ValidationBuilder.js +69 -0
- package/dist/ValidationBuilder.js.map +1 -0
- package/dist/core.d.cts +1393 -0
- package/dist/core.d.cts.map +1 -0
- package/dist/core.d.ts +1393 -0
- package/dist/core.d.ts.map +1 -0
- package/dist/index.cjs +863 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1038 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.ts +1038 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +809 -0
- package/dist/index.js.map +1 -0
- package/dist/plugin/index.cjs +24 -0
- package/dist/plugin/index.d.cts +89 -0
- package/dist/plugin/index.d.cts.map +1 -0
- package/dist/plugin/index.d.ts +89 -0
- package/dist/plugin/index.d.ts.map +1 -0
- package/dist/plugin/index.js +2 -0
- package/dist/utility/index.cjs +9 -0
- package/dist/utility/index.d.cts +2 -0
- package/dist/utility/index.d.ts +2 -0
- package/dist/utility/index.js +3 -0
- 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
|
+
·
|
|
23
|
+
<a href="https://chrisitopherus.github.io/tyneq/api/">API Reference</a>
|
|
24
|
+
·
|
|
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)
|