feat(database): added listMemories method (#11806)

This commit is contained in:
Neko
2026-01-25 16:40:00 +08:00
committed by GitHub
parent 8ea08dd1e0
commit 5929f7b196

View File

@@ -844,6 +844,7 @@ export class UserMemoryModel {
};
};
// TODO(@nekomeowww): should use L & R generic for helping to determine what result type to return
queryMemories = async (params: QueryUserMemoriesParams = {}): Promise<GetMemoriesResult> => {
const {
categories,
@@ -1344,6 +1345,196 @@ export class UserMemoryModel {
}
};
/**
* NOTICE: internal service / backend / data aggregation use only, for most of the UI related use-cases,
* please use `queryMemories` method instead.
*/
listMemories = async <
L extends LayersEnum,
R extends UserMemoryDetail = L extends LayersEnum.Context
? UserMemoryDetail & { context: UserMemoryContext; layer: LayersEnum.Context }
: L extends LayersEnum.Experience
? UserMemoryDetail & { experience: UserMemoryExperience; layer: LayersEnum.Experience }
: L extends LayersEnum.Identity
? UserMemoryDetail & { identity: UserMemoryIdentity; layer: LayersEnum.Identity }
: L extends LayersEnum.Preference
? UserMemoryDetail & { layer: LayersEnum.Preference; preference: UserMemoryPreference }
: L extends LayersEnum.Activity
? UserMemoryDetail & { activity: UserMemoryActivity; layer: LayersEnum.Activity }
: never,
>(params: {
layer?: L;
order?: 'asc' | 'desc';
page?: number;
pageSize?: number;
}): Promise<R[]> => {
const { layer = LayersEnum.Context, order = 'desc', page = 1, pageSize = 20 } = params;
const normalizedPage = Math.max(1, page ?? 1);
const normalizedPageSize = Math.min(Math.max(pageSize ?? 20, 1), 100);
const offset = (normalizedPage - 1) * normalizedPageSize;
const applyOrder = order === 'asc' ? asc : desc;
const baseSelection = selectNonVectorColumns(userMemories);
const contextSelection = selectNonVectorColumns(userMemoriesContexts);
const experienceSelection = selectNonVectorColumns(userMemoriesExperiences);
const identitySelection = selectNonVectorColumns(userMemoriesIdentities);
const preferenceSelection = selectNonVectorColumns(userMemoriesPreferences);
// TODO(@nekomeowww): activity
// eslint-disable-next-line unused-imports/no-unused-vars, @typescript-eslint/no-unused-vars
const activitySelection = selectNonVectorColumns(userMemoriesActivities);
const baseConditions: Array<SQL | undefined> = [
eq(userMemories.userId, this.userId),
eq(userMemories.memoryLayer, layer),
];
const baseWhere = baseConditions.filter(Boolean) as SQL[];
const buildOrderBy = (updatedAtColumn: AnyColumn | SQL, createdAtColumn: AnyColumn | SQL) =>
[applyOrder(updatedAtColumn), applyOrder(createdAtColumn)].filter(
(item): item is SQL => item !== undefined,
);
switch (layer) {
case LayersEnum.Context: {
const orderByClauses = buildOrderBy(
userMemoriesContexts.updatedAt,
userMemoriesContexts.createdAt,
);
const joinCondition = and(
eq(userMemories.userId, userMemoriesContexts.userId),
sql<boolean>`
COALESCE(${userMemoriesContexts.userMemoryIds}, '[]'::jsonb) ? (${userMemories.id})::text
`,
);
const contextFilters: Array<SQL | undefined> = [
baseWhere.length ? and(...baseWhere) : undefined,
];
const contextWhere = contextFilters.filter(Boolean) as SQL[];
const rows = await this.db
.select({
context: contextSelection,
memory: baseSelection,
})
.from(userMemories)
.innerJoin(userMemoriesContexts, joinCondition)
.where(contextWhere.length ? and(...contextWhere) : undefined)
.orderBy(...orderByClauses)
.limit(normalizedPageSize)
.offset(offset);
return rows.map((row) => ({
context: row.context,
layer: LayersEnum.Context,
memory: row.memory,
})) as unknown as R[];
}
case LayersEnum.Experience: {
const orderByClauses = buildOrderBy(
userMemoriesExperiences.updatedAt,
userMemoriesExperiences.createdAt,
);
const joinCondition = and(
eq(userMemories.id, userMemoriesExperiences.userMemoryId),
eq(userMemoriesExperiences.userId, this.userId),
);
const experienceFilters: Array<SQL | undefined> = [
baseWhere.length ? and(...baseWhere) : undefined,
];
const experienceWhere = experienceFilters.filter(Boolean) as SQL[];
const rows = await this.db
.select({
experience: experienceSelection,
memory: baseSelection,
})
.from(userMemories)
.innerJoin(userMemoriesExperiences, joinCondition)
.where(experienceWhere.length ? and(...experienceWhere) : undefined)
.orderBy(...orderByClauses)
.limit(normalizedPageSize)
.offset(offset);
return rows.map((row) => ({
experience: row.experience,
layer: LayersEnum.Experience,
memory: row.memory,
})) as unknown as R[];
}
case LayersEnum.Identity: {
const orderByClauses = buildOrderBy(
userMemoriesIdentities.updatedAt,
userMemoriesIdentities.createdAt,
);
const joinCondition = and(
eq(userMemories.id, userMemoriesIdentities.userMemoryId),
eq(userMemoriesIdentities.userId, this.userId),
);
const identityFilters: Array<SQL | undefined> = [
baseWhere.length ? and(...baseWhere) : undefined,
];
const identityWhere = identityFilters.filter(Boolean) as SQL[];
const rows = await this.db
.select({
identity: identitySelection,
memory: baseSelection,
})
.from(userMemories)
.innerJoin(userMemoriesIdentities, joinCondition)
.where(identityWhere.length ? and(...identityWhere) : undefined)
.orderBy(...orderByClauses)
.limit(normalizedPageSize)
.offset(offset);
return rows.map((row) => ({
identity: row.identity,
layer: LayersEnum.Identity,
memory: row.memory,
})) as unknown as R[];
}
case LayersEnum.Preference: {
const orderByClauses = buildOrderBy(
userMemoriesPreferences.updatedAt,
userMemoriesPreferences.createdAt,
);
const joinCondition = and(
eq(userMemories.id, userMemoriesPreferences.userMemoryId),
eq(userMemoriesPreferences.userId, this.userId),
);
const preferenceFilters: Array<SQL | undefined> = [
baseWhere.length ? and(...baseWhere) : undefined,
];
const preferenceWhere = preferenceFilters.filter(Boolean) as SQL[];
const rows = await this.db
.select({
memory: baseSelection,
preference: preferenceSelection,
})
.from(userMemories)
.innerJoin(userMemoriesPreferences, joinCondition)
.where(preferenceWhere.length ? and(...preferenceWhere) : undefined)
.orderBy(...orderByClauses)
.limit(normalizedPageSize)
.offset(offset);
return rows.map((row) => ({
layer: LayersEnum.Preference,
memory: row.memory,
preference: row.preference,
})) as unknown as R[];
}
}
return [];
};
getMemoryDetail = async (
params: GetMemoryDetailParams,
): Promise<UserMemoryDetail | undefined> => {
@@ -2318,10 +2509,7 @@ export class UserMemoryModel {
.where(eq(userMemoriesIdentities.userId, this.userId))
.orderBy(desc(userMemoriesIdentities.capturedAt));
return res as Array<{
identity: typeof userMemoriesIdentities.$inferSelect;
memory: typeof userMemories.$inferSelect;
}>;
return res;
};
getIdentitiesByType = async (type: string): Promise<UserMemoryIdentityWithoutVectors[]> => {