/** * Opportunities resource namespace for the CommonGrants API. */ import { z } from "zod"; import type { Client, FetchManyOptions } from "../client"; import type { OpportunityBase, OppStatusOptions } from "../../types"; import { OpportunityBaseSchema } from "../../schemas"; import type { CustomFilterInput } from "../../extensions/custom-filters"; import type { CustomFilterType, PluginRoutes } from "../../extensions/types"; import type { ListResult, SearchResult } from "../results"; import { Resource } from "./base"; /** * Constrains the schema parameter to any Zod schema whose `.parse()` output * is at least an `OpportunityBase`. * * We intentionally constrain on the OUTPUT type (`OpportunityBase`) rather than * the concrete schema type (`typeof OpportunityBaseSchema`). This is because * `withCustomFields()` returns a schema with a different internal Zod type tree * (e.g. a typed `ZodOptional` for `customFields` instead of `ZodNullable`), * even though its parsed output is still a superset of `OpportunityBase`. Constraining * on the output type accepts both the base schema and any extended variant. */ type OppSchema = z.ZodType; /** Raw `{ operator, value }` filter object, as produced by the `F.*` helpers. */ type RawFilter = { operator: string; value: unknown; }; /** * The declared filter specs for `opportunities.search` in a routes type. * `definePlugin` preserves the literal `routes` type (its `const TRoutes` generic), * so a plugin defined inline yields concrete filter-name and filterType literals here. */ type RouteFilters = R extends { opportunities: { search: { filters: infer Fs; }; }; } ? Fs : never; /** The declared custom-filter names for `opportunities.search` in a routes type. */ type CustomFilterNames = Extract, string>; /** * Typed filter bag for `search({ filters })`. * * Declared filter names surface in editor autocomplete with the value typed by * their declared `filterType` (a wrong value family is a compile error), while * arbitrary keys remain accepted — the spec supports ad-hoc (escape-hatch) filters, * so an unknown key cannot be rejected at the type level without dropping ad-hoc * support (a typo is structurally an intentional ad-hoc key). Runtime validation * backstops both cases. */ export type CustomFilterBag = { [K in CustomFilterNames]?: RouteFilters[K] extends { filterType: infer FT extends CustomFilterType; } ? CustomFilterInput : RawFilter; } & Record; /** Options for getting a single opportunity */ export interface GetOptions { /** Zod schema to parse and type the response. Defaults to the bound (plugin or base) schema. */ schema?: S; } /** Options for listing opportunities */ export interface ListOptions extends FetchManyOptions> { /** Zod schema to parse and type each item. Defaults to the bound (plugin or base) schema. */ schema?: S; } /** Options for searching opportunities */ export interface SearchOptions extends FetchManyOptions> { /** Text query to search for in opportunity titles and descriptions */ query?: string; /** * Filter by opportunity statuses (shorthand for the `status` filter). * @deprecated Pass status through `filters` instead; this shorthand will be * removed in a future release. */ statuses?: OppStatusOptions[]; /** * Flat custom-filter bag (filter name → `{ operator, value }`, e.g. built with `F.*`). * Classified into the `OppFilters` request body via `classifyFilters` when present; * an invalid value on any key throws `FilterError` before the request is sent. */ filters?: CustomFilterBag; /** Zod schema to parse and type each item. Defaults to the bound (plugin or base) schema. */ schema?: S; } /** * Opportunities resource - provides methods for interacting with opportunities. * * @example * ```ts * const client = new Client({ baseUrl: "https://api.example.org" }); * * // Get a single opportunity * const opp = await client.opportunities.get("opp-123"); * * // List opportunities * const list = await client.opportunities.list(); * ``` */ export declare class Opportunities extends Resource { private readonly basePath; constructor(client: Client, boundSchema?: z.ZodType, routes?: R); /** Per-call override wins; otherwise the bound (plugin or base) schema. */ private resolveSchema; /** * Get a specific opportunity by ID. * * A single requested entity that does not parse is a real error, so `get()` * is fail-hard: a malformed response throws. * * @param id - The opportunity ID * @param options - Optional settings; use `schema` for typed custom field access. * @returns The opportunity data * @throws {Error} If the request fails or the response does not parse * * @example * ```ts * // Default usage * const opp = await client.opportunities.get("123e4567-e89b-12d3-a456-426614174000"); * console.log(opp.title); * * // With a custom-fields schema for typed access * const OpportunitySchema = withCustomFields(OpportunityBaseSchema, [ * { key: "legacyId", fieldType: "integer", value: z.number().int() }, * ] as const); * const typed = await client.opportunities.get(id, { schema: OpportunitySchema }); * console.log(typed.customFields?.legacyId?.value); // typed as number * ``` */ get>(id: string, options?: GetOptions): Promise>; /** * List opportunities with auto-pagination by default. * * Rows are parsed individually: valid rows land in `items`, per-row failures * in `errors` (set `onParseError: "throw"` to fail hard on the first bad row). * * @param options - Pagination and schema options. If `page` is specified, fetches only that page. * Use `schema` for typed custom field access. * @returns Paginated list of opportunities plus per-row parse failures * @throws {Error} If the request fails * * @example * ```ts * // Auto-paginate to fetch all opportunities (default) * const all = await client.opportunities.list(); * * // Auto-paginate with custom limits * const limited = await client.opportunities.list({ maxItems: 500, pageSize: 50 }); * * // Get a specific page (disables auto-pagination) * const page2 = await client.opportunities.list({ page: 2, pageSize: 10 }); * * // With a custom-fields schema * const typed = await client.opportunities.list({ schema: OpportunitySchema }); * ``` */ list>(options?: ListOptions): Promise>>; /** * Search for opportunities based on query text and filters. * * Supports auto-pagination by default. If `page` is specified, only fetches that page. * * Filter validation is fail-fast: an invalid value on any filter — standard, * registered custom, or ad-hoc — throws `FilterError` before the request is * sent. A caught `FilterError`'s `.sourceValue` may carry PII; redact before * logging. `filterInfo.errors` on the response carries server-returned errors only. * Rows are parsed individually: valid rows land in `items`, per-row failures * in `errors` (set `onParseError: "throw"` to fail hard on the first bad row). * * @param options - Search options including query text, status filters, pagination, and schema * @returns Filtered list of opportunities plus per-row parse failures * @throws {FilterError} If a filter value is invalid (before any request) * @throws {Error} If the request fails * * @example * ```ts * // Search with query text (auto-paginates) * const results = await client.opportunities.search({ query: "education" }); * * // Search with status filter * const openOpps = await client.opportunities.search({ statuses: ["open"] }); * * // Search with both query and statuses * const filtered = await client.opportunities.search({ * query: "community", * statuses: ["open", "forecasted"], * }); * * // Get a specific page (disables auto-pagination) * const page2 = await client.opportunities.search({ * query: "grants", * page: 2, * pageSize: 10, * }); * * // Auto-paginate with limits * const limited = await client.opportunities.search({ * query: "research", * maxItems: 100, * pageSize: 25, * }); * * // With a custom-fields schema * const typed = await client.opportunities.search({ query: "test", schema: OpportunitySchema }); * ``` */ search>(options?: SearchOptions): Promise>>; /** * Builds the search request body from options. * * Filter classification is fail-fast: `classifyFilters` throws `FilterError` * on the first invalid value, so a body is only produced for valid input. */ private buildSearchBody; /** Fetches a single search page */ private fetchSearchPage; } export {}; //# sourceMappingURL=opportunities.d.ts.map