@enspirit/bmg-js 1.0.2 → 1.1.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/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 +14 -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 +40 -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 +16 -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 +305 -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,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
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { AsyncRelationOperand } from '../types';
|
|
2
|
+
import type { AttrName, Tuple } from '../../types';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Wraps specified attributes into a nested object.
|
|
6
|
+
*/
|
|
7
|
+
export async function* wrap<T>(
|
|
8
|
+
operand: AsyncRelationOperand<T>,
|
|
9
|
+
attrs: AttrName[],
|
|
10
|
+
as: AttrName
|
|
11
|
+
): AsyncIterable<Tuple> {
|
|
12
|
+
const wrappedSet = new Set(attrs);
|
|
13
|
+
|
|
14
|
+
for await (const tuple of operand) {
|
|
15
|
+
const t = tuple as Tuple;
|
|
16
|
+
const wrapped: Tuple = {};
|
|
17
|
+
const remaining: Tuple = {};
|
|
18
|
+
|
|
19
|
+
for (const [key, value] of Object.entries(t)) {
|
|
20
|
+
if (wrappedSet.has(key)) {
|
|
21
|
+
wrapped[key] = value;
|
|
22
|
+
} else {
|
|
23
|
+
remaining[key] = value;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
yield {
|
|
28
|
+
...remaining,
|
|
29
|
+
[as]: wrapped
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { AsyncRelationOperand } from '../types';
|
|
2
|
+
import type { AttrName, Tuple } from '../../types';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Builds a key-value map from two attributes.
|
|
6
|
+
* Terminal operation - returns a Promise.
|
|
7
|
+
*/
|
|
8
|
+
export const yByX = async <T>(
|
|
9
|
+
operand: AsyncRelationOperand<T>,
|
|
10
|
+
y: AttrName,
|
|
11
|
+
x: AttrName
|
|
12
|
+
): Promise<Tuple> => {
|
|
13
|
+
const hash: Tuple = {};
|
|
14
|
+
for await (const tuple of operand) {
|
|
15
|
+
const t = tuple as Tuple;
|
|
16
|
+
hash[`${t[x]}`] = t[y];
|
|
17
|
+
}
|
|
18
|
+
return hash;
|
|
19
|
+
};
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import type { Tuple, TypedPredicate, Relation, TypedExtension, RenameMap, Renamed, Prefixed, Suffixed, Transformation, JoinKeys, AttrName, Aggregators, AutowrapOptions, TextOptions } from '../types';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Async version of the Relation interface.
|
|
5
|
+
* Operations are lazy - they build a pipeline that executes only when
|
|
6
|
+
* a terminal method (one(), toArray()) is awaited.
|
|
7
|
+
*
|
|
8
|
+
* @typeParam T - The tuple type for this relation.
|
|
9
|
+
*/
|
|
10
|
+
export interface AsyncRelation<T = Tuple> {
|
|
11
|
+
// === Type-preserving operators ===
|
|
12
|
+
|
|
13
|
+
restrict(p: TypedPredicate<T>): AsyncRelation<T>
|
|
14
|
+
where(p: TypedPredicate<T>): AsyncRelation<T>
|
|
15
|
+
exclude(p: TypedPredicate<T>): AsyncRelation<T>
|
|
16
|
+
|
|
17
|
+
// === Projection operators ===
|
|
18
|
+
|
|
19
|
+
project<K extends keyof T>(attrs: K[]): AsyncRelation<Pick<T, K>>
|
|
20
|
+
allbut<K extends keyof T>(attrs: K[]): AsyncRelation<Omit<T, K>>
|
|
21
|
+
|
|
22
|
+
// === Extension operators ===
|
|
23
|
+
|
|
24
|
+
extend<E extends Record<string, unknown>>(e: TypedExtension<T, E>): AsyncRelation<T & E>
|
|
25
|
+
constants<C extends Tuple>(consts: C): AsyncRelation<T & C>
|
|
26
|
+
|
|
27
|
+
// === Rename operators ===
|
|
28
|
+
|
|
29
|
+
rename<R extends RenameMap<T>>(r: R): AsyncRelation<Renamed<T, R>>
|
|
30
|
+
prefix<P extends string, Ex extends keyof T = never>(pfx: P, options?: { except?: Ex[] }): AsyncRelation<Prefixed<T, P, Ex>>
|
|
31
|
+
suffix<S extends string, Ex extends keyof T = never>(sfx: S, options?: { except?: Ex[] }): AsyncRelation<Suffixed<T, S, Ex>>
|
|
32
|
+
transform(t: Transformation): AsyncRelation<T>
|
|
33
|
+
|
|
34
|
+
// === Set operations ===
|
|
35
|
+
|
|
36
|
+
union(other: AsyncRelationOperand<T>): AsyncRelation<T>
|
|
37
|
+
minus(other: AsyncRelationOperand<T>): AsyncRelation<T>
|
|
38
|
+
intersect(other: AsyncRelationOperand<T>): AsyncRelation<T>
|
|
39
|
+
|
|
40
|
+
// === Semi-joins ===
|
|
41
|
+
|
|
42
|
+
matching<U = Tuple>(other: AsyncRelationOperand<U>, keys?: JoinKeys): AsyncRelation<T>
|
|
43
|
+
not_matching<U = Tuple>(other: AsyncRelationOperand<U>, keys?: JoinKeys): AsyncRelation<T>
|
|
44
|
+
|
|
45
|
+
// === Joins ===
|
|
46
|
+
|
|
47
|
+
join<U = Tuple>(other: AsyncRelationOperand<U>, keys?: JoinKeys): AsyncRelation<T & U>
|
|
48
|
+
left_join<U = Tuple>(other: AsyncRelationOperand<U>, keys?: JoinKeys): AsyncRelation<T & Partial<U>>
|
|
49
|
+
cross_product<U = Tuple>(other: AsyncRelationOperand<U>): AsyncRelation<T & U>
|
|
50
|
+
cross_join<U = Tuple>(other: AsyncRelationOperand<U>): AsyncRelation<T & U>
|
|
51
|
+
|
|
52
|
+
// === Nesting/Grouping ===
|
|
53
|
+
|
|
54
|
+
group<K extends keyof T>(attrs: K[], as: AttrName): AsyncRelation<Omit<T, K> & Record<string, Relation<Pick<T, K>>>>
|
|
55
|
+
ungroup(attr: AttrName): AsyncRelation<Tuple>
|
|
56
|
+
wrap<K extends keyof T>(attrs: K[], as: AttrName): AsyncRelation<Omit<T, K> & Record<string, Pick<T, K>>>
|
|
57
|
+
unwrap(attr: AttrName): AsyncRelation<Tuple>
|
|
58
|
+
image<U = Tuple>(other: AsyncRelationOperand<U>, as: AttrName, keys?: JoinKeys): AsyncRelation<T & Record<string, Relation<U>>>
|
|
59
|
+
summarize<K extends keyof T>(by: K[], aggs: Aggregators): AsyncRelation<Pick<T, K> & Tuple>
|
|
60
|
+
autowrap(options?: AutowrapOptions): AsyncRelation<Tuple>
|
|
61
|
+
|
|
62
|
+
// === Terminal operations (return Promises) ===
|
|
63
|
+
|
|
64
|
+
one(): Promise<T>
|
|
65
|
+
toArray(): Promise<T[]>
|
|
66
|
+
toRelation(): Promise<Relation<T>>
|
|
67
|
+
yByX(y: AttrName, x: AttrName): Promise<Tuple>
|
|
68
|
+
toText(options?: TextOptions): Promise<string>
|
|
69
|
+
|
|
70
|
+
// === Async iteration ===
|
|
71
|
+
|
|
72
|
+
[Symbol.asyncIterator](): AsyncIterator<T>
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* What can be passed as an operand to async operators.
|
|
77
|
+
*/
|
|
78
|
+
export type AsyncRelationOperand<T = Tuple> = AsyncRelation<T> | AsyncIterable<T>
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Async version of OperationalOperand for internal use by operators.
|
|
82
|
+
*/
|
|
83
|
+
export interface AsyncOperationalOperand<T = Tuple> {
|
|
84
|
+
tuples(): AsyncIterable<T>
|
|
85
|
+
output(tuples: AsyncIterable<T>): AsyncRelationOperand<T>
|
|
86
|
+
}
|
package/src/async.ts
ADDED
package/src/index.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
export * from './operators';
|
|
1
|
+
export * from './sync/operators';
|
|
2
2
|
export * from './types';
|
|
3
3
|
export { LIB_DEFINITIONS } from './lib-definitions';
|
|
4
|
+
export { toText } from './writer';
|
|
4
5
|
|
|
5
|
-
import { MemoryRelation } from './Relation';
|
|
6
|
-
import { isRelation } from './operators';
|
|
6
|
+
import { MemoryRelation } from './sync/Relation';
|
|
7
|
+
import { isRelation } from './sync/operators';
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* Creates a new in-memory relation from an array of tuples.
|
|
@@ -29,3 +30,15 @@ export function Bmg<T>(tuples: T[]): MemoryRelation<T> {
|
|
|
29
30
|
}
|
|
30
31
|
|
|
31
32
|
Bmg.isRelation = isRelation;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* DEE - The relation with no attributes and exactly one tuple.
|
|
36
|
+
* This is the identity element for natural join.
|
|
37
|
+
*/
|
|
38
|
+
export const DEE = new MemoryRelation<Record<string, never>>([{}]);
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* DUM - The relation with no attributes and no tuples.
|
|
42
|
+
* This is the identity element for union.
|
|
43
|
+
*/
|
|
44
|
+
export const DUM = new MemoryRelation<Record<string, never>>([]);
|
package/src/lib-definitions.ts
CHANGED
|
@@ -152,6 +152,13 @@ interface PrefixOptions {
|
|
|
152
152
|
except?: AttrName[]
|
|
153
153
|
}
|
|
154
154
|
|
|
155
|
+
interface TextOptions {
|
|
156
|
+
/** Precision for floating point numbers (default: 3) */
|
|
157
|
+
floatPrecision?: number;
|
|
158
|
+
/** Maximum width to trim output at */
|
|
159
|
+
trimAt?: number;
|
|
160
|
+
}
|
|
161
|
+
|
|
155
162
|
interface SuffixOptions {
|
|
156
163
|
except?: AttrName[]
|
|
157
164
|
}
|
|
@@ -256,6 +263,10 @@ interface Relation<T = Tuple> {
|
|
|
256
263
|
yByX<Y extends keyof T, X extends keyof T>(y: Y, x: X): Record<T[X] & PropertyKey, T[Y]>
|
|
257
264
|
toArray(): T[]
|
|
258
265
|
isEqual(right: any): boolean
|
|
266
|
+
|
|
267
|
+
// === Display ===
|
|
268
|
+
|
|
269
|
+
toText(options?: TextOptions): string
|
|
259
270
|
}
|
|
260
271
|
|
|
261
272
|
// ============================================================================
|
package/src/operators/index.ts
CHANGED
|
@@ -1,31 +1,2 @@
|
|
|
1
|
-
export
|
|
2
|
-
export * from '
|
|
3
|
-
export * from './exclude'
|
|
4
|
-
export * from './constants'
|
|
5
|
-
export * from './rename'
|
|
6
|
-
export * from './prefix'
|
|
7
|
-
export * from './suffix'
|
|
8
|
-
export * from './project'
|
|
9
|
-
export * from './allbut'
|
|
10
|
-
export * from './extend'
|
|
11
|
-
export * from './union'
|
|
12
|
-
export * from './minus'
|
|
13
|
-
export * from './intersect'
|
|
14
|
-
export * from './matching'
|
|
15
|
-
export * from './not_matching'
|
|
16
|
-
export * from './join'
|
|
17
|
-
export * from './left_join'
|
|
18
|
-
export * from './cross_product'
|
|
19
|
-
export * from './image'
|
|
20
|
-
export * from './summarize'
|
|
21
|
-
export * from './group'
|
|
22
|
-
export * from './ungroup'
|
|
23
|
-
export * from './wrap'
|
|
24
|
-
export * from './unwrap'
|
|
25
|
-
export * from './autowrap'
|
|
26
|
-
export * from './transform'
|
|
27
|
-
|
|
28
|
-
export * from './isRelation'
|
|
29
|
-
export * from './isEqual'
|
|
30
|
-
export * from './one'
|
|
31
|
-
export * from './yByX'
|
|
1
|
+
// Re-export sync operators for backward compatibility
|
|
2
|
+
export * from '../sync/operators';
|