{"version":3,"sources":["../../src/runtime/common.ts"],"sourcesContent":["/* eslint-disable @typescript-eslint/no-unused-vars */\n/* eslint-disable @typescript-eslint/no-explicit-any */\nimport { deserialize, serialize } from '@zenstackhq/runtime/browser';\nimport {\n    applyMutation,\n    getMutatedModels,\n    getReadModels,\n    type ModelMeta,\n    type PrismaWriteActionType,\n} from '@zenstackhq/runtime/cross';\nimport * as crossFetch from 'cross-fetch';\n\n/**\n * The default query endpoint.\n */\nexport const DEFAULT_QUERY_ENDPOINT = '/api/model';\n\n/**\n * Prefix for react-query keys.\n */\nexport const QUERY_KEY_PREFIX = 'zenstack';\n\n/**\n * Function signature for `fetch`.\n */\nexport type FetchFn = (url: string, options?: RequestInit) => Promise<Response>;\n\n/**\n * Type for query and mutation errors.\n */\nexport type QueryError = Error & {\n    /**\n     * Additional error information.\n     */\n    info?: unknown;\n\n    /**\n     * HTTP status code.\n     */\n    status?: number;\n};\n\n/**\n * Result of optimistic data provider.\n */\nexport type OptimisticDataProviderResult = {\n    /**\n     * Kind of the result.\n     *   - Update: use the `data` field to update the query cache.\n     *   - Skip: skip the optimistic update for this query.\n     *   - ProceedDefault: proceed with the default optimistic update.\n     */\n    kind: 'Update' | 'Skip' | 'ProceedDefault';\n\n    /**\n     * Data to update the query cache. Only applicable if `kind` is 'Update'.\n     *\n     * If the data is an object with fields updated, it should have a `$optimistic`\n     * field set to `true`. If it's an array and an element object is created or updated,\n     * the element should have a `$optimistic` field set to `true`.\n     */\n    data?: any;\n};\n\n/**\n * Optimistic data provider.\n *\n * @param args Arguments.\n * @param args.queryModel The model of the query.\n * @param args.queryOperation The operation of the query, `findMany`, `count`, etc.\n * @param args.queryArgs The arguments of the query.\n * @param args.currentData The current cache data for the query.\n * @param args.mutationArgs The arguments of the mutation.\n */\nexport type OptimisticDataProvider = (args: {\n    queryModel: string;\n    queryOperation: string;\n    queryArgs: any;\n    currentData: any;\n    mutationArgs: any;\n}) => OptimisticDataProviderResult | Promise<OptimisticDataProviderResult>;\n\n/**\n * Extra mutation options.\n */\nexport type ExtraMutationOptions = {\n    /**\n     * Whether to automatically invalidate queries potentially affected by the mutation. Defaults to `true`.\n     */\n    invalidateQueries?: boolean;\n\n    /**\n     * Whether to optimistically update queries potentially affected by the mutation. Defaults to `false`.\n     */\n    optimisticUpdate?: boolean;\n\n    /**\n     * A callback for computing optimistic update data for each query cache entry.\n     */\n    optimisticDataProvider?: OptimisticDataProvider;\n};\n\n/**\n * Extra query options.\n */\nexport type ExtraQueryOptions = {\n    /**\n     * Whether to opt-in to optimistic updates for this query. Defaults to `true`.\n     */\n    optimisticUpdate?: boolean;\n};\n\n/**\n * Context type for configuring the hooks.\n */\nexport type APIContext = {\n    /**\n     * The endpoint to use for the queries.\n     */\n    endpoint?: string;\n\n    /**\n     * A custom fetch function for sending the HTTP requests.\n     */\n    fetch?: FetchFn;\n\n    /**\n     * If logging is enabled.\n     */\n    logging?: boolean;\n};\n\nexport async function fetcher<R, C extends boolean>(\n    url: string,\n    options?: RequestInit,\n    fetch?: FetchFn,\n    checkReadBack?: C,\n    rawResult = false\n): Promise<C extends true ? R | undefined : R> {\n    const _fetch = fetch ?? crossFetch.fetch;\n    const res = await _fetch(url, options);\n    if (!res.ok) {\n        const errData = unmarshal(await res.text());\n        if (\n            checkReadBack !== false &&\n            errData.error?.prisma &&\n            errData.error?.code === 'P2004' &&\n            errData.error?.reason === 'RESULT_NOT_READABLE'\n        ) {\n            // policy doesn't allow mutation result to be read back, just return undefined\n            return undefined as any;\n        }\n        const error: QueryError = new Error('An error occurred while fetching the data.');\n        error.info = errData.error;\n        error.status = res.status;\n        throw error;\n    }\n\n    const textResult = await res.text();\n    try {\n        if (rawResult === true) {\n            return unmarshal(textResult) as R;\n        } else {\n            return unmarshal(textResult).data as R;\n        }\n    } catch (err) {\n        console.error(`Unable to deserialize data:`, textResult);\n        throw err;\n    }\n}\n\ntype QueryKey = [\n    string /* prefix */,\n    string /* model */,\n    string /* operation */,\n    unknown /* args */,\n    {\n        infinite: boolean;\n        optimisticUpdate: boolean;\n    } /* flags */\n];\n\n/**\n * Computes query key for the given model, operation and query args.\n * @param model Model name.\n * @param urlOrOperation Prisma operation (e.g, `findMany`) or request URL. If it's a URL, the last path segment will be used as the operation name.\n * @param args Prisma query arguments.\n * @param options Query options, including `infinite` indicating if it's an infinite query (defaults to false), and `optimisticUpdate` indicating if optimistic updates are enabled (defaults to true).\n * @returns Query key\n */\nexport function getQueryKey(\n    model: string,\n    urlOrOperation: string,\n    args: unknown,\n    options: { infinite: boolean; optimisticUpdate: boolean } = { infinite: false, optimisticUpdate: true }\n): QueryKey {\n    if (!urlOrOperation) {\n        throw new Error('Invalid urlOrOperation');\n    }\n    const operation = urlOrOperation.split('/').pop();\n\n    const infinite = options.infinite;\n    // infinite query doesn't support optimistic updates\n    const optimisticUpdate = options.infinite ? false : options.optimisticUpdate;\n\n    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n    return [QUERY_KEY_PREFIX, model, operation!, args, { infinite, optimisticUpdate }];\n}\n\nexport function marshal(value: unknown) {\n    const { data, meta } = serialize(value);\n    if (meta) {\n        return JSON.stringify({ ...(data as any), meta: { serialization: meta } });\n    } else {\n        return JSON.stringify(data);\n    }\n}\n\nexport function unmarshal(value: string) {\n    const parsed = JSON.parse(value);\n    if (parsed.data && parsed.meta?.serialization) {\n        const deserializedData = deserialize(parsed.data, parsed.meta.serialization);\n        return { ...parsed, data: deserializedData };\n    } else {\n        return parsed;\n    }\n}\n\nexport function makeUrl(url: string, args: unknown) {\n    if (!args) {\n        return url;\n    }\n\n    const { data, meta } = serialize(args);\n    let result = `${url}?q=${encodeURIComponent(JSON.stringify(data))}`;\n    if (meta) {\n        result += `&meta=${encodeURIComponent(JSON.stringify({ serialization: meta }))}`;\n    }\n    return result;\n}\n\ntype InvalidationPredicate = ({ queryKey }: { queryKey: readonly unknown[] }) => boolean;\ntype InvalidateFunc = (predicate: InvalidationPredicate) => Promise<void>;\ntype MutationOptions = {\n    onMutate?: (...args: any[]) => any;\n    onSuccess?: (...args: any[]) => any;\n    onSettled?: (...args: any[]) => any;\n};\n\n// sets up invalidation hook for a mutation\nexport function setupInvalidation(\n    model: string,\n    operation: string,\n    modelMeta: ModelMeta,\n    options: MutationOptions,\n    invalidate: InvalidateFunc,\n    logging = false\n) {\n    const origOnSuccess = options?.onSuccess;\n    options.onSuccess = async (...args: unknown[]) => {\n        const [_, variables] = args;\n        const predicate = await getInvalidationPredicate(\n            model,\n            operation as PrismaWriteActionType,\n            variables,\n            modelMeta,\n            logging\n        );\n        await invalidate(predicate);\n        return origOnSuccess?.(...args);\n    };\n}\n\n// gets a predicate for evaluating whether a query should be invalidated\nasync function getInvalidationPredicate(\n    model: string,\n    operation: PrismaWriteActionType,\n    mutationArgs: any,\n    modelMeta: ModelMeta,\n    logging = false\n) {\n    const mutatedModels = await getMutatedModels(model, operation, mutationArgs, modelMeta);\n\n    return ({ queryKey }: { queryKey: readonly unknown[] }) => {\n        const [_, queryModel, , args] = queryKey as QueryKey;\n\n        if (mutatedModels.includes(queryModel)) {\n            // direct match\n            if (logging) {\n                console.log(`Invalidating query ${JSON.stringify(queryKey)} due to mutation \"${model}.${operation}\"`);\n            }\n            return true;\n        }\n\n        if (args) {\n            // traverse query args to find nested reads that match the model under mutation\n            if (findNestedRead(queryModel, mutatedModels, modelMeta, args)) {\n                if (logging) {\n                    console.log(\n                        `Invalidating query ${JSON.stringify(queryKey)} due to mutation \"${model}.${operation}\"`\n                    );\n                }\n                return true;\n            }\n        }\n\n        return false;\n    };\n}\n\n// find nested reads that match the given models\nfunction findNestedRead(visitingModel: string, targetModels: string[], modelMeta: ModelMeta, args: any) {\n    const modelsRead = getReadModels(visitingModel, modelMeta, args);\n    return targetModels.some((m) => modelsRead.includes(m));\n}\n\ntype QueryCache = {\n    queryKey: readonly unknown[];\n    state: {\n        data: unknown;\n        error: unknown;\n    };\n}[];\n\ntype SetCacheFunc = (queryKey: readonly unknown[], data: unknown) => void;\n\n/**\n * Sets up optimistic update and invalidation (after settled) for a mutation.\n */\nexport function setupOptimisticUpdate(\n    model: string,\n    operation: string,\n    modelMeta: ModelMeta,\n    options: MutationOptions & ExtraMutationOptions,\n    queryCache: QueryCache,\n    setCache: SetCacheFunc,\n    invalidate?: InvalidateFunc,\n    logging = false\n) {\n    const origOnMutate = options?.onMutate;\n    const origOnSettled = options?.onSettled;\n\n    // optimistic update on mutate\n    options.onMutate = async (...args: unknown[]) => {\n        const [variables] = args;\n        await optimisticUpdate(\n            model,\n            operation as PrismaWriteActionType,\n            variables,\n            options,\n            modelMeta,\n            queryCache,\n            setCache,\n            logging\n        );\n        return origOnMutate?.(...args);\n    };\n\n    // invalidate on settled\n    options.onSettled = async (...args: unknown[]) => {\n        if (invalidate) {\n            const [, , variables] = args;\n            const predicate = await getInvalidationPredicate(\n                model,\n                operation as PrismaWriteActionType,\n                variables,\n                modelMeta,\n                logging\n            );\n            await invalidate(predicate);\n        }\n        return origOnSettled?.(...args);\n    };\n}\n\n// optimistically updates query cache\nasync function optimisticUpdate(\n    mutationModel: string,\n    mutationOp: string,\n    mutationArgs: any,\n    options: MutationOptions & ExtraMutationOptions,\n    modelMeta: ModelMeta,\n    queryCache: QueryCache,\n    setCache: SetCacheFunc,\n    logging = false\n) {\n    for (const cacheItem of queryCache) {\n        const {\n            queryKey,\n            state: { data, error },\n        } = cacheItem;\n\n        if (error) {\n            if (logging) {\n                console.warn(`Skipping optimistic update for ${JSON.stringify(queryKey)} due to error:`, error);\n            }\n            continue;\n        }\n\n        const [_, queryModel, queryOperation, queryArgs, { optimisticUpdate }] = queryKey as QueryKey;\n        if (!optimisticUpdate) {\n            if (logging) {\n                console.log(`Skipping optimistic update for ${JSON.stringify(queryKey)} due to opt-out`);\n            }\n            continue;\n        }\n\n        if (options.optimisticDataProvider) {\n            const providerResult = await options.optimisticDataProvider({\n                queryModel,\n                queryOperation,\n                queryArgs,\n                currentData: data,\n                mutationArgs,\n            });\n\n            if (providerResult?.kind === 'Skip') {\n                // skip\n                if (logging) {\n                    console.log(`Skipping optimistic update for ${JSON.stringify(queryKey)} due to provider`);\n                }\n                continue;\n            } else if (providerResult?.kind === 'Update') {\n                // update cache\n                if (logging) {\n                    console.log(`Optimistically updating query ${JSON.stringify(queryKey)} due to provider`);\n                }\n                setCache(queryKey, providerResult.data);\n                continue;\n            }\n        }\n\n        // proceed with default optimistic update\n        const mutatedData = await applyMutation(\n            queryModel,\n            queryOperation,\n            data,\n            mutationModel,\n            mutationOp as PrismaWriteActionType,\n            mutationArgs,\n            modelMeta,\n            logging\n        );\n\n        if (mutatedData !== undefined) {\n            // mutation applicable to this query, update cache\n            if (logging) {\n                console.log(\n                    `Optimistically updating query ${JSON.stringify(\n                        queryKey\n                    )} due to mutation \"${mutationModel}.${mutationOp}\"`\n                );\n            }\n            setCache(queryKey, mutatedData);\n        }\n    }\n}\n"],"mappings":";AAEA,SAAS,aAAa,iBAAiB;AACvC;AAAA,EACI;AAAA,EACA;AAAA,EACA;AAAA,OAGG;AACP,YAAY,gBAAgB;AAUrB,IAAM,mBAAmB;AA0KzB,SAAS,YACZ,OACA,gBACA,MACA,UAA4D,EAAE,UAAU,OAAO,kBAAkB,KAAK,GAC9F;AACR,MAAI,CAAC,gBAAgB;AACjB,UAAM,IAAI,MAAM,wBAAwB;AAAA,EAC5C;AACA,QAAM,YAAY,eAAe,MAAM,GAAG,EAAE,IAAI;AAEhD,QAAM,WAAW,QAAQ;AAEzB,QAAM,mBAAmB,QAAQ,WAAW,QAAQ,QAAQ;AAG5D,SAAO,CAAC,kBAAkB,OAAO,WAAY,MAAM,EAAE,UAAU,iBAAiB,CAAC;AACrF;","names":[]}