{"version":3,"sources":["../src/server-query.ts"],"sourcesContent":["import {\n  createFarmCacheKey,\n  createRouteDataCacheKey,\n  createRouteDataCacheTag,\n  getFarmDataCache,\n  type RouteDataCacheKey,\n} from \"./cache\";\nimport { getServerActionExecutionContext } from \"./server-action-security\";\nimport {\n  createServerFn,\n  type AnyServerFnMiddleware,\n  type InferServerFnSchemaInput,\n  type InferServerFnSchemaOutput,\n  type ServerFn,\n  type ServerFnContext,\n  type ServerFnHandler,\n  type ServerFnHandlerContext,\n  type ServerFnMiddlewareContext,\n  type ServerFnSchema,\n  _parseServerFnSchema,\n} from \"./server-fn\";\nimport { createFarmServerQueryResult, type FarmServerQueryResult } from \"./server-query-protocol\";\n\ntype MaybePromise<T> = T | Promise<T>;\n\nexport type ServerQueryStaleTime = number | false | `${number}${\"ms\" | \"s\" | \"m\" | \"h\"}`;\n\nexport type ServerQueryKey<TInput, TContext extends object = {}> = (\n  context: ServerFnContext<TInput, TContext>,\n) => MaybePromise<RouteDataCacheKey>;\n\nexport type ServerQueryOptions<\n  TSchema extends ServerFnSchema | undefined,\n  TResult,\n  TMiddlewares extends readonly AnyServerFnMiddleware[] = readonly [],\n> = {\n  input?: TSchema;\n  output?: undefined;\n  middleware?: TMiddlewares;\n  key: ServerQueryKey<\n    TSchema extends ServerFnSchema ? InferServerFnSchemaOutput<TSchema> : unknown,\n    ServerFnMiddlewareContext<TMiddlewares>\n  >;\n  /** Milliseconds, a duration string such as \"30s\", or false for no expiry. */\n  staleTime?: ServerQueryStaleTime;\n  /** Allow the configured client cache persistence adapter to store results on the device. */\n  persist?: boolean;\n  handler: ServerFnHandler<\n    TSchema extends ServerFnSchema ? InferServerFnSchemaOutput<TSchema> : unknown,\n    TResult,\n    ServerFnMiddlewareContext<TMiddlewares>\n  >;\n};\n\nexport type ServerQueryOutputOptions<\n  TInputSchema extends ServerFnSchema | undefined,\n  TOutputSchema extends ServerFnSchema,\n  TMiddlewares extends readonly AnyServerFnMiddleware[] = readonly [],\n> = {\n  input?: TInputSchema;\n  output: TOutputSchema;\n  middleware?: TMiddlewares;\n  key: ServerQueryKey<\n    TInputSchema extends ServerFnSchema ? InferServerFnSchemaOutput<TInputSchema> : unknown,\n    ServerFnMiddlewareContext<TMiddlewares>\n  >;\n  /** Milliseconds, a duration string such as \"30s\", or false for no expiry. */\n  staleTime?: ServerQueryStaleTime;\n  /** Allow the configured client cache persistence adapter to store results on the device. */\n  persist?: boolean;\n  handler: ServerFnHandler<\n    TInputSchema extends ServerFnSchema ? InferServerFnSchemaOutput<TInputSchema> : unknown,\n    unknown,\n    ServerFnMiddlewareContext<TMiddlewares>\n  >;\n};\n\nexport type ServerQuery<TInput, TResult> = ([unknown] extends [TInput]\n  ? (input?: TInput) => Promise<TResult>\n  : (input: TInput) => Promise<TResult>) & {\n  readonly __farmServerQuery: true;\n  readonly __farmServerQueryInput?: unknown;\n  readonly __farmServerQueryOutput?: unknown;\n};\n\nexport const FARM_SERVER_QUERY_SYMBOL = Symbol.for(\"farm.server-query\");\n\ntype AnyServerQueryOptions = {\n  input?: ServerFnSchema;\n  output?: ServerFnSchema;\n  middleware?: readonly AnyServerFnMiddleware[];\n  key: ServerQueryKey<any, any>;\n  staleTime?: ServerQueryStaleTime;\n  persist?: boolean;\n  handler: ServerFnHandler<any, any, any>;\n};\n\nconst requestInflight = new WeakMap<Request, Map<string, Promise<unknown>>>();\nconst detachedInflight = new Map<string, Promise<unknown>>();\n\nexport function createServerQuery<\n  TInputSchema extends ServerFnSchema,\n  TOutputSchema extends ServerFnSchema,\n  const TMiddlewares extends readonly AnyServerFnMiddleware[] = readonly [],\n>(\n  options: ServerQueryOutputOptions<TInputSchema, TOutputSchema, TMiddlewares>,\n): ServerQuery<\n  InferServerFnSchemaInput<TInputSchema>,\n  Awaited<InferServerFnSchemaOutput<TOutputSchema>>\n>;\nexport function createServerQuery<\n  TOutputSchema extends ServerFnSchema,\n  const TMiddlewares extends readonly AnyServerFnMiddleware[] = readonly [],\n>(\n  options: ServerQueryOutputOptions<undefined, TOutputSchema, TMiddlewares>,\n): ServerQuery<unknown, Awaited<InferServerFnSchemaOutput<TOutputSchema>>>;\nexport function createServerQuery<\n  TSchema extends ServerFnSchema,\n  TResult,\n  const TMiddlewares extends readonly AnyServerFnMiddleware[] = readonly [],\n>(\n  options: ServerQueryOptions<TSchema, TResult, TMiddlewares>,\n): ServerQuery<InferServerFnSchemaInput<TSchema>, Awaited<TResult>>;\nexport function createServerQuery<\n  TResult,\n  const TMiddlewares extends readonly AnyServerFnMiddleware[] = readonly [],\n>(\n  options: ServerQueryOptions<undefined, TResult, TMiddlewares>,\n): ServerQuery<unknown, Awaited<TResult>>;\nexport function createServerQuery(options: AnyServerQueryOptions): ServerQuery<unknown, unknown> {\n  if (!options || typeof options.handler !== \"function\") {\n    throw new TypeError(\"createServerQuery requires a handler function\");\n  }\n  if (typeof options.key !== \"function\") {\n    throw new TypeError(\"createServerQuery requires a key function\");\n  }\n\n  const staleTime = normalizeServerQueryStaleTime(options.staleTime);\n  const execute = (\n    createServerFn as (options: any) => ServerFn<unknown, FarmServerQueryResult<any>>\n  )({\n    input: options.input,\n    middleware: options.middleware,\n    handler: async (context: ServerFnHandlerContext<any, any>) => {\n      const routeKey = await options.key(context);\n      assertServerQueryKey(routeKey);\n\n      const clientKey = createRouteDataCacheKey(routeKey);\n      const cacheKey = createFarmCacheKey([\"route-data\", routeKey]);\n      const producer = async () => {\n        const value = await options.handler(context);\n        return _parseServerFnSchema(options.output, value, \"output\", \"createServerQuery\");\n      };\n\n      let data: unknown;\n      let updatedAt: number;\n\n      if (staleTime === 0) {\n        data = await dedupeServerQueryRequest(cacheKey, context.request, producer);\n        updatedAt = Date.now();\n      } else {\n        const cache = getFarmDataCache();\n        data = await cache.getOrSet(cacheKey, producer, {\n          tags: [createRouteDataCacheTag(routeKey)],\n          revalidate: staleTime === false ? false : staleTime / 1000,\n        });\n        updatedAt =\n          (await cache.getEntryAsync(cacheKey, { allowStale: true }))?.createdAt ?? Date.now();\n      }\n\n      return createFarmServerQueryResult(data, {\n        key: clientKey,\n        staleTime,\n        updatedAt,\n        ...(options.persist === true ? { persist: true } : {}),\n      });\n    },\n  });\n\n  const query = async (input?: unknown) => {\n    const result = await execute(input);\n    return getServerActionExecutionContext() ? result : result.data;\n  };\n\n  Object.defineProperties(query, {\n    [FARM_SERVER_QUERY_SYMBOL]: {\n      value: true,\n      enumerable: false,\n    },\n    __farmServerQuery: {\n      value: true,\n      enumerable: false,\n    },\n    __farmServerQueryInput: {\n      value: options.input,\n      enumerable: false,\n    },\n    __farmServerQueryOutput: {\n      value: options.output,\n      enumerable: false,\n    },\n  });\n\n  return query as ServerQuery<unknown, unknown>;\n}\n\nasync function dedupeServerQueryRequest<TData>(\n  key: string,\n  request: Request | undefined,\n  producer: () => Promise<TData>,\n): Promise<TData> {\n  let inflight: Map<string, Promise<unknown>>;\n  if (request) {\n    inflight = requestInflight.get(request) ?? new Map();\n    requestInflight.set(request, inflight);\n  } else {\n    inflight = detachedInflight;\n  }\n\n  const existing = inflight.get(key) as Promise<TData> | undefined;\n  if (existing) return existing;\n\n  const promise = Promise.resolve().then(producer);\n  inflight.set(key, promise);\n\n  try {\n    return await promise;\n  } finally {\n    inflight.delete(key);\n  }\n}\n\nfunction normalizeServerQueryStaleTime(value: ServerQueryStaleTime | undefined): number | false {\n  if (value === false) return false;\n  if (value === undefined) return 0;\n\n  if (typeof value === \"number\") {\n    if (!Number.isFinite(value) || value < 0) {\n      throw new TypeError(\"createServerQuery staleTime must be a non-negative duration\");\n    }\n    return value;\n  }\n\n  const match = value.match(/^(\\d+(?:\\.\\d+)?)(ms|s|m|h)$/);\n  if (!match) {\n    throw new TypeError('createServerQuery staleTime must look like \"250ms\", \"30s\", \"5m\", or \"1h\"');\n  }\n\n  const amount = Number(match[1]);\n  const unit = match[2];\n  const multiplier = unit === \"ms\" ? 1 : unit === \"s\" ? 1000 : unit === \"m\" ? 60_000 : 3_600_000;\n  return amount * multiplier;\n}\n\nfunction assertServerQueryKey(key: unknown): asserts key is RouteDataCacheKey {\n  if (typeof key === \"string\") {\n    if (key.trim().length > 0) return;\n  } else if (Array.isArray(key)) {\n    return;\n  }\n\n  throw new TypeError(\"createServerQuery key must return a non-empty string or an array\");\n}\n"],"mappings":"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","names":[]}