import { type EdenClient, type EdenRequestParams, type EmptyToVoid, type ExtractEdenTreatyRouteParams, type ExtractEdenTreatyRouteParamsInput, type HttpMutationMethod, type HttpQueryMethod, type HttpSubscriptionMethod, type InferRouteError, type InferRouteOptions, type InferRouteOutput, parsePathsAndMethod, type TypeError, } from '@aydee-app/eden' import type { CreateQueryOptions, QueriesOptions, QueriesResults, QueryKey, QueryOptions, } from '@tanstack/svelte-query' import type { AnyElysia, RouteSchema } from 'elysia' import type { Readable } from 'svelte/store' import type { EdenQueryConfig } from '../../config' import type { CreateQueryOptionsForCreateQueries } from '../../integration/internal/create-query-options-for-create-queries' import type { EdenQueryBaseOptions } from '../../integration/internal/query-base-options' import { type EdenQueryKey, getQueryKey } from '../../integration/internal/query-key' /** * A function that accepts a callback that's called with a proxy object. * Invoking the proxy object returns strongly typed query options. */ export type EdenTreatySvelteQueryCreateQueries = < TData extends any[], TCombinedResult = QueriesResults, >( callback: (t: EdenTreatySvelteQueryCreateQueriesProxy) => readonly [...QueriesOptions], ) => Readable /** * RPC proxy derived from {@link AnyElysia._routes} that generates query options for `createQueries`. * This proxy is passed to the callback provided to the eden-treaty-svelte-query `createQueries` helper. */ export type EdenTreatySvelteQueryCreateQueriesProxy = T extends { _routes: infer TSchema extends Record } ? EdenTreatySvelteQueryCreateQueriesProxyMapping : TypeError<'Please install Elysia before using Eden'> /** * Recursively iterate over all keys in {@link AnyElyisa._routes}, processing path parameters * and regular path segments separately. * * Regular path parameters will be mapped to a nested object, and then intersected * with anything generated by dynamic path parameters. * * @template TSchema The current level of {@link AnyElysia._routes} being processed. * @template TPath The current path segments up to this point (excluding dynamic path parameters). */ export type EdenTreatySvelteQueryCreateQueriesProxyMapping< TSchema extends Record, TPath extends any[] = [], TRouteParams = ExtractEdenTreatyRouteParams, > = EdenTreatySvelteQueryCreateQueriesPathHooks & EdenTreatySvelteQueryCreateQueriesPathParameterHook /** * This intersects the object created by {@link EdenTreatySvelteQueryCreateQueriesPathHooks} * (for regular path parameters) to handle dynamic path parameters. * * If there are no dynamic path parameters, then return an empty object. * Intersecting with empty object does nothing. * * Otherwise, return a function that returns the next level of the proxy, omitting * the current dynamic path parameter. * * @template TSchema The current level of {@link AnyElysia._routes} being processed. * @template TPath The current path segments up to this point (excluding dynamic path parameters). * @template TRouteParams Keys that are considered path parameters instead of regular path segments. */ type EdenTreatySvelteQueryCreateQueriesPathParameterHook< TSchema extends Record, TPath extends any[] = [], TRouteParams = {}, > = {} extends TRouteParams ? {} : ( params: ExtractEdenTreatyRouteParamsInput, ) => EdenTreatySvelteQueryCreateQueriesProxyMapping< TSchema[Extract], TPath > /** * Recursively handle regular path segments (i.e. NOT path parameters). * * If the value is a {@link RouteSchema}, then it's a "leaf" that does not need to be * recursively processed. The result should be the key, an HTTP method, mapped to svelte-query hooks. * * @template TSchema The current level of {@link AnyElysia._routes} being processed. * @template TPath The current path segments up to this point (excluding dynamic path parameters). * @template TRouteParams Keys that are considered path parameters instead of regular path segments. */ export type EdenTreatySvelteQueryCreateQueriesPathHooks< TSchema extends Record, TPath extends any[] = [], TRouteParams = ExtractEdenTreatyRouteParams, > = { [K in Exclude]: TSchema[K] extends RouteSchema ? EdenTreatySvelteQueryCreateQueriesLeaf : EdenTreatySvelteQueryCreateQueriesProxyMapping } /** * When a {@link RouteSchema} is found, map it to leaves and stop recursive processing. * Based on the HTTP method, leaves may be functions that return {@link CreateQueryOptions}. * * @template TRoute The {@link RouteSchema} that was found. * @template TPath The current path segments up to this point (excluding dynamic path parameters). */ export type EdenTreatySvelteQueryCreateQueriesLeaf< TRoute extends RouteSchema, TMethod, TPath extends any[] = [], > = TMethod extends HttpQueryMethod ? EdenTreatySvelteQueryCreateQueriesQueryLeaf : TMethod extends HttpMutationMethod ? EdenTreatySvelteQueryCreateQueriesMutationLeaf : TMethod extends HttpSubscriptionMethod ? EdenTreatySvelteQueryCreateQueriesSubscriptionLeaf : EdenTreatySvelteQueryCreateQueriesUnknownLeaf /** * Routes that support queries, e.g. "GET" requests, will be mapped to a function that returns {@link CreateQueryOptions}. */ export type EdenTreatySvelteQueryCreateQueriesQueryLeaf< TRoute extends RouteSchema, TPath extends any[] = [], TInput = InferRouteOptions['query'], TOutput = InferRouteOutput, TError = InferRouteError, TKey extends QueryKey = EdenQueryKey, > = ( input: EmptyToVoid, opts?: CreateQueryOptionsForCreateQueries, ) => CreateQueryOptions /** * Routes that support mutations, e.g. anything besides "GET" requests, will be mapped to something unknown for now... * * @todo Decide what mutations should be mapped to... */ export type EdenTreatySvelteQueryCreateQueriesMutationLeaf< _TRoute extends RouteSchema, _TPath extends any[] = [], > = {} /** * Routes that support queries, e.g. anything besides "GET" requests, will be mapped to something unknown for now... * * @todo Decide what subscriptions should be mapped to... */ export type EdenTreatySvelteQueryCreateQueriesSubscriptionLeaf< _TRoute extends RouteSchema, _TPath extends any[] = [], > = {} /** * Routes that support unknown HTTP methods will be mapped to something unknown for now... * * @todo Decide what unknown methods should be mapped to... */ export type EdenTreatySvelteQueryCreateQueriesUnknownLeaf< _TRoute extends RouteSchema, _TPath extends any[] = [], > = {} /** * Utility type for strongly-typing args in the proxy. * @internal */ type CreateQueriesProxyArgs = [InferRouteOptions, (Partial & EdenQueryBaseOptions)?] export function createTreatySvelteQueryCreateQueriesProxy( client: EdenClient, paths: string[] = [], config?: EdenQueryConfig, ): EdenTreatySvelteQueryCreateQueriesProxy { const edenTreatySvelteQueryCreateQueriesProxy = new Proxy(() => {}, { get: (_target, path: string, _receiver) => { // Copy the paths so that it will not be mutated in a nested proxy. // Only add the current path if is not "index". const nextPaths = path === 'index' ? [...paths] : [...paths, path] // Return a nested proxy that has the new paths. return createTreatySvelteQueryCreateQueriesProxy(client, nextPaths) }, apply: (_target, _thisArg, args: CreateQueriesProxyArgs) => { const { path, method } = parsePathsAndMethod(paths) const options = args[0] const { eden, ...queryOptionsOverrides } = args[1] ?? {} const queryOptions: QueryOptions = { queryKey: getQueryKey(paths, options, 'query'), queryFn: async (context) => { const params: EdenRequestParams = { ...config, ...eden, options, path, method, fetcher: eden?.fetcher ?? config?.fetcher ?? globalThis.fetch, } const shouldForwardSignal = config?.abortOnUnmount ?? eden?.abortOnUnmount if (shouldForwardSignal) { params.fetch = { ...params.fetch, signal: context.signal } } const result = await client.query(params) if (result.error != null) { throw result.error } return result.data }, ...queryOptionsOverrides, } return queryOptions }, }) return edenTreatySvelteQueryCreateQueriesProxy as any }