feat(rename): complete searchSpace to workspace transition across frontend and backend

- Introduced a comprehensive specification for renaming `searchSpace` to `workspace` in `surfsense_web` and `surfsense_desktop`, ensuring all TypeScript identifiers, React props, and local data structures are updated.
- Implemented migration shims for persisted local state to prevent data loss during the transition.
- Updated observability metrics and IPC channels to reflect the new naming convention.
- Removed legacy `active-search-space` module and replaced it with `active-workspace` to maintain consistency.
- Ensured no behavioral changes or data loss for users during the renaming process.
This commit is contained in:
Anish Sarkar 2026-07-06 15:12:40 +05:30
parent 2a020629c5
commit a8c1fb660d
259 changed files with 5480 additions and 2285 deletions

View file

@ -4,8 +4,8 @@ type SpaceScopedQuery = {
where: (...args: unknown[]) => SpaceScopedQuery;
};
export function canReadSpace(ctx: Context, searchSpaceId: number): boolean {
return !!ctx?.allowedSpaceIds?.includes(searchSpaceId);
export function canReadSpace(ctx: Context, workspaceId: number): boolean {
return !!ctx?.allowedSpaceIds?.includes(workspaceId);
}
export function denySpace<T extends SpaceScopedQuery>(query: T): T {
@ -18,7 +18,7 @@ export function constrainToAllowedSpaces<T extends SpaceScopedQuery>(query: T, c
return denySpace(query);
}
if (allowedSpaceIds.length === 1) {
return query.where("searchSpaceId", allowedSpaceIds[0]) as T;
return query.where("workspaceId", allowedSpaceIds[0]) as T;
}
return query.where(
({
@ -27,6 +27,6 @@ export function constrainToAllowedSpaces<T extends SpaceScopedQuery>(query: T, c
}: {
cmp: (column: string, value: number) => unknown;
or: (...args: unknown[]) => unknown;
}) => or(...allowedSpaceIds.map((id) => cmp("searchSpaceId", id)))
}) => or(...allowedSpaceIds.map((id) => cmp("workspaceId", id)))
) as T;
}

View file

@ -4,7 +4,7 @@ import { zql } from "../schema/index";
import { constrainToAllowedSpaces } from "./authz";
// Mirrors chat byThread: client passes the parent id, the REST route still
// authorizes via `automation_id -> search_space`. No workspace_id on the
// authorizes via `automation_id -> workspace`. No workspace_id on the
// table by design.
export const automationRunQueries = {
byAutomation: defineQuery(

View file

@ -4,23 +4,17 @@ import { zql } from "../schema/index";
import { canReadSpace, constrainToAllowedSpaces, denySpace } from "./authz";
export const documentQueries = {
bySpace: defineQuery(
z.object({ searchSpaceId: z.number() }),
({ args: { searchSpaceId }, ctx }) => {
const query = zql.documents.where("searchSpaceId", searchSpaceId);
if (!canReadSpace(ctx, searchSpaceId)) return denySpace(query).orderBy("createdAt", "desc");
return constrainToAllowedSpaces(query, ctx).orderBy("createdAt", "desc");
}
),
bySpace: defineQuery(z.object({ workspaceId: z.number() }), ({ args: { workspaceId }, ctx }) => {
const query = zql.documents.where("workspaceId", workspaceId);
if (!canReadSpace(ctx, workspaceId)) return denySpace(query).orderBy("createdAt", "desc");
return constrainToAllowedSpaces(query, ctx).orderBy("createdAt", "desc");
}),
};
export const connectorQueries = {
bySpace: defineQuery(
z.object({ searchSpaceId: z.number() }),
({ args: { searchSpaceId }, ctx }) => {
const query = zql.search_source_connectors.where("searchSpaceId", searchSpaceId);
if (!canReadSpace(ctx, searchSpaceId)) return denySpace(query).orderBy("createdAt", "desc");
return constrainToAllowedSpaces(query, ctx).orderBy("createdAt", "desc");
}
),
bySpace: defineQuery(z.object({ workspaceId: z.number() }), ({ args: { workspaceId }, ctx }) => {
const query = zql.search_source_connectors.where("workspaceId", workspaceId);
if (!canReadSpace(ctx, workspaceId)) return denySpace(query).orderBy("createdAt", "desc");
return constrainToAllowedSpaces(query, ctx).orderBy("createdAt", "desc");
}),
};

View file

@ -4,12 +4,9 @@ import { zql } from "../schema/index";
import { canReadSpace, constrainToAllowedSpaces, denySpace } from "./authz";
export const folderQueries = {
bySpace: defineQuery(
z.object({ searchSpaceId: z.number() }),
({ args: { searchSpaceId }, ctx }) => {
const query = zql.folders.where("searchSpaceId", searchSpaceId);
if (!canReadSpace(ctx, searchSpaceId)) return denySpace(query).orderBy("position", "asc");
return constrainToAllowedSpaces(query, ctx).orderBy("position", "asc");
}
),
bySpace: defineQuery(z.object({ workspaceId: z.number() }), ({ args: { workspaceId }, ctx }) => {
const query = zql.folders.where("workspaceId", workspaceId);
if (!canReadSpace(ctx, workspaceId)) return denySpace(query).orderBy("position", "asc");
return constrainToAllowedSpaces(query, ctx).orderBy("position", "asc");
}),
};

View file

@ -4,14 +4,11 @@ import { zql } from "../schema/index";
import { canReadSpace, constrainToAllowedSpaces, denySpace } from "./authz";
export const podcastQueries = {
bySpace: defineQuery(
z.object({ searchSpaceId: z.number() }),
({ args: { searchSpaceId }, ctx }) => {
const query = zql.podcasts.where("searchSpaceId", searchSpaceId);
if (!canReadSpace(ctx, searchSpaceId)) return denySpace(query).orderBy("createdAt", "desc");
return constrainToAllowedSpaces(query, ctx).orderBy("createdAt", "desc");
}
),
bySpace: defineQuery(z.object({ workspaceId: z.number() }), ({ args: { workspaceId }, ctx }) => {
const query = zql.podcasts.where("workspaceId", workspaceId);
if (!canReadSpace(ctx, workspaceId)) return denySpace(query).orderBy("createdAt", "desc");
return constrainToAllowedSpaces(query, ctx).orderBy("createdAt", "desc");
}),
byId: defineQuery(z.object({ podcastId: z.number() }), ({ args: { podcastId }, ctx }) =>
constrainToAllowedSpaces(zql.podcasts.where("id", podcastId), ctx).one()
),

View file

@ -3,7 +3,7 @@ import { json, number, string, table } from "@rocicorp/zero";
export const automationTable = table("automations")
.columns({
id: number(),
searchSpaceId: number().from("workspace_id"),
workspaceId: number().from("workspace_id"),
})
.primaryKey("id");

View file

@ -23,7 +23,7 @@ export const newChatMessageTable = table("new_chat_messages")
export const newChatThreadTable = table("new_chat_threads")
.columns({
id: number(),
searchSpaceId: number().from("workspace_id"),
workspaceId: number().from("workspace_id"),
})
.primaryKey("id");

View file

@ -5,7 +5,7 @@ export const documentTable = table("documents")
id: number(),
title: string(),
documentType: string().from("document_type"),
searchSpaceId: number().from("workspace_id"),
workspaceId: number().from("workspace_id"),
folderId: number().optional().from("folder_id"),
createdById: string().optional().from("created_by_id"),
status: json(),
@ -24,7 +24,7 @@ export const searchSourceConnectorTable = table("search_source_connectors")
periodicIndexingEnabled: boolean().from("periodic_indexing_enabled"),
indexingFrequencyMinutes: number().optional().from("indexing_frequency_minutes"),
nextScheduledAt: number().optional().from("next_scheduled_at"),
searchSpaceId: number().from("workspace_id"),
workspaceId: number().from("workspace_id"),
userId: string().from("user_id"),
createdAt: number().from("created_at"),
})

View file

@ -6,7 +6,7 @@ export const folderTable = table("folders")
name: string(),
position: string(),
parentId: number().optional().from("parent_id"),
searchSpaceId: number().from("workspace_id"),
workspaceId: number().from("workspace_id"),
createdById: string().optional().from("created_by_id"),
createdAt: number().from("created_at"),
updatedAt: number().from("updated_at"),

View file

@ -4,7 +4,7 @@ export const notificationTable = table("notifications")
.columns({
id: number(),
userId: string().from("user_id"),
searchSpaceId: number().optional().from("workspace_id"),
workspaceId: number().optional().from("workspace_id"),
type: string(),
title: string(),
message: string(),

View file

@ -12,7 +12,7 @@ export const podcastTable = table("podcasts")
specVersion: number().from("spec_version"),
durationSeconds: number().optional().from("duration_seconds"),
error: string().optional(),
searchSpaceId: number().from("workspace_id"),
workspaceId: number().from("workspace_id"),
threadId: number().optional().from("thread_id"),
createdAt: number().from("created_at"),
})