@enspirit/bmg-js 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/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 +1 -0
- package/dist/src/Relation/Memory.d.ts +46 -0
- package/dist/src/Relation/index.d.ts +1 -0
- package/dist/src/Relation.d.ts +8 -0
- package/dist/src/index.d.ts +27 -0
- package/dist/src/operators/_helpers.d.ts +142 -0
- package/dist/src/operators/allbut.d.ts +2 -0
- package/dist/src/operators/autowrap.d.ts +2 -0
- package/dist/src/operators/constants.d.ts +2 -0
- package/dist/src/operators/cross_product.d.ts +3 -0
- package/dist/src/operators/exclude.d.ts +2 -0
- package/dist/src/operators/extend.d.ts +2 -0
- package/dist/src/operators/group.d.ts +2 -0
- package/dist/src/operators/image.d.ts +2 -0
- package/dist/src/operators/index.d.ts +30 -0
- package/dist/src/operators/intersect.d.ts +2 -0
- package/dist/src/operators/isEqual.d.ts +2 -0
- package/dist/src/operators/isRelation.d.ts +1 -0
- package/dist/src/operators/join.d.ts +2 -0
- package/dist/src/operators/left_join.d.ts +2 -0
- package/dist/src/operators/matching.d.ts +2 -0
- package/dist/src/operators/minus.d.ts +2 -0
- package/dist/src/operators/not_matching.d.ts +2 -0
- package/dist/src/operators/one.d.ts +2 -0
- package/dist/src/operators/prefix.d.ts +2 -0
- package/dist/src/operators/project.d.ts +2 -0
- package/dist/src/operators/rename.d.ts +2 -0
- package/dist/src/operators/restrict.d.ts +2 -0
- package/dist/src/operators/suffix.d.ts +2 -0
- package/dist/src/operators/summarize.d.ts +2 -0
- package/dist/src/operators/transform.d.ts +2 -0
- package/dist/src/operators/ungroup.d.ts +2 -0
- package/dist/src/operators/union.d.ts +2 -0
- package/dist/src/operators/unwrap.d.ts +2 -0
- package/dist/src/operators/where.d.ts +1 -0
- package/dist/src/operators/wrap.d.ts +2 -0
- package/dist/src/operators/yByX.d.ts +2 -0
- package/dist/src/src/Relation/Memory.d.ts +46 -0
- package/dist/src/src/Relation/index.d.ts +1 -0
- package/dist/src/src/index.d.ts +27 -0
- package/dist/src/src/operators/_helpers.d.ts +142 -0
- package/dist/src/src/operators/allbut.d.ts +2 -0
- package/dist/src/src/operators/autowrap.d.ts +2 -0
- package/dist/src/src/operators/constants.d.ts +2 -0
- package/dist/src/src/operators/cross_product.d.ts +3 -0
- package/dist/src/src/operators/exclude.d.ts +2 -0
- package/dist/src/src/operators/extend.d.ts +2 -0
- package/dist/src/src/operators/group.d.ts +2 -0
- package/dist/src/src/operators/image.d.ts +2 -0
- package/dist/src/src/operators/index.d.ts +30 -0
- package/dist/src/src/operators/intersect.d.ts +2 -0
- package/dist/src/src/operators/isEqual.d.ts +2 -0
- package/dist/src/src/operators/isRelation.d.ts +1 -0
- package/dist/src/src/operators/join.d.ts +2 -0
- package/dist/src/src/operators/left_join.d.ts +2 -0
- package/dist/src/src/operators/matching.d.ts +2 -0
- package/dist/src/src/operators/minus.d.ts +2 -0
- package/dist/src/src/operators/not_matching.d.ts +2 -0
- package/dist/src/src/operators/one.d.ts +2 -0
- package/dist/src/src/operators/prefix.d.ts +2 -0
- package/dist/src/src/operators/project.d.ts +2 -0
- package/dist/src/src/operators/rename.d.ts +2 -0
- package/dist/src/src/operators/restrict.d.ts +2 -0
- package/dist/src/src/operators/suffix.d.ts +2 -0
- package/dist/src/src/operators/summarize.d.ts +2 -0
- package/dist/src/src/operators/transform.d.ts +2 -0
- package/dist/src/src/operators/ungroup.d.ts +2 -0
- package/dist/src/src/operators/union.d.ts +2 -0
- package/dist/src/src/operators/unwrap.d.ts +2 -0
- package/dist/src/src/operators/where.d.ts +1 -0
- package/dist/src/src/operators/wrap.d.ts +2 -0
- package/dist/src/src/operators/yByX.d.ts +2 -0
- package/dist/src/src/support/toPredicateFunc.d.ts +2 -0
- package/dist/src/src/types.d.ts +101 -0
- package/dist/src/src/utility-types.d.ts +43 -0
- package/dist/src/support/toPredicateFunc.d.ts +2 -0
- package/dist/src/tests/bmg.test.d.ts +1 -0
- package/dist/src/tests/fixtures.d.ts +6 -0
- package/dist/src/tests/operators/allbut.test.d.ts +1 -0
- package/dist/src/tests/operators/autowrap.test.d.ts +1 -0
- package/dist/src/tests/operators/constants.test.d.ts +1 -0
- package/dist/src/tests/operators/cross_product.test.d.ts +1 -0
- package/dist/src/tests/operators/exclude.test.d.ts +1 -0
- package/dist/src/tests/operators/extend.test.d.ts +1 -0
- package/dist/src/tests/operators/group.test.d.ts +1 -0
- package/dist/src/tests/operators/image.test.d.ts +1 -0
- package/dist/src/tests/operators/intersect.test.d.ts +1 -0
- package/dist/src/tests/operators/isEqual.test.d.ts +1 -0
- package/dist/src/tests/operators/join.test.d.ts +1 -0
- package/dist/src/tests/operators/left_join.test.d.ts +1 -0
- package/dist/src/tests/operators/matching.test.d.ts +1 -0
- package/dist/src/tests/operators/minus.test.d.ts +1 -0
- package/dist/src/tests/operators/not_matching.test.d.ts +1 -0
- package/dist/src/tests/operators/one.test.d.ts +1 -0
- package/dist/src/tests/operators/prefix.test.d.ts +1 -0
- package/dist/src/tests/operators/project.test.d.ts +1 -0
- package/dist/src/tests/operators/rename.test.d.ts +1 -0
- package/dist/src/tests/operators/restrict.test.d.ts +1 -0
- package/dist/src/tests/operators/suffix.test.d.ts +1 -0
- package/dist/src/tests/operators/summarize.test.d.ts +1 -0
- package/dist/src/tests/operators/transform.test.d.ts +1 -0
- package/dist/src/tests/operators/ungroup.test.d.ts +1 -0
- package/dist/src/tests/operators/union.test.d.ts +1 -0
- package/dist/src/tests/operators/unwrap.test.d.ts +1 -0
- package/dist/src/tests/operators/where.test.d.ts +1 -0
- package/dist/src/tests/operators/wrap.test.d.ts +1 -0
- package/dist/src/tests/operators/yByX.test.d.ts +1 -0
- package/dist/src/tests/types/relation.test.d.ts +1 -0
- package/dist/src/types.d.ts +101 -0
- package/dist/src/utility-types.d.ts +43 -0
- package/dist/tests/bmg.test.d.ts +1 -0
- package/dist/tests/fixtures.d.ts +6 -0
- package/dist/tests/operators/allbut.test.d.ts +1 -0
- package/dist/tests/operators/autowrap.test.d.ts +1 -0
- package/dist/tests/operators/constants.test.d.ts +1 -0
- package/dist/tests/operators/cross_product.test.d.ts +1 -0
- package/dist/tests/operators/exclude.test.d.ts +1 -0
- package/dist/tests/operators/extend.test.d.ts +1 -0
- package/dist/tests/operators/group.test.d.ts +1 -0
- package/dist/tests/operators/image.test.d.ts +1 -0
- package/dist/tests/operators/intersect.test.d.ts +1 -0
- package/dist/tests/operators/isEqual.test.d.ts +1 -0
- package/dist/tests/operators/join.test.d.ts +1 -0
- package/dist/tests/operators/left_join.test.d.ts +1 -0
- package/dist/tests/operators/matching.test.d.ts +1 -0
- package/dist/tests/operators/minus.test.d.ts +1 -0
- package/dist/tests/operators/not_matching.test.d.ts +1 -0
- package/dist/tests/operators/one.test.d.ts +1 -0
- package/dist/tests/operators/prefix.test.d.ts +1 -0
- package/dist/tests/operators/project.test.d.ts +1 -0
- package/dist/tests/operators/rename.test.d.ts +1 -0
- package/dist/tests/operators/restrict.test.d.ts +1 -0
- package/dist/tests/operators/suffix.test.d.ts +1 -0
- package/dist/tests/operators/summarize.test.d.ts +1 -0
- package/dist/tests/operators/transform.test.d.ts +1 -0
- package/dist/tests/operators/ungroup.test.d.ts +1 -0
- package/dist/tests/operators/union.test.d.ts +1 -0
- package/dist/tests/operators/unwrap.test.d.ts +1 -0
- package/dist/tests/operators/where.test.d.ts +1 -0
- package/dist/tests/operators/wrap.test.d.ts +1 -0
- package/dist/tests/operators/yByX.test.d.ts +1 -0
- package/dist/tests/types/relation.test.d.ts +1 -0
- package/package.json +15 -3
- 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/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
|
@@ -1,273 +0,0 @@
|
|
|
1
|
-
# TypeScript Type Safety Implementation Plan
|
|
2
|
-
|
|
3
|
-
## Goal
|
|
4
|
-
Add opt-in generic type parameters to enable:
|
|
5
|
-
- IDE autocomplete on tuple attributes
|
|
6
|
-
- Compile-time validation of attribute names
|
|
7
|
-
- Type transformation tracking through operator chains
|
|
8
|
-
|
|
9
|
-
## Core Changes
|
|
10
|
-
|
|
11
|
-
### 1. Generic Relation Interface (`src/types.ts`)
|
|
12
|
-
|
|
13
|
-
```typescript
|
|
14
|
-
export interface Relation<T extends Tuple = Tuple> {
|
|
15
|
-
one(): T;
|
|
16
|
-
toArray(): T[];
|
|
17
|
-
|
|
18
|
-
// Preserve type
|
|
19
|
-
restrict(p: TypedPredicate<T>): Relation<T>;
|
|
20
|
-
where(p: TypedPredicate<T>): Relation<T>;
|
|
21
|
-
|
|
22
|
-
// Transform type
|
|
23
|
-
project<K extends keyof T>(attrs: K[]): Relation<Pick<T, K>>;
|
|
24
|
-
allbut<K extends keyof T>(attrs: K[]): Relation<Omit<T, K>>;
|
|
25
|
-
rename<R extends RenameMap<T>>(r: R): Relation<Renamed<T, R>>;
|
|
26
|
-
extend<E extends Record<string, unknown>>(e: TypedExtension<T, E>): Relation<T & E>;
|
|
27
|
-
constants<C extends Tuple>(c: C): Relation<T & C>;
|
|
28
|
-
|
|
29
|
-
// Binary ops - combine types
|
|
30
|
-
join<R extends Tuple>(right: RelationOperand<R>, keys?: JoinKeys<T, R>): Relation<Joined<T, R>>;
|
|
31
|
-
left_join<R extends Tuple>(right: RelationOperand<R>, keys?: JoinKeys<T, R>): Relation<LeftJoined<T, R>>;
|
|
32
|
-
cross_product<R extends Tuple>(right: RelationOperand<R>): Relation<T & R>;
|
|
33
|
-
|
|
34
|
-
// Nesting
|
|
35
|
-
group<K extends keyof T, As extends string>(attrs: K[], as: As): Relation<Grouped<T, K, As>>;
|
|
36
|
-
ungroup<K extends keyof T>(attr: K): Relation<Ungrouped<T, K>>;
|
|
37
|
-
// ...
|
|
38
|
-
}
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
### 2. Generic MemoryRelation Class (`src/Relation/Memory.ts`)
|
|
42
|
-
|
|
43
|
-
```typescript
|
|
44
|
-
export class MemoryRelation<T extends Tuple = Tuple> implements Relation<T> {
|
|
45
|
-
constructor(private tuples: T[]) {}
|
|
46
|
-
|
|
47
|
-
project<K extends keyof T>(attrs: K[]): MemoryRelation<Pick<T, K>> {
|
|
48
|
-
return project(this, attrs as string[]) as MemoryRelation<Pick<T, K>>;
|
|
49
|
-
}
|
|
50
|
-
// ...
|
|
51
|
-
}
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
### 3. Generic Bmg Factory (`src/index.ts`)
|
|
55
|
-
|
|
56
|
-
```typescript
|
|
57
|
-
export function Bmg<T extends Tuple>(tuples: T[]): MemoryRelation<T>;
|
|
58
|
-
export function Bmg(tuples: Tuple[]): MemoryRelation<Tuple>;
|
|
59
|
-
export function Bmg<T extends Tuple = Tuple>(tuples: T[]): MemoryRelation<T> {
|
|
60
|
-
return new MemoryRelation<T>(tuples);
|
|
61
|
-
}
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
### 4. Utility Types (`src/utility-types.ts` - new file)
|
|
65
|
-
|
|
66
|
-
```typescript
|
|
67
|
-
// Rename: { name: 'fullName' } transforms { name, age } to { fullName, age }
|
|
68
|
-
export type RenameMap<T> = { [K in keyof T]?: string };
|
|
69
|
-
export type Renamed<T, R extends RenameMap<T>> = {
|
|
70
|
-
[K in keyof T as K extends keyof R ? (R[K] extends string ? R[K] : K) : K]: T[K];
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
// Join: combine left & right, remove duplicate keys from right
|
|
74
|
-
export type CommonKeys<L, R> = Extract<keyof L, keyof R>;
|
|
75
|
-
export type Joined<L, R> = L & Omit<R, CommonKeys<L, R>>;
|
|
76
|
-
export type LeftJoined<L, R> = L & Partial<Omit<R, CommonKeys<L, R>>>;
|
|
77
|
-
|
|
78
|
-
// Group/Ungroup
|
|
79
|
-
export type Grouped<T, K extends keyof T, As extends string> =
|
|
80
|
-
Omit<T, K> & Record<As, Relation<Pick<T, K>>>;
|
|
81
|
-
export type Ungrouped<T, K extends keyof T> =
|
|
82
|
-
T[K] extends Relation<infer N> ? Omit<T, K> & N : never;
|
|
83
|
-
|
|
84
|
-
// Wrap/Unwrap
|
|
85
|
-
export type Wrapped<T, K extends keyof T, As extends string> =
|
|
86
|
-
Omit<T, K> & Record<As, Pick<T, K>>;
|
|
87
|
-
export type Unwrapped<T, K extends keyof T> =
|
|
88
|
-
T[K] extends Record<string, unknown> ? Omit<T, K> & T[K] : never;
|
|
89
|
-
|
|
90
|
-
// Prefix/Suffix (template literal types)
|
|
91
|
-
export type Prefixed<T, P extends string, Ex extends keyof T = never> = {
|
|
92
|
-
[K in keyof T as K extends Ex ? K : `${P}${K & string}`]: T[K];
|
|
93
|
-
};
|
|
94
|
-
```
|
|
95
|
-
|
|
96
|
-
### 5. Typed Predicates (`src/types.ts`)
|
|
97
|
-
|
|
98
|
-
```typescript
|
|
99
|
-
export type TypedPredicateFunc<T> = (t: T) => boolean;
|
|
100
|
-
export type TypedPredicate<T> = TypedPredicateFunc<T> | Partial<T>;
|
|
101
|
-
```
|
|
102
|
-
|
|
103
|
-
## Implementation Phases
|
|
104
|
-
|
|
105
|
-
### Phase 1: Foundation
|
|
106
|
-
1. Add `Relation<T = Tuple>` interface with generics
|
|
107
|
-
2. Add `MemoryRelation<T = Tuple>` class
|
|
108
|
-
3. Update `Bmg<T>()` factory
|
|
109
|
-
4. Type these operators first (highest value):
|
|
110
|
-
- `one()`, `toArray()` - return `T` / `T[]`
|
|
111
|
-
- `project()` - `Pick<T, K>`
|
|
112
|
-
- `allbut()` - `Omit<T, K>`
|
|
113
|
-
- `restrict()`, `where()`, `exclude()` - preserve `T` with typed predicate
|
|
114
|
-
|
|
115
|
-
### Phase 2: Structure-Changing Operators
|
|
116
|
-
- `extend()` - `T & E`
|
|
117
|
-
- `constants()` - `T & C`
|
|
118
|
-
- `rename()` - `Renamed<T, R>`
|
|
119
|
-
- `prefix()`, `suffix()` - template literal types
|
|
120
|
-
|
|
121
|
-
### Phase 3: Binary Operations
|
|
122
|
-
- `union()`, `minus()`, `intersect()` - require same `T`
|
|
123
|
-
- `join()` - `Joined<T, R>`
|
|
124
|
-
- `left_join()` - `LeftJoined<T, R>`
|
|
125
|
-
- `cross_product()` - `T & R`
|
|
126
|
-
- `matching()`, `not_matching()` - preserve `T`
|
|
127
|
-
|
|
128
|
-
### Phase 4: Nesting Operators
|
|
129
|
-
- `group()` - `Grouped<T, K, As>`
|
|
130
|
-
- `ungroup()` - `Ungrouped<T, K>`
|
|
131
|
-
- `wrap()` - `Wrapped<T, K, As>`
|
|
132
|
-
- `unwrap()` - `Unwrapped<T, K>`
|
|
133
|
-
- `image()` - adds `Relation<...>` attribute
|
|
134
|
-
|
|
135
|
-
### Phase 5: Complex Operators
|
|
136
|
-
- `summarize()` - infer result from aggregators
|
|
137
|
-
- `transform()` - preserve structure, optional value type tracking
|
|
138
|
-
- `autowrap()` - return `Relation<Tuple>` (dynamic, un-typeable)
|
|
139
|
-
|
|
140
|
-
## Backwards Compatibility
|
|
141
|
-
|
|
142
|
-
- Default type parameter `= Tuple` ensures existing untyped code compiles
|
|
143
|
-
- When `T = Tuple = Record<string, unknown>`, `keyof T = string`, so any string[] is accepted
|
|
144
|
-
- No breaking changes to existing API
|
|
145
|
-
|
|
146
|
-
## Files to Modify
|
|
147
|
-
|
|
148
|
-
| File | Changes |
|
|
149
|
-
|------|---------|
|
|
150
|
-
| `src/types.ts` | Add generics to `Relation`, typed predicates |
|
|
151
|
-
| `src/utility-types.ts` (new) | Utility types: `Renamed`, `Joined`, `Grouped`, etc. |
|
|
152
|
-
| `src/Relation/Memory.ts` | Add `<T>` parameter, update method signatures |
|
|
153
|
-
| `src/index.ts` | Update `Bmg` factory with type inference |
|
|
154
|
-
| `src/operators/_helpers.ts` | Generic `toOperationalOperand<T>` |
|
|
155
|
-
|
|
156
|
-
## Example Usage After Implementation
|
|
157
|
-
|
|
158
|
-
```typescript
|
|
159
|
-
// Opt-in: provide type for full type safety
|
|
160
|
-
interface Supplier {
|
|
161
|
-
sid: string;
|
|
162
|
-
name: string;
|
|
163
|
-
status: number;
|
|
164
|
-
city: string;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
const suppliers = Bmg<Supplier>([
|
|
168
|
-
{ sid: 'S1', name: 'Smith', status: 20, city: 'London' },
|
|
169
|
-
]);
|
|
170
|
-
|
|
171
|
-
// IDE autocomplete: suggests 'sid', 'name', 'status', 'city'
|
|
172
|
-
const result = suppliers
|
|
173
|
-
.project(['sid', 'name']) // Relation<{ sid: string; name: string }>
|
|
174
|
-
.rename({ name: 'fullName' }) // Relation<{ sid: string; fullName: string }>
|
|
175
|
-
.extend({ upper: t => t.fullName.toUpperCase() }); // t.fullName autocompletes
|
|
176
|
-
|
|
177
|
-
// Compile error for invalid attributes
|
|
178
|
-
suppliers.project(['invalid']); // Error: 'invalid' not in keyof Supplier
|
|
179
|
-
|
|
180
|
-
// Untyped usage still works (backwards compatible)
|
|
181
|
-
const r = Bmg([{ a: 1 }]); // Relation<Tuple>
|
|
182
|
-
r.project(['a']); // Works, no validation
|
|
183
|
-
```
|
|
184
|
-
|
|
185
|
-
## Test Plan
|
|
186
|
-
|
|
187
|
-
### 1. Compile-Time Type Tests
|
|
188
|
-
|
|
189
|
-
Create `tests/types/relation.test-d.ts` using vitest's type testing:
|
|
190
|
-
|
|
191
|
-
```typescript
|
|
192
|
-
import { Bmg } from 'src';
|
|
193
|
-
import { expectTypeOf } from 'vitest';
|
|
194
|
-
|
|
195
|
-
interface Person { id: number; name: string; age: number }
|
|
196
|
-
|
|
197
|
-
describe('Type Safety', () => {
|
|
198
|
-
const people = Bmg<Person>([{ id: 1, name: 'Alice', age: 30 }]);
|
|
199
|
-
|
|
200
|
-
it('project narrows type to Pick<T, K>', () => {
|
|
201
|
-
const result = people.project(['id', 'name']);
|
|
202
|
-
expectTypeOf(result.one()).toEqualTypeOf<{ id: number; name: string }>();
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
it('rejects invalid attribute names', () => {
|
|
206
|
-
// @ts-expect-error - 'invalid' is not a key of Person
|
|
207
|
-
people.project(['invalid']);
|
|
208
|
-
});
|
|
209
|
-
|
|
210
|
-
it('restrict preserves type with typed predicate', () => {
|
|
211
|
-
const result = people.restrict(t => t.age > 25);
|
|
212
|
-
expectTypeOf(result.one()).toEqualTypeOf<Person>();
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
it('predicate function receives typed tuple', () => {
|
|
216
|
-
people.restrict(t => {
|
|
217
|
-
expectTypeOf(t.id).toBeNumber();
|
|
218
|
-
expectTypeOf(t.name).toBeString();
|
|
219
|
-
return true;
|
|
220
|
-
});
|
|
221
|
-
});
|
|
222
|
-
|
|
223
|
-
it('join combines types correctly', () => {
|
|
224
|
-
interface Order { orderId: number; personId: number; total: number }
|
|
225
|
-
const orders = Bmg<Order>([]);
|
|
226
|
-
const joined = people.join(orders, { id: 'personId' });
|
|
227
|
-
expectTypeOf(joined.one()).toEqualTypeOf<{
|
|
228
|
-
id: number; name: string; age: number; orderId: number; total: number
|
|
229
|
-
}>();
|
|
230
|
-
});
|
|
231
|
-
|
|
232
|
-
it('rename transforms keys', () => {
|
|
233
|
-
const renamed = people.rename({ name: 'fullName' });
|
|
234
|
-
expectTypeOf(renamed.one()).toEqualTypeOf<{ id: number; fullName: string; age: number }>();
|
|
235
|
-
});
|
|
236
|
-
|
|
237
|
-
it('untyped usage still works', () => {
|
|
238
|
-
const r = Bmg([{ a: 1 }]); // Relation<Tuple>
|
|
239
|
-
r.project(['a']); // Should compile
|
|
240
|
-
r.project(['anything']); // Should compile (no validation when T=Tuple)
|
|
241
|
-
});
|
|
242
|
-
});
|
|
243
|
-
```
|
|
244
|
-
|
|
245
|
-
### 2. Runtime Tests
|
|
246
|
-
|
|
247
|
-
All 152 existing tests must continue to pass without modification - validates backwards compatibility.
|
|
248
|
-
|
|
249
|
-
### 3. Type Test Coverage by Phase
|
|
250
|
-
|
|
251
|
-
| Phase | Type Tests |
|
|
252
|
-
|-------|-----------|
|
|
253
|
-
| Phase 1 | `project`, `allbut`, `restrict`, `one`, `toArray` |
|
|
254
|
-
| Phase 2 | `extend`, `constants`, `rename`, `prefix`, `suffix` |
|
|
255
|
-
| Phase 3 | `join`, `left_join`, `cross_product`, `union`, `minus` |
|
|
256
|
-
| Phase 4 | `group`, `ungroup`, `wrap`, `unwrap`, `image` |
|
|
257
|
-
| Phase 5 | `summarize`, `transform`, `autowrap` (escape hatch) |
|
|
258
|
-
|
|
259
|
-
### 4. Test File Structure
|
|
260
|
-
|
|
261
|
-
```
|
|
262
|
-
tests/
|
|
263
|
-
types/
|
|
264
|
-
relation.test-d.ts # Core Relation<T> type tests
|
|
265
|
-
operators.test-d.ts # Operator type transformation tests
|
|
266
|
-
utility-types.test-d.ts # Utility type unit tests
|
|
267
|
-
```
|
|
268
|
-
|
|
269
|
-
## Limitations
|
|
270
|
-
|
|
271
|
-
- Function-based `rename()` loses type tracking (only object syntax typed)
|
|
272
|
-
- `autowrap()` returns `Relation<Tuple>` (dynamic structure)
|
|
273
|
-
- Requires TypeScript 4.1+ for template literal types
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
name: Tests
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
push:
|
|
5
|
-
branches: ['*']
|
|
6
|
-
pull_request:
|
|
7
|
-
branches: ['*']
|
|
8
|
-
|
|
9
|
-
jobs:
|
|
10
|
-
test:
|
|
11
|
-
runs-on: ubuntu-latest
|
|
12
|
-
|
|
13
|
-
steps:
|
|
14
|
-
- uses: actions/checkout@v4
|
|
15
|
-
|
|
16
|
-
- uses: pnpm/action-setup@v4
|
|
17
|
-
|
|
18
|
-
- uses: actions/setup-node@v4
|
|
19
|
-
with:
|
|
20
|
-
node-version: '20'
|
|
21
|
-
|
|
22
|
-
- name: Install dependencies
|
|
23
|
-
run: pnpm install
|
|
24
|
-
|
|
25
|
-
- name: Run tests
|
|
26
|
-
run: pnpm test
|
package/CLAUDE.md
DELETED
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
# What is Bmg.js ?
|
|
2
|
-
|
|
3
|
-
A Typescript implementation of BMG, a relational algebra originally implemented
|
|
4
|
-
in/for Ruby :
|
|
5
|
-
|
|
6
|
-
- https://github.com/enspirit/bmg
|
|
7
|
-
- https://www.relational-algebra.dev/
|
|
8
|
-
|
|
9
|
-
The aim of bmg.js is NOT to implement the SQL compiler, but only to provide an
|
|
10
|
-
implementation of main algebra operators to work on arrays of javascript objects.
|
|
11
|
-
|
|
12
|
-
## Development flow
|
|
13
|
-
|
|
14
|
-
* List operators to support, by order of importance
|
|
15
|
-
* Add each operator, in order, with unit tests.
|
|
16
|
-
* One commit per operator, don't forget to adapt the README.
|
|
17
|
-
* Tests MUST succeed at all times, run them with `npm run test`
|
|
18
|
-
|
|
19
|
-
## Theory
|
|
20
|
-
|
|
21
|
-
IMPORTANT rules:
|
|
22
|
-
|
|
23
|
-
1. Relations NEVER have duplicates
|
|
24
|
-
2. Order of tuples and order or attributes in a tuple are not important semantically
|
|
25
|
-
3. Mathematically, relations are sets of tuples ; tuples are sets of (attr, value) pairs.
|
|
26
|
-
4. Two relations are equal of they have the exact same set of exact same tuples.
|
|
27
|
-
|
|
28
|
-
## About unit tests
|
|
29
|
-
|
|
30
|
-
IMPORTANT rules:
|
|
31
|
-
|
|
32
|
-
* Favor purely relational tests: compare an obtained relation with the expected relation
|
|
33
|
-
using `isEqual`.
|
|
34
|
-
* Do NEVER access the "first" tuple, since there is no such tuple.
|
|
35
|
-
* Instead, use `r.restrict(...predicate...).one` with a predicate that selects the
|
|
36
|
-
tuple you are interested in. `one` will correctly fail if your assumption is wrong.
|
|
37
|
-
|
|
38
|
-
## Implemented operators
|
|
39
|
-
|
|
40
|
-
**Relational:** restrict, where, exclude, project, allbut, extend, rename, prefix, suffix, constants, union, minus, intersect, matching, not_matching, join, left_join, cross_product, cross_join, image, summarize, group, ungroup, wrap, unwrap, autowrap, transform
|
|
41
|
-
|
|
42
|
-
**Non-relational:** one, yByX, toArray, isRelation, isEqual
|
|
43
|
-
|
|
44
|
-
## TODO
|
|
45
|
-
|
|
46
|
-
- [ ] sort - Order tuples by attributes
|
|
47
|
-
- [ ] page - Pagination (offset + limit)
|
|
48
|
-
- [ ] size, empty, first, exists - Utility helpers
|
package/Makefile
DELETED
package/example/README.md
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
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`
|
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');
|