// finsert.ts
import { omitMetadataValues, serialize } from 'domain-objects';
import { HasId, HasUuid } from 'simple-type-guards';
import { ChatMessage } from '../../../domain';
import { DatabaseConnection } from '../../../utils/database/getDatabaseConnection';
import { FinsertIdempotencyImpossibleError } from '../.generated/errors';
import { findByUnique } from './findByUnique';
import { upsert } from './upsert';
/**
* find or insert the entity
*/
export const finsert = async ({
dbConnection,
chatMessage: expected,
idempotent,
}: {
dbConnection: DatabaseConnection;
chatMessage: ChatMessage;
/**
* idempotency guarantees that, for the same input, the first result and subsequent results are equivalent
*
* usecases
* - guarantee that either the domain-object was inserted or that the found domain-object values are equivalent to the input
* - throw an error if we can not guarantee that this is the original request or a duplicate request
*
* specifically,
* - is it the original request? if so, succeed
* - is it a duplicate request? if so, succeed for idempotency
* - is it not the original and not a duplicate request? if so, throw an error to warn user this can not be idempotent
*/
idempotent?: boolean;
}): Promise<HasId<HasUuid<ChatMessage>>> => {
const found = await findByUnique({
dbConnection,
...expected,
});
if (found) {
if (
idempotent &&
serialize(omitMetadataValues(found)) !==
serialize(omitMetadataValues(expected))
)
throw new FinsertIdempotencyImpossibleError({ expected, found });
return found;
}
return await upsert({
dbConnection,
chatMessage: expected,
});
};
// .generated/errors.ts
import { DomainObject } from 'domain-objects';
/**
* an error thrown when idempotency is impossible for a finsert request
* - either
* - it's not a request that idempotency was ever possible for (i.e., it couldn't possibly be a duplicate)
* - or
* - it's not a request that idempotency can be possible for any longer (i.e., underlying state changed and its no longer supported)
*
* common causes
* - the domain-object was previously created && someone is trying to unknowingly reuse it's identity
* - the domain-object was updated since the original request && now the operation can not be repeated
*
* explanation
* - a find-or-insert operation, finsert, can only be idempotent when
* - the domain-object does not exist yet (i.e., original request)
* - or
* - the domain-object exists already and it's found properties are equivalent to the input properties (i.e., duplicate request)
* - otherwise,
* - the insert would have produced an domain-object with the input properties
* - while the find produces an domain-object with the found properties
* - => the find and insert produce different results
* - => idempotency impossible
* ref:
* - def:`idempotency`
* - > when the repeating the request is guaranteed to produce the same results
*/
export class FinsertIdempotencyImpossibleError<
T extends DomainObject<any>,
> extends Error {
constructor({ found, expected }: { found: T; expected: T }) {
const domainObjectName = found.constructor.name;
super(
`
FinsertIdempotencyImpossible: Some ${domainObjectName} already exits with the same identity but with different updatable properties. Due to this, this request can not possibly be idempotent.
${JSON.stringify({ found, expected })}
`.trim(),
);
}
}
add
finsert
withidempotency