Skip to content

Commit ed502e6

Browse files
committed
POC: Dynamically generated Backend service
Create the hooks dynamically out of the query definitions and expose them from a singleton class. The rules service consumes this
1 parent 657e98b commit ed502e6

File tree

3 files changed

+308
-67
lines changed

3 files changed

+308
-67
lines changed
Lines changed: 153 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,153 @@
1-
export default class BackendService {}
1+
import {
2+
createMutationEndpoint,
3+
createQueryEndpointWithTransform,
4+
type CustomBuilder,
5+
type EndpointMap
6+
} from '$lib/state/butlerModule';
7+
import { invalidatesItem, invalidatesList, providesItems, ReduxTag } from '$lib/state/tags';
8+
import { createEntityAdapter, type EntityState } from '@reduxjs/toolkit';
9+
import type {
10+
CreateRuleRequest,
11+
UpdateRuleRequest,
12+
WorkspaceRule,
13+
WorkspaceRuleId
14+
} from '$lib/rules/rule';
15+
import type { BackendApi } from '$lib/state/clientState.svelte';
16+
17+
function typedEntries<T extends Record<string, unknown>>(obj: T): [keyof T, T[keyof T]][] {
18+
return Object.entries(obj) as [keyof T, T[keyof T]][];
19+
}
20+
21+
function typedFromEntries<T extends Record<string, unknown>>(entries: [keyof T, T[keyof T]][]): T {
22+
return Object.fromEntries(entries) as T;
23+
}
24+
25+
export default class BackendService {
26+
private static instance: BackendService;
27+
private mutationApi: ReturnType<typeof injectMutationEndpoints>;
28+
private queryApi: ReturnType<typeof injectQueryEndpoints>;
29+
30+
private constructor(backendApi: BackendApi) {
31+
this.mutationApi = injectMutationEndpoints(backendApi);
32+
this.queryApi = injectQueryEndpoints(backendApi);
33+
}
34+
35+
static getInstance(backendApi: BackendApi): BackendService {
36+
if (!BackendService.instance) {
37+
BackendService.instance = new BackendService(backendApi);
38+
}
39+
return BackendService.instance;
40+
}
41+
42+
get() {
43+
type MutationEndpoints = typeof this.mutationApi.endpoints;
44+
45+
type MutateMap = {
46+
[K in keyof MutationEndpoints as `${K}Mutate`]: MutationEndpoints[K]['mutate'];
47+
};
48+
49+
type UseMutationMap = {
50+
[K in keyof MutationEndpoints as `${K}UseMutation`]: MutationEndpoints[K]['useMutation'];
51+
};
52+
53+
const mutate = typedFromEntries(
54+
typedEntries(this.mutationApi.endpoints).map(
55+
([key, value]) => [`${key}Mutate`, value.mutate] as const
56+
)
57+
) as MutateMap;
58+
59+
const useMutation = typedFromEntries(
60+
typedEntries(this.mutationApi.endpoints).map(
61+
([key, value]) => [`${key}UseMutation`, value.useMutation] as const
62+
)
63+
) as UseMutationMap;
64+
65+
type QueryEndpoints = typeof this.queryApi.endpoints;
66+
67+
type UseQueryMap = {
68+
[K in keyof QueryEndpoints as `${K}UseQuery`]: (typeof this.queryApi.endpoints)[K]['useQuery'];
69+
};
70+
71+
type FetchMap = {
72+
[K in keyof QueryEndpoints as `${K}Fetch`]: (typeof this.queryApi.endpoints)[K]['fetch'];
73+
};
74+
75+
const useQuery = typedFromEntries(
76+
typedEntries(this.queryApi.endpoints).map(
77+
([key, value]) => [`${key}UseQuery`, value.useQuery] as const
78+
)
79+
) as UseQueryMap;
80+
81+
const fetchMap = typedFromEntries(
82+
typedEntries(this.queryApi.endpoints).map(
83+
([key, value]) => [`${key}Fetch`, value.fetch] as const
84+
)
85+
) as FetchMap;
86+
87+
return {
88+
...mutate,
89+
...useMutation,
90+
...useQuery,
91+
...fetchMap
92+
};
93+
}
94+
}
95+
96+
function injectQueryEndpoints(api: BackendApi) {
97+
return api.injectEndpoints({
98+
endpoints: (build) => getQueryEndpointMap(build)
99+
});
100+
}
101+
102+
function injectMutationEndpoints(api: BackendApi) {
103+
return api.injectEndpoints({
104+
endpoints: (build) => getMutationEndpointMap(build)
105+
});
106+
}
107+
108+
function getMutationEndpointMap(builder: CustomBuilder) {
109+
return {
110+
createWorkspaceRule: createMutationEndpoint<
111+
WorkspaceRule,
112+
{ projectId: string; request: CreateRuleRequest }
113+
>(builder, 'create_workspace_rule', () => [invalidatesList(ReduxTag.WorkspaceRules)]),
114+
deleteWorkspaceRule: createMutationEndpoint<
115+
void,
116+
{ projectId: string; ruleId: WorkspaceRuleId }
117+
>(builder, 'delete_workspace_rule', () => [invalidatesList(ReduxTag.WorkspaceRules)]),
118+
updateWorkspaceRule: createMutationEndpoint<
119+
WorkspaceRule,
120+
{ projectId: string; request: UpdateRuleRequest }
121+
>(builder, 'update_workspace_rule', (result) =>
122+
result
123+
? [
124+
invalidatesItem(ReduxTag.WorkspaceRules, result.id),
125+
invalidatesList(ReduxTag.WorkspaceRules)
126+
]
127+
: []
128+
)
129+
} satisfies EndpointMap;
130+
}
131+
132+
function getQueryEndpointMap(builder: CustomBuilder) {
133+
return {
134+
listWorkspaceRules: createQueryEndpointWithTransform<
135+
WorkspaceRule[],
136+
{ projectId: string },
137+
EntityState<WorkspaceRule, WorkspaceRuleId>
138+
>(
139+
builder,
140+
'list_workspace_rules',
141+
(response: WorkspaceRule[]) => {
142+
return workspaceRulesAdapter.addMany(workspaceRulesAdapter.getInitialState(), response);
143+
},
144+
(result) => providesItems(ReduxTag.WorkspaceRules, result?.ids ?? [])
145+
)
146+
} satisfies EndpointMap;
147+
}
148+
149+
const workspaceRulesAdapter = createEntityAdapter<WorkspaceRule, WorkspaceRuleId>({
150+
selectId: (rule) => rule.id
151+
});
152+
153+
export const workspaceRulesSelectors = workspaceRulesAdapter.getSelectors();
Lines changed: 9 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,87 +1,31 @@
1-
import { invalidatesItem, invalidatesList, providesItems, ReduxTag } from '$lib/state/tags';
2-
import { createEntityAdapter, type EntityState } from '@reduxjs/toolkit';
3-
import type {
4-
CreateRuleRequest,
5-
UpdateRuleRequest,
6-
WorkspaceRule,
7-
WorkspaceRuleId
8-
} from '$lib/rules/rule';
1+
import BackendService, { workspaceRulesSelectors } from '$lib/backend/backendService.svelte';
92
import type { BackendApi } from '$lib/state/clientState.svelte';
103

114
export default class RulesService {
12-
private api: ReturnType<typeof injectEndpoints>;
5+
private backendService: BackendService;
6+
private apis: ReturnType<typeof this.backendService.get>;
137

148
constructor(backendApi: BackendApi) {
15-
this.api = injectEndpoints(backendApi);
9+
this.backendService = BackendService.getInstance(backendApi);
10+
this.apis = this.backendService.get();
1611
}
1712

1813
get createWorkspaceRule() {
19-
return this.api.endpoints.createWorkspaceRule.useMutation();
14+
return this.apis.createWorkspaceRuleUseMutation();
2015
}
2116

2217
get deleteWorkspaceRule() {
23-
return this.api.endpoints.deleteWorkspaceRule.useMutation();
18+
return this.apis.deleteWorkspaceRuleUseMutation();
2419
}
2520

2621
get updateWorkspaceRule() {
27-
return this.api.endpoints.updateWorkspaceRule.useMutation();
22+
return this.apis.updateWorkspaceRuleUseMutation();
2823
}
2924

3025
listWorkspaceRules(projectId: string) {
31-
return this.api.endpoints.listWorkspaceRules.useQuery(
26+
return this.apis.listWorkspaceRulesUseQuery(
3227
{ projectId },
3328
{ transform: (result) => workspaceRulesSelectors.selectAll(result) }
3429
);
3530
}
3631
}
37-
38-
function injectEndpoints(api: BackendApi) {
39-
return api.injectEndpoints({
40-
endpoints: (build) => ({
41-
createWorkspaceRule: build.mutation<
42-
WorkspaceRule,
43-
{ projectId: string; request: CreateRuleRequest }
44-
>({
45-
extraOptions: { command: 'create_workspace_rule' },
46-
query: (args) => args,
47-
invalidatesTags: () => [invalidatesList(ReduxTag.WorkspaceRules)]
48-
}),
49-
deleteWorkspaceRule: build.mutation<void, { projectId: string; ruleId: WorkspaceRuleId }>({
50-
extraOptions: { command: 'delete_workspace_rule' },
51-
query: (args) => args,
52-
invalidatesTags: () => [invalidatesList(ReduxTag.WorkspaceRules)]
53-
}),
54-
updateWorkspaceRule: build.mutation<
55-
WorkspaceRule,
56-
{ projectId: string; request: UpdateRuleRequest }
57-
>({
58-
extraOptions: { command: 'update_workspace_rule' },
59-
query: (args) => args,
60-
invalidatesTags: (result) =>
61-
result
62-
? [
63-
invalidatesItem(ReduxTag.WorkspaceRules, result.id),
64-
invalidatesList(ReduxTag.WorkspaceRules)
65-
]
66-
: []
67-
}),
68-
listWorkspaceRules: build.query<
69-
EntityState<WorkspaceRule, WorkspaceRuleId>,
70-
{ projectId: string }
71-
>({
72-
extraOptions: { command: 'list_workspace_rules' },
73-
query: (args) => args,
74-
providesTags: (result) => providesItems(ReduxTag.WorkspaceRules, result?.ids ?? []),
75-
transformResponse: (response: WorkspaceRule[]) => {
76-
return workspaceRulesAdapter.addMany(workspaceRulesAdapter.getInitialState(), response);
77-
}
78-
})
79-
})
80-
});
81-
}
82-
83-
const workspaceRulesAdapter = createEntityAdapter<WorkspaceRule, WorkspaceRuleId>({
84-
selectId: (rule) => rule.id
85-
});
86-
87-
const workspaceRulesSelectors = workspaceRulesAdapter.getSelectors();

0 commit comments

Comments
 (0)