import {z} from 'zod' export type NumericBucketizerDef = {bucketWidth?: number | undefined, logBase?: number | undefined, manualPartitions?: Array | undefined}; export type Dimension = {numericBucketizer?: NumericBucketizerDef | undefined, sqlExpression?: string | undefined, timeBucketizer?: string | undefined, timeUnit?: string | undefined}; export type Join = {alias?: string | undefined, conditions?: Array | undefined, table?: string | undefined}; export type Measure = {rowFilters?: Array | undefined, sqlExpression?: string | undefined}; export type TimeFilter = {column?: string | undefined, from?: string | undefined, to?: string | undefined}; export type AQLQuery = {dimensions?: Array | undefined, joins?: Array | undefined, measures?: Array | undefined, now?: number | undefined, rowFilters?: Array | undefined, table?: string | undefined, timeFilter?: TimeFilter | undefined, timezone?: string | undefined}; export type AQLRequest = {queries?: Array | undefined}; export type CreateApiQueryAqlArgs = {device?: number | undefined, verbose?: number | undefined, debug?: number | undefined, profiling?: string | undefined, q?: string | undefined, timeout?: number | undefined, accept?: string | undefined, origin?: string | undefined, body: AQLRequest}; export type CreateApiQueryAqlOptions = {baseUrl: string, fetch?: typeof fetch | undefined}; export const oopkQueryStats = z.object({batches: z.number().int().optional(), latency: z.number().optional(), numBatchSkipped: z.number().int().optional(), records: z.number().int().optional(), stages: z.unknown(), tranBytes: z.number().int().optional(), tranCalls: z.number().int().optional()}); export const type = z.number().int(); export const expr = z.object({String: z.string().optional(), Type: type.optional()}); export const duration = z.number().int(); export const dimCountsPerDimWidth = z.array(z.number().int()); export const oOPKContext = z.object({aggregate: z.number().int().optional(), archiveStats: oopkQueryStats.optional(), deviceMem: z.number().int().optional(), dimRowBytes: z.number().int().optional(), dimensionVectorIndex: z.array(z.number().int()).optional(), dimensions: z.array(expr).optional(), durationWaitedForDevice: duration.optional(), foreignTableCommonFilters: z.array(expr).optional(), liveStats: oopkQueryStats.optional(), mainTableCommonFilters: z.array(expr).optional(), measure: expr.optional(), measureBytes: z.number().int().optional(), numDims: dimCountsPerDimWidth.optional(), prefilters: z.array(expr).optional(), resultSize: z.number().int().optional(), timeFilters: z.array(expr).optional()}); export const numericBucketizerDef = z.object({bucketWidth: z.number().optional(), logBase: z.number().optional(), manualPartitions: z.array(z.number()).optional()}); export const dimension = z.object({numericBucketizer: numericBucketizerDef.optional(), sqlExpression: z.string().optional(), timeBucketizer: z.string().optional(), timeUnit: z.string().optional()}); export const join = z.object({alias: z.string().optional(), conditions: z.array(z.string()).optional(), table: z.string().optional()}); export const measure = z.object({rowFilters: z.array(z.string()).optional(), sqlExpression: z.string().optional()}); export const timeFilter = z.object({column: z.string().optional(), from: z.string().optional(), to: z.string().optional()}); export const aQLQuery = z.object({dimensions: z.array(dimension).optional(), joins: z.array(join).optional(), measures: z.array(measure).optional(), now: z.number().int().optional(), rowFilters: z.array(z.string()).optional(), table: z.string().optional(), timeFilter: timeFilter.optional(), timezone: z.string().optional()}); export const boundaryType = z.number().int(); export const tableScanner = z.object({archiveBatchIDEnd: z.number().int().optional(), archiveBatchIDStart: z.number().int().optional(), columnUsage: z.unknown(), columns: z.array(z.number().int()).optional(), equalityPrefilterValues: z.array(z.number().int()).optional(), rangePrefilterBoundaries: z.array(boundaryType).optional(), rangePrefilterValues: z.array(z.number().int()).optional(), shards: z.array(z.number().int()).optional()}); export const aQLQueryContext = z.object({ReturnHLLData: z.boolean().optional(), debug: z.boolean().optional(), device: z.number().int().optional(), error: z.string().optional(), oopk: oOPKContext.optional(), prefilters: z.array(z.number().int()).optional(), profiling: z.string().optional(), query: aQLQuery.optional(), scanners: z.array(tableScanner).optional(), tableIDs: z.record(z.string(), z.number().int()).optional()}); export const aQLTimeSeriesResult = z.record(z.string(), z.object({})); export const aQLResponse = z.object({context: z.array(aQLQueryContext).optional(), errors: z.array(z.string()).optional(), results: z.array(aQLTimeSeriesResult).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 }; /** * query in AQL * * POST /query/aql */ export const createApiQueryAql = async (args: CreateApiQueryAqlArgs, options: CreateApiQueryAqlOptions) => { const {baseUrl, fetch: fetchFn = fetch} = options ?? {} const query = new URLSearchParams() const headers = new Headers() headers.set('content-type', "application/json") headers.set('accept', "application/json") if (args.device != null) query.append("device", String(args.device)) if (args.verbose != null) query.append("verbose", String(args.verbose)) if (args.debug != null) query.append("debug", String(args.debug)) if (args.profiling != null) query.append("profiling", String(args.profiling)) if (args.q != null) query.append("q", String(args.q)) if (args.timeout != null) query.append("timeout", String(args.timeout)) if (args.accept != null) headers.set("accept", String(args.accept)) if (args.origin != null) headers.set("origin", String(args.origin)) const url = `${baseUrl.replace(/\/+$/, '')}/query/aql${query.size > 0 ? `?${query}` : ''}` const res = await fetchFn(url, {method: "POST", headers, body: JSON.stringify(args.body)}) if (!res.ok) throw new Error(`Request failed with status ${res.status}`, {cause: {status: res.status, body: await res.text()}}) return validateResponse(aQLResponse, await res.json()) };