222 lines
7.3 KiB
TypeScript
222 lines
7.3 KiB
TypeScript
/**
|
|
* MindLyst backend client — typed HTTP wrappers for the mindlyst-backend (port 4014).
|
|
*
|
|
* Auth: Bearer token from the caller's JWT (same JWT_SECRET as platform-service).
|
|
* Admin callers may pass x-admin-user-id to operate on behalf of any user.
|
|
*/
|
|
|
|
import { config } from './config.js';
|
|
|
|
export interface MindlystClientOptions {
|
|
token?: string;
|
|
requestId?: string;
|
|
/** When set (admin only) — overrides the JWT sub used as userId in backend routes */
|
|
adminUserId?: string;
|
|
}
|
|
|
|
// ── Shared fetch helper ────────────────────────────────────────────────────
|
|
|
|
async function mindlystFetch<T>(
|
|
path: string,
|
|
init: RequestInit,
|
|
opts: MindlystClientOptions
|
|
): Promise<T> {
|
|
const headers: Record<string, string> = {
|
|
'Content-Type': 'application/json',
|
|
...(opts.token ? { Authorization: `Bearer ${opts.token}` } : {}),
|
|
...(opts.requestId ? { 'x-request-id': opts.requestId } : {}),
|
|
...(opts.adminUserId ? { 'x-admin-user-id': opts.adminUserId } : {}),
|
|
};
|
|
const res = await fetch(`${config.MINDLYST_BACKEND_URL}/api${path}`, {
|
|
...init,
|
|
headers: { ...((init.headers as Record<string, string>) ?? {}), ...headers },
|
|
signal: AbortSignal.timeout(15_000),
|
|
});
|
|
if (!res.ok) {
|
|
const body = await res.text().catch(() => '');
|
|
throw new Error(`mindlyst-backend ${init.method ?? 'GET'} ${path} → ${res.status}: ${body}`);
|
|
}
|
|
return res.json() as Promise<T>;
|
|
}
|
|
|
|
// ── Memory items ───────────────────────────────────────────────────────────
|
|
|
|
export interface MemoryItemDoc {
|
|
id: string;
|
|
productId: string;
|
|
userId: string;
|
|
sourceType: string;
|
|
captureSurface: string;
|
|
rawContent: string;
|
|
triageResult: {
|
|
contentType: string;
|
|
summary?: string;
|
|
urgencyScore: number;
|
|
emotionScore: number;
|
|
confidenceScore: number;
|
|
suggestedBrainId: string;
|
|
entities: string[];
|
|
suggestedActions: string[];
|
|
};
|
|
brainIds: string[];
|
|
actedOn: boolean;
|
|
actedOnAt: string | null;
|
|
nudgeCount: number;
|
|
reminderAt?: string;
|
|
isSensitive: boolean;
|
|
createdAt: string;
|
|
updatedAt: string;
|
|
}
|
|
|
|
export function mindlystMemoryList(
|
|
params: {
|
|
brainId?: string;
|
|
filter?: 'forgotten' | 'completed_today';
|
|
limit?: number;
|
|
offset?: number;
|
|
productId?: string;
|
|
},
|
|
opts: MindlystClientOptions
|
|
): Promise<{ items: MemoryItemDoc[]; limit: number; offset: number }> {
|
|
const qs = new URLSearchParams();
|
|
if (params.brainId) qs.set('brainId', params.brainId);
|
|
if (params.filter) qs.set('filter', params.filter);
|
|
if (params.limit !== undefined) qs.set('limit', String(params.limit));
|
|
if (params.offset !== undefined) qs.set('offset', String(params.offset));
|
|
if (params.productId) qs.set('productId', params.productId);
|
|
const q = qs.toString();
|
|
return mindlystFetch(`/memory-items${q ? `?${q}` : ''}`, { method: 'GET' }, opts);
|
|
}
|
|
|
|
export function mindlystMemoryRetriage(
|
|
itemId: string,
|
|
opts: MindlystClientOptions
|
|
): Promise<MemoryItemDoc> {
|
|
return mindlystFetch(`/memory-items/${itemId}/retriage`, { method: 'POST' }, opts);
|
|
}
|
|
|
|
export function mindlystMemoryPatch(
|
|
itemId: string,
|
|
action: 'mark_done' | 'mark_undone' | 'increment_nudge' | 'set_reminder',
|
|
reminderAt: string | undefined,
|
|
opts: MindlystClientOptions
|
|
): Promise<MemoryItemDoc> {
|
|
return mindlystFetch(
|
|
`/memory-items/${itemId}`,
|
|
{ method: 'PATCH', body: JSON.stringify({ action, ...(reminderAt ? { reminderAt } : {}) }) },
|
|
opts
|
|
);
|
|
}
|
|
|
|
export function mindlystMemoryReassign(
|
|
itemId: string,
|
|
newBrainId: string,
|
|
opts: MindlystClientOptions
|
|
): Promise<MemoryItemDoc> {
|
|
return mindlystFetch(
|
|
`/memory-items/${itemId}/reassign`,
|
|
{ method: 'PUT', body: JSON.stringify({ newBrainId }) },
|
|
opts
|
|
);
|
|
}
|
|
|
|
// ── Brains ─────────────────────────────────────────────────────────────────
|
|
|
|
export interface BrainDoc {
|
|
id: string;
|
|
userId: string;
|
|
productId: string;
|
|
name: string;
|
|
rolePrompt?: string;
|
|
tone?: string;
|
|
colorFrom?: string;
|
|
colorTo?: string;
|
|
createdAt: string;
|
|
updatedAt: string | null;
|
|
}
|
|
|
|
export function mindlystBrainsList(
|
|
params: { limit?: number; offset?: number },
|
|
opts: MindlystClientOptions
|
|
): Promise<{ items: BrainDoc[]; total: number; limit: number; offset: number }> {
|
|
const qs = new URLSearchParams();
|
|
if (params.limit !== undefined) qs.set('limit', String(params.limit));
|
|
if (params.offset !== undefined) qs.set('offset', String(params.offset));
|
|
const q = qs.toString();
|
|
return mindlystFetch(`/brains${q ? `?${q}` : ''}`, { method: 'GET' }, opts);
|
|
}
|
|
|
|
// ── Daily briefs ───────────────────────────────────────────────────────────
|
|
|
|
export interface DailyBriefDoc {
|
|
id: string;
|
|
userId: string;
|
|
productId: string;
|
|
date: string;
|
|
greeting?: string;
|
|
priorityItems: unknown[];
|
|
brainSummaries: Record<string, string>;
|
|
streakMessage?: string;
|
|
motivationalQuote?: string;
|
|
createdAt: string;
|
|
}
|
|
|
|
export function mindlystBriefsGetToday(opts: MindlystClientOptions): Promise<DailyBriefDoc> {
|
|
return mindlystFetch('/daily-briefs/today', { method: 'GET' }, opts);
|
|
}
|
|
|
|
export function mindlystBriefsList(
|
|
params: { limit?: number; offset?: number },
|
|
opts: MindlystClientOptions
|
|
): Promise<{ items: DailyBriefDoc[]; total: number }> {
|
|
const qs = new URLSearchParams();
|
|
if (params.limit !== undefined) qs.set('limit', String(params.limit));
|
|
if (params.offset !== undefined) qs.set('offset', String(params.offset));
|
|
const q = qs.toString();
|
|
return mindlystFetch(`/daily-briefs${q ? `?${q}` : ''}`, { method: 'GET' }, opts);
|
|
}
|
|
|
|
// ── Streaks ────────────────────────────────────────────────────────────────
|
|
|
|
export interface StreakDoc {
|
|
id: string;
|
|
userId: string;
|
|
productId: string;
|
|
currentStreak: number;
|
|
longestStreak: number;
|
|
lastActiveDate: string;
|
|
streakFreezeAvailable: boolean;
|
|
totalActiveDays: number;
|
|
createdAt: string;
|
|
updatedAt: string;
|
|
}
|
|
|
|
export function mindlystStreaksGet(opts: MindlystClientOptions): Promise<StreakDoc> {
|
|
return mindlystFetch('/streaks', { method: 'GET' }, opts);
|
|
}
|
|
|
|
// ── Reflections ────────────────────────────────────────────────────────────
|
|
|
|
export interface ReflectionDoc {
|
|
id: string;
|
|
userId: string;
|
|
productId: string;
|
|
weekStartDate: string;
|
|
themes: string[];
|
|
highlights: string[];
|
|
challenges: string[];
|
|
summary?: string;
|
|
createdAt: string;
|
|
}
|
|
|
|
export function mindlystReflectionsList(
|
|
params: { limit?: number; offset?: number },
|
|
opts: MindlystClientOptions
|
|
): Promise<{ items: ReflectionDoc[]; total: number }> {
|
|
const qs = new URLSearchParams();
|
|
if (params.limit !== undefined) qs.set('limit', String(params.limit));
|
|
if (params.offset !== undefined) qs.set('offset', String(params.offset));
|
|
const q = qs.toString();
|
|
return mindlystFetch(`/reflections${q ? `?${q}` : ''}`, { method: 'GET' }, opts);
|
|
}
|