Skip to content

Commit 2cf6709

Browse files
dnplkndllclaude
andcommitted
feat: auto-reindex on empty Typesense backend startup
When the fulltext pod starts and the Typesense collection has 0 documents (e.g. after pod restart with emptyDir), automatically enumerate all active workspaces and queue a fullReindex event for each. This makes Typesense self-healing without requiring a PVC. - Add optional getDocCount() to FullTextAdapter interface - Implement getDocCount() in TypesenseAdapter via collections().retrieve() - Add checkAndTriggerReindex() to WorkspaceManager.startIndexer() - Elastic/OpenSearch backends skip the check (no getDocCount method) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Signed-off-by: Don Kendall <kendall@donkendall.com>
1 parent cb313eb commit 2cf6709

3 files changed

Lines changed: 53 additions & 0 deletions

File tree

foundations/server/packages/core/src/types.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -409,6 +409,12 @@ export interface FullTextAdapter {
409409

410410
// If no field is provided, will return existing mapping of all dimms.
411411
initMapping: (ctx: MeasureContext, field?: { key: string, dims: number }) => Promise<boolean>
412+
413+
// Optional: return the total document count in the index/collection.
414+
// Used by non-persistent backends (e.g. Typesense with emptyDir) to detect
415+
// empty state on startup and trigger automatic reindex.
416+
// Returns -1 if the count cannot be determined (e.g. connection error).
417+
getDocCount?: (ctx: MeasureContext) => Promise<number>
412418
}
413419

414420
/**

foundations/server/packages/typesense/src/adapter.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,21 @@ class TypesenseAdapter implements FullTextAdapter {
207207
// Typesense client does not require explicit close
208208
}
209209

210+
async getDocCount (ctx: MeasureContext): Promise<number> {
211+
try {
212+
const col = await this.client.collections(this.collectionName).retrieve()
213+
return (col as any).num_documents ?? 0
214+
} catch (err: any) {
215+
if (isConnectionError(err)) {
216+
ctx.warn('Typesense DB is not available for doc count check')
217+
return -1
218+
}
219+
Analytics.handleError(err)
220+
ctx.error('Typesense getDocCount error', { error: err })
221+
return -1
222+
}
223+
}
224+
210225
async searchString (
211226
ctx: MeasureContext,
212227
workspaceId: WorkspaceUuid,

pods/fulltext/src/manager.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,8 @@ export class WorkspaceManager {
105105
}
106106
}
107107

108+
await this.checkAndTriggerReindex()
109+
108110
this.shutdownInterval = setInterval(() => {
109111
for (const [k, v] of [...this.indexers.entries()]) {
110112
if (v instanceof Promise) {
@@ -159,6 +161,36 @@ export class WorkspaceManager {
159161
)
160162
}
161163

164+
private async checkAndTriggerReindex (): Promise<void> {
165+
if (this.fulltextAdapter.getDocCount === undefined) {
166+
return
167+
}
168+
169+
const docCount = await this.fulltextAdapter.getDocCount(this.ctx)
170+
if (docCount < 0) {
171+
this.ctx.warn('Could not determine backend doc count, skipping auto-reindex check')
172+
return
173+
}
174+
if (docCount > 0) {
175+
this.ctx.info('Fulltext backend has documents, skipping auto-reindex', { docCount })
176+
return
177+
}
178+
179+
this.ctx.warn('Fulltext backend is empty (0 documents), triggering auto-reindex for all workspaces')
180+
try {
181+
const token = generateToken(systemAccountUuid, undefined, { service: 'tool' })
182+
const accountClient = getAccountClient(token)
183+
const workspaces = await accountClient.listWorkspaces(null, 'active')
184+
this.ctx.info('Auto-reindex: found workspaces', { count: workspaces.length })
185+
for (const ws of workspaces) {
186+
await this.fulltextProducer.send(this.ctx, ws.uuid, [workspaceEvents.fullReindex()])
187+
this.ctx.info('Queued full reindex', { workspace: ws.uuid })
188+
}
189+
} catch (err: any) {
190+
this.ctx.error('Failed to trigger auto-reindex', { error: err })
191+
}
192+
}
193+
162194
private async processTransactions (
163195
m: ConsumerMessage<TxCUD<Doc<Space>> | TxDomainEvent<QueueSourced<Event>>>,
164196
control: ConsumerControl

0 commit comments

Comments
 (0)