// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 import { type DeepReadOnlyObject, type UnwrapArray, type UnionToIntersection, type Prettify, __modelMeta__, } from '@aws-amplify/data-schema-types'; import type { Observable } from 'rxjs'; import type { ConversationRoute } from '../../ai/ConversationType'; import type { ClientSchemaByEntityType, ClientSchemaByEntityTypeBaseShape, } from '../../ClientSchema'; import type { ExtractNestedTypes } from '../../ClientSchema/utilities/'; import type { Select, StringFilter, NumericFilter, BooleanFilters, SubscriptionStringFilter, SubscriptionNumericFilter, SubscriptionBooleanFilters, } from '../../util'; import { AmplifyServer } from '../bridge-types'; // temporarily export symbols from `data-schema-types` because in case part of the // problem with the runtime -> data-schema migration comes down to a mismatch // around this symbol and it's extractor. // // before switching to declare these here, we need to prove it won't break any // customer experiences. this *might* need to happen as a breaking change. // // export declare const __modelMeta__: unique symbol; // export type ExtractModelMeta> = // T[typeof __modelMeta__]; export { __modelMeta__, ExtractModelMeta, } from '@aws-amplify/data-schema-types'; type Model = Record; // #region Return Value Mapped Types /** * Currently this omits any object-type fields. Update this when we add custom types/enums. */ type NonRelationshipFields = { [Field in keyof M as UnwrapArray extends Record ? never : Field]: M[Field]; }; type WithOptionalsAsNullishOnly = T extends Array ? Array> : T extends (...args: any) => any ? T : T extends object ? { [K in keyof T]-?: WithOptionalsAsNullishOnly; } : Exclude; /** * Selection set-aware CRUDL operation return value type * * @returns model type with default selection set; otherwise generates return type from custom sel. set. Optionality is removed from both return types. */ type ReturnValue< M extends ClientSchemaByEntityTypeBaseShape['models'][string], FlatModel extends Model, Paths extends ReadonlyArray>, > = Paths extends undefined | never[] ? WithOptionalsAsNullishOnly : WithOptionalsAsNullishOnly< CustomSelectionSetReturnValue >; /** * This mapped type traverses the SelectionSetReturnValue result and the original FlatModel, restoring array types * that were flattened in DeepPickFromPath * * @typeParam Result - this is the result of applying the selection set path to FlatModel; return type of UnionToIntersection> * @typeParam FlatModel - the reference model shape; return type of ResolvedModel * * Note: we wrap `Result` and `FlatModel` in NonNullable, because recursive invocations of this mapped type * can result in the type arguments containing `{} | null | undefined` which breaks indexed access, e.g. Result[K] * * Using NonNullable<> directly inside the mapped type is significantly more performant here than attempting to pre-compute in the type params, * e.g., `type RestoreArrays, NonNullableFlatModel = NonNullable> = {...}` */ type RestoreArrays = { [K in keyof NonNullable]: K extends keyof NonNullable ? Array extends NonNullable[K] ? HandleArrayNullability< NonNullable[K], NonNullable[K] > : NonNullable[K] extends Record ? RestoreArrays[K], NonNullable[K]> : NonNullable[K] : never; }; /** * This mapped type gets called by RestoreArrays and it restores the expected * nullability in array fields (e.g. nullable vs. required value & nullable vs. required array) */ type HandleArrayNullability = Array extends Result ? // If Result is already an array, return it as is. Result : NonNullable extends Array ? // is the array nullable? null extends FlatModel ? // is the value nullable? null extends InnerValue ? // value and array are nullable - a.ref('SomeType').array() Array> | null> | null : // value required; array nullable - a.ref('SomeType').required().array() Array>> | null : null extends InnerValue ? // value nullable; array required - a.ref('SomeType').array().required() Array> | null> : // value required; array required - a.ref('SomeType').required().array().required() Array>> : never; /** * Generates flattened, readonly return type using specified custom sel. set */ type CustomSelectionSetReturnValue< FlatModel extends Model, Paths extends string, > = Prettify< DeepReadOnlyObject< RestoreArrays< UnionToIntersection>, FlatModel > > >; /** * Picks object properties that match provided dot-separated Path * * @typeParam FlatModel * @typeParam Path - string union of dot-separated paths * * @returns union of object slices * * @example * ### Given * ```ts * FlatModel = { title: string; description?: string | null; comments: { content: string; readonly id: string; readonly createdAt: string; readonly updatedAt: string; }[]; readonly id: string; readonly createdAt: string; readonly updatedAt: string; } Path = 'title' | 'comments.id' | 'comments.content' * ``` * ### Returns * ```ts * { title: string } | { comments: { id: string} } | { comments: { content: string} } * ``` * * @privateRemarks * * Intersections on arrays have unexpected behavior in TypeScript: * see: https://github.com/microsoft/TypeScript/issues/41874 and https://github.com/microsoft/TypeScript/issues/39693 * * To work around this limitation, DeepPickFromPath flattens Arrays of Models (e.g. { comments: { id: string}[] } => { comments: { id: string} }) * Arrays are then restored downstream in RestoreArrays */ type DeepPickFromPath< FlatModel extends Model, Path extends string, > = FlatModel extends undefined ? DeepPickFromPath, Path> | undefined : FlatModel extends null ? DeepPickFromPath, Path> | null : FlatModel extends any[] ? DeepPickFromPath, Path> : Path extends `${infer Head}.${infer Tail}` ? Head extends keyof FlatModel ? Tail extends '*' ? { [k in Head]: FlatModel[Head] extends Record ? NonRelationshipFields> : NonRelationshipFields>; } : { [k in Head]: DeepPickFromPath } : never : Path extends keyof FlatModel ? { [K in Path]: FlatModel[Path] } : never; /** * This mapped type gets called by DeepPickFromPath when user uses selection * set on a query to a many-to-many relationship join table. It flattens the Arrays of Models referenced in the join table. * * (e.g. { customer: { orders: Order[]} } => { customer: { orders: Order} }) */ type FlattenArrays = { [K in keyof T]: UnwrapArray; }; /** * Generates custom selection set type with up to 6 levels of nested fields * * @returns string[] where each string is a field in the model * recurses over nested objects - such as relationships and custom types - generating a `field.*` type value to select all fields in that nested type, * as well as a dot-delimited set of fields for fine-grained selection of particular fields in the nested type (see example below) * * @example * ```ts * FlatModel = { * id: string * title: string * comments: { * id:: string * content: string * }[] * } *``` * * ### Result * ``` * 'id' | 'title' | 'comments.*' | 'comments.id' | 'comments.content' * ``` * * @privateRemarks * * explicit recursion depth pattern ref: https://github.com/microsoft/TypeScript/blob/main/src/lib/es2019.array.d.ts#L1-L5 * * this pattern puts an upper bound on the levels of recursion in our mapped type * * it guards against infinite recursion when generating the selection set type for deeply-nested models * and especially for bi-directional relationships which are infinitely recursable by their nature * */ type ModelPathInner< FlatModel extends Record, // actual recursive Depth is 6, since we decrement down to 0 Depth extends number = 5, // think of this as the initialization expr. in a for loop (e.g. `let depth = 5`) RecursionLoop extends number[] = [-1, 0, 1, 2, 3, 4], Field = keyof FlatModel, > = { done: Field extends string ? `${Field}.*` : never; recur: Field extends string ? NonNullable> extends Record ? | `${Field}.${ModelPathInner< NonNullable>, // this decrements `Depth` by 1 in each recursive call; it's equivalent to the update expr. afterthought in a for loop (e.g. `depth -= 1`) RecursionLoop[Depth] >}` | `${Field}.*` : `${Field}` : never; // this is equivalent to the condition expr. in a for loop (e.g. `depth !== -1`) }[Depth extends -1 ? 'done' : 'recur']; export type ModelPath> = ModelPathInner; export type SelectionSet< Model, Path extends ReadonlyArray>, FlatModel extends Record< string, unknown // Remove conditional in next major version > = Model extends ClientSchemaByEntityTypeBaseShape['models'][string] ? Model['__meta']['flatModel'] : Record, > = // Remove conditional in next major version Model extends ClientSchemaByEntityTypeBaseShape['models'][string] ? WithOptionalsAsNullishOnly< CustomSelectionSetReturnValue > : any; // #endregion // #region Interfaces copied from `graphql` package // From https://github.com/graphql/graphql-js/blob/main/src/error/GraphQLError.ts /** * See: https://spec.graphql.org/draft/#sec-Errors */ export interface GraphQLFormattedError { /** * A short, human-readable summary of the problem that **SHOULD NOT** change * from occurrence to occurrence of the problem, except for purposes of * localization. */ readonly message: string; /** * The AppSync exception category. Indicates the source of the error. */ readonly errorType: string; /** * Additional error metadata that can be surfaced via error handling resolver utils: * * JS - https://docs.aws.amazon.com/appsync/latest/devguide/built-in-util-js.html#utility-helpers-in-error-js * * VTL - https://docs.aws.amazon.com/appsync/latest/devguide/utility-helpers-in-util.html#utility-helpers-in-error */ readonly errorInfo: null | { [key: string]: unknown }; /** * If an error can be associated to a particular point in the requested * GraphQL document, it should contain a list of locations. */ readonly locations?: ReadonlyArray; /** * If an error can be associated to a particular field in the GraphQL result, * it _must_ contain an entry with the key `path` that details the path of * the response field which experienced the error. This allows clients to * identify whether a null result is intentional or caused by a runtime error. */ readonly path?: ReadonlyArray; /** * Reserved for implementors to extend the protocol however they see fit, * and hence there are no additional restrictions on its contents. */ readonly extensions?: { [key: string]: unknown }; } /** * Represents a location in a Source. */ export interface SourceLocation { readonly line: number; readonly column: number; } // #endregion export type SingularReturnValue = Promise<{ data: T | null; errors?: GraphQLFormattedError[]; extensions?: { [key: string]: any; }; }>; export type ListReturnValue = Promise<{ data: Array; nextToken?: string | null; errors?: GraphQLFormattedError[]; extensions?: { [key: string]: any; }; }>; export type ObservedReturnValue = Observable; export type ObserveQueryReturnValue = Observable<{ items: T[]; isSynced: boolean; }>; export type LazyLoader = ( options?: IsArray extends true ? { authMode?: AuthMode; authToken?: string; limit?: number; nextToken?: string | null; headers?: CustomHeaders; } : { authMode?: AuthMode; authToken?: string; headers?: CustomHeaders; }, ) => IsArray extends true ? ListReturnValue>> : SingularReturnValue>; export type AuthMode = | 'apiKey' | 'iam' | 'identityPool' | 'oidc' | 'userPool' | 'lambda' | 'none'; type LogicalFilters< Model extends ClientSchemaByEntityTypeBaseShape['models'][string], > = { and?: ModelFilter | ModelFilter[]; or?: ModelFilter | ModelFilter[]; not?: ModelFilter; }; type ModelFilter< Model extends ClientSchemaByEntityTypeBaseShape['models'][string], > = LogicalFilters & { [K in keyof Model['type'] as Model['type'][K] extends LazyLoader ? never : K]?: boolean extends Model['type'][K] ? BooleanFilters : number extends Model['type'][K] ? NumericFilter : StringFilter; }; type LogicalSubscriptionFilters< Model extends ClientSchemaByEntityTypeBaseShape['models'][string], > = { and?: ModelSubscriptionFilter | ModelSubscriptionFilter[]; or?: ModelSubscriptionFilter | ModelSubscriptionFilter[]; not?: ModelSubscriptionFilter; }; type ModelSubscriptionFilter< Model extends ClientSchemaByEntityTypeBaseShape['models'][string], > = LogicalSubscriptionFilters & { [K in keyof Model['type'] as Model['type'][K] extends LazyLoader ? never : K]?: boolean extends Model['type'][K] ? SubscriptionBooleanFilters : number extends Model['type'][K] ? SubscriptionNumericFilter : SubscriptionStringFilter; }; export type ModelSortDirection = 'ASC' | 'DESC'; type ListCpkOptions< Model extends ClientSchemaByEntityTypeBaseShape['models'][string], > = unknown extends Model['__meta']['listOptionsPkParams'] ? unknown : Model['__meta']['listOptionsPkParams'] & { sortDirection?: ModelSortDirection; }; interface ClientSecondaryIndexField { input: object; } type IndexQueryMethods< Model extends ClientSchemaByEntityTypeBaseShape['models'][string], Context extends ContextType = 'CLIENT', > = { [K in keyof Select< Model['secondaryIndexes'], ClientSecondaryIndexField >]: IndexQueryMethod< Model, Select[K], Context >; }; type IndexQueryMethod< Model extends ClientSchemaByEntityTypeBaseShape['models'][string], Method extends ClientSecondaryIndexField, Context extends ContextType = 'CLIENT', FlatModel extends Record = Model['__meta']['flatModel'], > = Context extends 'CLIENT' | 'COOKIES' ? > = never[]>( input: Method['input'], options?: { filter?: ModelFilter; sortDirection?: ModelSortDirection; limit?: number; nextToken?: string | null; selectionSet?: SelectionSet; authMode?: AuthMode; authToken?: string; headers?: CustomHeaders; }, ) => ListReturnValue>> : > = never[]>( contextSpec: AmplifyServer.ContextSpec, input: Method['input'], options?: { filter?: ModelFilter; sortDirection?: ModelSortDirection; limit?: number; nextToken?: string | null; selectionSet?: SelectionSet; authMode?: AuthMode; authToken?: string; headers?: CustomHeaders; }, ) => ListReturnValue>>; type ModelTypesClient< Model extends ClientSchemaByEntityTypeBaseShape['models'][string], FlatModel extends Record = Model['__meta']['flatModel'], > = IndexQueryMethods & // Omit any disabled operations Omit< { create> = never[]>( model: Model['createType'], options?: { selectionSet?: SelectionSet; authMode?: AuthMode; authToken?: string; headers?: CustomHeaders; }, ): SingularReturnValue< Prettify> >; update> = never[]>( model: Model['updateType'], options?: { selectionSet?: SelectionSet; authMode?: AuthMode; authToken?: string; headers?: CustomHeaders; }, ): SingularReturnValue< Prettify> >; delete> = never[]>( identifier: Model['deleteType'], options?: { selectionSet?: SelectionSet; authMode?: AuthMode; authToken?: string; headers?: CustomHeaders; }, ): SingularReturnValue< Prettify> >; get> = never[]>( identifier: Model['identifier'], options?: { selectionSet?: SelectionSet; authMode?: AuthMode; authToken?: string; headers?: CustomHeaders; }, ): SingularReturnValue< Prettify> >; list> = never[]>( options?: ListCpkOptions & { filter?: ModelFilter; limit?: number; nextToken?: string | null; selectionSet?: SelectionSet; authMode?: AuthMode; authToken?: string; headers?: CustomHeaders; }, ): ListReturnValue>>; onCreate< SelectionSet extends ReadonlyArray> = never[], >(options?: { filter?: ModelSubscriptionFilter; selectionSet?: SelectionSet; authMode?: AuthMode; authToken?: string; headers?: CustomHeaders; }): ObservedReturnValue< Prettify> >; onUpdate< SelectionSet extends ReadonlyArray> = never[], >(options?: { filter?: ModelSubscriptionFilter; selectionSet?: SelectionSet; authMode?: AuthMode; authToken?: string; headers?: CustomHeaders; }): ObservedReturnValue< Prettify> >; onDelete< SelectionSet extends ReadonlyArray> = never[], >(options?: { filter?: ModelSubscriptionFilter; selectionSet?: SelectionSet; authMode?: AuthMode; authToken?: string; headers?: CustomHeaders; }): ObservedReturnValue< Prettify> >; observeQuery[] = never[]>(options?: { filter?: ModelFilter; selectionSet?: SelectionSet; authMode?: AuthMode; authToken?: string; }): ObserveQueryReturnValue< Prettify> >; }, keyof Model['__meta']['disabledOperations'] >; type ModelTypesSSRCookies< Model extends ClientSchemaByEntityTypeBaseShape['models'][string], FlatModel extends Record = Model['__meta']['flatModel'], > = IndexQueryMethods & // Omit any disabled operations Omit< { create> = never[]>( model: Model['createType'], options?: { selectionSet?: SelectionSet; authMode?: AuthMode; authToken?: string; headers?: CustomHeaders; }, ): SingularReturnValue< Prettify> >; update> = never[]>( model: Model['updateType'], options?: { selectionSet?: SelectionSet; authMode?: AuthMode; authToken?: string; headers?: CustomHeaders; }, ): SingularReturnValue< Prettify> >; delete> = never[]>( identifier: Model['deleteType'], options?: { selectionSet?: SelectionSet; authMode?: AuthMode; authToken?: string; headers?: CustomHeaders; }, ): SingularReturnValue< Prettify> >; get> = never[]>( identifier: Model['identifier'], options?: { selectionSet?: SelectionSet; authMode?: AuthMode; authToken?: string; headers?: CustomHeaders; }, ): SingularReturnValue< Prettify> >; list> = never[]>( options?: ListCpkOptions & { filter?: ModelFilter; sortDirection?: ModelSortDirection; limit?: number; nextToken?: string | null; selectionSet?: SelectionSet; authMode?: AuthMode; authToken?: string; headers?: CustomHeaders; }, ): ListReturnValue>>; }, keyof Model['__meta']['disabledOperations'] >; type ModelTypesSSRRequest< Model extends ClientSchemaByEntityTypeBaseShape['models'][string], FlatModel extends Record = Model['__meta']['flatModel'], > = IndexQueryMethods & // Omit any disabled operations Omit< { create> = never[]>( contextSpec: AmplifyServer.ContextSpec, model: Model['createType'], options?: { selectionSet?: SelectionSet; authMode?: AuthMode; authToken?: string; headers?: CustomHeaders; }, ): SingularReturnValue< Prettify> >; update> = never[]>( contextSpec: AmplifyServer.ContextSpec, model: Model['updateType'], options?: { selectionSet?: SelectionSet; authMode?: AuthMode; authToken?: string; headers?: CustomHeaders; }, ): SingularReturnValue< Prettify> >; delete> = never[]>( contextSpec: AmplifyServer.ContextSpec, identifier: Model['deleteType'], options?: { selectionSet?: SelectionSet; authMode?: AuthMode; authToken?: string; headers?: CustomHeaders; }, ): SingularReturnValue< Prettify> >; get> = never[]>( contextSpec: AmplifyServer.ContextSpec, identifier: Model['identifier'], options?: { selectionSet?: SelectionSet; authMode?: AuthMode; authToken?: string; headers?: CustomHeaders; }, ): SingularReturnValue< Prettify> >; list> = never[]>( contextSpec: AmplifyServer.ContextSpec, options?: ListCpkOptions & { filter?: ModelFilter; sortDirection?: ModelSortDirection; limit?: number; nextToken?: string | null; selectionSet?: SelectionSet; authMode?: AuthMode; authToken?: string; headers?: CustomHeaders; }, ): ListReturnValue>>; }, keyof Model['__meta']['disabledOperations'] >; type ContextType = 'CLIENT' | 'COOKIES' | 'REQUEST'; export type ModelTypes< T extends Record, Context extends ContextType = 'CLIENT', Schema extends ClientSchemaByEntityType = ClientSchemaByEntityType, > = { [ModelName in keyof Schema['models']]: Context extends 'CLIENT' ? ModelTypesClient : Context extends 'COOKIES' ? ModelTypesSSRCookies : Context extends 'REQUEST' ? ModelTypesSSRRequest : never; }; export type CustomQueries< T extends Record, Context extends ContextType = 'CLIENT', Schema extends ClientSchemaByEntityType = ClientSchemaByEntityType, > = CustomOperations; export type CustomMutations< T extends Record, Context extends ContextType = 'CLIENT', Schema extends ClientSchemaByEntityType = ClientSchemaByEntityType, > = CustomOperations; export type CustomSubscriptions< T extends Record, Context extends ContextType = 'CLIENT', Schema extends ClientSchemaByEntityType = ClientSchemaByEntityType, > = CustomOperations; type CustomOperationMethodOptions = { // selectionSet?: SelectionSet; authMode?: AuthMode; authToken?: string; headers?: CustomHeaders; }; /** * Generates Custom Operations function params based on whether .arguments() were specified in the schema builder */ type CustomOperationFnParams | never> = [ Args, ] extends [never] ? [CustomOperationMethodOptions?] : [Args, CustomOperationMethodOptions?]; export type CustomOperations< OperationDefs extends ClientSchemaByEntityTypeBaseShape[ | 'queries' | 'mutations' | 'subscriptions' | 'generations'], Context extends ContextType = 'CLIENT', > = { [OpName in keyof OperationDefs]: { CLIENT: ( ...params: CustomOperationFnParams ) => // we only generate subscriptions on the clientside; so this isn't applied to COOKIES | REQUEST OperationDefs[OpName]['operationType'] extends 'Subscription' ? ObservedReturnValue : SingularReturnValue; COOKIES: ( ...params: CustomOperationFnParams ) => SingularReturnValue; REQUEST: ( contextSpec: AmplifyServer.ContextSpec, ...params: CustomOperationFnParams ) => SingularReturnValue; }[Context]; }; /** * The utility type that is used to infer the type (interface) of the generated * `client.enums` property. * * @example * // The schema: * { * TodoStatus: a.enum(['Planned' | 'InProgress' | 'Completed']), * } * * // The inferred interface of the `client.enums`: * { * TodoStatus: { * values: () => Array<'Planned' | 'InProgress' | 'Completed'>; * } * } */ export type EnumTypes> = { [EnumName in keyof AllEnumTypesRecursively]: { values: () => Array[EnumName]['type']>; }; }; type AllEnumTypesRecursively> = ClientSchemaByEntityType['enums'] & Select< ExtractNestedTypes>, { __entityType: 'enum' } >; /** * Request options that are passed to custom header functions. * `method` and `headers` are not included in custom header functions passed to * subscriptions. */ export type RequestOptions = { url: string; queryString: string; method?: string; }; /** * Custom headers that can be passed either to the client or to individual * model operations, either as a static object or a function that returns a * promise. */ export type CustomHeaders = | Record | ((requestOptions?: RequestOptions) => Promise>); export type ClientExtensions = never> = { models: ModelTypes; enums: EnumTypes; queries: CustomQueries; mutations: CustomMutations; subscriptions: CustomSubscriptions; conversations: ConversationRoutes; generations: Generations; }; export type ClientExtensionsSSRRequest = never> = { models: ModelTypes; enums: EnumTypes; queries: CustomQueries; mutations: CustomMutations; }; export type ClientExtensionsSSRCookies = never> = { models: ModelTypes; enums: EnumTypes; queries: CustomQueries; mutations: CustomMutations; }; export type ConversationRoutes< T extends Record, Schema extends ClientSchemaByEntityType = ClientSchemaByEntityType, > = { [ConversationName in keyof Schema['conversations']]: ConversationRoute; }; export type Generations< T extends Record, Schema extends ClientSchemaByEntityType = ClientSchemaByEntityType, > = CustomOperations;