Skip to content

Commit 309ca43

Browse files
committed
[skip/helpers] Add JoinOneMapper/JoinManyMapper helpers.
Example usage for `JoinOneMapper`: ``` type User = { name: string; email: string; }; type Post = { title: string; body: string; author_id: number; }; type PostWithAuthor = { title: string; body: string; author: User; }; ... // The following `map()` turns `Post`s and `User`s into `PostWithAuthor`s. posts.map(JoinOneMapper, users, { on: "author_id", name: "author", }) ``` Example usage for `JoinManyMapper`: ``` type Upvote = { post_id: number; user_id: number; }; type Post = { title: string; body: string; }; type PostWithUpvotes = { title: string; body: string; upvotes: { user_id: number; }[]; }; ... // The following `map()` turns `Post`s and `Upvote`s into `PostWithUpvote`s. posts.map(JoinManyMapper, upvotes, { on: "post_id", name: "upvotes", }) ```
1 parent 0f15986 commit 309ca43

File tree

2 files changed

+114
-0
lines changed

2 files changed

+114
-0
lines changed

skipruntime-ts/helpers/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,4 @@ export {
1414
export { SkipExternalService } from "./remote.js";
1515
export { SkipServiceBroker, fetchJSON, type Entrypoint } from "./rest.js";
1616
export { Count, Max, Min, Sum } from "./utils.js";
17+
export { JoinOneMapper, JoinManyMapper } from "./join.js";

skipruntime-ts/helpers/src/join.ts

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
import type {
2+
EagerCollection,
3+
Json,
4+
JsonObject,
5+
Values,
6+
Mapper,
7+
} from "@skipruntime/core";
8+
9+
export class JoinOneMapper<
10+
IdProperty extends string,
11+
JoinedProperty extends string,
12+
KLeft extends Json,
13+
VLeft extends JsonObject & { [K in IdProperty]: KRight },
14+
KRight extends Json,
15+
VRight extends Json,
16+
> implements
17+
Mapper<
18+
KLeft,
19+
VLeft,
20+
KLeft,
21+
Omit<VLeft, IdProperty> & Record<JoinedProperty, VRight>
22+
>
23+
{
24+
constructor(
25+
private readonly right: EagerCollection<KRight, VRight>,
26+
private readonly params: {
27+
on: IdProperty;
28+
name: JoinedProperty;
29+
},
30+
) {}
31+
32+
mapEntry(
33+
key: KLeft,
34+
values: Values<VLeft>,
35+
): Iterable<
36+
[KLeft, Omit<VLeft, IdProperty> & Record<JoinedProperty, VRight>]
37+
> {
38+
return values.toArray().map((v: VLeft) => {
39+
const { [this.params.on]: key_right, ...value_left } = v;
40+
const value_right = {
41+
[this.params.name]: this.right.getUnique(key_right),
42+
} as Record<JoinedProperty, VRight>;
43+
const value_out = { ...value_left, ...value_right };
44+
return [key, value_out];
45+
});
46+
}
47+
}
48+
49+
class ProjectionMapper<
50+
ProjectionProperty extends string,
51+
KIn extends Json,
52+
V extends JsonObject & { [K in ProjectionProperty]: KOut },
53+
KOut extends Json,
54+
> implements Mapper<KIn, V, KOut, Omit<V, ProjectionProperty>>
55+
{
56+
constructor(private readonly proj_property: ProjectionProperty) {}
57+
58+
mapEntry(
59+
_key: KIn,
60+
values: Values<V>,
61+
): Iterable<[KOut, Omit<V, ProjectionProperty>]> {
62+
return values.toArray().map((v: V) => {
63+
const { [this.proj_property]: new_key, ...new_value } = v;
64+
return [new_key, new_value];
65+
});
66+
}
67+
}
68+
69+
export class JoinManyMapper<
70+
IdProperty extends string,
71+
JoinedProperty extends string,
72+
KLeft extends Json,
73+
VLeft extends JsonObject,
74+
KRight extends Json,
75+
VRight extends JsonObject & { [K in IdProperty]: KLeft },
76+
> implements
77+
Mapper<
78+
KLeft,
79+
VLeft,
80+
KLeft,
81+
VLeft & Record<JoinedProperty, Omit<VRight, IdProperty>[]>
82+
>
83+
{
84+
private readonly right: EagerCollection<KLeft, Omit<VRight, IdProperty>>;
85+
private readonly name: JoinedProperty;
86+
87+
constructor(
88+
right: EagerCollection<KRight, VRight>,
89+
params: {
90+
on: IdProperty;
91+
name: JoinedProperty;
92+
},
93+
) {
94+
this.right = right.map(
95+
ProjectionMapper<IdProperty, KRight, VRight, KLeft>,
96+
params.on,
97+
);
98+
this.name = params.name;
99+
}
100+
101+
mapEntry(
102+
key: KLeft,
103+
values: Values<VLeft>,
104+
): Iterable<
105+
[KLeft, VLeft & Record<JoinedProperty, Omit<VRight, IdProperty>[]>]
106+
> {
107+
return values.toArray().map((value_left: VLeft) => {
108+
const value_right = { [this.name]: this.right.getArray(key) };
109+
const value_out = { ...value_left, ...value_right };
110+
return [key, value_out];
111+
});
112+
}
113+
}

0 commit comments

Comments
 (0)