Skip to content

Commit b4fda64

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 b4fda64

File tree

3 files changed

+310
-67
lines changed

3 files changed

+310
-67
lines changed
Lines changed: 155 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,155 @@
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+
// Mutations
44+
type MutationEndpoints = typeof this.mutationApi.endpoints;
45+
46+
type MutateMap = {
47+
[K in keyof MutationEndpoints as `${K}Mutate`]: MutationEndpoints[K]['mutate'];
48+
};
49+
50+
type UseMutationMap = {
51+
[K in keyof MutationEndpoints as `${K}UseMutation`]: MutationEndpoints[K]['useMutation'];
52+
};
53+
54+
const mutate = typedFromEntries(
55+
typedEntries(this.mutationApi.endpoints).map(
56+
([key, value]) => [`${key}Mutate`, value.mutate] as const
57+
)
58+
) as MutateMap;
59+
60+
const useMutation = typedFromEntries(
61+
typedEntries(this.mutationApi.endpoints).map(
62+
([key, value]) => [`${key}UseMutation`, value.useMutation] as const
63+
)
64+
) as UseMutationMap;
65+
66+
// Queries
67+
type QueryEndpoints = typeof this.queryApi.endpoints;
68+
69+
type UseQueryMap = {
70+
[K in keyof QueryEndpoints as `${K}UseQuery`]: (typeof this.queryApi.endpoints)[K]['useQuery'];
71+
};
72+
73+
type FetchMap = {
74+
[K in keyof QueryEndpoints as `${K}Fetch`]: (typeof this.queryApi.endpoints)[K]['fetch'];
75+
};
76+
77+
const useQuery = typedFromEntries(
78+
typedEntries(this.queryApi.endpoints).map(
79+
([key, value]) => [`${key}UseQuery`, value.useQuery] as const
80+
)
81+
) as UseQueryMap;
82+
83+
const fetchMap = typedFromEntries(
84+
typedEntries(this.queryApi.endpoints).map(
85+
([key, value]) => [`${key}Fetch`, value.fetch] as const
86+
)
87+
) as FetchMap;
88+
89+
return {
90+
...mutate,
91+
...useMutation,
92+
...useQuery,
93+
...fetchMap
94+
};
95+
}
96+
}
97+
98+
function injectQueryEndpoints(api: BackendApi) {
99+
return api.injectEndpoints({
100+
endpoints: (build) => getQueryEndpointMap(build)
101+
});
102+
}
103+
104+
function injectMutationEndpoints(api: BackendApi) {
105+
return api.injectEndpoints({
106+
endpoints: (build) => getMutationEndpointMap(build)
107+
});
108+
}
109+
110+
function getMutationEndpointMap(builder: CustomBuilder) {
111+
return {
112+
createWorkspaceRule: createMutationEndpoint<
113+
WorkspaceRule,
114+
{ projectId: string; request: CreateRuleRequest }
115+
>(builder, 'create_workspace_rule', () => [invalidatesList(ReduxTag.WorkspaceRules)]),
116+
deleteWorkspaceRule: createMutationEndpoint<
117+
void,
118+
{ projectId: string; ruleId: WorkspaceRuleId }
119+
>(builder, 'delete_workspace_rule', () => [invalidatesList(ReduxTag.WorkspaceRules)]),
120+
updateWorkspaceRule: createMutationEndpoint<
121+
WorkspaceRule,
122+
{ projectId: string; request: UpdateRuleRequest }
123+
>(builder, 'update_workspace_rule', (result) =>
124+
result
125+
? [
126+
invalidatesItem(ReduxTag.WorkspaceRules, result.id),
127+
invalidatesList(ReduxTag.WorkspaceRules)
128+
]
129+
: []
130+
)
131+
} satisfies EndpointMap;
132+
}
133+
134+
function getQueryEndpointMap(builder: CustomBuilder) {
135+
return {
136+
listWorkspaceRules: createQueryEndpointWithTransform<
137+
WorkspaceRule[],
138+
{ projectId: string },
139+
EntityState<WorkspaceRule, WorkspaceRuleId>
140+
>(
141+
builder,
142+
'list_workspace_rules',
143+
(response: WorkspaceRule[]) => {
144+
return workspaceRulesAdapter.addMany(workspaceRulesAdapter.getInitialState(), response);
145+
},
146+
(result) => providesItems(ReduxTag.WorkspaceRules, result?.ids ?? [])
147+
)
148+
} satisfies EndpointMap;
149+
}
150+
151+
const workspaceRulesAdapter = createEntityAdapter<WorkspaceRule, WorkspaceRuleId>({
152+
selectId: (rule) => rule.id
153+
});
154+
155+
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)