98 lines
2.6 KiB
TypeScript
98 lines
2.6 KiB
TypeScript
import type {
|
|
BrowserDataResult,
|
|
IndexedDbRepositoryPort,
|
|
} from "../../../../src/application/ports/browser-file-storage/index.ts";
|
|
|
|
export type LocalDraft = Readonly<{
|
|
draftId: string;
|
|
title: string;
|
|
body: string;
|
|
}>;
|
|
|
|
export type SaveLocalDraftCommand = Readonly<{
|
|
draft: LocalDraft;
|
|
expectedRevision: number | null;
|
|
idempotencyKey: string;
|
|
}>;
|
|
|
|
export type RemoveLocalDraftCommand = Readonly<{
|
|
draftId: string;
|
|
expectedRevision: number;
|
|
idempotencyKey: string;
|
|
}>;
|
|
|
|
export type LocalDraftRecord = Readonly<{
|
|
draft: LocalDraft;
|
|
revision: number;
|
|
}>;
|
|
|
|
export interface LocalDraftStore {
|
|
save(
|
|
command: SaveLocalDraftCommand,
|
|
signal?: AbortSignal,
|
|
): Promise<BrowserDataResult<Readonly<{ revision: number }>>>;
|
|
find(
|
|
draftId: string,
|
|
signal?: AbortSignal,
|
|
): Promise<BrowserDataResult<LocalDraftRecord | null>>;
|
|
remove(
|
|
command: RemoveLocalDraftCommand,
|
|
signal?: AbortSignal,
|
|
): Promise<BrowserDataResult<void>>;
|
|
}
|
|
|
|
/**
|
|
* Feature-owned binding over the technology-neutral IndexedDB application port.
|
|
*
|
|
* The feature knows its domain type and optimistic concurrency inputs. It does
|
|
* not know database names, stores, transactions, native IDB objects, codecs,
|
|
* migrations, quota handling or connection lifecycle.
|
|
*/
|
|
export function createLocalDraftStore(
|
|
repository: IndexedDbRepositoryPort<LocalDraft, never>,
|
|
): LocalDraftStore {
|
|
const store: LocalDraftStore = {
|
|
async save(command, signal) {
|
|
const result = await repository.compareAndSwap({
|
|
key: command.draft.draftId,
|
|
value: command.draft,
|
|
expectedRevision: command.expectedRevision,
|
|
idempotencyKey: command.idempotencyKey,
|
|
signal,
|
|
});
|
|
if (!result.ok) return result;
|
|
return Object.freeze({
|
|
ok: true as const,
|
|
value: Object.freeze({ revision: result.value.revision }),
|
|
});
|
|
},
|
|
|
|
async find(draftId, signal) {
|
|
const result = await repository.read(draftId, signal);
|
|
if (!result.ok) return result;
|
|
return Object.freeze({
|
|
ok: true as const,
|
|
value:
|
|
result.value === null
|
|
? null
|
|
: Object.freeze({
|
|
draft: result.value.value,
|
|
revision: result.value.revision,
|
|
}),
|
|
});
|
|
},
|
|
|
|
async remove(command, signal) {
|
|
const result = await repository.remove({
|
|
key: command.draftId,
|
|
expectedRevision: command.expectedRevision,
|
|
idempotencyKey: command.idempotencyKey,
|
|
signal,
|
|
});
|
|
if (!result.ok) return result;
|
|
return Object.freeze({ ok: true as const, value: undefined });
|
|
},
|
|
};
|
|
return Object.freeze(store);
|
|
}
|