import { Entity , EntityConstructor , EntityManager , getMetadata , IdOf , isEntity , isKey , OptIdsOf , OptsOf , } from "./EntityManager" ; import { getConstructorFromTaggedId , PartialOrNull } from "./index" ; import { NullOrDefinedOr } from "./utils" ; /**
 * The type for `EntityManager.createOrUpdateUnsafe` that allows "upsert"-ish behavior.
 *
 * I.e. `T` is an entity with an optional id (create if unset, update if set), and we recurse
 * into any relations (references or collections) to allow those relations themselves to be
 * any combination of 1) ids to existing entities, 2) entities directly, 3) null/undefined
 * with the appropriate partial-update behavior, or 4) partials.
 */ export type DeepPartialOrNull < T extends Entity > = { id ? : IdOf < T > | null ; } & AllowRelationsToBeIdsOrEntitiesOrPartials < PartialOrNull < OptsOf < T > & OptIdsOf < T > > > & OptIdsOf < T > ; type AllowRelationsToBeIdsOrEntitiesOrPartials < T > = { [ P in keyof T ] : T [ P ] extends NullOrDefinedOr < infer U > ? U extends Array < infer V > ? V extends Entity ? Array < V | ( DeepPartialOrNull < V > & { delete ? : boolean | null ; remove ? : boolean | null ; op ? : "remove" | "delete" | "include" | "incremental" ; } ) | IdOf < V > > | null : T [ P ] : U extends Entity ? U | DeepPartialOrNull < U > | IdOf < U > | null : T [ P ] : T [ P ] ; } ; /**
 * A utility function to create-or-update entities coming from a partial-update style API.
 */ export async function createOrUpdatePartial < T extends Entity > ( em , constructor , opts ) { const { id , ... others } = opts as any ; const meta = getMetadata ( constructor ) ; const isNew = id === null || id === undefined ; const collectionsToLoad = [ ] ; const p = Object . entries ( others ) . map ( async ( [ key , value ] ) => { const field = meta . fields [ key ] || Object . values ( meta . fields ) . find ( ( f ) => f . fieldIdName === key ) ; if ( ! field ) { const flagField = key === "delete" || key === "remove" || key === "op" ; if ( flagField ) { return [ key , value ] ; } throw new Error ( `Unknown field ${ key }` ) ; } const name = field . fieldName ; if ( field . kind === "poly" && ! isEntity ( value ) ) { if ( ! value ) { return [ name , value ] ; } else if ( isKey ( value ) ) { const entity = await em . load ( getConstructorFromTaggedId ( value ) , value ) ; return [ name , entity ] ; } else { throw new Error ( `Cannot use partial value for polymorphic field ` ) ; } } else if ( field . kind === "m2o" && ! isEntity ( value ) ) { if ( ! value || isEntity ( value ) ) { return [ name , value ] ; } else if ( isKey ( value ) ) { const entity = await em . load ( field . otherMetadata ( ) . cstr , value ) ; return [ name , entity ] ; } else if ( typeof value === "object" && value && ! ( "id" in value ) ) { let currentValue ; if ( isNew ) { currentValue = await createOrUpdatePartial ( em , field . otherMetadata ( ) . cstr , value ) ; } else { const parentEntity = await em . load ( constructor , id , [ name ] as any ) ; currentValue = ( parentEntity as any ) [ name ] . get ; if ( currentValue ) { await createOrUpdatePartial ( em , field . otherMetadata ( ) . cstr , { id : currentValue . id , ... value } ) ; } else { currentValue = await createOrUpdatePartial ( em , field . otherMetadata ( ) . cstr , value ) ; } } return [ name , currentValue ] ; } else { const entity = await createOrUpdatePartial ( em , field . otherMetadata ( ) . cstr , value as any ) ; return [ name , entity ] ; } } else if ( field . kind === "o2m" || field . kind === "m2m" ) { const allowDelete = ! field . otherMetadata ( ) . fields [ "delete" ] ; const allowRemove = ! field . otherMetadata ( ) . fields [ "remove" ] ; const allowOp = ! field . otherMetadata ( ) . fields [ "op" ] ; collectionsToLoad . push ( field . fieldName ) ; const entities = ! value ? [ ] : ( value as Array < any > ) . map ( async ( value ) => { if ( ! value || isEntity ( value ) ) { return value ; } else if ( isKey ( value ) ) { return await em . load ( field . otherMetadata ( ) . cstr , value ) ; } else { const deleteMarker = allowDelete && value [ "delete" ] ; const removeMarker = allowRemove && value [ "remove" ] ; const opMarker = allowOp && value [ "op" ] ; if ( opMarker === "incremental" ) { return value ; } if ( deleteMarker !== undefined ) delete value . delete ; if ( removeMarker !== undefined ) delete value . remove ; if ( opMarker !== undefined ) delete value . op ; const entity = await createOrUpdatePartial ( em , field . otherMetadata ( ) . cstr , value as any ) ; if ( deleteMarker === true ) entity . delete = true ; if ( removeMarker === true ) entity . remove = true ; if ( opMarker ) entity . op = opMarker ; return entity ; } } ) ; return [ name , await Promise . all ( entities ) ] ; } else { return [ name , value ] ; } } ) ; const _opts = Object . fromEntries ( await Promise . all ( p ) ) as OptsOf < T > ; if ( isNew ) { return em . createPartial ( constructor , _opts ) ; } else { const entity = await em . load ( constructor , id ) ; await Promise . all ( collectionsToLoad . map ( ( fieldName ) => ( entity as any ) [ fieldName ] . load ( ) ) ) ; entity . setPartial ( _opts ) ; return entity ; } }