@mbc-cqrs-serverless/core
Version:
CQRS and event base core
109 lines (108 loc) • 5.1 kB
TypeScript
import { SessionService } from '../data-store/session.service';
import { CommandModel, CommandModuleOptions, DataListEntity, DataModel, DetailKey, ICommandOptions } from '../interfaces';
import { CommandService } from './command.service';
import { DataService } from './data.service';
export interface IMergeOptions<TItem extends {
id: string;
}> {
/** * Set true to merge pending async commands into the result.
* If false/undefined -> acts as a simple passthrough.
*/
latestFlg?: boolean;
/**
* Optional: Extract version from the query result item (e.g. RDS row).
* If the provided version is >= session.version, the DynamoDB Data Table
* check is skipped to reduce latency.
*/
getVersion?: (item: TItem) => number | undefined;
/**
* Transform a CommandModel into the same shape as a TItem returned by the query.
* The `existing` parameter is provided for update cases to merge unchanged fields.
*/
transformCommand: (cmd: CommandModel, existing?: TItem) => TItem;
/**
* Optional filter applied AFTER transformCommand for "create-new" rows.
* Prevents newly created items from appearing in lists where they don't match the current search criteria.
*/
matchesFilter?: (item: TItem) => boolean;
}
export declare class Repository {
private readonly options;
private readonly dataService;
private readonly commandService;
private readonly sessionService;
private readonly logger;
private readonly moduleTableName;
constructor(options: CommandModuleOptions, dataService: DataService, commandService: CommandService, sessionService: SessionService);
/**
* Get a single data item, merging a pending async command when a session exists.
*/
getItem(key: DetailKey, options: ICommandOptions): Promise<DataModel>;
/**
* List items from the DynamoDB data table with an optional merge of pending async commands.
*
* When `mergeOptions.latestFlg` is true, the result is augmented with any
* pending writes the current user has not yet seen reflected in the data table:
*
* - **update** — the existing item is replaced in-place, preserving its
* position in the list.
* - **delete** — the item is removed from the result.
* - **create-new** — the item is prepended to the top of the list, sorted by
* `updatedAt` descending when there are multiple pending creates.
*
* > **Sort-order note**: Prepended create-new items are not integrated into
* > the caller's sort order (e.g. by `name` or `code`). They will appear
* > at the top of the result until the async DynamoDB Stream sync completes and
* > the next read returns fully sorted data. This is intentional — the
* > guarantee is visibility, not sort position.
*/
listItemsByPk(pk: string, opts?: {
sk?: {
skExpression: string;
skAttributeValues: Record<string, string>;
skAttributeNames?: Record<string, string>;
};
startFromSk?: string;
limit?: number;
order?: 'asc' | 'desc';
}, mergeOptions?: {
latestFlg?: boolean;
}, options?: ICommandOptions): Promise<DataListEntity>;
/**
* List items from an external source (e.g., RDS/Elasticsearch) with an
* optional merge of pending async commands.
*
* When `mergeOptions.latestFlg` is true, the result is augmented with any
* pending writes the current user has not yet seen reflected in the query
* source:
*
* - **update** — the existing item is replaced in-place via `transformCommand`,
* preserving its position. `existing` is passed to the transformer so join
* data (e.g. related RDS rows) can be carried over.
* - **delete** — the item is removed and `total` is decremented.
* - **create-new** — the item is transformed, optionally filtered by
* `matchesFilter`, and prepended to the top of the result sorted by
* `updatedAt` descending. `total` is incremented.
*
* > **Sort-order note**: Prepended create-new items are not integrated into
* > the caller's sort order (e.g. by `name` or `code`). They will appear
* > at the top of the result until the async Stream → RDS sync completes and
* > the next read returns fully sorted data. This is intentional — the
* > guarantee is visibility, not sort position.
*
* > **Pagination note**: `total` reflects the adjusted count after merge.
* > However, prepended items sit outside the RDS `LIMIT`/`OFFSET` window, so
* > on page 2+ the caller may see a one-item overlap or gap until sync
* > completes. For most UX patterns (redirect-after-write, optimistic UI)
* > this is not noticeable.
*/
listItems<TItem extends {
id: string;
}>(query: () => Promise<{
total: number;
items: TItem[];
}>, mergeOptions?: IMergeOptions<TItem>, options?: ICommandOptions): Promise<{
total: number;
items: TItem[];
}>;
}