@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.
- package/LICENSE.md +21 -0
- package/dist/Relation/Memory.d.ts +45 -0
- package/dist/Relation/index.d.ts +1 -0
- package/dist/bmg.cjs +2 -0
- package/dist/bmg.cjs.map +1 -0
- package/dist/bmg.modern.js +2 -0
- package/dist/bmg.modern.js.map +1 -0
- package/dist/bmg.module.js +2 -0
- package/dist/bmg.module.js.map +1 -0
- package/dist/bmg.umd.js +2 -0
- package/dist/bmg.umd.js.map +1 -0
- package/dist/index.d.ts +27 -0
- package/dist/lib-definitions.d.ts +1 -0
- package/dist/operators/_helpers.d.ts +142 -0
- package/dist/operators/allbut.d.ts +2 -0
- package/dist/operators/autowrap.d.ts +2 -0
- package/dist/operators/constants.d.ts +2 -0
- package/dist/operators/cross_product.d.ts +3 -0
- package/dist/operators/exclude.d.ts +2 -0
- package/dist/operators/extend.d.ts +2 -0
- package/dist/operators/group.d.ts +2 -0
- package/dist/operators/image.d.ts +2 -0
- package/dist/operators/index.d.ts +30 -0
- package/dist/operators/intersect.d.ts +2 -0
- package/dist/operators/isEqual.d.ts +2 -0
- package/dist/operators/isRelation.d.ts +1 -0
- package/dist/operators/join.d.ts +2 -0
- package/dist/operators/left_join.d.ts +2 -0
- package/dist/operators/matching.d.ts +2 -0
- package/dist/operators/minus.d.ts +2 -0
- package/dist/operators/not_matching.d.ts +2 -0
- package/dist/operators/one.d.ts +2 -0
- package/dist/operators/prefix.d.ts +2 -0
- package/dist/operators/project.d.ts +2 -0
- package/dist/operators/rename.d.ts +2 -0
- package/dist/operators/restrict.d.ts +2 -0
- package/dist/operators/suffix.d.ts +2 -0
- package/dist/operators/summarize.d.ts +2 -0
- package/dist/operators/transform.d.ts +2 -0
- package/dist/operators/ungroup.d.ts +2 -0
- package/dist/operators/union.d.ts +2 -0
- package/dist/operators/unwrap.d.ts +2 -0
- package/dist/operators/where.d.ts +1 -0
- package/dist/operators/wrap.d.ts +2 -0
- package/dist/operators/yByX.d.ts +2 -0
- package/dist/support/toPredicateFunc.d.ts +2 -0
- package/dist/types.d.ts +162 -0
- package/package.json +20 -6
- package/src/Relation/Memory.ts +13 -12
- package/src/index.ts +1 -1
- package/src/lib-definitions.ts +281 -0
- package/src/types.ts +142 -54
- package/.claude/safe-setup/.env.example +0 -3
- package/.claude/safe-setup/Dockerfile.claude +0 -36
- package/.claude/safe-setup/HACKING.md +0 -63
- package/.claude/safe-setup/Makefile +0 -22
- package/.claude/safe-setup/docker-compose.yml +0 -18
- package/.claude/safe-setup/entrypoint.sh +0 -13
- package/.claude/settings.local.json +0 -9
- package/.claude/typescript-annotations.md +0 -273
- package/.github/workflows/test.yml +0 -26
- package/CLAUDE.md +0 -48
- package/Makefile +0 -2
- package/example/README.md +0 -22
- package/example/index.ts +0 -316
- package/example/package.json +0 -16
- package/example/tsconfig.json +0 -11
- package/src/utility-types.ts +0 -77
- package/tests/bmg.test.ts +0 -16
- package/tests/fixtures.ts +0 -9
- package/tests/operators/allbut.test.ts +0 -51
- package/tests/operators/autowrap.test.ts +0 -82
- package/tests/operators/constants.test.ts +0 -37
- package/tests/operators/cross_product.test.ts +0 -90
- package/tests/operators/exclude.test.ts +0 -43
- package/tests/operators/extend.test.ts +0 -45
- package/tests/operators/group.test.ts +0 -69
- package/tests/operators/image.test.ts +0 -152
- package/tests/operators/intersect.test.ts +0 -53
- package/tests/operators/isEqual.test.ts +0 -111
- package/tests/operators/join.test.ts +0 -116
- package/tests/operators/left_join.test.ts +0 -116
- package/tests/operators/matching.test.ts +0 -91
- package/tests/operators/minus.test.ts +0 -47
- package/tests/operators/not_matching.test.ts +0 -104
- package/tests/operators/one.test.ts +0 -19
- package/tests/operators/prefix.test.ts +0 -37
- package/tests/operators/project.test.ts +0 -48
- package/tests/operators/rename.test.ts +0 -39
- package/tests/operators/restrict.test.ts +0 -27
- package/tests/operators/suffix.test.ts +0 -37
- package/tests/operators/summarize.test.ts +0 -109
- package/tests/operators/transform.test.ts +0 -94
- package/tests/operators/ungroup.test.ts +0 -67
- package/tests/operators/union.test.ts +0 -51
- package/tests/operators/unwrap.test.ts +0 -50
- package/tests/operators/where.test.ts +0 -33
- package/tests/operators/wrap.test.ts +0 -54
- package/tests/operators/yByX.test.ts +0 -32
- package/tests/types/relation.test.ts +0 -296
- package/tsconfig.json +0 -37
- package/tsconfig.node.json +0 -9
- package/vitest.config.ts +0 -15
package/example/index.ts
DELETED
|
@@ -1,316 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Bmg.js Example - Relational Algebra Operations (TypeScript)
|
|
3
|
-
*
|
|
4
|
-
* This example demonstrates chaining various relational operators
|
|
5
|
-
* using the classic suppliers/parts/shipments dataset with full type safety.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { Bmg, Relation } from '@enspirit/bmg-js';
|
|
9
|
-
|
|
10
|
-
// =============================================================================
|
|
11
|
-
// Type Definitions
|
|
12
|
-
// =============================================================================
|
|
13
|
-
|
|
14
|
-
interface Supplier {
|
|
15
|
-
sid: string;
|
|
16
|
-
name: string;
|
|
17
|
-
status: number;
|
|
18
|
-
city: string;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
interface Part {
|
|
22
|
-
pid: string;
|
|
23
|
-
pname: string;
|
|
24
|
-
color: string;
|
|
25
|
-
weight: number;
|
|
26
|
-
city: string;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
interface Shipment {
|
|
30
|
-
sid: string;
|
|
31
|
-
pid: string;
|
|
32
|
-
qty: number;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// =============================================================================
|
|
36
|
-
// Sample Data: Suppliers, Parts, and Shipments
|
|
37
|
-
// =============================================================================
|
|
38
|
-
|
|
39
|
-
const suppliers = Bmg<Supplier>([
|
|
40
|
-
{ sid: 'S1', name: 'Smith', status: 20, city: 'London' },
|
|
41
|
-
{ sid: 'S2', name: 'Jones', status: 10, city: 'Paris' },
|
|
42
|
-
{ sid: 'S3', name: 'Blake', status: 30, city: 'Paris' },
|
|
43
|
-
{ sid: 'S4', name: 'Clark', status: 20, city: 'London' },
|
|
44
|
-
{ sid: 'S5', name: 'Adams', status: 30, city: 'Athens' },
|
|
45
|
-
]);
|
|
46
|
-
|
|
47
|
-
const parts = Bmg<Part>([
|
|
48
|
-
{ pid: 'P1', pname: 'Nut', color: 'Red', weight: 12, city: 'London' },
|
|
49
|
-
{ pid: 'P2', pname: 'Bolt', color: 'Green', weight: 17, city: 'Paris' },
|
|
50
|
-
{ pid: 'P3', pname: 'Screw', color: 'Blue', weight: 17, city: 'Oslo' },
|
|
51
|
-
{ pid: 'P4', pname: 'Screw', color: 'Red', weight: 14, city: 'London' },
|
|
52
|
-
{ pid: 'P5', pname: 'Cam', color: 'Blue', weight: 12, city: 'Paris' },
|
|
53
|
-
{ pid: 'P6', pname: 'Cog', color: 'Red', weight: 19, city: 'London' },
|
|
54
|
-
]);
|
|
55
|
-
|
|
56
|
-
const shipments = Bmg<Shipment>([
|
|
57
|
-
{ sid: 'S1', pid: 'P1', qty: 300 },
|
|
58
|
-
{ sid: 'S1', pid: 'P2', qty: 200 },
|
|
59
|
-
{ sid: 'S1', pid: 'P3', qty: 400 },
|
|
60
|
-
{ sid: 'S1', pid: 'P4', qty: 200 },
|
|
61
|
-
{ sid: 'S1', pid: 'P5', qty: 100 },
|
|
62
|
-
{ sid: 'S1', pid: 'P6', qty: 100 },
|
|
63
|
-
{ sid: 'S2', pid: 'P1', qty: 300 },
|
|
64
|
-
{ sid: 'S2', pid: 'P2', qty: 400 },
|
|
65
|
-
{ sid: 'S3', pid: 'P2', qty: 200 },
|
|
66
|
-
{ sid: 'S4', pid: 'P2', qty: 200 },
|
|
67
|
-
{ sid: 'S4', pid: 'P4', qty: 300 },
|
|
68
|
-
{ sid: 'S4', pid: 'P5', qty: 400 },
|
|
69
|
-
]);
|
|
70
|
-
|
|
71
|
-
// Helper to print results
|
|
72
|
-
const printRelation = <T>(title: string, relation: Relation<T>): void => {
|
|
73
|
-
console.log(`\n${'='.repeat(60)}`);
|
|
74
|
-
console.log(title);
|
|
75
|
-
console.log('='.repeat(60));
|
|
76
|
-
console.log(relation.toArray());
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
// =============================================================================
|
|
80
|
-
// Example 1: Basic Operations - restrict, project, rename
|
|
81
|
-
// =============================================================================
|
|
82
|
-
|
|
83
|
-
console.log('\n*** EXAMPLE 1: Basic Operations ***');
|
|
84
|
-
|
|
85
|
-
// Find suppliers in Paris, showing only their id and name
|
|
86
|
-
// Type: Relation<{ sid: string; name: string }>
|
|
87
|
-
const parisSuppliers = suppliers
|
|
88
|
-
.restrict({ city: 'Paris' })
|
|
89
|
-
.project(['sid', 'name']);
|
|
90
|
-
|
|
91
|
-
printRelation('Suppliers in Paris (id and name only)', parisSuppliers);
|
|
92
|
-
|
|
93
|
-
// Rename attributes for clarity
|
|
94
|
-
// Type: Relation<{ supplierId: string; supplierName: string; city: string }>
|
|
95
|
-
const renamedSuppliers = suppliers
|
|
96
|
-
.project(['sid', 'name', 'city'])
|
|
97
|
-
.rename({ sid: 'supplierId', name: 'supplierName' });
|
|
98
|
-
|
|
99
|
-
printRelation('Suppliers with renamed attributes', renamedSuppliers);
|
|
100
|
-
|
|
101
|
-
// =============================================================================
|
|
102
|
-
// Example 2: Extending Relations - extend, constants, prefix
|
|
103
|
-
// =============================================================================
|
|
104
|
-
|
|
105
|
-
console.log('\n*** EXAMPLE 2: Extending Relations ***');
|
|
106
|
-
|
|
107
|
-
// Add computed attributes
|
|
108
|
-
// Type: Relation<Part & { weightInKg: number; description: string }>
|
|
109
|
-
const partsWithMetrics = parts
|
|
110
|
-
.extend({
|
|
111
|
-
weightInKg: (t: Part) => t.weight / 1000,
|
|
112
|
-
description: (t: Part) => `${t.color} ${t.pname}`,
|
|
113
|
-
})
|
|
114
|
-
.project(['pid', 'description', 'weight', 'weightInKg']);
|
|
115
|
-
|
|
116
|
-
printRelation('Parts with computed metrics', partsWithMetrics);
|
|
117
|
-
|
|
118
|
-
// Add constant values
|
|
119
|
-
const taggedParts = parts
|
|
120
|
-
.restrict({ color: 'Red' })
|
|
121
|
-
.constants({ category: 'Primary', inStock: true })
|
|
122
|
-
.project(['pid', 'pname', 'category', 'inStock']);
|
|
123
|
-
|
|
124
|
-
printRelation('Red parts with constant tags', taggedParts);
|
|
125
|
-
|
|
126
|
-
// =============================================================================
|
|
127
|
-
// Example 3: Set Operations - union, minus, intersect
|
|
128
|
-
// =============================================================================
|
|
129
|
-
|
|
130
|
-
console.log('\n*** EXAMPLE 3: Set Operations ***');
|
|
131
|
-
|
|
132
|
-
const partCities = parts.project(['city']);
|
|
133
|
-
|
|
134
|
-
// Cities where we have either suppliers or parts
|
|
135
|
-
const allCities = suppliers.project(['city']).union(partCities);
|
|
136
|
-
printRelation('All cities (suppliers OR parts)', allCities);
|
|
137
|
-
|
|
138
|
-
// Cities where we have both suppliers and parts
|
|
139
|
-
const commonCities = suppliers.project(['city']).intersect(partCities);
|
|
140
|
-
printRelation('Common cities (suppliers AND parts)', commonCities);
|
|
141
|
-
|
|
142
|
-
// Cities with parts but no suppliers
|
|
143
|
-
const partOnlyCities = partCities.minus(suppliers.project(['city']));
|
|
144
|
-
printRelation('Cities with parts but no suppliers', partOnlyCities);
|
|
145
|
-
|
|
146
|
-
// =============================================================================
|
|
147
|
-
// Example 4: Join Operations - join, left_join, matching
|
|
148
|
-
// =============================================================================
|
|
149
|
-
|
|
150
|
-
console.log('\n*** EXAMPLE 4: Join Operations ***');
|
|
151
|
-
|
|
152
|
-
// Natural join: suppliers and parts in the same city
|
|
153
|
-
const supplierPartsInSameCity = suppliers
|
|
154
|
-
.project(['sid', 'name', 'city'])
|
|
155
|
-
.join(parts.project(['pid', 'pname', 'city']));
|
|
156
|
-
|
|
157
|
-
printRelation('Suppliers and parts in the same city', supplierPartsInSameCity);
|
|
158
|
-
|
|
159
|
-
// Full shipment details with supplier and part info
|
|
160
|
-
const fullShipments = shipments
|
|
161
|
-
.join(suppliers.project(['sid', 'name']), ['sid'])
|
|
162
|
-
.join(parts.project(['pid', 'pname', 'color']), ['pid'])
|
|
163
|
-
.project(['name', 'pname', 'color', 'qty']);
|
|
164
|
-
|
|
165
|
-
printRelation('Full shipment details', fullShipments);
|
|
166
|
-
|
|
167
|
-
// Left join: all suppliers with their shipments (if any)
|
|
168
|
-
const suppliersWithShipments = suppliers
|
|
169
|
-
.project(['sid', 'name'])
|
|
170
|
-
.left_join(
|
|
171
|
-
shipments.summarize(['sid'], { totalQty: { op: 'sum', attr: 'qty' } }),
|
|
172
|
-
['sid']
|
|
173
|
-
);
|
|
174
|
-
|
|
175
|
-
printRelation('All suppliers with total shipped qty', suppliersWithShipments);
|
|
176
|
-
|
|
177
|
-
// Matching: suppliers who have shipped something
|
|
178
|
-
const activeSuppliers = suppliers.matching(shipments, ['sid']);
|
|
179
|
-
printRelation('Suppliers who have made shipments', activeSuppliers);
|
|
180
|
-
|
|
181
|
-
// Not matching: suppliers who haven't shipped anything
|
|
182
|
-
const inactiveSuppliers = suppliers.not_matching(shipments, ['sid']);
|
|
183
|
-
printRelation('Suppliers with no shipments', inactiveSuppliers);
|
|
184
|
-
|
|
185
|
-
// =============================================================================
|
|
186
|
-
// Example 5: Aggregation - summarize
|
|
187
|
-
// =============================================================================
|
|
188
|
-
|
|
189
|
-
console.log('\n*** EXAMPLE 5: Aggregation ***');
|
|
190
|
-
|
|
191
|
-
// Count suppliers per city
|
|
192
|
-
const suppliersPerCity = suppliers.summarize(['city'], {
|
|
193
|
-
supplierCount: 'count',
|
|
194
|
-
avgStatus: { op: 'avg', attr: 'status' },
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
printRelation('Suppliers per city with average status', suppliersPerCity);
|
|
198
|
-
|
|
199
|
-
// Shipment statistics per supplier
|
|
200
|
-
const shipmentStats = shipments
|
|
201
|
-
.join(suppliers.project(['sid', 'name']), ['sid'])
|
|
202
|
-
.summarize(['name'], {
|
|
203
|
-
shipmentCount: 'count',
|
|
204
|
-
totalQty: { op: 'sum', attr: 'qty' },
|
|
205
|
-
avgQty: { op: 'avg', attr: 'qty' },
|
|
206
|
-
minQty: { op: 'min', attr: 'qty' },
|
|
207
|
-
maxQty: { op: 'max', attr: 'qty' },
|
|
208
|
-
});
|
|
209
|
-
|
|
210
|
-
printRelation('Shipment statistics per supplier', shipmentStats);
|
|
211
|
-
|
|
212
|
-
// Grand total
|
|
213
|
-
const grandTotal = shipments.summarize([], {
|
|
214
|
-
totalShipments: 'count',
|
|
215
|
-
totalQuantity: { op: 'sum', attr: 'qty' },
|
|
216
|
-
});
|
|
217
|
-
|
|
218
|
-
printRelation('Grand total', grandTotal);
|
|
219
|
-
|
|
220
|
-
// =============================================================================
|
|
221
|
-
// Example 6: Grouping and Nesting - group, image, wrap
|
|
222
|
-
// =============================================================================
|
|
223
|
-
|
|
224
|
-
console.log('\n*** EXAMPLE 6: Grouping and Nesting ***');
|
|
225
|
-
|
|
226
|
-
// Group parts: nest pid and pname into a 'items' relation, grouped by color
|
|
227
|
-
const partsByColor = parts
|
|
228
|
-
.project(['color', 'pid', 'pname'])
|
|
229
|
-
.group(['pid', 'pname'], 'items');
|
|
230
|
-
|
|
231
|
-
printRelation('Parts grouped by color (nested items)', partsByColor);
|
|
232
|
-
|
|
233
|
-
// Show expanded view of one group
|
|
234
|
-
const redParts = partsByColor.restrict({ color: 'Red' }).one();
|
|
235
|
-
console.log('Red parts items:', redParts.items.toArray());
|
|
236
|
-
|
|
237
|
-
// Image: for each supplier, get their shipped parts
|
|
238
|
-
const supplierParts = suppliers
|
|
239
|
-
.project(['sid', 'name'])
|
|
240
|
-
.image(shipments.project(['sid', 'pid', 'qty']), 'shipments', ['sid']);
|
|
241
|
-
|
|
242
|
-
printRelation('Each supplier with their shipments', supplierParts);
|
|
243
|
-
|
|
244
|
-
// Wrap: combine part attributes into a nested object
|
|
245
|
-
const wrappedParts = parts.wrap(['color', 'weight'], 'specs');
|
|
246
|
-
|
|
247
|
-
printRelation('Parts with wrapped specs', wrappedParts);
|
|
248
|
-
|
|
249
|
-
// =============================================================================
|
|
250
|
-
// Example 7: Complex Chained Query
|
|
251
|
-
// =============================================================================
|
|
252
|
-
|
|
253
|
-
console.log('\n*** EXAMPLE 7: Complex Chained Query ***');
|
|
254
|
-
|
|
255
|
-
// Find supplier quantities by part color
|
|
256
|
-
const supplierQtyByColor = shipments
|
|
257
|
-
.join(parts.project(['pid', 'color']), ['pid'])
|
|
258
|
-
.join(suppliers.project(['sid', 'name']), ['sid'])
|
|
259
|
-
.summarize(['color', 'name'], {
|
|
260
|
-
totalQty: { op: 'sum', attr: 'qty' },
|
|
261
|
-
})
|
|
262
|
-
.project(['color', 'name', 'totalQty']);
|
|
263
|
-
|
|
264
|
-
printRelation('Supplier quantities by part color', supplierQtyByColor);
|
|
265
|
-
|
|
266
|
-
// =============================================================================
|
|
267
|
-
// Example 8: Data Transformation - transform, allbut
|
|
268
|
-
// =============================================================================
|
|
269
|
-
|
|
270
|
-
console.log('\n*** EXAMPLE 8: Data Transformation ***');
|
|
271
|
-
|
|
272
|
-
// Transform all string values to uppercase
|
|
273
|
-
const uppercasedSuppliers = suppliers.transform({
|
|
274
|
-
name: (v: unknown) => (v as string).toUpperCase(),
|
|
275
|
-
city: (v: unknown) => (v as string).toUpperCase(),
|
|
276
|
-
});
|
|
277
|
-
|
|
278
|
-
printRelation('Suppliers with uppercase names and cities', uppercasedSuppliers);
|
|
279
|
-
|
|
280
|
-
// Allbut: select all attributes except some
|
|
281
|
-
// Type: Relation<{ pid: string; pname: string; color: string; weight: number }>
|
|
282
|
-
const partsWithoutLocation = parts.allbut(['city']);
|
|
283
|
-
|
|
284
|
-
printRelation('Parts without city attribute', partsWithoutLocation);
|
|
285
|
-
|
|
286
|
-
// =============================================================================
|
|
287
|
-
// Example 9: Using .one() to extract a single tuple
|
|
288
|
-
// =============================================================================
|
|
289
|
-
|
|
290
|
-
console.log('\n*** EXAMPLE 9: Extracting Single Tuples ***');
|
|
291
|
-
|
|
292
|
-
// Get a specific supplier - returns typed Supplier
|
|
293
|
-
const smith: Supplier = suppliers.restrict({ sid: 'S1' }).one();
|
|
294
|
-
console.log('\nSupplier S1 (Smith):', smith);
|
|
295
|
-
|
|
296
|
-
// Get the total quantity shipped
|
|
297
|
-
const total = shipments.summarize([], { total: { op: 'sum', attr: 'qty' } }).one();
|
|
298
|
-
console.log('Total quantity shipped:', total.total);
|
|
299
|
-
|
|
300
|
-
// =============================================================================
|
|
301
|
-
// Example 10: Checking Relation Properties
|
|
302
|
-
// =============================================================================
|
|
303
|
-
|
|
304
|
-
console.log('\n*** EXAMPLE 10: Relation Properties ***');
|
|
305
|
-
|
|
306
|
-
// Check if two relations are equal
|
|
307
|
-
const parisSuppliers1 = suppliers.restrict({ city: 'Paris' });
|
|
308
|
-
const parisSuppliers2 = suppliers.restrict((t: Supplier) => t.city === 'Paris');
|
|
309
|
-
|
|
310
|
-
console.log('\nAre the two Paris supplier queries equal?', parisSuppliers1.isEqual(parisSuppliers2));
|
|
311
|
-
|
|
312
|
-
// Check if something is a relation
|
|
313
|
-
console.log('Is suppliers a relation?', Bmg.isRelation(suppliers));
|
|
314
|
-
console.log('Is an array a relation?', Bmg.isRelation([{ a: 1 }]));
|
|
315
|
-
|
|
316
|
-
console.log('\n*** ALL EXAMPLES COMPLETED ***\n');
|
package/example/package.json
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "bmg-example",
|
|
3
|
-
"type": "module",
|
|
4
|
-
"version": "1.0.0",
|
|
5
|
-
"description": "Examples demonstrating Bmg.js relational algebra operations",
|
|
6
|
-
"scripts": {
|
|
7
|
-
"start": "tsx index.ts"
|
|
8
|
-
},
|
|
9
|
-
"dependencies": {
|
|
10
|
-
"@enspirit/bmg-js": "file:.."
|
|
11
|
-
},
|
|
12
|
-
"devDependencies": {
|
|
13
|
-
"tsx": "^4.7.0",
|
|
14
|
-
"typescript": "^5.3.0"
|
|
15
|
-
}
|
|
16
|
-
}
|
package/example/tsconfig.json
DELETED
package/src/utility-types.ts
DELETED
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Utility types for type transformations in relational operators.
|
|
3
|
-
* These enable compile-time tracking of how operators transform tuple types.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import type { Tuple } from './types';
|
|
7
|
-
|
|
8
|
-
// ============================================================================
|
|
9
|
-
// Rename Types
|
|
10
|
-
// ============================================================================
|
|
11
|
-
|
|
12
|
-
/** Map from old attribute names to new attribute names */
|
|
13
|
-
export type RenameMap<T> = { [K in keyof T]?: string };
|
|
14
|
-
|
|
15
|
-
/** Transform tuple type by renaming keys according to RenameMap */
|
|
16
|
-
export type Renamed<T, R extends RenameMap<T>> = {
|
|
17
|
-
[K in keyof T as K extends keyof R ? (R[K] extends string ? R[K] : K) : K]: T[K];
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
// ============================================================================
|
|
21
|
-
// Prefix/Suffix Types
|
|
22
|
-
// ============================================================================
|
|
23
|
-
|
|
24
|
-
/** Prefix all keys except those in Except */
|
|
25
|
-
export type Prefixed<T, P extends string, Except extends keyof T = never> = {
|
|
26
|
-
[K in keyof T as K extends Except ? K : `${P}${K & string}`]: T[K];
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
/** Suffix all keys except those in Except */
|
|
30
|
-
export type Suffixed<T, S extends string, Except extends keyof T = never> = {
|
|
31
|
-
[K in keyof T as K extends Except ? K : `${K & string}${S}`]: T[K];
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
// ============================================================================
|
|
35
|
-
// Join Types
|
|
36
|
-
// ============================================================================
|
|
37
|
-
|
|
38
|
-
/** Extract common keys between two tuple types */
|
|
39
|
-
export type CommonKeys<L, R> = Extract<keyof L, keyof R>;
|
|
40
|
-
|
|
41
|
-
/** Result of inner join: L & R with R's common keys removed */
|
|
42
|
-
export type Joined<L, R> = L & Omit<R, CommonKeys<L, R>>;
|
|
43
|
-
|
|
44
|
-
/** Result of left join: L & optional R attributes (common keys removed) */
|
|
45
|
-
export type LeftJoined<L, R> = L & Partial<Omit<R, CommonKeys<L, R>>>;
|
|
46
|
-
|
|
47
|
-
// ============================================================================
|
|
48
|
-
// Wrap/Unwrap Types
|
|
49
|
-
// ============================================================================
|
|
50
|
-
|
|
51
|
-
/** Result of wrap: remove wrapped attrs, add nested object */
|
|
52
|
-
export type Wrapped<T, K extends keyof T, As extends string> =
|
|
53
|
-
Omit<T, K> & Record<As, Pick<T, K>>;
|
|
54
|
-
|
|
55
|
-
/** Result of unwrap: remove object attr, spread its properties */
|
|
56
|
-
export type Unwrapped<T, K extends keyof T> =
|
|
57
|
-
T[K] extends Record<string, unknown> ? Omit<T, K> & T[K] : Omit<T, K>;
|
|
58
|
-
|
|
59
|
-
// Note: Ungrouped is defined in types.ts since it references Relation
|
|
60
|
-
|
|
61
|
-
// ============================================================================
|
|
62
|
-
// Aggregator Result Types
|
|
63
|
-
// ============================================================================
|
|
64
|
-
|
|
65
|
-
/** Infer result type from aggregator specification */
|
|
66
|
-
export type AggregatorResult<A> =
|
|
67
|
-
A extends 'count' ? number :
|
|
68
|
-
A extends { op: 'count' } ? number :
|
|
69
|
-
A extends { op: 'sum' | 'avg' | 'min' | 'max' } ? number | null :
|
|
70
|
-
A extends { op: 'collect' } ? unknown[] :
|
|
71
|
-
A extends (tuples: Tuple[]) => infer R ? R :
|
|
72
|
-
unknown;
|
|
73
|
-
|
|
74
|
-
/** Map aggregator definitions to their result types */
|
|
75
|
-
export type AggregatorResults<Aggs extends Record<string, unknown>> = {
|
|
76
|
-
[K in keyof Aggs]: AggregatorResult<Aggs[K]>;
|
|
77
|
-
};
|
package/tests/bmg.test.ts
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import { Bmg } from 'src';
|
|
3
|
-
|
|
4
|
-
describe('BMG.js', () => {
|
|
5
|
-
it('exists', () => {
|
|
6
|
-
expect(Bmg).not.toBe(undefined);
|
|
7
|
-
});
|
|
8
|
-
|
|
9
|
-
it('allows building relations', () => {
|
|
10
|
-
const r = Bmg([
|
|
11
|
-
{sid: 'S1', name: 'Smith'},
|
|
12
|
-
{sid: 'S2', name: 'Jones'},
|
|
13
|
-
])
|
|
14
|
-
expect(Bmg.isRelation(r)).toBeTruthy()
|
|
15
|
-
})
|
|
16
|
-
});
|
package/tests/fixtures.ts
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { Bmg } from 'src'
|
|
2
|
-
|
|
3
|
-
export const SUPPLIERS = Bmg([
|
|
4
|
-
{sid: 'S1', name: 'Smith', status: 20, city: 'London' },
|
|
5
|
-
{sid: 'S2', name: 'Jones', status: 10, city: 'Paris' },
|
|
6
|
-
{sid: 'S3', name: 'Blake', status: 30, city: 'Paris' },
|
|
7
|
-
{sid: 'S4', name: 'Clark', status: 20, city: 'London' },
|
|
8
|
-
{sid: 'S5', name: 'Adams', status: 30, city: 'Athens' },
|
|
9
|
-
])
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import { Bmg } from 'src';
|
|
3
|
-
import { SUPPLIERS } from 'tests/fixtures';
|
|
4
|
-
import { allbut, isEqual } from 'src/operators';
|
|
5
|
-
|
|
6
|
-
describe('.allbut', () => {
|
|
7
|
-
|
|
8
|
-
it('removes specified attributes', () => {
|
|
9
|
-
const result = SUPPLIERS.allbut(['status', 'city']);
|
|
10
|
-
const expected = Bmg([
|
|
11
|
-
{ sid: 'S1', name: 'Smith' },
|
|
12
|
-
{ sid: 'S2', name: 'Jones' },
|
|
13
|
-
{ sid: 'S3', name: 'Blake' },
|
|
14
|
-
{ sid: 'S4', name: 'Clark' },
|
|
15
|
-
{ sid: 'S5', name: 'Adams' },
|
|
16
|
-
]);
|
|
17
|
-
expect(result.isEqual(expected)).to.be.true;
|
|
18
|
-
})
|
|
19
|
-
|
|
20
|
-
it('removes duplicates (set semantics)', () => {
|
|
21
|
-
// Keeping only status should yield 3 unique values, not 5 tuples
|
|
22
|
-
const result = SUPPLIERS.allbut(['sid', 'name', 'city']);
|
|
23
|
-
const expected = Bmg([
|
|
24
|
-
{ status: 20 },
|
|
25
|
-
{ status: 10 },
|
|
26
|
-
{ status: 30 },
|
|
27
|
-
]);
|
|
28
|
-
expect(result.isEqual(expected)).to.be.true;
|
|
29
|
-
})
|
|
30
|
-
|
|
31
|
-
it('keeps all attributes when excluding none', () => {
|
|
32
|
-
const result = SUPPLIERS.allbut([]);
|
|
33
|
-
expect(result.isEqual(SUPPLIERS)).to.be.true;
|
|
34
|
-
})
|
|
35
|
-
|
|
36
|
-
it('ignores non-existent attributes', () => {
|
|
37
|
-
// @ts-expect-error - testing runtime behavior with invalid attribute
|
|
38
|
-
const result = SUPPLIERS.allbut(['nonexistent']);
|
|
39
|
-
expect(result.isEqual(SUPPLIERS)).to.be.true;
|
|
40
|
-
})
|
|
41
|
-
|
|
42
|
-
///
|
|
43
|
-
|
|
44
|
-
it('can be used standalone', () => {
|
|
45
|
-
const input = SUPPLIERS.toArray();
|
|
46
|
-
const res = allbut(input, ['status', 'city']);
|
|
47
|
-
const expected = SUPPLIERS.allbut(['status', 'city']);
|
|
48
|
-
expect(isEqual(res, expected)).to.be.true;
|
|
49
|
-
})
|
|
50
|
-
|
|
51
|
-
});
|
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import { Bmg } from 'src';
|
|
3
|
-
import { autowrap , isEqual } from 'src/operators';
|
|
4
|
-
|
|
5
|
-
describe('.autowrap', () => {
|
|
6
|
-
|
|
7
|
-
it('wraps attributes based on underscore separator by default', () => {
|
|
8
|
-
const flat = Bmg([
|
|
9
|
-
{ id: 1, address_street: '123 Main', address_city: 'NYC' },
|
|
10
|
-
]);
|
|
11
|
-
const result = flat.autowrap();
|
|
12
|
-
const tuple = result.one();
|
|
13
|
-
expect(tuple.id).to.eql(1);
|
|
14
|
-
expect(tuple.address).to.eql({ street: '123 Main', city: 'NYC' });
|
|
15
|
-
})
|
|
16
|
-
|
|
17
|
-
it('allows custom separator', () => {
|
|
18
|
-
const flat = Bmg([
|
|
19
|
-
{ id: 1, 'address.street': '123 Main', 'address.city': 'NYC' },
|
|
20
|
-
]);
|
|
21
|
-
const result = flat.autowrap({ separator: '.' });
|
|
22
|
-
const tuple = result.one();
|
|
23
|
-
expect(tuple.id).to.eql(1);
|
|
24
|
-
expect(tuple.address).to.eql({ street: '123 Main', city: 'NYC' });
|
|
25
|
-
})
|
|
26
|
-
|
|
27
|
-
it('handles attributes without separator', () => {
|
|
28
|
-
const flat = Bmg([
|
|
29
|
-
{ id: 1, name: 'John' },
|
|
30
|
-
]);
|
|
31
|
-
const result = flat.autowrap();
|
|
32
|
-
const tuple = result.one();
|
|
33
|
-
expect(tuple).to.eql({ id: 1, name: 'John' });
|
|
34
|
-
})
|
|
35
|
-
|
|
36
|
-
it('handles multiple prefixes', () => {
|
|
37
|
-
const flat = Bmg([
|
|
38
|
-
{ id: 1, home_city: 'NYC', work_city: 'Boston' },
|
|
39
|
-
]);
|
|
40
|
-
const result = flat.autowrap();
|
|
41
|
-
const tuple = result.one();
|
|
42
|
-
expect(tuple.id).to.eql(1);
|
|
43
|
-
expect(tuple.home).to.eql({ city: 'NYC' });
|
|
44
|
-
expect(tuple.work).to.eql({ city: 'Boston' });
|
|
45
|
-
})
|
|
46
|
-
|
|
47
|
-
it('handles nested separators (one level only)', () => {
|
|
48
|
-
const flat = Bmg([
|
|
49
|
-
{ id: 1, address_home_city: 'NYC' },
|
|
50
|
-
]);
|
|
51
|
-
const result = flat.autowrap();
|
|
52
|
-
const tuple = result.one();
|
|
53
|
-
expect(tuple.id).to.eql(1);
|
|
54
|
-
// Only first level split: address -> { home_city: 'NYC' }
|
|
55
|
-
expect(tuple.address).to.eql({ home_city: 'NYC' });
|
|
56
|
-
})
|
|
57
|
-
|
|
58
|
-
it('autowraps multiple tuples correctly', () => {
|
|
59
|
-
const flat = Bmg([
|
|
60
|
-
{ id: 1, address_street: '123 Main', address_city: 'NYC' },
|
|
61
|
-
{ id: 2, address_street: '456 Oak', address_city: 'Boston' },
|
|
62
|
-
{ id: 3, address_street: '789 Pine', address_city: 'Chicago' },
|
|
63
|
-
]);
|
|
64
|
-
const result = flat.autowrap();
|
|
65
|
-
const expected = Bmg([
|
|
66
|
-
{ id: 1, address: { street: '123 Main', city: 'NYC' } },
|
|
67
|
-
{ id: 2, address: { street: '456 Oak', city: 'Boston' } },
|
|
68
|
-
{ id: 3, address: { street: '789 Pine', city: 'Chicago' } },
|
|
69
|
-
]);
|
|
70
|
-
expect(result.isEqual(expected)).to.be.true;
|
|
71
|
-
})
|
|
72
|
-
|
|
73
|
-
///
|
|
74
|
-
|
|
75
|
-
it('can be used standalone', () => {
|
|
76
|
-
const input = Bmg([{ id: 1, addr_city: 'NYC' }]);
|
|
77
|
-
const res = autowrap(input.toArray());
|
|
78
|
-
const expected = input.autowrap();
|
|
79
|
-
expect(isEqual(res, expected)).to.be.true;
|
|
80
|
-
})
|
|
81
|
-
|
|
82
|
-
});
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
|
|
3
|
-
import { SUPPLIERS } from 'tests/fixtures';
|
|
4
|
-
import { constants , isEqual } from 'src/operators';
|
|
5
|
-
|
|
6
|
-
describe('.constants', () => {
|
|
7
|
-
|
|
8
|
-
it('adds constant attributes to all tuples', () => {
|
|
9
|
-
const withType = SUPPLIERS.constants({type: 'supplier'});
|
|
10
|
-
const smith = withType.restrict({sid: 'S1'}).one();
|
|
11
|
-
expect(smith.type).to.eql('supplier');
|
|
12
|
-
expect(smith.name).to.eql('Smith');
|
|
13
|
-
})
|
|
14
|
-
|
|
15
|
-
it('can add multiple constants', () => {
|
|
16
|
-
const result = SUPPLIERS.constants({type: 'supplier', active: true});
|
|
17
|
-
const jones = result.restrict({sid: 'S2'}).one();
|
|
18
|
-
expect(jones.type).to.eql('supplier');
|
|
19
|
-
expect(jones.active).to.eql(true);
|
|
20
|
-
})
|
|
21
|
-
|
|
22
|
-
it('overwrites existing attributes', () => {
|
|
23
|
-
const result = SUPPLIERS.constants({city: 'Unknown'});
|
|
24
|
-
const smith = result.restrict({sid: 'S1'}).one();
|
|
25
|
-
expect(smith.city).to.eql('Unknown');
|
|
26
|
-
})
|
|
27
|
-
|
|
28
|
-
///
|
|
29
|
-
|
|
30
|
-
it('can be used standalone', () => {
|
|
31
|
-
const input = SUPPLIERS.toArray();
|
|
32
|
-
const res = constants(input, { type: 'supplier' });
|
|
33
|
-
const expected = SUPPLIERS.constants({ type: 'supplier' });
|
|
34
|
-
expect(isEqual(res, expected)).to.be.true;
|
|
35
|
-
})
|
|
36
|
-
|
|
37
|
-
});
|