SPB Git forge

spb/rareindex

Public
54commits 1branches 0releases
7.1 MBsize
maindefault branch
10 days agolast push
TypeScript 61.9% HTML 37.2% SQL 0.7%
2.4 KB · 57 lines typescript
Raw Blame History
1import { sha256, type ConnectorHealth, type NormalizedRecord } from '@rareindex/shared';2import { healthFromRuns } from './health.js';3import { policyFor, type DomainPolicy } from './domains.js';4import type { ConnectorMeta, CrawlContext, HealthContext, RareIndexConnector, RawRecordInput, RawRecordLike } from './types.js';56/**7 * Convenience base class. Connectors may implement the interface directly; this class adds8 * rate limiting, checkpointing helpers and a default health check.9 */10export abstract class BaseConnector implements RareIndexConnector {11  abstract readonly version: string;12  abstract readonly parserVersion: string;13  readonly urlPatterns?: RegExp[];14  /** minimum ms between requests to the source (politeness) */15  protected minIntervalMs = 500;16  private lastRequestAt = 0;1718  constructor(public readonly meta: ConnectorMeta) {}1920  abstract crawl(ctx: CrawlContext): AsyncIterable<RawRecordInput>;21  abstract normalize(raw: RawRecordLike): Promise<NormalizedRecord[]>;2223  async healthCheck(ctx: HealthContext): Promise<ConnectorHealth> {24    const h = healthFromRuns(ctx, this.meta.schemaVersion);25    if (ctx.recentRuns.length === 0) {26      // Fresh connector: light probe of the source root.27      const res = await ctx.fetch(this.meta.sourceUrl, { engines: ['api'], failOnHttpError: false, responseType: 'text', minQuality: 0 });28      h.status = res.httpStatus && res.httpStatus < 500 ? 'unknown' : 'failing';29      h.last_error = res.error;30    }31    return h;32  }3334  /** Politeness delay between requests: the larger of the connector's own interval and the domain policy (SPEC §16). */35  protected async throttle(url?: string): Promise<void> {36    const interval = Math.max(this.minIntervalMs, policyFor(url ?? this.meta.sourceUrl).minIntervalMs);37    const wait = this.lastRequestAt + interval - Date.now();38    if (wait > 0) await new Promise((r) => setTimeout(r, wait));39    this.lastRequestAt = Date.now();40  }4142  /** Effective per-domain policy for this connector's source. */43  protected get policy(): DomainPolicy {44    return policyFor(this.meta.sourceUrl);45  }4647  /** Stable content hash for dedupe of raw payloads. */48  protected hash(payload: unknown): string {49    return sha256(typeof payload === 'string' ? payload : JSON.stringify(payload));50  }5152  /** Helper: yield-limit aware counter. */53  protected reached(ctx: CrawlContext, count: number): boolean {54    return ctx.options.limit !== undefined && count >= ctx.options.limit;55  }56}57