Skip to content

POC: Dynamically generated Backend service #9503

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

estib-vega
Copy link
Contributor

Create the hooks dynamically out of the query definitions and expose them from a singleton class. The rules service consumes this

Copy link

vercel bot commented Jul 21, 2025

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
gitbutler-components ✅ Ready (Inspect) Visit Preview 💬 Add feedback Jul 21, 2025 5:23pm
1 Skipped Deployment
Name Status Preview Comments Updated (UTC)
gitbutler-web ⬜️ Skipped (Inspect) Jul 21, 2025 5:23pm

@vercel vercel bot temporarily deployed to Preview – gitbutler-web July 21, 2025 17:21 Inactive
Create the hooks dynamically out of the query definitions and expose them from a singleton class. The rules service consumes this
Comment on lines +110 to +149
function getMutationEndpointMap(builder: CustomBuilder) {
return {
createWorkspaceRule: createMutationEndpoint<
WorkspaceRule,
{ projectId: string; request: CreateRuleRequest }
>(builder, 'create_workspace_rule', () => [invalidatesList(ReduxTag.WorkspaceRules)]),
deleteWorkspaceRule: createMutationEndpoint<
void,
{ projectId: string; ruleId: WorkspaceRuleId }
>(builder, 'delete_workspace_rule', () => [invalidatesList(ReduxTag.WorkspaceRules)]),
updateWorkspaceRule: createMutationEndpoint<
WorkspaceRule,
{ projectId: string; request: UpdateRuleRequest }
>(builder, 'update_workspace_rule', (result) =>
result
? [
invalidatesItem(ReduxTag.WorkspaceRules, result.id),
invalidatesList(ReduxTag.WorkspaceRules)
]
: []
)
} satisfies EndpointMap;
}

function getQueryEndpointMap(builder: CustomBuilder) {
return {
listWorkspaceRules: createQueryEndpointWithTransform<
WorkspaceRule[],
{ projectId: string },
EntityState<WorkspaceRule, WorkspaceRuleId>
>(
builder,
'list_workspace_rules',
(response: WorkspaceRule[]) => {
return workspaceRulesAdapter.addMany(workspaceRulesAdapter.getInitialState(), response);
},
(result) => providesItems(ReduxTag.WorkspaceRules, result?.ids ?? [])
)
} satisfies EndpointMap;
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So first we defined the endpoints.
The injection happens for queries and mutations separately in order to preserve the type safety when iterating over the entries

Comment on lines +30 to +33
private constructor(backendApi: BackendApi) {
this.mutationApi = injectMutationEndpoints(backendApi);
this.queryApi = injectQueryEndpoints(backendApi);
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We then store them in private properties

Comment on lines +42 to +95
get() {
// Mutations
type MutationEndpoints = typeof this.mutationApi.endpoints;

type MutateMap = {
[K in keyof MutationEndpoints as `${K}Mutate`]: MutationEndpoints[K]['mutate'];
};

type UseMutationMap = {
[K in keyof MutationEndpoints as `${K}UseMutation`]: MutationEndpoints[K]['useMutation'];
};

const mutate = typedFromEntries(
typedEntries(this.mutationApi.endpoints).map(
([key, value]) => [`${key}Mutate`, value.mutate] as const
)
) as MutateMap;

const useMutation = typedFromEntries(
typedEntries(this.mutationApi.endpoints).map(
([key, value]) => [`${key}UseMutation`, value.useMutation] as const
)
) as UseMutationMap;

// Queries
type QueryEndpoints = typeof this.queryApi.endpoints;

type UseQueryMap = {
[K in keyof QueryEndpoints as `${K}UseQuery`]: (typeof this.queryApi.endpoints)[K]['useQuery'];
};

type FetchMap = {
[K in keyof QueryEndpoints as `${K}Fetch`]: (typeof this.queryApi.endpoints)[K]['fetch'];
};

const useQuery = typedFromEntries(
typedEntries(this.queryApi.endpoints).map(
([key, value]) => [`${key}UseQuery`, value.useQuery] as const
)
) as UseQueryMap;

const fetchMap = typedFromEntries(
typedEntries(this.queryApi.endpoints).map(
([key, value]) => [`${key}Fetch`, value.fetch] as const
)
) as FetchMap;

return {
...mutate,
...useMutation,
...useQuery,
...fetchMap
};
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And then, out of the endpoint entries, we generate the map of functions

Comment on lines -15 to +10
this.api = injectEndpoints(backendApi);
this.backendService = BackendService.getInstance(backendApi);
this.apis = this.backendService.get();
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The backend service here is consumed as a singleton

Comment on lines +26 to 29
return this.apis.listWorkspaceRulesUseQuery(
{ projectId },
{ transform: (result) => workspaceRulesSelectors.selectAll(result) }
);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the result is transformed into an entity state instance, we need to select it when consuming it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant