import { Dictionary } from 'ts-essentials'; import { SchemaLoader } from '../schema'; import { OriginProvider } from './graphql-writer'; import { Logger, TimestampedRecord } from './types'; export interface GraphQLBackend { healthCheck(): Promise; postQuery(query: any, variables?: any): Promise; } export interface Upsert { id?: string; readonly model: string; readonly object: Dictionary; readonly foreignKeys: Dictionary; } export declare class UpsertBuffer { private readonly upsertBuffer; add(upsert: Upsert): void; size(): number; pop(model: string): Upsert[] | undefined; get(model: string): Upsert[] | undefined; private getInternal; clear(): number; } export declare function serialize(obj: Dictionary): string; export declare function serializeValue(obj: any): string; /** * Like lodash pick with (1) null value replacement and (2) type-aware * normalization. */ export declare function strictPick(obj: any, keys: string[], keyTypes?: Dictionary, nullValue?: string): any; /** * Groups objects by primary key and then merges all related objects into * a single object where last-wins for overlapping properties. */ export declare function mergeByPrimaryKey(objects: any[], primaryKeys: string[]): any[]; /** * Execute async callback for each batch of upserts. */ export declare function batchIterator(batches: Upsert[][], callback: (batch: Upsert[]) => Promise): AsyncIterable; /** * Separates objects into arrays of objects * that all have the same keys. */ export declare function groupByKeys(objects: any[]): any[][]; /** * Separates upserts into levels. Level 0 has no dependencies on other levels. * Level 1 depends on Level 0, 2 on 1 and so on. */ export declare function toLevels(upserts: Upsert[]): Upsert[][]; export declare function toPostgresArrayLiteral(value: any[]): string; /** * Client for writing records as GraphQL mutations. The client supports 3 * kinds of writes: Upserts, Updates and Deletes. * * For upserts (when upsertBatchSize is greater than 0), the high-level * algorithm is: * * > For each record, build a tree of Upserts. The tree has more than one node * if the current record represents a * nested entity (e.g. branch record referencing repo which, in-turn, * references org). * > Buffer Upserts and index each by model (e.g. vcs_Branch) * > When batch size limit is reached, execute a single insert mutation per * model. Start with leaves of Upsert tree. * > After inserting a batch, copy the id of each record back to the Upsert * object. When inserting subsequent batches, * required foreign keys will be read from the Upsert tree and copied to * the appropriate batch mutation. * * Note: there is a complication in this algorithm for self-referent models * (i.e. org_Employee's manager relationship). * For these models, we split the batch into "levels". The first level consists * of records with no dependencies on * records of the same type. The second depends on the first and so on. * * For Updates and Deletes (which are much less frequent) we have a separate * write buffer. This buffer is * flushed if it reaches capacity. There is no attempt to combine these into * bulk mutations (as done w/ upserts). */ export declare class GraphQLClient { private readonly logger; private readonly schemaLoader; private readonly backend; private schema; private tableNames; private tableDependencies; private supportsSetCtx; private readonly mutationBatchSize; private readonly upsertBatchSize; private readonly writeBuffer; private readonly upsertBuffer; private readonly selfReferentModels; private readonly updateResetLimit; private readonly resetPageSize; private resetLimitMillis; constructor(logger: Logger, schemaLoader: SchemaLoader, backend: GraphQLBackend, upsertBatchSize: number, mutationBatchSize: number, updateResetLimit?: boolean, resetPageSize?: number); healthCheck(): Promise; loadSchema(): Promise; private getSchema; resetData(originProvider: OriginProvider, models: ReadonlyArray, isResetSync: boolean): Promise; /** * FAI-15611 - Added deleteByIdWithConditions to put guardrails around * deleteByIds. This prevents deletes of records that are updated after * the ids are fetched or if the id lookup is performed against a stale * replica. */ deleteByIdWithConditions(model: string, ids: string[], deleteConditions: any, session: string): Promise; deleteById(model: string, ids: string[], session: string): Promise; writeRecord(model: string, record: Dictionary, origin: string): Promise; private flushUpsertBuffer; private execUpsert; private doFlushUpsertBuffer; writeTimestampedRecord(record: TimestampedRecord): Promise; private writeUpdateRecord; private writeDeletionRecord; private postQuery; flush(): Promise; private flushWriteBuffer; /** * Constructs a gql query from an array of json mutations. * The outputted qql mutation might look like: * * mutation { * i1: insert_cicd_Artifact_one(object: {uid: "u1b"}) { * id * refreshedAt * } * i2: insert_cicd_Artifact_one(object: {uid: "u2b"}) { * id * refreshedAt * } * } * * Notable here are the i1/i2 aliases. These are required when multiple * operations * share the same name (e.g. insert_cicd_Artifact_one) and are supported in * jsonToGraphQLQuery with __aliasFor directive. * * @return batch gql mutation or undefined if the input is undefined, empty * or doesn't contain any mutations. */ static batchMutation(queries: any[]): string | undefined; /** * Like batchMutation, but groups insert_*_one mutations by model * and on_conflict into bulk insert_* mutations with objects arrays. * Inserts of different types can be interleaved and will still be * grouped (e.g. [insert_A, insert_B, insert_A] groups both A's). * Only non-insert mutations (e.g. deletes) break grouping — any * non-insert resets all active groups so that subsequent inserts * of the same type start a new bulk group. This preserves the * ordering guarantee that non-insert mutations execute between * the insert groups before and after them. * * Note: bulk inserts return { affected_rows } instead of * per-object { id, refreshedAt }. Callers that need individual * record IDs should use batchMutation instead. * * @return batch gql mutation or undefined if the input is * undefined, empty or doesn't contain any mutations. */ static bulkBatchMutation(queries: any[]): string | undefined; private createWhereClause; private createMutationObject; private addUpsert; private objectWithForeignKeys; /** * returns serialized version of keys fields */ private serializedPrimaryKey; private toUpsertOps; private isValidField; private formatFieldValue; private createConflictClause; private createUpsertConflictClause; }