Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
617 changes: 617 additions & 0 deletions frontend/src/features/reminder/application/LocalReminderApplication.ts

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
/**
* 应用层对外名称。具体实现由组合根注入,后续替换为原生协调器时无需修改
* 展示层代码。
* 应用层对外名称。具体实现由组合根注入,后续替换适配器时无需修改展示层代码。
*/
export type {
ReminderApplicationDependencies,
ReminderApplicationPort,
ReminderApplicationResult,
ReminderSnoozeRequest,
} from './interfaces/ReminderApplicationPort';
export { LocalReminderApplication } from './LocalReminderApplication';
1 change: 1 addition & 0 deletions frontend/src/features/reminder/application/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export { LocalReminderApplication } from './LocalReminderApplication';
export type {
AlarmScheduleReceipt,
AlarmScheduleRequest,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ export type AlarmScheduleRequest = {
export type AlarmScheduleReceipt = {
alarm_id: string;
schedule_id: string;
/** false 表示未真正挂上系统闹钟,应用层不得当作成功注册。 */
scheduled: boolean;
};

/** 原生闹钟映射边界;触发时间的选择留在应用层或领域层。 */
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import type { ReminderStateStore } from '../../application/interfaces';
import type { ReminderDisposition, ReminderRuntimeState } from '../../domain';

/** 进程内提醒运行时状态存储,供真实协调器在接入本地 DB 前使用。 */
export class MemoryReminderStateStore implements ReminderStateStore {
private readonly states = new Map<string, ReminderRuntimeState>();
private readonly dispositions = new Map<string, ReminderDisposition>();

async read(scheduleId: string): Promise<ReminderRuntimeState | null> {
const state = this.states.get(scheduleId);
return state == null ? null : { ...state };
}

async write(scheduleId: string, state: ReminderRuntimeState): Promise<void> {
this.states.set(scheduleId, { ...state });
}

async setDisposition(scheduleId: string, disposition: ReminderDisposition): Promise<void> {
this.dispositions.set(scheduleId, { ...disposition });
const current = this.states.get(scheduleId);
this.states.set(scheduleId, {
...(current ?? {
reminder_disposition_state: null,
next_trigger_at: null,
snoozed_until: null,
geofence_armed: false,
disposition_updated_at: null,
sync_status: 'pending',
recorded_location: null,
}),
reminder_disposition_state: disposition.state,
snoozed_until: disposition.snoozed_until,
disposition_updated_at: disposition.updated_at,
sync_status: disposition.sync_status,
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { MOCK_REMINDER_SCHEDULES } from './mockReminderSchedules';
/** 代替本地数据库端口的固定只读适配器。 */
export class MockLocalScheduleReader implements LocalScheduleReader {
readonly schedules = MOCK_REMINDER_SCHEDULES;
private readonly listeners = new Set<(schedules: readonly LocalReminderSchedule[]) => void>();

async listReminderSchedules(): Promise<readonly LocalReminderSchedule[]> {
return this.schedules;
Expand All @@ -16,7 +17,16 @@ export class MockLocalScheduleReader implements LocalScheduleReader {
}

subscribe(listener: (schedules: readonly LocalReminderSchedule[]) => void): () => void {
listener(this.schedules);
return () => undefined;
this.listeners.add(listener);
return () => {
this.listeners.delete(listener);
};
}

/** 测试或本地写入后通知订阅方;subscribe 本身不重放,避免与 start/rebuild 重入。 */
notify(): void {
for (const listener of this.listeners) {
listener(this.schedules);
}
}
}
1 change: 1 addition & 0 deletions frontend/src/features/reminder/data/local/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ export { MockLocalScheduleReader } from './MockLocalScheduleReader';
export { MockReminderApplication } from './MockReminderApplication';
export { MockReminderDispositionSync } from './MockReminderDispositionSync';
export { MockReminderStateStore } from './MockReminderStateStore';
export { MemoryReminderStateStore } from './MemoryReminderStateStore';
export { MOCK_REMINDER_SCHEDULES } from './mockReminderSchedules';
2 changes: 2 additions & 0 deletions frontend/src/features/reminder/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,11 +62,13 @@ export type {
TimeListenerPort,
VibrationPort,
} from './application';
export { LocalReminderApplication } from './application';
export {
MockLocalScheduleReader,
MockReminderApplication,
MockReminderDispositionSync,
MockReminderStateStore,
MOCK_REMINDER_SCHEDULES,
MemoryReminderStateStore,
} from './data/local';
export { MockReminderPresenter } from './presentation';
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@ import type {
AlarmSchedulerPort,
} from '../../features/reminder/application/interfaces';

/** 固定闹钟适配器,后续可替换为原生精确闹钟接口。 */
/** 固定闹钟适配器,始终标记为已调度(进程内占位 id)。 */
export class MockAlarmScheduler implements AlarmSchedulerPort {
async schedule(request: AlarmScheduleRequest): Promise<AlarmScheduleReceipt> {
return {
alarm_id: `mock-alarm-${request.schedule_id}`,
schedule_id: request.schedule_id,
scheduled: true,
};
}

Expand All @@ -20,9 +21,6 @@ export class MockAlarmScheduler implements AlarmSchedulerPort {
async rebuild(
requests: readonly AlarmScheduleRequest[],
): Promise<readonly AlarmScheduleReceipt[]> {
return requests.map((request) => ({
alarm_id: `mock-alarm-${request.schedule_id}`,
schedule_id: request.schedule_id,
}));
return Promise.all(requests.map((request) => this.schedule(request)));
}
}