@enspirit/bmg-js 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. package/.claude/safe-setup/.env.example +3 -0
  2. package/.claude/safe-setup/Dockerfile.claude +36 -0
  3. package/.claude/safe-setup/HACKING.md +63 -0
  4. package/.claude/safe-setup/Makefile +22 -0
  5. package/.claude/safe-setup/docker-compose.yml +18 -0
  6. package/.claude/safe-setup/entrypoint.sh +13 -0
  7. package/.claude/settings.local.json +9 -0
  8. package/.claude/typescript-annotations.md +273 -0
  9. package/.github/workflows/test.yml +26 -0
  10. package/CLAUDE.md +48 -0
  11. package/Makefile +2 -0
  12. package/README.md +170 -0
  13. package/example/README.md +22 -0
  14. package/example/index.ts +316 -0
  15. package/example/package.json +16 -0
  16. package/example/tsconfig.json +11 -0
  17. package/package.json +34 -0
  18. package/src/Relation/Memory.ts +213 -0
  19. package/src/Relation/index.ts +1 -0
  20. package/src/index.ts +31 -0
  21. package/src/operators/_helpers.ts +240 -0
  22. package/src/operators/allbut.ts +19 -0
  23. package/src/operators/autowrap.ts +26 -0
  24. package/src/operators/constants.ts +12 -0
  25. package/src/operators/cross_product.ts +20 -0
  26. package/src/operators/exclude.ts +14 -0
  27. package/src/operators/extend.ts +20 -0
  28. package/src/operators/group.ts +53 -0
  29. package/src/operators/image.ts +27 -0
  30. package/src/operators/index.ts +31 -0
  31. package/src/operators/intersect.ts +24 -0
  32. package/src/operators/isEqual.ts +29 -0
  33. package/src/operators/isRelation.ts +5 -0
  34. package/src/operators/join.ts +25 -0
  35. package/src/operators/left_join.ts +41 -0
  36. package/src/operators/matching.ts +24 -0
  37. package/src/operators/minus.ts +24 -0
  38. package/src/operators/not_matching.ts +24 -0
  39. package/src/operators/one.ts +17 -0
  40. package/src/operators/prefix.ts +7 -0
  41. package/src/operators/project.ts +18 -0
  42. package/src/operators/rename.ts +17 -0
  43. package/src/operators/restrict.ts +14 -0
  44. package/src/operators/suffix.ts +7 -0
  45. package/src/operators/summarize.ts +85 -0
  46. package/src/operators/transform.ts +40 -0
  47. package/src/operators/ungroup.ts +41 -0
  48. package/src/operators/union.ts +27 -0
  49. package/src/operators/unwrap.ts +29 -0
  50. package/src/operators/where.ts +1 -0
  51. package/src/operators/wrap.ts +29 -0
  52. package/src/operators/yByX.ts +12 -0
  53. package/src/support/toPredicateFunc.ts +12 -0
  54. package/src/types.ts +178 -0
  55. package/src/utility-types.ts +77 -0
  56. package/tests/bmg.test.ts +16 -0
  57. package/tests/fixtures.ts +9 -0
  58. package/tests/operators/allbut.test.ts +51 -0
  59. package/tests/operators/autowrap.test.ts +82 -0
  60. package/tests/operators/constants.test.ts +37 -0
  61. package/tests/operators/cross_product.test.ts +90 -0
  62. package/tests/operators/exclude.test.ts +43 -0
  63. package/tests/operators/extend.test.ts +45 -0
  64. package/tests/operators/group.test.ts +69 -0
  65. package/tests/operators/image.test.ts +152 -0
  66. package/tests/operators/intersect.test.ts +53 -0
  67. package/tests/operators/isEqual.test.ts +111 -0
  68. package/tests/operators/join.test.ts +116 -0
  69. package/tests/operators/left_join.test.ts +116 -0
  70. package/tests/operators/matching.test.ts +91 -0
  71. package/tests/operators/minus.test.ts +47 -0
  72. package/tests/operators/not_matching.test.ts +104 -0
  73. package/tests/operators/one.test.ts +19 -0
  74. package/tests/operators/prefix.test.ts +37 -0
  75. package/tests/operators/project.test.ts +48 -0
  76. package/tests/operators/rename.test.ts +39 -0
  77. package/tests/operators/restrict.test.ts +27 -0
  78. package/tests/operators/suffix.test.ts +37 -0
  79. package/tests/operators/summarize.test.ts +109 -0
  80. package/tests/operators/transform.test.ts +94 -0
  81. package/tests/operators/ungroup.test.ts +67 -0
  82. package/tests/operators/union.test.ts +51 -0
  83. package/tests/operators/unwrap.test.ts +50 -0
  84. package/tests/operators/where.test.ts +33 -0
  85. package/tests/operators/wrap.test.ts +54 -0
  86. package/tests/operators/yByX.test.ts +32 -0
  87. package/tests/types/relation.test.ts +296 -0
  88. package/tsconfig.json +37 -0
  89. package/tsconfig.node.json +9 -0
  90. package/vitest.config.ts +15 -0
@@ -0,0 +1,22 @@
1
+ # Bmg.js Example
2
+
3
+ Demonstrates relational algebra operations using the classic suppliers/parts/shipments dataset with full TypeScript type safety.
4
+
5
+ ## Running the example
6
+
7
+ ```bash
8
+ npm install
9
+ npm start
10
+ ```
11
+
12
+ ## What's covered
13
+
14
+ - Basic operations: `restrict`, `project`, `rename`
15
+ - Extending relations: `extend`, `constants`
16
+ - Set operations: `union`, `minus`, `intersect`
17
+ - Join operations: `join`, `left_join`, `matching`, `not_matching`
18
+ - Aggregation: `summarize` with `count`, `sum`, `avg`, `min`, `max`
19
+ - Grouping and nesting: `group`, `image`, `wrap`
20
+ - Data transformation: `transform`, `allbut`
21
+ - Extracting tuples: `one()`
22
+ - Relation properties: `isEqual`, `Bmg.isRelation`
@@ -0,0 +1,316 @@
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');
@@ -0,0 +1,16 @@
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
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "strict": true,
7
+ "esModuleInterop": true,
8
+ "skipLibCheck": true
9
+ },
10
+ "include": ["*.ts"]
11
+ }
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "@enspirit/bmg-js",
3
+ "version": "1.0.0",
4
+ "description": "Relational algebra for JavaScript/TypeScript - work with arrays of objects using powerful set operations",
5
+ "keywords": ["relational", "algebra", "relation", "set", "query", "data", "functional"],
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/enspirit/bmg.js"
10
+ },
11
+ "type": "module",
12
+ "source": "src/index.ts",
13
+ "exports": {
14
+ "types": "./dist/src/index.d.ts",
15
+ "require": "./dist/bmg.cjs",
16
+ "default": "./dist/bmg.modern.js"
17
+ },
18
+ "main": "./dist/bmg.cjs",
19
+ "module": "./dist/bmg.module.js",
20
+ "types": "./dist/src/index.d.ts",
21
+ "unpkg": "./dist/bmg.umd.js",
22
+ "scripts": {
23
+ "build": "microbundle",
24
+ "dev": "microbundle watch",
25
+ "test": "vitest run",
26
+ "test:watch": "vitest"
27
+ },
28
+ "devDependencies": {
29
+ "microbundle": "^0.15.1",
30
+ "vite-tsconfig-paths": "^5.1.4",
31
+ "vitest": "^1.4.0"
32
+ },
33
+ "packageManager": "pnpm@8.11.0+sha1.01bf39424f1008f29012bbae851d2acb8a20a0d0"
34
+ }
@@ -0,0 +1,213 @@
1
+ import {
2
+ allbut,
3
+ autowrap,
4
+ constants,
5
+ cross_product,
6
+ exclude,
7
+ extend,
8
+ group,
9
+ image,
10
+ intersect,
11
+ isEqual,
12
+ join,
13
+ left_join,
14
+ matching,
15
+ minus,
16
+ not_matching,
17
+ one,
18
+ prefix,
19
+ project,
20
+ rename,
21
+ restrict,
22
+ suffix,
23
+ summarize,
24
+ where,
25
+ transform,
26
+ ungroup,
27
+ union,
28
+ unwrap,
29
+ wrap,
30
+ yByX,
31
+ } from "../operators";
32
+ import type {
33
+ AttrName,
34
+ AutowrapOptions,
35
+ JoinKeys,
36
+ Relation,
37
+ RelationOperand,
38
+ Transformation,
39
+ Tuple,
40
+ TypedPredicate,
41
+ TypedExtension,
42
+ RenameMap,
43
+ Renamed,
44
+ Prefixed,
45
+ Suffixed,
46
+ Joined,
47
+ LeftJoined,
48
+ Wrapped,
49
+ Unwrapped,
50
+ Ungrouped,
51
+ } from "../types";
52
+ import type { AggregatorResults } from "../utility-types";
53
+
54
+ /**
55
+ * In-memory implementation of the Relation interface.
56
+ *
57
+ * @typeParam T - The tuple type for this relation. Defaults to `Tuple` (Record<string, unknown>).
58
+ */
59
+ export class MemoryRelation<T = Tuple> implements Relation<T> {
60
+
61
+ constructor(private tuples: T[]) {
62
+ this.tuples = tuples;
63
+ }
64
+
65
+ // === Type-preserving operators ===
66
+
67
+ restrict(p: TypedPredicate<T>): Relation<T> {
68
+ return restrict(this as any, p as any) as unknown as Relation<T>;
69
+ }
70
+
71
+ where(p: TypedPredicate<T>): Relation<T> {
72
+ return where(this as any, p as any) as unknown as Relation<T>;
73
+ }
74
+
75
+ exclude(p: TypedPredicate<T>): Relation<T> {
76
+ return exclude(this as any, p as any) as unknown as Relation<T>;
77
+ }
78
+
79
+ // === Projection operators ===
80
+
81
+ project<K extends keyof T>(attrs: K[]): Relation<Pick<T, K>> {
82
+ return project(this as any, attrs as AttrName[]) as unknown as Relation<Pick<T, K>>;
83
+ }
84
+
85
+ allbut<K extends keyof T>(attrs: K[]): Relation<Omit<T, K>> {
86
+ return allbut(this as any, attrs as AttrName[]) as unknown as Relation<Omit<T, K>>;
87
+ }
88
+
89
+ // === Extension operators ===
90
+
91
+ extend<E extends Record<string, unknown>>(e: TypedExtension<T, E>): Relation<T & E> {
92
+ return extend(this as any, e as any) as unknown as Relation<T & E>;
93
+ }
94
+
95
+ constants<C extends Tuple>(consts: C): Relation<T & C> {
96
+ return constants(this as any, consts) as unknown as Relation<T & C>;
97
+ }
98
+
99
+ // === Rename operators ===
100
+
101
+ rename<R extends RenameMap<T>>(r: R): Relation<Renamed<T, R>> {
102
+ return rename(this as any, r as any) as unknown as Relation<Renamed<T, R>>;
103
+ }
104
+
105
+ prefix<P extends string, Ex extends keyof T = never>(pfx: P, options?: { except?: Ex[] }): Relation<Prefixed<T, P, Ex>> {
106
+ return prefix(this as any, pfx, options as any) as unknown as Relation<Prefixed<T, P, Ex>>;
107
+ }
108
+
109
+ suffix<S extends string, Ex extends keyof T = never>(sfx: S, options?: { except?: Ex[] }): Relation<Suffixed<T, S, Ex>> {
110
+ return suffix(this as any, sfx, options as any) as unknown as Relation<Suffixed<T, S, Ex>>;
111
+ }
112
+
113
+ // === Set operators ===
114
+
115
+ union(right: RelationOperand<T>): Relation<T> {
116
+ return union(this as any, right as any) as unknown as Relation<T>;
117
+ }
118
+
119
+ minus(right: RelationOperand<T>): Relation<T> {
120
+ return minus(this as any, right as any) as unknown as Relation<T>;
121
+ }
122
+
123
+ intersect(right: RelationOperand<T>): Relation<T> {
124
+ return intersect(this as any, right as any) as unknown as Relation<T>;
125
+ }
126
+
127
+ // === Semi-join operators ===
128
+
129
+ matching<R>(right: RelationOperand<R>, keys?: JoinKeys): Relation<T> {
130
+ return matching(this as any, right as any, keys) as unknown as Relation<T>;
131
+ }
132
+
133
+ not_matching<R>(right: RelationOperand<R>, keys?: JoinKeys): Relation<T> {
134
+ return not_matching(this as any, right as any, keys) as unknown as Relation<T>;
135
+ }
136
+
137
+ // === Join operators ===
138
+
139
+ join<R>(right: RelationOperand<R>, keys?: JoinKeys): Relation<Joined<T, R>> {
140
+ return join(this as any, right as any, keys) as unknown as Relation<Joined<T, R>>;
141
+ }
142
+
143
+ left_join<R>(right: RelationOperand<R>, keys?: JoinKeys): Relation<LeftJoined<T, R>> {
144
+ return left_join(this as any, right as any, keys) as unknown as Relation<LeftJoined<T, R>>;
145
+ }
146
+
147
+ cross_product<R>(right: RelationOperand<R>): Relation<T & R> {
148
+ return cross_product(this as any, right as any) as unknown as Relation<T & R>;
149
+ }
150
+
151
+ cross_join<R>(right: RelationOperand<R>): Relation<T & R> {
152
+ return cross_product(this as any, right as any) as unknown as Relation<T & R>;
153
+ }
154
+
155
+ // === Nesting operators ===
156
+
157
+ image<R, As extends string>(right: RelationOperand<R>, as: As, keys?: JoinKeys): Relation<T & Record<As, Relation<Omit<R, keyof T & keyof R>>>> {
158
+ return image(this as any, right as any, as, keys) as unknown as Relation<T & Record<As, Relation<Omit<R, keyof T & keyof R>>>>;
159
+ }
160
+
161
+ group<K extends keyof T, As extends string>(attrs: K[], as: As): Relation<Omit<T, K> & Record<As, Relation<Pick<T, K>>>> {
162
+ return group(this as any, attrs as AttrName[], as) as unknown as Relation<Omit<T, K> & Record<As, Relation<Pick<T, K>>>>;
163
+ }
164
+
165
+ ungroup<K extends keyof T>(attr: K): Relation<Ungrouped<T, K>> {
166
+ return ungroup(this as any, attr as AttrName) as unknown as Relation<Ungrouped<T, K>>;
167
+ }
168
+
169
+ wrap<K extends keyof T, As extends string>(attrs: K[], as: As): Relation<Wrapped<T, K, As>> {
170
+ return wrap(this as any, attrs as AttrName[], as) as unknown as Relation<Wrapped<T, K, As>>;
171
+ }
172
+
173
+ unwrap<K extends keyof T>(attr: K): Relation<Unwrapped<T, K>> {
174
+ return unwrap(this as any, attr as AttrName) as unknown as Relation<Unwrapped<T, K>>;
175
+ }
176
+
177
+ // === Aggregation ===
178
+
179
+ summarize<By extends keyof T, Aggs extends Record<string, unknown>>(by: By[], aggs: Aggs): Relation<Pick<T, By> & AggregatorResults<Aggs>> {
180
+ return summarize(this as any, by as AttrName[], aggs as any) as unknown as Relation<Pick<T, By> & AggregatorResults<Aggs>>;
181
+ }
182
+
183
+ // === Transform ===
184
+
185
+ transform(t: Transformation): Relation<T> {
186
+ return transform(this as any, t) as unknown as Relation<T>;
187
+ }
188
+
189
+ // === Dynamic ===
190
+
191
+ autowrap(options?: AutowrapOptions): Relation<Tuple> {
192
+ return autowrap(this as any, options) as Relation<Tuple>;
193
+ }
194
+
195
+ // === Non-relational ===
196
+
197
+ one(): T {
198
+ return one(this as any) as T;
199
+ }
200
+
201
+ toArray(): T[] {
202
+ return this.tuples;
203
+ }
204
+
205
+ yByX<Y extends keyof T, X extends keyof T>(y: Y, x: X): Record<T[X] & PropertyKey, T[Y]> {
206
+ return yByX(this as any, y as AttrName, x as AttrName) as Record<T[X] & PropertyKey, T[Y]>;
207
+ }
208
+
209
+ isEqual(right: any): boolean {
210
+ return isEqual(this as any, right as any);
211
+ }
212
+
213
+ }
@@ -0,0 +1 @@
1
+ export * from './Memory';
package/src/index.ts ADDED
@@ -0,0 +1,31 @@
1
+ export * from './operators';
2
+ export * from './types';
3
+ export * from './utility-types';
4
+
5
+ import { MemoryRelation } from './Relation';
6
+ import { isRelation } from './operators';
7
+
8
+ /**
9
+ * Creates a new in-memory relation from an array of tuples.
10
+ *
11
+ * @typeParam T - The tuple type. Inferred from input or explicitly provided.
12
+ *
13
+ * @example
14
+ * // Untyped usage (backwards compatible)
15
+ * const r = Bmg([{ id: 1, name: 'Alice' }]);
16
+ *
17
+ * @example
18
+ * // Typed usage with explicit type parameter
19
+ * interface Person { id: number; name: string }
20
+ * const r = Bmg<Person>([{ id: 1, name: 'Alice' }]);
21
+ * r.project(['id']); // Autocomplete suggests 'id' | 'name'
22
+ *
23
+ * @example
24
+ * // Type is inferred from input
25
+ * const r = Bmg([{ id: 1, name: 'Alice' }] as const);
26
+ */
27
+ export function Bmg<T>(tuples: T[]): MemoryRelation<T> {
28
+ return new MemoryRelation<T>(tuples);
29
+ }
30
+
31
+ Bmg.isRelation = isRelation;