@enspirit/bmg-js 1.0.2 → 1.1.1
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/README.md +3 -2
- package/dist/AsyncRelation/Base.d.ts +47 -0
- package/dist/AsyncRelation/index.d.ts +25 -0
- package/dist/Relation/Memory.d.ts +2 -1
- package/dist/Relation/index.d.ts +1 -1
- package/dist/async/Relation/Base.d.ts +47 -0
- package/dist/async/Relation/index.d.ts +25 -0
- package/dist/async/operators/_helpers.d.ts +16 -0
- package/dist/async/operators/autowrap.d.ts +7 -0
- package/dist/async/operators/constants.d.ts +6 -0
- package/dist/async/operators/cross_product.d.ts +9 -0
- package/dist/async/operators/extend.d.ts +7 -0
- package/dist/async/operators/group.d.ts +7 -0
- package/dist/async/operators/image.d.ts +8 -0
- package/dist/async/operators/index.d.ts +28 -0
- package/dist/async/operators/intersect.d.ts +7 -0
- package/dist/async/operators/isEqual.d.ts +7 -0
- package/dist/async/operators/join.d.ts +7 -0
- package/dist/async/operators/left_join.d.ts +8 -0
- package/dist/async/operators/matching.d.ts +7 -0
- package/dist/async/operators/minus.d.ts +7 -0
- package/dist/async/operators/not_matching.d.ts +7 -0
- package/dist/async/operators/one.d.ts +6 -0
- package/dist/async/operators/prefix.d.ts +6 -0
- package/dist/async/operators/project.d.ts +10 -0
- package/dist/async/operators/rename.d.ts +6 -0
- package/dist/async/operators/restrict.d.ts +14 -0
- package/dist/async/operators/suffix.d.ts +6 -0
- package/dist/async/operators/summarize.d.ts +8 -0
- package/dist/async/operators/toArray.d.ts +5 -0
- package/dist/async/operators/transform.d.ts +9 -0
- package/dist/async/operators/ungroup.d.ts +7 -0
- package/dist/async/operators/union.d.ts +6 -0
- package/dist/async/operators/unwrap.d.ts +6 -0
- package/dist/async/operators/wrap.d.ts +6 -0
- package/dist/async/operators/yByX.d.ts +7 -0
- package/dist/async/types.d.ts +58 -0
- package/dist/async-operators/_helpers.d.ts +16 -0
- package/dist/async-operators/autowrap.d.ts +7 -0
- package/dist/async-operators/constants.d.ts +6 -0
- package/dist/async-operators/cross_product.d.ts +9 -0
- package/dist/async-operators/extend.d.ts +7 -0
- package/dist/async-operators/group.d.ts +7 -0
- package/dist/async-operators/image.d.ts +8 -0
- package/dist/async-operators/index.d.ts +28 -0
- package/dist/async-operators/intersect.d.ts +7 -0
- package/dist/async-operators/isEqual.d.ts +7 -0
- package/dist/async-operators/join.d.ts +7 -0
- package/dist/async-operators/left_join.d.ts +8 -0
- package/dist/async-operators/matching.d.ts +7 -0
- package/dist/async-operators/minus.d.ts +7 -0
- package/dist/async-operators/not_matching.d.ts +7 -0
- package/dist/async-operators/one.d.ts +6 -0
- package/dist/async-operators/prefix.d.ts +6 -0
- package/dist/async-operators/project.d.ts +10 -0
- package/dist/async-operators/rename.d.ts +6 -0
- package/dist/async-operators/restrict.d.ts +14 -0
- package/dist/async-operators/suffix.d.ts +6 -0
- package/dist/async-operators/summarize.d.ts +8 -0
- package/dist/async-operators/toArray.d.ts +5 -0
- package/dist/async-operators/transform.d.ts +9 -0
- package/dist/async-operators/ungroup.d.ts +7 -0
- package/dist/async-operators/union.d.ts +6 -0
- package/dist/async-operators/unwrap.d.ts +6 -0
- package/dist/async-operators/wrap.d.ts +6 -0
- package/dist/async-operators/yByX.d.ts +7 -0
- package/dist/async-types.d.ts +58 -0
- package/dist/async.d.ts +4 -0
- package/dist/bmg.cjs +1 -1
- package/dist/bmg.cjs.map +1 -1
- package/dist/bmg.modern.js +1 -1
- package/dist/bmg.modern.js.map +1 -1
- package/dist/bmg.module.js +1 -1
- package/dist/bmg.module.js.map +1 -1
- package/dist/bmg.umd.js +1 -1
- package/dist/bmg.umd.js.map +1 -1
- package/dist/index.d.ts +15 -3
- package/dist/lib-definitions.d.ts +1 -1
- package/dist/operators/index.d.ts +1 -30
- package/dist/operators/isEqual.d.ts +1 -2
- package/dist/operators/isRelation.d.ts +2 -1
- package/dist/sync/Relation/Memory.d.ts +46 -0
- package/dist/sync/Relation/index.d.ts +1 -0
- package/dist/sync/operators/_helpers.d.ts +142 -0
- package/dist/sync/operators/allbut.d.ts +2 -0
- package/dist/sync/operators/autowrap.d.ts +2 -0
- package/dist/sync/operators/constants.d.ts +2 -0
- package/dist/sync/operators/cross_product.d.ts +3 -0
- package/dist/sync/operators/exclude.d.ts +2 -0
- package/dist/sync/operators/extend.d.ts +2 -0
- package/dist/sync/operators/group.d.ts +2 -0
- package/dist/sync/operators/image.d.ts +2 -0
- package/dist/sync/operators/index.d.ts +30 -0
- package/dist/sync/operators/intersect.d.ts +2 -0
- package/dist/sync/operators/isEqual.d.ts +1 -0
- package/dist/sync/operators/isRelation.d.ts +2 -0
- package/dist/sync/operators/join.d.ts +2 -0
- package/dist/sync/operators/left_join.d.ts +2 -0
- package/dist/sync/operators/matching.d.ts +2 -0
- package/dist/sync/operators/minus.d.ts +2 -0
- package/dist/sync/operators/not_matching.d.ts +2 -0
- package/dist/sync/operators/one.d.ts +2 -0
- package/dist/sync/operators/prefix.d.ts +2 -0
- package/dist/sync/operators/project.d.ts +2 -0
- package/dist/sync/operators/rename.d.ts +2 -0
- package/dist/sync/operators/restrict.d.ts +2 -0
- package/dist/sync/operators/suffix.d.ts +2 -0
- package/dist/sync/operators/summarize.d.ts +2 -0
- package/dist/sync/operators/transform.d.ts +2 -0
- package/dist/sync/operators/ungroup.d.ts +2 -0
- package/dist/sync/operators/union.d.ts +2 -0
- package/dist/sync/operators/unwrap.d.ts +2 -0
- package/dist/sync/operators/wrap.d.ts +2 -0
- package/dist/sync/operators/yByX.d.ts +2 -0
- package/dist/types.d.ts +7 -0
- package/dist/writer/Text.d.ts +68 -0
- package/dist/writer/index.d.ts +1 -0
- package/package.json +1 -1
- package/src/Relation/index.ts +2 -1
- package/src/async/Relation/Base.ts +245 -0
- package/src/async/Relation/index.ts +31 -0
- package/src/async/operators/_helpers.ts +60 -0
- package/src/async/operators/autowrap.ts +31 -0
- package/src/async/operators/constants.ts +26 -0
- package/src/async/operators/cross_product.ts +39 -0
- package/src/async/operators/extend.ts +36 -0
- package/src/async/operators/group.ts +61 -0
- package/src/async/operators/image.ts +42 -0
- package/src/async/operators/index.ts +28 -0
- package/src/async/operators/intersect.ts +28 -0
- package/src/async/operators/isEqual.ts +39 -0
- package/src/async/operators/join.ts +39 -0
- package/src/async/operators/left_join.ts +55 -0
- package/src/async/operators/matching.ts +39 -0
- package/src/async/operators/minus.ts +28 -0
- package/src/async/operators/not_matching.ts +39 -0
- package/src/async/operators/one.ts +25 -0
- package/src/async/operators/prefix.ts +15 -0
- package/src/async/operators/project.ts +64 -0
- package/src/async/operators/rename.ts +33 -0
- package/src/async/operators/restrict.ts +61 -0
- package/src/async/operators/suffix.ts +15 -0
- package/src/async/operators/summarize.ts +90 -0
- package/src/async/operators/toArray.ts +18 -0
- package/src/async/operators/transform.ts +43 -0
- package/src/async/operators/ungroup.ts +43 -0
- package/src/async/operators/union.ts +29 -0
- package/src/async/operators/unwrap.ts +31 -0
- package/src/async/operators/wrap.ts +32 -0
- package/src/async/operators/yByX.ts +19 -0
- package/src/async/types.ts +86 -0
- package/src/async.ts +4 -0
- package/src/index.ts +17 -3
- package/src/lib-definitions.ts +11 -0
- package/src/operators/index.ts +2 -31
- package/src/{Relation → sync/Relation}/Memory.ts +9 -1
- package/src/sync/Relation/index.ts +1 -0
- package/src/{operators → sync/operators}/_helpers.ts +1 -1
- package/src/{operators → sync/operators}/allbut.ts +1 -1
- package/src/{operators → sync/operators}/autowrap.ts +1 -1
- package/src/{operators → sync/operators}/constants.ts +1 -1
- package/src/{operators → sync/operators}/cross_product.ts +1 -1
- package/src/{operators → sync/operators}/exclude.ts +2 -2
- package/src/{operators → sync/operators}/extend.ts +1 -1
- package/src/{operators → sync/operators}/group.ts +2 -2
- package/src/{operators → sync/operators}/image.ts +2 -2
- package/src/sync/operators/index.ts +31 -0
- package/src/{operators → sync/operators}/intersect.ts +1 -1
- package/src/{operators → sync/operators}/isEqual.ts +1 -2
- package/src/sync/operators/isRelation.ts +6 -0
- package/src/{operators → sync/operators}/join.ts +1 -1
- package/src/{operators → sync/operators}/left_join.ts +1 -1
- package/src/{operators → sync/operators}/matching.ts +1 -1
- package/src/{operators → sync/operators}/minus.ts +1 -1
- package/src/{operators → sync/operators}/not_matching.ts +1 -1
- package/src/{operators → sync/operators}/one.ts +1 -1
- package/src/{operators → sync/operators}/prefix.ts +1 -1
- package/src/{operators → sync/operators}/project.ts +1 -1
- package/src/{operators → sync/operators}/rename.ts +1 -1
- package/src/{operators → sync/operators}/restrict.ts +2 -2
- package/src/{operators → sync/operators}/suffix.ts +1 -1
- package/src/{operators → sync/operators}/summarize.ts +1 -1
- package/src/{operators → sync/operators}/transform.ts +1 -1
- package/src/{operators → sync/operators}/ungroup.ts +1 -1
- package/src/{operators → sync/operators}/union.ts +1 -1
- package/src/{operators → sync/operators}/unwrap.ts +1 -1
- package/src/sync/operators/where.ts +1 -0
- package/src/{operators → sync/operators}/wrap.ts +1 -1
- package/src/{operators → sync/operators}/yByX.ts +1 -1
- package/src/types.ts +11 -0
- package/src/writer/Text.ts +415 -0
- package/src/writer/index.ts +1 -0
- package/src/operators/isRelation.ts +0 -5
- /package/{src/operators/where.ts → dist/sync/operators/where.d.ts} +0 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { AsyncRelationOperand } from '../types';
|
|
2
|
+
import type { JoinKeys, Tuple, AttrName } from '../../types';
|
|
3
|
+
import { normalizeKeys, tuplesMatch, projectOutKeys } from '../../sync/operators/_helpers';
|
|
4
|
+
|
|
5
|
+
const mergeTuples = (left: Tuple, right: Tuple, keyMap: Record<AttrName, AttrName>): Tuple => {
|
|
6
|
+
return { ...left, ...projectOutKeys(right, keyMap) };
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Natural join: combines tuples from left and right where join keys match.
|
|
11
|
+
* Materializes both sides (needed for key detection and nested loop join).
|
|
12
|
+
*/
|
|
13
|
+
export async function* join<T, U>(
|
|
14
|
+
left: AsyncRelationOperand<T>,
|
|
15
|
+
right: AsyncRelationOperand<U>,
|
|
16
|
+
keys?: JoinKeys
|
|
17
|
+
): AsyncIterable<Tuple> {
|
|
18
|
+
// Materialize both sides
|
|
19
|
+
const leftTuples: Tuple[] = [];
|
|
20
|
+
const rightTuples: Tuple[] = [];
|
|
21
|
+
|
|
22
|
+
for await (const tuple of left) {
|
|
23
|
+
leftTuples.push(tuple as Tuple);
|
|
24
|
+
}
|
|
25
|
+
for await (const tuple of right) {
|
|
26
|
+
rightTuples.push(tuple as Tuple);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const keyMap = normalizeKeys(keys, leftTuples, rightTuples);
|
|
30
|
+
|
|
31
|
+
// Nested loop join
|
|
32
|
+
for (const leftTuple of leftTuples) {
|
|
33
|
+
for (const rightTuple of rightTuples) {
|
|
34
|
+
if (tuplesMatch(leftTuple, rightTuple, keyMap)) {
|
|
35
|
+
yield mergeTuples(leftTuple, rightTuple, keyMap);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { AsyncRelationOperand } from '../types';
|
|
2
|
+
import type { JoinKeys, Tuple, AttrName } from '../../types';
|
|
3
|
+
import { normalizeKeys, tuplesMatch } from '../../sync/operators/_helpers';
|
|
4
|
+
|
|
5
|
+
const getRightAttrs = (rightTuples: Tuple[], keyMap: Record<AttrName, AttrName>): AttrName[] => {
|
|
6
|
+
if (rightTuples.length === 0) return [];
|
|
7
|
+
const rightKeys = new Set(Object.values(keyMap));
|
|
8
|
+
return Object.keys(rightTuples[0]).filter(attr => !rightKeys.has(attr));
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const mergeTuples = (left: Tuple, right: Tuple | null, rightAttrs: AttrName[]): Tuple => {
|
|
12
|
+
const result = { ...left };
|
|
13
|
+
for (const attr of rightAttrs) {
|
|
14
|
+
result[attr] = right ? right[attr] : null;
|
|
15
|
+
}
|
|
16
|
+
return result;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Left outer join: combines tuples, keeping all left tuples.
|
|
21
|
+
* Non-matching left tuples have null for right attributes.
|
|
22
|
+
* Materializes both sides.
|
|
23
|
+
*/
|
|
24
|
+
export async function* left_join<T, U>(
|
|
25
|
+
left: AsyncRelationOperand<T>,
|
|
26
|
+
right: AsyncRelationOperand<U>,
|
|
27
|
+
keys?: JoinKeys
|
|
28
|
+
): AsyncIterable<Tuple> {
|
|
29
|
+
// Materialize both sides
|
|
30
|
+
const leftTuples: Tuple[] = [];
|
|
31
|
+
const rightTuples: Tuple[] = [];
|
|
32
|
+
|
|
33
|
+
for await (const tuple of left) {
|
|
34
|
+
leftTuples.push(tuple as Tuple);
|
|
35
|
+
}
|
|
36
|
+
for await (const tuple of right) {
|
|
37
|
+
rightTuples.push(tuple as Tuple);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const keyMap = normalizeKeys(keys, leftTuples, rightTuples);
|
|
41
|
+
const rightAttrs = getRightAttrs(rightTuples, keyMap);
|
|
42
|
+
|
|
43
|
+
for (const leftTuple of leftTuples) {
|
|
44
|
+
let matched = false;
|
|
45
|
+
for (const rightTuple of rightTuples) {
|
|
46
|
+
if (tuplesMatch(leftTuple, rightTuple, keyMap)) {
|
|
47
|
+
yield mergeTuples(leftTuple, rightTuple, rightAttrs);
|
|
48
|
+
matched = true;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (!matched) {
|
|
52
|
+
yield mergeTuples(leftTuple, null, rightAttrs);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { AsyncRelationOperand } from '../types';
|
|
2
|
+
import type { JoinKeys, Tuple } from '../../types';
|
|
3
|
+
import { normalizeKeys, matchKey } from '../../sync/operators/_helpers';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Semi-join: returns tuples from left that have a matching tuple in right.
|
|
7
|
+
* Materializes right side first to build key set.
|
|
8
|
+
*/
|
|
9
|
+
export async function* matching<T, U>(
|
|
10
|
+
left: AsyncRelationOperand<T>,
|
|
11
|
+
right: AsyncRelationOperand<U>,
|
|
12
|
+
keys?: JoinKeys
|
|
13
|
+
): AsyncIterable<Tuple> {
|
|
14
|
+
// Materialize both sides to normalize keys (need sample tuples)
|
|
15
|
+
const leftTuples: Tuple[] = [];
|
|
16
|
+
const rightTuples: Tuple[] = [];
|
|
17
|
+
|
|
18
|
+
for await (const tuple of left) {
|
|
19
|
+
leftTuples.push(tuple as Tuple);
|
|
20
|
+
}
|
|
21
|
+
for await (const tuple of right) {
|
|
22
|
+
rightTuples.push(tuple as Tuple);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const keyMap = normalizeKeys(keys, leftTuples, rightTuples);
|
|
26
|
+
|
|
27
|
+
// Build right key set
|
|
28
|
+
const rightKeys = new Set<string>();
|
|
29
|
+
for (const tuple of rightTuples) {
|
|
30
|
+
rightKeys.add(matchKey(tuple, keyMap, 'right'));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Yield left tuples that match
|
|
34
|
+
for (const tuple of leftTuples) {
|
|
35
|
+
if (rightKeys.has(matchKey(tuple, keyMap, 'left'))) {
|
|
36
|
+
yield tuple;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { AsyncRelationOperand } from '../types';
|
|
2
|
+
import type { Tuple } from '../../types';
|
|
3
|
+
import { tupleKey } from '../../sync/operators/_helpers';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Set difference: returns tuples in left but not in right.
|
|
7
|
+
* Materializes right side first, then streams left.
|
|
8
|
+
*/
|
|
9
|
+
export async function* minus<T>(
|
|
10
|
+
left: AsyncRelationOperand<T>,
|
|
11
|
+
right: AsyncRelationOperand<T>
|
|
12
|
+
): AsyncIterable<Tuple> {
|
|
13
|
+
// Materialize right side into a set of keys
|
|
14
|
+
const rightKeys = new Set<string>();
|
|
15
|
+
for await (const tuple of right) {
|
|
16
|
+
rightKeys.add(tupleKey(tuple as Tuple));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Stream left, filtering out tuples present in right
|
|
20
|
+
const seen = new Set<string>();
|
|
21
|
+
for await (const tuple of left) {
|
|
22
|
+
const key = tupleKey(tuple as Tuple);
|
|
23
|
+
if (!rightKeys.has(key) && !seen.has(key)) {
|
|
24
|
+
seen.add(key);
|
|
25
|
+
yield tuple as Tuple;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { AsyncRelationOperand } from '../types';
|
|
2
|
+
import type { JoinKeys, Tuple } from '../../types';
|
|
3
|
+
import { normalizeKeys, matchKey } from '../../sync/operators/_helpers';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Anti semi-join: returns tuples from left that have no matching tuple in right.
|
|
7
|
+
* Materializes right side first to build key set.
|
|
8
|
+
*/
|
|
9
|
+
export async function* not_matching<T, U>(
|
|
10
|
+
left: AsyncRelationOperand<T>,
|
|
11
|
+
right: AsyncRelationOperand<U>,
|
|
12
|
+
keys?: JoinKeys
|
|
13
|
+
): AsyncIterable<Tuple> {
|
|
14
|
+
// Materialize both sides to normalize keys (need sample tuples)
|
|
15
|
+
const leftTuples: Tuple[] = [];
|
|
16
|
+
const rightTuples: Tuple[] = [];
|
|
17
|
+
|
|
18
|
+
for await (const tuple of left) {
|
|
19
|
+
leftTuples.push(tuple as Tuple);
|
|
20
|
+
}
|
|
21
|
+
for await (const tuple of right) {
|
|
22
|
+
rightTuples.push(tuple as Tuple);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const keyMap = normalizeKeys(keys, leftTuples, rightTuples);
|
|
26
|
+
|
|
27
|
+
// Build right key set
|
|
28
|
+
const rightKeys = new Set<string>();
|
|
29
|
+
for (const tuple of rightTuples) {
|
|
30
|
+
rightKeys.add(matchKey(tuple, keyMap, 'right'));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Yield left tuples that don't match
|
|
34
|
+
for (const tuple of leftTuples) {
|
|
35
|
+
if (!rightKeys.has(matchKey(tuple, keyMap, 'left'))) {
|
|
36
|
+
yield tuple;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { AsyncRelationOperand } from '../types';
|
|
2
|
+
import { toAsyncOperationalOperand, error } from './_helpers';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Returns the single tuple from the relation.
|
|
6
|
+
* Throws if the relation is empty or has more than one tuple.
|
|
7
|
+
*/
|
|
8
|
+
export const one = async <T>(
|
|
9
|
+
operand: AsyncRelationOperand<T>
|
|
10
|
+
): Promise<T> => {
|
|
11
|
+
const op = toAsyncOperationalOperand(operand);
|
|
12
|
+
let result: T | undefined;
|
|
13
|
+
|
|
14
|
+
for await (const tuple of op.tuples()) {
|
|
15
|
+
if (result !== undefined) {
|
|
16
|
+
return error('More than one tuple found');
|
|
17
|
+
}
|
|
18
|
+
result = tuple;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (result !== undefined) {
|
|
22
|
+
return result;
|
|
23
|
+
}
|
|
24
|
+
return error('Relation is empty');
|
|
25
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { AsyncRelationOperand } from '../types';
|
|
2
|
+
import type { PrefixOptions, Tuple } from '../../types';
|
|
3
|
+
import { rename } from './rename';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Prefixes all attribute names (except those in options.except).
|
|
7
|
+
*/
|
|
8
|
+
export const prefix = <T>(
|
|
9
|
+
operand: AsyncRelationOperand<T>,
|
|
10
|
+
pfx: string,
|
|
11
|
+
options?: PrefixOptions
|
|
12
|
+
): AsyncIterable<Tuple> => {
|
|
13
|
+
const except = new Set(options?.except ?? []);
|
|
14
|
+
return rename(operand, (attr) => except.has(attr) ? attr : `${pfx}${attr}`);
|
|
15
|
+
};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { AsyncRelationOperand } from '../types';
|
|
2
|
+
import type { Tuple, AttrName } from '../../types';
|
|
3
|
+
import { toAsyncOperationalOperand, deduplicateAsync } from './_helpers';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Async generator that projects tuples to specified attributes.
|
|
7
|
+
*/
|
|
8
|
+
async function* projectGen<T>(
|
|
9
|
+
source: AsyncIterable<T>,
|
|
10
|
+
attrs: AttrName[]
|
|
11
|
+
): AsyncGenerator<Tuple> {
|
|
12
|
+
for await (const tuple of source) {
|
|
13
|
+
const t = tuple as Tuple;
|
|
14
|
+
const projected = attrs.reduce((memo, attr) => {
|
|
15
|
+
if (attr in t) {
|
|
16
|
+
memo[attr] = t[attr];
|
|
17
|
+
}
|
|
18
|
+
return memo;
|
|
19
|
+
}, {} as Tuple);
|
|
20
|
+
yield projected;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Projects tuples to specified attributes with deduplication.
|
|
26
|
+
*/
|
|
27
|
+
export const project = <T>(
|
|
28
|
+
operand: AsyncRelationOperand<T>,
|
|
29
|
+
attrs: AttrName[]
|
|
30
|
+
): AsyncIterable<Tuple> => {
|
|
31
|
+
const op = toAsyncOperationalOperand(operand);
|
|
32
|
+
return deduplicateAsync(projectGen(op.tuples(), attrs));
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Async generator that projects out (removes) specified attributes.
|
|
37
|
+
*/
|
|
38
|
+
async function* allbutGen<T>(
|
|
39
|
+
source: AsyncIterable<T>,
|
|
40
|
+
attrs: AttrName[]
|
|
41
|
+
): AsyncGenerator<Tuple> {
|
|
42
|
+
const excludeSet = new Set(attrs);
|
|
43
|
+
for await (const tuple of source) {
|
|
44
|
+
const t = tuple as Tuple;
|
|
45
|
+
const projected: Tuple = {};
|
|
46
|
+
for (const [attr, value] of Object.entries(t)) {
|
|
47
|
+
if (!excludeSet.has(attr)) {
|
|
48
|
+
projected[attr] = value;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
yield projected;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Projects out (removes) specified attributes with deduplication.
|
|
57
|
+
*/
|
|
58
|
+
export const allbut = <T>(
|
|
59
|
+
operand: AsyncRelationOperand<T>,
|
|
60
|
+
attrs: AttrName[]
|
|
61
|
+
): AsyncIterable<Tuple> => {
|
|
62
|
+
const op = toAsyncOperationalOperand(operand);
|
|
63
|
+
return deduplicateAsync(allbutGen(op.tuples(), attrs));
|
|
64
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { AsyncRelationOperand } from '../types';
|
|
2
|
+
import type { Renaming, Tuple } from '../../types';
|
|
3
|
+
import { toAsyncOperationalOperand } from './_helpers';
|
|
4
|
+
import { toRenamingFunc } from '../../sync/operators/_helpers';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Async generator that renames attributes in each tuple.
|
|
8
|
+
*/
|
|
9
|
+
async function* renameGen<T>(
|
|
10
|
+
source: AsyncIterable<T>,
|
|
11
|
+
renaming: Renaming
|
|
12
|
+
): AsyncGenerator<Tuple> {
|
|
13
|
+
const renamingFunc = toRenamingFunc(renaming);
|
|
14
|
+
for await (const tuple of source) {
|
|
15
|
+
const t = tuple as Tuple;
|
|
16
|
+
const renamed = Object.keys(t).reduce((memo, attr) => {
|
|
17
|
+
memo[renamingFunc(attr)] = t[attr];
|
|
18
|
+
return memo;
|
|
19
|
+
}, {} as Tuple);
|
|
20
|
+
yield renamed;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Renames attributes in each tuple.
|
|
26
|
+
*/
|
|
27
|
+
export const rename = <T>(
|
|
28
|
+
operand: AsyncRelationOperand<T>,
|
|
29
|
+
renaming: Renaming
|
|
30
|
+
): AsyncIterable<Tuple> => {
|
|
31
|
+
const op = toAsyncOperationalOperand(operand);
|
|
32
|
+
return renameGen(op.tuples(), renaming);
|
|
33
|
+
};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { AsyncRelationOperand } from '../types';
|
|
2
|
+
import type { Predicate } from '../../types';
|
|
3
|
+
import { toPredicateFunc } from '../../support/toPredicateFunc';
|
|
4
|
+
import { toAsyncOperationalOperand } from './_helpers';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Async generator that filters tuples based on a predicate.
|
|
8
|
+
*/
|
|
9
|
+
async function* restrictGen<T>(
|
|
10
|
+
source: AsyncIterable<T>,
|
|
11
|
+
predicate: (t: T) => boolean
|
|
12
|
+
): AsyncGenerator<T> {
|
|
13
|
+
for await (const tuple of source) {
|
|
14
|
+
if (predicate(tuple)) {
|
|
15
|
+
yield tuple;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Filters tuples that match the predicate.
|
|
22
|
+
*/
|
|
23
|
+
export const restrict = <T>(
|
|
24
|
+
operand: AsyncRelationOperand<T>,
|
|
25
|
+
p: Predicate
|
|
26
|
+
): AsyncIterable<T> => {
|
|
27
|
+
const op = toAsyncOperationalOperand(operand);
|
|
28
|
+
const f = toPredicateFunc(p) as (t: T) => boolean;
|
|
29
|
+
return restrictGen(op.tuples(), f);
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Alias for restrict.
|
|
34
|
+
*/
|
|
35
|
+
export const where = restrict;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Async generator that excludes tuples based on a predicate.
|
|
39
|
+
*/
|
|
40
|
+
async function* excludeGen<T>(
|
|
41
|
+
source: AsyncIterable<T>,
|
|
42
|
+
predicate: (t: T) => boolean
|
|
43
|
+
): AsyncGenerator<T> {
|
|
44
|
+
for await (const tuple of source) {
|
|
45
|
+
if (!predicate(tuple)) {
|
|
46
|
+
yield tuple;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Filters tuples that do NOT match the predicate.
|
|
53
|
+
*/
|
|
54
|
+
export const exclude = <T>(
|
|
55
|
+
operand: AsyncRelationOperand<T>,
|
|
56
|
+
p: Predicate
|
|
57
|
+
): AsyncIterable<T> => {
|
|
58
|
+
const op = toAsyncOperationalOperand(operand);
|
|
59
|
+
const f = toPredicateFunc(p) as (t: T) => boolean;
|
|
60
|
+
return excludeGen(op.tuples(), f);
|
|
61
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { AsyncRelationOperand } from '../types';
|
|
2
|
+
import type { SuffixOptions, Tuple } from '../../types';
|
|
3
|
+
import { rename } from './rename';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Suffixes all attribute names (except those in options.except).
|
|
7
|
+
*/
|
|
8
|
+
export const suffix = <T>(
|
|
9
|
+
operand: AsyncRelationOperand<T>,
|
|
10
|
+
sfx: string,
|
|
11
|
+
options?: SuffixOptions
|
|
12
|
+
): AsyncIterable<Tuple> => {
|
|
13
|
+
const except = new Set(options?.except ?? []);
|
|
14
|
+
return rename(operand, (attr) => except.has(attr) ? attr : `${attr}${sfx}`);
|
|
15
|
+
};
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import type { AsyncRelationOperand } from '../types';
|
|
2
|
+
import type { AttrName, Tuple, Aggregator, Aggregators } from '../../types';
|
|
3
|
+
|
|
4
|
+
const groupKey = (tuple: Tuple, by: AttrName[]): string => {
|
|
5
|
+
const keyParts = by.map(attr => JSON.stringify(tuple[attr]));
|
|
6
|
+
return keyParts.join('|');
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
const pickAttrs = (tuple: Tuple, attrs: AttrName[]): Tuple => {
|
|
10
|
+
return attrs.reduce((acc, attr) => {
|
|
11
|
+
acc[attr] = tuple[attr];
|
|
12
|
+
return acc;
|
|
13
|
+
}, {} as Tuple);
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const applyAggregator = (tuples: Tuple[], agg: Aggregator): unknown => {
|
|
17
|
+
if (typeof agg === 'function') {
|
|
18
|
+
return agg(tuples);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const spec = typeof agg === 'string' ? { op: agg, attr: '' } : agg;
|
|
22
|
+
const { op, attr } = spec;
|
|
23
|
+
|
|
24
|
+
switch (op) {
|
|
25
|
+
case 'count':
|
|
26
|
+
return tuples.length;
|
|
27
|
+
|
|
28
|
+
case 'sum': {
|
|
29
|
+
return tuples.reduce((sum, t) => sum + (Number(t[attr]) || 0), 0);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
case 'min': {
|
|
33
|
+
const values = tuples.map(t => t[attr]).filter(v => v !== undefined && v !== null);
|
|
34
|
+
return values.length > 0 ? Math.min(...values.map(Number)) : null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
case 'max': {
|
|
38
|
+
const values = tuples.map(t => t[attr]).filter(v => v !== undefined && v !== null);
|
|
39
|
+
return values.length > 0 ? Math.max(...values.map(Number)) : null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
case 'avg': {
|
|
43
|
+
const values = tuples.map(t => Number(t[attr])).filter(v => !isNaN(v));
|
|
44
|
+
return values.length > 0 ? values.reduce((a, b) => a + b, 0) / values.length : null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
case 'collect': {
|
|
48
|
+
return tuples.map(t => t[attr]);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
default:
|
|
52
|
+
throw new Error(`Unknown aggregator: ${op}`);
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Aggregates tuples by grouping attributes.
|
|
58
|
+
* Supports count, sum, min, max, avg, collect, and custom functions.
|
|
59
|
+
* Materializes all tuples to perform grouping.
|
|
60
|
+
*/
|
|
61
|
+
export async function* summarize<T>(
|
|
62
|
+
operand: AsyncRelationOperand<T>,
|
|
63
|
+
by: AttrName[],
|
|
64
|
+
aggs: Aggregators
|
|
65
|
+
): AsyncIterable<Tuple> {
|
|
66
|
+
// Materialize all tuples
|
|
67
|
+
const tuples: Tuple[] = [];
|
|
68
|
+
for await (const tuple of operand) {
|
|
69
|
+
tuples.push(tuple as Tuple);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Group tuples
|
|
73
|
+
const groups = new Map<string, Tuple[]>();
|
|
74
|
+
for (const tuple of tuples) {
|
|
75
|
+
const key = groupKey(tuple, by);
|
|
76
|
+
if (!groups.has(key)) {
|
|
77
|
+
groups.set(key, []);
|
|
78
|
+
}
|
|
79
|
+
groups.get(key)!.push(tuple);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Apply aggregators to each group
|
|
83
|
+
for (const groupTuples of groups.values()) {
|
|
84
|
+
const row: Tuple = pickAttrs(groupTuples[0], by);
|
|
85
|
+
for (const [resultAttr, agg] of Object.entries(aggs)) {
|
|
86
|
+
row[resultAttr] = applyAggregator(groupTuples, agg);
|
|
87
|
+
}
|
|
88
|
+
yield row;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { AsyncRelationOperand } from '../types';
|
|
2
|
+
import { toAsyncOperationalOperand } from './_helpers';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Collects all tuples from the async relation into an array.
|
|
6
|
+
*/
|
|
7
|
+
export const toArray = async <T>(
|
|
8
|
+
operand: AsyncRelationOperand<T>
|
|
9
|
+
): Promise<T[]> => {
|
|
10
|
+
const op = toAsyncOperationalOperand(operand);
|
|
11
|
+
const result: T[] = [];
|
|
12
|
+
|
|
13
|
+
for await (const tuple of op.tuples()) {
|
|
14
|
+
result.push(tuple);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return result;
|
|
18
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { AsyncRelationOperand } from '../types';
|
|
2
|
+
import type { Transformation, Tuple } from '../../types';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Applies transformation functions to attribute values.
|
|
6
|
+
* - Single function: applies to all values
|
|
7
|
+
* - Array of functions: chains them in order
|
|
8
|
+
* - Object with attr keys: applies per-attribute transformers
|
|
9
|
+
*/
|
|
10
|
+
export async function* transform<T>(
|
|
11
|
+
operand: AsyncRelationOperand<T>,
|
|
12
|
+
transformation: Transformation
|
|
13
|
+
): AsyncIterable<Tuple> {
|
|
14
|
+
for await (const tuple of operand) {
|
|
15
|
+
const transformed: Tuple = {};
|
|
16
|
+
|
|
17
|
+
for (const [attr, value] of Object.entries(tuple as Tuple)) {
|
|
18
|
+
transformed[attr] = applyTransformation(value, attr, transformation);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
yield transformed;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const applyTransformation = (value: unknown, attr: string, transformation: Transformation): unknown => {
|
|
26
|
+
if (typeof transformation === 'function') {
|
|
27
|
+
// Single function - apply to all values
|
|
28
|
+
return transformation(value);
|
|
29
|
+
} else if (Array.isArray(transformation)) {
|
|
30
|
+
// Array of functions - chain them
|
|
31
|
+
return transformation.reduce((v, fn) => fn(v), value);
|
|
32
|
+
} else {
|
|
33
|
+
// Object with attr-specific transformers
|
|
34
|
+
const fn = transformation[attr];
|
|
35
|
+
if (fn) {
|
|
36
|
+
if (Array.isArray(fn)) {
|
|
37
|
+
return fn.reduce((v, f) => f(v), value);
|
|
38
|
+
}
|
|
39
|
+
return fn(value);
|
|
40
|
+
}
|
|
41
|
+
return value;
|
|
42
|
+
}
|
|
43
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { AsyncRelationOperand } from '../types';
|
|
2
|
+
import type { AttrName, Tuple, Relation } from '../../types';
|
|
3
|
+
import { isRelation } from '../../sync/operators/isRelation';
|
|
4
|
+
|
|
5
|
+
const toTupleArray = (value: unknown): Tuple[] => {
|
|
6
|
+
if (isRelation(value)) {
|
|
7
|
+
return (value as Relation).toArray();
|
|
8
|
+
}
|
|
9
|
+
if (Array.isArray(value)) {
|
|
10
|
+
return value;
|
|
11
|
+
}
|
|
12
|
+
throw new Error(`Value is not a relation or array`);
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Flattens a nested relation attribute back into parent tuples.
|
|
17
|
+
* Materializes all tuples to perform ungrouping.
|
|
18
|
+
*/
|
|
19
|
+
export async function* ungroup<T>(
|
|
20
|
+
operand: AsyncRelationOperand<T>,
|
|
21
|
+
attr: AttrName
|
|
22
|
+
): AsyncIterable<Tuple> {
|
|
23
|
+
for await (const tuple of operand) {
|
|
24
|
+
const t = tuple as Tuple;
|
|
25
|
+
const nested = toTupleArray(t[attr]);
|
|
26
|
+
|
|
27
|
+
// Get base attributes (all except the grouped one)
|
|
28
|
+
const base: Tuple = {};
|
|
29
|
+
for (const [key, value] of Object.entries(t)) {
|
|
30
|
+
if (key !== attr) {
|
|
31
|
+
base[key] = value;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Flatten each nested tuple
|
|
36
|
+
for (const nestedTuple of nested) {
|
|
37
|
+
yield {
|
|
38
|
+
...base,
|
|
39
|
+
...nestedTuple
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { AsyncRelationOperand } from '../types';
|
|
2
|
+
import type { Tuple } from '../../types';
|
|
3
|
+
import { tupleKey } from '../../sync/operators/_helpers';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Combines tuples from two relations, removing duplicates.
|
|
7
|
+
*/
|
|
8
|
+
export async function* union<T>(
|
|
9
|
+
left: AsyncRelationOperand<T>,
|
|
10
|
+
right: AsyncRelationOperand<T>
|
|
11
|
+
): AsyncIterable<Tuple> {
|
|
12
|
+
const seen = new Set<string>();
|
|
13
|
+
|
|
14
|
+
for await (const tuple of left) {
|
|
15
|
+
const key = tupleKey(tuple as Tuple);
|
|
16
|
+
if (!seen.has(key)) {
|
|
17
|
+
seen.add(key);
|
|
18
|
+
yield tuple as Tuple;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
for await (const tuple of right) {
|
|
23
|
+
const key = tupleKey(tuple as Tuple);
|
|
24
|
+
if (!seen.has(key)) {
|
|
25
|
+
seen.add(key);
|
|
26
|
+
yield tuple as Tuple;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { AsyncRelationOperand } from '../types';
|
|
2
|
+
import type { AttrName, Tuple } from '../../types';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Unwraps a nested object attribute back into the parent tuple.
|
|
6
|
+
*/
|
|
7
|
+
export async function* unwrap<T>(
|
|
8
|
+
operand: AsyncRelationOperand<T>,
|
|
9
|
+
attr: AttrName
|
|
10
|
+
): AsyncIterable<Tuple> {
|
|
11
|
+
for await (const tuple of operand) {
|
|
12
|
+
const t = tuple as Tuple;
|
|
13
|
+
const wrapped = t[attr] as Tuple;
|
|
14
|
+
|
|
15
|
+
if (typeof wrapped !== 'object' || wrapped === null || Array.isArray(wrapped)) {
|
|
16
|
+
throw new Error(`Attribute '${attr}' is not a tuple (object)`);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const unwrapped: Tuple = {};
|
|
20
|
+
for (const [key, value] of Object.entries(t)) {
|
|
21
|
+
if (key !== attr) {
|
|
22
|
+
unwrapped[key] = value;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
yield {
|
|
27
|
+
...unwrapped,
|
|
28
|
+
...wrapped
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
}
|