@enspirit/bmg-js 1.0.0 → 1.0.2

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 (103) hide show
  1. package/LICENSE.md +21 -0
  2. package/dist/Relation/Memory.d.ts +45 -0
  3. package/dist/Relation/index.d.ts +1 -0
  4. package/dist/bmg.cjs +2 -0
  5. package/dist/bmg.cjs.map +1 -0
  6. package/dist/bmg.modern.js +2 -0
  7. package/dist/bmg.modern.js.map +1 -0
  8. package/dist/bmg.module.js +2 -0
  9. package/dist/bmg.module.js.map +1 -0
  10. package/dist/bmg.umd.js +2 -0
  11. package/dist/bmg.umd.js.map +1 -0
  12. package/dist/index.d.ts +27 -0
  13. package/dist/lib-definitions.d.ts +1 -0
  14. package/dist/operators/_helpers.d.ts +142 -0
  15. package/dist/operators/allbut.d.ts +2 -0
  16. package/dist/operators/autowrap.d.ts +2 -0
  17. package/dist/operators/constants.d.ts +2 -0
  18. package/dist/operators/cross_product.d.ts +3 -0
  19. package/dist/operators/exclude.d.ts +2 -0
  20. package/dist/operators/extend.d.ts +2 -0
  21. package/dist/operators/group.d.ts +2 -0
  22. package/dist/operators/image.d.ts +2 -0
  23. package/dist/operators/index.d.ts +30 -0
  24. package/dist/operators/intersect.d.ts +2 -0
  25. package/dist/operators/isEqual.d.ts +2 -0
  26. package/dist/operators/isRelation.d.ts +1 -0
  27. package/dist/operators/join.d.ts +2 -0
  28. package/dist/operators/left_join.d.ts +2 -0
  29. package/dist/operators/matching.d.ts +2 -0
  30. package/dist/operators/minus.d.ts +2 -0
  31. package/dist/operators/not_matching.d.ts +2 -0
  32. package/dist/operators/one.d.ts +2 -0
  33. package/dist/operators/prefix.d.ts +2 -0
  34. package/dist/operators/project.d.ts +2 -0
  35. package/dist/operators/rename.d.ts +2 -0
  36. package/dist/operators/restrict.d.ts +2 -0
  37. package/dist/operators/suffix.d.ts +2 -0
  38. package/dist/operators/summarize.d.ts +2 -0
  39. package/dist/operators/transform.d.ts +2 -0
  40. package/dist/operators/ungroup.d.ts +2 -0
  41. package/dist/operators/union.d.ts +2 -0
  42. package/dist/operators/unwrap.d.ts +2 -0
  43. package/dist/operators/where.d.ts +1 -0
  44. package/dist/operators/wrap.d.ts +2 -0
  45. package/dist/operators/yByX.d.ts +2 -0
  46. package/dist/support/toPredicateFunc.d.ts +2 -0
  47. package/dist/types.d.ts +162 -0
  48. package/package.json +20 -6
  49. package/src/Relation/Memory.ts +13 -12
  50. package/src/index.ts +1 -1
  51. package/src/lib-definitions.ts +281 -0
  52. package/src/types.ts +142 -54
  53. package/.claude/safe-setup/.env.example +0 -3
  54. package/.claude/safe-setup/Dockerfile.claude +0 -36
  55. package/.claude/safe-setup/HACKING.md +0 -63
  56. package/.claude/safe-setup/Makefile +0 -22
  57. package/.claude/safe-setup/docker-compose.yml +0 -18
  58. package/.claude/safe-setup/entrypoint.sh +0 -13
  59. package/.claude/settings.local.json +0 -9
  60. package/.claude/typescript-annotations.md +0 -273
  61. package/.github/workflows/test.yml +0 -26
  62. package/CLAUDE.md +0 -48
  63. package/Makefile +0 -2
  64. package/example/README.md +0 -22
  65. package/example/index.ts +0 -316
  66. package/example/package.json +0 -16
  67. package/example/tsconfig.json +0 -11
  68. package/src/utility-types.ts +0 -77
  69. package/tests/bmg.test.ts +0 -16
  70. package/tests/fixtures.ts +0 -9
  71. package/tests/operators/allbut.test.ts +0 -51
  72. package/tests/operators/autowrap.test.ts +0 -82
  73. package/tests/operators/constants.test.ts +0 -37
  74. package/tests/operators/cross_product.test.ts +0 -90
  75. package/tests/operators/exclude.test.ts +0 -43
  76. package/tests/operators/extend.test.ts +0 -45
  77. package/tests/operators/group.test.ts +0 -69
  78. package/tests/operators/image.test.ts +0 -152
  79. package/tests/operators/intersect.test.ts +0 -53
  80. package/tests/operators/isEqual.test.ts +0 -111
  81. package/tests/operators/join.test.ts +0 -116
  82. package/tests/operators/left_join.test.ts +0 -116
  83. package/tests/operators/matching.test.ts +0 -91
  84. package/tests/operators/minus.test.ts +0 -47
  85. package/tests/operators/not_matching.test.ts +0 -104
  86. package/tests/operators/one.test.ts +0 -19
  87. package/tests/operators/prefix.test.ts +0 -37
  88. package/tests/operators/project.test.ts +0 -48
  89. package/tests/operators/rename.test.ts +0 -39
  90. package/tests/operators/restrict.test.ts +0 -27
  91. package/tests/operators/suffix.test.ts +0 -37
  92. package/tests/operators/summarize.test.ts +0 -109
  93. package/tests/operators/transform.test.ts +0 -94
  94. package/tests/operators/ungroup.test.ts +0 -67
  95. package/tests/operators/union.test.ts +0 -51
  96. package/tests/operators/unwrap.test.ts +0 -50
  97. package/tests/operators/where.test.ts +0 -33
  98. package/tests/operators/wrap.test.ts +0 -54
  99. package/tests/operators/yByX.test.ts +0 -32
  100. package/tests/types/relation.test.ts +0 -296
  101. package/tsconfig.json +0 -37
  102. package/tsconfig.node.json +0 -9
  103. package/vitest.config.ts +0 -15
@@ -1,90 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { Bmg } from 'src';
3
- import { cross_product, cross_join , isEqual } from 'src/operators';
4
-
5
- describe('.cross_product', () => {
6
-
7
- const colors = Bmg([
8
- { color: 'red' },
9
- { color: 'blue' },
10
- ]);
11
-
12
- const sizes = Bmg([
13
- { size: 'S' },
14
- { size: 'M' },
15
- { size: 'L' },
16
- ]);
17
-
18
- it('computes cartesian product of two relations', () => {
19
- const result = colors.cross_product(sizes);
20
- const expected = Bmg([
21
- { color: 'red', size: 'S' },
22
- { color: 'red', size: 'M' },
23
- { color: 'red', size: 'L' },
24
- { color: 'blue', size: 'S' },
25
- { color: 'blue', size: 'M' },
26
- { color: 'blue', size: 'L' },
27
- ]);
28
- expect(result.isEqual(expected)).to.be.true;
29
- })
30
-
31
- it('cross_join is an alias for cross_product', () => {
32
- const withCrossProduct = colors.cross_product(sizes);
33
- const withCrossJoin = colors.cross_join(sizes);
34
- expect(withCrossProduct.isEqual(withCrossJoin)).to.be.true;
35
- })
36
-
37
- it('handles DUM', () => {
38
- const empty = Bmg([]);
39
- const result = colors.cross_product(empty);
40
- expect(result.toArray()).to.eql([]);
41
- })
42
-
43
- it('handles DEE', () => {
44
- const empty = Bmg([{}]);
45
- const result = colors.cross_product(empty);
46
- expect(result.isEqual(colors)).to.be.true;
47
- })
48
-
49
- it('clashing right attributes are ignored', () => {
50
- const left = Bmg([{ x: 1, y: 2 }]);
51
- const right = Bmg([{ y: 100, z: 3 }]);
52
- const result = left.cross_product(right);
53
- const expected = Bmg([{ x: 1, y: 2, z: 3 }]);
54
- expect(result.isEqual(expected)).to.be.true;
55
- })
56
-
57
- it('clashing right attributes are ignored with multiple tuples', () => {
58
- const left = Bmg([
59
- { x: 1, y: 'a' },
60
- { x: 2, y: 'b' },
61
- ]);
62
- const right = Bmg([
63
- { y: 'ignored', z: 10 },
64
- { y: 'also_ignored', z: 20 },
65
- ]);
66
- const result = left.cross_product(right);
67
- const expected = Bmg([
68
- { x: 1, y: 'a', z: 10 },
69
- { x: 1, y: 'a', z: 20 },
70
- { x: 2, y: 'b', z: 10 },
71
- { x: 2, y: 'b', z: 20 },
72
- ]);
73
- expect(result.isEqual(expected)).to.be.true;
74
- })
75
-
76
- ///
77
-
78
- it('can be used standalone', () => {
79
- const res = cross_product(colors.toArray(), sizes.toArray());
80
- const expected = colors.cross_product(sizes);
81
- expect(isEqual(res, expected)).to.be.true;
82
- })
83
-
84
- it('cross_join standalone is also available', () => {
85
- const res = cross_join(colors.toArray(), sizes.toArray());
86
- const expected = colors.cross_join(sizes);
87
- expect(isEqual(res, expected)).to.be.true;
88
- })
89
-
90
- });
@@ -1,43 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { Bmg } from 'src';
3
- import { SUPPLIERS } from 'tests/fixtures';
4
- import { exclude , isEqual } from 'src/operators';
5
-
6
- describe('.exclude', () => {
7
-
8
- it('filters out tuples matching the predicate', () => {
9
- const nonParis = SUPPLIERS.exclude({city: 'Paris'});
10
- const expected = Bmg([
11
- {sid: 'S1', name: 'Smith', status: 20, city: 'London' },
12
- {sid: 'S4', name: 'Clark', status: 20, city: 'London' },
13
- {sid: 'S5', name: 'Adams', status: 30, city: 'Athens' },
14
- ]);
15
- expect(nonParis.isEqual(expected)).to.be.true;
16
- })
17
-
18
- it('works with a function predicate', () => {
19
- const lowStatus = SUPPLIERS.exclude((t) => (t.status as number) >= 20);
20
- const expected = Bmg([
21
- {sid: 'S2', name: 'Jones', status: 10, city: 'Paris' },
22
- ]);
23
- expect(lowStatus.isEqual(expected)).to.be.true;
24
- })
25
-
26
- it('is the inverse of restrict', () => {
27
- const restricted = SUPPLIERS.restrict({city: 'Paris'});
28
- const excluded = SUPPLIERS.exclude({city: 'Paris'});
29
- // Union of restrict and exclude should give the original relation
30
- const reunion = restricted.union(excluded);
31
- expect(reunion.isEqual(SUPPLIERS)).to.be.true;
32
- })
33
-
34
- ///
35
-
36
- it('can be used standalone', () => {
37
- const input = SUPPLIERS.toArray();
38
- const res = exclude(input, { city: 'Paris' });
39
- const expected = SUPPLIERS.exclude({ city: 'Paris' });
40
- expect(isEqual(res, expected)).to.be.true;
41
- })
42
-
43
- });
@@ -1,45 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
-
3
- import { SUPPLIERS } from 'tests/fixtures';
4
- import { extend , isEqual } from 'src/operators';
5
-
6
- describe('.extend', () => {
7
-
8
- it('adds computed attributes', () => {
9
- const result = SUPPLIERS.extend({
10
- statusLabel: (t) => `Status: ${t.status}`
11
- });
12
- const smith = result.restrict({ sid: 'S1' }).one();
13
- expect(smith.statusLabel).to.eql('Status: 20');
14
- expect(smith.name).to.eql('Smith');
15
- })
16
-
17
- it('copies attributes with string shortcut', () => {
18
- const result = SUPPLIERS.extend({
19
- location: 'city'
20
- });
21
- const smith = result.restrict({ sid: 'S1' }).one();
22
- expect(smith.location).to.eql('London');
23
- expect(smith.city).to.eql('London');
24
- })
25
-
26
- it('supports multiple extensions', () => {
27
- const result = SUPPLIERS.extend({
28
- location: 'city',
29
- doubled: (t) => (t.status as number) * 2
30
- });
31
- const smith = result.restrict({ sid: 'S1' }).one();
32
- expect(smith.location).to.eql('London');
33
- expect(smith.doubled).to.eql(40);
34
- })
35
-
36
- ///
37
-
38
- it('can be used standalone', () => {
39
- const input = SUPPLIERS.toArray();
40
- const res = extend(input, { location: 'city' });
41
- const expected = SUPPLIERS.extend({ location: 'city' });
42
- expect(isEqual(res, expected)).to.be.true;
43
- })
44
-
45
- });
@@ -1,69 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { Bmg } from 'src';
3
- import { group , isEqual } from 'src/operators';
4
-
5
- describe('.group', () => {
6
-
7
- const orders = Bmg([
8
- { order_id: 1, customer: 'Alice', item: 'Apple', qty: 2 },
9
- { order_id: 1, customer: 'Alice', item: 'Banana', qty: 3 },
10
- { order_id: 2, customer: 'Bob', item: 'Cherry', qty: 1 },
11
- ]);
12
-
13
- it('groups specified attributes into nested relation', () => {
14
- const result = orders.group(['item', 'qty'], 'items');
15
- const expected = Bmg([
16
- {
17
- order_id: 1,
18
- customer: 'Alice',
19
- items: Bmg([
20
- { item: 'Apple', qty: 2 },
21
- { item: 'Banana', qty: 3 },
22
- ])
23
- },
24
- {
25
- order_id: 2,
26
- customer: 'Bob',
27
- items: Bmg([
28
- { item: 'Cherry', qty: 1 },
29
- ])
30
- },
31
- ]);
32
- expect(result.isEqual(expected)).to.be.true;
33
- })
34
-
35
- it('preserves non-grouped attributes', () => {
36
- const result = orders.group(['item', 'qty'], 'items');
37
- const bob = result.restrict({ customer: 'Bob' }).one();
38
- expect(bob).to.have.property('order_id', 2);
39
- expect(bob).to.have.property('customer', 'Bob');
40
- expect(bob).to.have.property('items');
41
- expect(Object.keys(bob).sort()).to.eql(['customer', 'items', 'order_id']);
42
- })
43
-
44
- it('handles single-item groups', () => {
45
- const result = orders.group(['item', 'qty'], 'items');
46
- const bobOnly = result.restrict({ customer: 'Bob' });
47
- const expected = Bmg([{
48
- order_id: 2,
49
- customer: 'Bob',
50
- items: Bmg([{ item: 'Cherry', qty: 1 }])
51
- }]);
52
- expect(bobOnly.isEqual(expected)).to.be.true;
53
- })
54
-
55
- it('handles empty relation', () => {
56
- const empty = Bmg([]);
57
- const result = empty.group(['x'], 'grouped');
58
- expect(result.isEqual(Bmg([]))).to.be.true;
59
- })
60
-
61
- ///
62
-
63
- it('can be used standalone', () => {
64
- const res = group(orders.toArray(), ['item', 'qty'], 'items');
65
- const expected = orders.group(['item', 'qty'], 'items');
66
- expect(isEqual(res, expected)).to.be.true;
67
- })
68
-
69
- });
@@ -1,152 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { Bmg } from 'src';
3
- import { image , isEqual } from 'src/operators';
4
-
5
- describe('.image', () => {
6
-
7
- const suppliers = Bmg([
8
- { sid: 'S1', name: 'Smith', city: 'London' },
9
- { sid: 'S2', name: 'Jones', city: 'Paris' },
10
- { sid: 'S3', name: 'Blake', city: 'Paris' },
11
- ]);
12
-
13
- const shipments = Bmg([
14
- { sid: 'S1', pid: 'P1', qty: 100 },
15
- { sid: 'S1', pid: 'P2', qty: 200 },
16
- { sid: 'S2', pid: 'P1', qty: 300 },
17
- ]);
18
-
19
- it('adds relation-valued attribute with matching tuples', () => {
20
- const result = suppliers.image(shipments, 'shipments');
21
- const expected = Bmg([
22
- {
23
- sid: 'S1', name: 'Smith', city: 'London',
24
- shipments: Bmg([
25
- { pid: 'P1', qty: 100 },
26
- { pid: 'P2', qty: 200 },
27
- ])
28
- },
29
- {
30
- sid: 'S2', name: 'Jones', city: 'Paris',
31
- shipments: Bmg([
32
- { pid: 'P1', qty: 300 },
33
- ])
34
- },
35
- {
36
- sid: 'S3', name: 'Blake', city: 'Paris',
37
- shipments: Bmg([])
38
- },
39
- ]);
40
- expect(result.isEqual(expected)).to.be.true;
41
- })
42
-
43
- it('supports explicit keys as { left: right }', () => {
44
- const cities = Bmg([
45
- { location: 'London', country: 'UK' },
46
- { location: 'Paris', country: 'France' },
47
- ]);
48
- const result = suppliers.image(cities, 'city_info', { city: 'location' });
49
- const expected = Bmg([
50
- { sid: 'S1', name: 'Smith', city: 'London', city_info: Bmg([{ country: 'UK' }]) },
51
- { sid: 'S2', name: 'Jones', city: 'Paris', city_info: Bmg([{ country: 'France' }]) },
52
- { sid: 'S3', name: 'Blake', city: 'Paris', city_info: Bmg([{ country: 'France' }]) },
53
- ]);
54
- expect(result.isEqual(expected)).to.be.true;
55
- })
56
-
57
- it('supports explicit keys as [common_attr]', () => {
58
- const result = suppliers.image(shipments, 'shipments', ['sid']);
59
- const expected = Bmg([
60
- {
61
- sid: 'S1', name: 'Smith', city: 'London',
62
- shipments: Bmg([
63
- { pid: 'P1', qty: 100 },
64
- { pid: 'P2', qty: 200 },
65
- ])
66
- },
67
- {
68
- sid: 'S2', name: 'Jones', city: 'Paris',
69
- shipments: Bmg([
70
- { pid: 'P1', qty: 300 },
71
- ])
72
- },
73
- {
74
- sid: 'S3', name: 'Blake', city: 'Paris',
75
- shipments: Bmg([])
76
- },
77
- ]);
78
- expect(result.isEqual(expected)).to.be.true;
79
- })
80
-
81
- it('supports multiple attributes as join key', () => {
82
- const inventory = Bmg([
83
- { warehouse: 'W1', city: 'London', stock: 100 },
84
- { warehouse: 'W2', city: 'London', stock: 200 },
85
- { warehouse: 'W3', city: 'Paris', stock: 150 },
86
- ]);
87
- const orders = Bmg([
88
- { warehouse: 'W1', city: 'London', item: 'A', qty: 10 },
89
- { warehouse: 'W1', city: 'London', item: 'B', qty: 20 },
90
- { warehouse: 'W3', city: 'Paris', item: 'C', qty: 30 },
91
- ]);
92
- const result = inventory.image(orders, 'orders', ['warehouse', 'city']);
93
- const expected = Bmg([
94
- {
95
- warehouse: 'W1', city: 'London', stock: 100,
96
- orders: Bmg([
97
- { item: 'A', qty: 10 },
98
- { item: 'B', qty: 20 },
99
- ])
100
- },
101
- {
102
- warehouse: 'W2', city: 'London', stock: 200,
103
- orders: Bmg([])
104
- },
105
- {
106
- warehouse: 'W3', city: 'Paris', stock: 150,
107
- orders: Bmg([
108
- { item: 'C', qty: 30 },
109
- ])
110
- },
111
- ]);
112
- expect(result.isEqual(expected)).to.be.true;
113
- })
114
-
115
- it('supports multiple attributes as { left: right } object', () => {
116
- const people = Bmg([
117
- { id: 1, first: 'John', last: 'Doe' },
118
- { id: 2, first: 'Jane', last: 'Smith' },
119
- ]);
120
- const records = Bmg([
121
- { fname: 'John', lname: 'Doe', year: 2020, score: 85 },
122
- { fname: 'John', lname: 'Doe', year: 2021, score: 90 },
123
- { fname: 'Jane', lname: 'Smith', year: 2020, score: 95 },
124
- ]);
125
- const result = people.image(records, 'scores', { first: 'fname', last: 'lname' });
126
- const expected = Bmg([
127
- {
128
- id: 1, first: 'John', last: 'Doe',
129
- scores: Bmg([
130
- { year: 2020, score: 85 },
131
- { year: 2021, score: 90 },
132
- ])
133
- },
134
- {
135
- id: 2, first: 'Jane', last: 'Smith',
136
- scores: Bmg([
137
- { year: 2020, score: 95 },
138
- ])
139
- },
140
- ]);
141
- expect(result.isEqual(expected)).to.be.true;
142
- })
143
-
144
- ///
145
-
146
- it('can be used standalone', () => {
147
- const res = image(suppliers.toArray(), shipments.toArray(), 'shipments');
148
- const expected = suppliers.image(shipments, 'shipments');
149
- expect(isEqual(res, expected)).to.be.true;
150
- })
151
-
152
- });
@@ -1,53 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { Bmg } from 'src';
3
- import { intersect , isEqual } from 'src/operators';
4
-
5
- describe('.intersect', () => {
6
-
7
- const left = Bmg([
8
- { id: 1, name: 'Alice' },
9
- { id: 2, name: 'Bob' },
10
- { id: 3, name: 'Charlie' },
11
- ]);
12
-
13
- const right = Bmg([
14
- { id: 2, name: 'Bob' },
15
- { id: 3, name: 'Charlie' },
16
- { id: 4, name: 'Diana' },
17
- ]);
18
-
19
- it('returns tuples present in both relations', () => {
20
- const result = left.intersect(right);
21
- const expected = Bmg([
22
- { id: 2, name: 'Bob' },
23
- { id: 3, name: 'Charlie' },
24
- ]);
25
- expect(result.isEqual(expected)).to.be.true;
26
- })
27
-
28
- it('returns empty when no overlap', () => {
29
- const other = Bmg([{ id: 99, name: 'Nobody' }]);
30
- const result = left.intersect(other);
31
- expect(result.isEqual(Bmg([]))).to.be.true;
32
- })
33
-
34
- it('returns all when identical', () => {
35
- const result = left.intersect(left);
36
- expect(result.isEqual(left)).to.be.true;
37
- })
38
-
39
- it('is commutative (set semantics)', () => {
40
- const lr = left.intersect(right);
41
- const rl = right.intersect(left);
42
- expect(lr.isEqual(rl)).to.be.true;
43
- })
44
-
45
- ///
46
-
47
- it('can be used standalone', () => {
48
- const res = intersect(left.toArray(), right.toArray());
49
- const expected = left.intersect(right);
50
- expect(isEqual(res, expected)).to.be.true;
51
- })
52
-
53
- });
@@ -1,111 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { Bmg } from 'src';
3
- import { isEqual } from 'src/operators';
4
-
5
- describe('.isEqual', () => {
6
-
7
- it('returns true for identical relations', () => {
8
- const r = Bmg([
9
- { id: 1, name: 'Alice' },
10
- { id: 2, name: 'Bob' },
11
- ]);
12
- expect(r.isEqual(r)).to.be.true;
13
- })
14
-
15
- it('returns true for relations with same tuples', () => {
16
- const r1 = Bmg([
17
- { id: 1, name: 'Alice' },
18
- { id: 2, name: 'Bob' },
19
- ]);
20
- const r2 = Bmg([
21
- { id: 1, name: 'Alice' },
22
- { id: 2, name: 'Bob' },
23
- ]);
24
- expect(r1.isEqual(r2)).to.be.true;
25
- })
26
-
27
- it('returns true regardless of tuple order', () => {
28
- const r1 = Bmg([
29
- { id: 1, name: 'Alice' },
30
- { id: 2, name: 'Bob' },
31
- ]);
32
- const r2 = Bmg([
33
- { id: 2, name: 'Bob' },
34
- { id: 1, name: 'Alice' },
35
- ]);
36
- expect(r1.isEqual(r2)).to.be.true;
37
- })
38
-
39
- it('returns true regardless of attribute order', () => {
40
- const r1 = Bmg([
41
- { id: 1, name: 'Alice' },
42
- ]);
43
- const r2 = Bmg([
44
- { name: 'Alice', id: 1 },
45
- ]);
46
- expect(r1.isEqual(r2)).to.be.true;
47
- })
48
-
49
- it('ignores duplicates (set semantics)', () => {
50
- const r1 = Bmg([
51
- { id: 1, name: 'Alice' },
52
- { id: 1, name: 'Alice' },
53
- ]);
54
- const r2 = Bmg([
55
- { id: 1, name: 'Alice' },
56
- ]);
57
- expect(r1.isEqual(r2)).to.be.true;
58
- })
59
-
60
- it('returns false for different tuple count', () => {
61
- const r1 = Bmg([
62
- { id: 1, name: 'Alice' },
63
- { id: 2, name: 'Bob' },
64
- ]);
65
- const r2 = Bmg([
66
- { id: 1, name: 'Alice' },
67
- ]);
68
- expect(r1.isEqual(r2)).to.be.false;
69
- })
70
-
71
- it('returns false for different tuple values', () => {
72
- const r1 = Bmg([
73
- { id: 1, name: 'Alice' },
74
- ]);
75
- const r2 = Bmg([
76
- { id: 1, name: 'Bob' },
77
- ]);
78
- expect(r1.isEqual(r2)).to.be.false;
79
- })
80
-
81
- it('returns false for different attributes', () => {
82
- const r1 = Bmg([
83
- { id: 1, name: 'Alice' },
84
- ]);
85
- const r2 = Bmg([
86
- { id: 1, city: 'Paris' },
87
- ]);
88
- expect(r1.isEqual(r2)).to.be.false;
89
- })
90
-
91
- it('returns true for empty relations', () => {
92
- const r1 = Bmg([]);
93
- const r2 = Bmg([]);
94
- expect(r1.isEqual(r2)).to.be.true;
95
- })
96
-
97
- ///
98
-
99
- it('can be used standalone', () => {
100
- const left = [{ id: 1, name: 'Alice' }];
101
- const right = [{ name: 'Alice', id: 1 }];
102
- expect(isEqual(left, right)).to.be.true;
103
- })
104
-
105
- it('works with array operand', () => {
106
- const r = Bmg([{ id: 1, name: 'Alice' }]);
107
- const arr = [{ id: 1, name: 'Alice' }];
108
- expect(r.isEqual(arr)).to.be.true;
109
- })
110
-
111
- });
@@ -1,116 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { Bmg } from 'src';
3
- import { join , isEqual } from 'src/operators';
4
-
5
- describe('.join', () => {
6
-
7
- const suppliers = Bmg([
8
- { sid: 'S1', name: 'Smith', city: 'London' },
9
- { sid: 'S2', name: 'Jones', city: 'Paris' },
10
- { sid: 'S3', name: 'Blake', city: 'Paris' },
11
- ]);
12
-
13
- const parts = Bmg([
14
- { pid: 'P1', pname: 'Nut', city: 'London' },
15
- { pid: 'P2', pname: 'Bolt', city: 'Paris' },
16
- { pid: 'P3', pname: 'Screw', city: 'Athens' },
17
- ]);
18
-
19
- it('performs natural join on common attributes', () => {
20
- const result = suppliers.join(parts);
21
- const expected = Bmg([
22
- { sid: 'S1', name: 'Smith', city: 'London', pid: 'P1', pname: 'Nut' },
23
- { sid: 'S2', name: 'Jones', city: 'Paris', pid: 'P2', pname: 'Bolt' },
24
- { sid: 'S3', name: 'Blake', city: 'Paris', pid: 'P2', pname: 'Bolt' },
25
- ]);
26
- expect(result.isEqual(expected)).to.be.true;
27
- })
28
-
29
- it('joins on specified attributes as [common_attr]', () => {
30
- const result = suppliers.join(parts, ['city']);
31
- const expected = Bmg([
32
- { sid: 'S1', name: 'Smith', city: 'London', pid: 'P1', pname: 'Nut' },
33
- { sid: 'S2', name: 'Jones', city: 'Paris', pid: 'P2', pname: 'Bolt' },
34
- { sid: 'S3', name: 'Blake', city: 'Paris', pid: 'P2', pname: 'Bolt' },
35
- ]);
36
- expect(result.isEqual(expected)).to.be.true;
37
- })
38
-
39
- it('joins on multiple attributes as [attr1, attr2]', () => {
40
- const inventory = Bmg([
41
- { warehouse: 'W1', city: 'London', stock: 100 },
42
- { warehouse: 'W2', city: 'London', stock: 200 },
43
- { warehouse: 'W1', city: 'Paris', stock: 150 },
44
- ]);
45
- const shipments = Bmg([
46
- { warehouse: 'W1', city: 'London', qty: 10 },
47
- { warehouse: 'W1', city: 'Paris', qty: 20 },
48
- ]);
49
- const result = inventory.join(shipments, ['warehouse', 'city']);
50
- const expected = Bmg([
51
- { warehouse: 'W1', city: 'London', stock: 100, qty: 10 },
52
- { warehouse: 'W1', city: 'Paris', stock: 150, qty: 20 },
53
- ]);
54
- expect(result.isEqual(expected)).to.be.true;
55
- })
56
-
57
- it('joins on different attribute names as { left: right }', () => {
58
- const locations = Bmg([
59
- { location: 'London', country: 'UK' },
60
- { location: 'Paris', country: 'France' },
61
- ]);
62
- const result = suppliers.join(locations, { city: 'location' });
63
- const expected = Bmg([
64
- { sid: 'S1', name: 'Smith', city: 'London', country: 'UK' },
65
- { sid: 'S2', name: 'Jones', city: 'Paris', country: 'France' },
66
- { sid: 'S3', name: 'Blake', city: 'Paris', country: 'France' },
67
- ]);
68
- expect(result.isEqual(expected)).to.be.true;
69
- })
70
-
71
- it('joins on multiple different attribute names as { left1: right1, left2: right2 }', () => {
72
- const people = Bmg([
73
- { id: 1, first: 'John', last: 'Doe' },
74
- { id: 2, first: 'Jane', last: 'Smith' },
75
- { id: 3, first: 'John', last: 'Smith' },
76
- ]);
77
- const records = Bmg([
78
- { fname: 'John', lname: 'Doe', score: 85 },
79
- { fname: 'Jane', lname: 'Smith', score: 90 },
80
- ]);
81
- const result = people.join(records, { first: 'fname', last: 'lname' });
82
- const expected = Bmg([
83
- { id: 1, first: 'John', last: 'Doe', score: 85 },
84
- { id: 2, first: 'Jane', last: 'Smith', score: 90 },
85
- ]);
86
- expect(result.isEqual(expected)).to.be.true;
87
- })
88
-
89
- it('returns empty for no matches', () => {
90
- const other = Bmg([{ city: 'Tokyo' }]);
91
- const result = suppliers.join(other);
92
- expect(result.isEqual(Bmg([]))).to.be.true;
93
- })
94
-
95
- it('handles cartesian product when no common attrs', () => {
96
- const colors = Bmg([{ color: 'red' }, { color: 'blue' }]);
97
- const sizes = Bmg([{ size: 'S' }, { size: 'M' }]);
98
- const result = colors.join(sizes);
99
- const expected = Bmg([
100
- { color: 'red', size: 'S' },
101
- { color: 'red', size: 'M' },
102
- { color: 'blue', size: 'S' },
103
- { color: 'blue', size: 'M' },
104
- ]);
105
- expect(result.isEqual(expected)).to.be.true;
106
- })
107
-
108
- ///
109
-
110
- it('can be used standalone', () => {
111
- const res = join(suppliers.toArray(), parts.toArray(), ['city']);
112
- const expected = suppliers.join(parts, ['city']);
113
- expect(isEqual(res, expected)).to.be.true;
114
- })
115
-
116
- });