import {z} from 'zod' export type GetApiV1SecuritiesTickerFundOwnersArgs = {ticker: string, limit?: number | undefined, offset?: number | undefined, registrantOrSeries?: string | undefined}; export type GetApiV1SecuritiesTickerFundOwnersOptions = {baseUrl?: string | undefined, ApiKey?: string | undefined, fetch?: typeof fetch | undefined}; export const fundOwnerDto = z.object({registrantName: z.string().nullable().optional(), seriesName: z.string().nullable().optional(), reportPeriodDate: z.string().optional(), balance: z.number().optional(), units: z.string().nullable().optional(), valueUsd: z.number().optional(), percentOfNetAssets: z.number().optional(), payoffProfile: z.string().nullable().optional()}); export const pageMeta = z.object({limit: z.number().int().optional(), offset: z.number().int().optional(), count: z.number().int().optional(), hasMore: z.boolean().optional(), total: z.number().int().nullable().optional()}); export const fundOwnersResponse = z.object({totalPositions: z.number().int().optional(), data: z.array(fundOwnerDto).nullable().optional(), meta: pageMeta.optional()}); export const validateResponse = async ( schema: { readonly '~standard': { readonly validate: (value: unknown) => ({ readonly value: Output; readonly issues?: undefined } | { readonly issues: ReadonlyArray<{ readonly message: string; readonly path?: ReadonlyArray | undefined }> }) | Promise<{ readonly value: Output; readonly issues?: undefined } | { readonly issues: ReadonlyArray<{ readonly message: string; readonly path?: ReadonlyArray | undefined }> }> } }, value: unknown ): Promise => { const result = await schema['~standard'].validate(value) if (result.issues !== undefined) { const detail = result.issues .map(({ message, path }) => path?.length ? `${path.map((segment) => String(typeof segment === 'object' ? segment.key : segment)).join('.')}: ${message}` : message ) .join('; ') throw new Error(`Response does not match the schema: ${detail}`, { cause: { issues: result.issues } }) } return result.value }; /** * Registered funds and ETFs holding an exact stock or ETF listing on each fund series' most recent SEC Form NPORT-P report, largest position first. Matched by the listed security's authoritative CUSIP, or by its ISIN for a listing on another exchange, and series that stopped filing more than 18 months ago are excluded, so an exited position never shows as current. Report dates differ per series (each fund files on its own fiscal quarter), so values are as of each row's reportPeriodDate. * * GET /v1/securities/{ticker}/fund-owners */ export const getApiV1SecuritiesTickerFundOwners = async (args: GetApiV1SecuritiesTickerFundOwnersArgs, options?: GetApiV1SecuritiesTickerFundOwnersOptions) => { const {baseUrl = "https://api.equibles.com", fetch: fetchFn = fetch, ApiKey} = options ?? {} const query = new URLSearchParams() const headers = new Headers() headers.set('accept', "application/json") if (args.limit != null) query.append("limit", String(args.limit)) if (args.offset != null) query.append("offset", String(args.offset)) if (args.registrantOrSeries != null) query.append("registrantOrSeries", String(args.registrantOrSeries)) if (ApiKey !== undefined) headers.set('authorization', `Bearer ${ApiKey}`) const url = `${baseUrl.replace(/\/+$/, '')}/v1/securities/${encodeURIComponent(String(args.ticker))}/fund-owners${query.size > 0 ? `?${query}` : ''}` const res = await fetchFn(url, {method: "GET", headers}) if (!res.ok) throw new Error(`Request failed with status ${res.status}`, {cause: {status: res.status, body: await res.text()}}) return validateResponse(fundOwnersResponse, await res.json()) };