Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions indexer/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,23 @@

export const WASTE_TYPES = ['Paper', 'Plastic', 'Metal', 'Glass', 'Organic', 'Electronic'] as const;
export const PARTICIPANT_ROLES = ['Recycler', 'Collector', 'Manufacturer'] as const;
export const DEFAULT_WASTE_TYPE = WASTE_TYPES[0];
export const DEFAULT_PARTICIPANT_ROLE = PARTICIPANT_ROLES[0];

export const CONTRACT_EVENT_TYPES = {
WASTE_REGISTERED: 'recycled',
PARTICIPANT_REGISTERED: 'reg',
WASTE_TRANSFERRED: 'transfer',
WASTE_CONFIRMED: 'confirmed',
TOKENS_REWARDED: 'rewarded',
WASTE_DEACTIVATED: 'deactive',
WASTE_GRADED: 'graded',
PROCESSING_STATUS_CHANGED: 'proc_upd',
WASTE_CONTAMINATED: 'contam',
AUCTION_CREATED: 'auc_cre',
AUCTION_ENDED: 'auc_end',
CARBON_CREDITS_EARNED: 'carbon',
} as const;

export const QUERY_LIMITS = {
DEFAULT: 20,
Expand All @@ -21,3 +38,19 @@ export const PAGINATION = {
DEFAULT_PAGE_SIZE: 20,
MAX_PAGE_SIZE: 100,
} as const;

export const ANALYTICS = {
DEFAULT_TOP_RESULTS_LIMIT: 10,
DEFAULT_TRANSFER_ACTIVITY_DAYS: 30,
} as const;

export const EVENT_PAGINATION = {
DEFAULT_RECENT_EVENTS_LIMIT: 50,
MAX_RECENT_EVENTS_LIMIT: 500,
} as const;

export const CACHE = {
METRICS_TTL_MS: 30_000,
} as const;

export const FULL_TEXT_SEARCH_CONFIG = 'english';
25 changes: 13 additions & 12 deletions indexer/src/pipeline/parse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
*/

import { RawContractEvent } from '../types';
import { CONTRACT_EVENT_TYPES } from '../constants';
import { ParsedEvent, EventMeta } from './types';

// ---------------------------------------------------------------------------
Expand Down Expand Up @@ -283,18 +284,18 @@ function parseCarbonCreditsEarned(event: RawContractEvent): ParsedEvent {
type ParserFn = (event: RawContractEvent) => ParsedEvent;

const PARSERS: Record<string, ParserFn> = {
recycled: parseWasteRegistered,
reg: parseParticipantRegistered,
transfer: parseWasteTransferred,
confirmed: parseWasteConfirmed,
rewarded: parseTokensRewarded,
deactive: parseWasteDeactivated,
graded: parseWasteGraded,
proc_upd: parseProcessingStatusChanged,
contam: parseWasteContaminated,
auc_cre: parseAuctionCreated,
auc_end: parseAuctionEnded,
carbon: parseCarbonCreditsEarned,
[CONTRACT_EVENT_TYPES.WASTE_REGISTERED]: parseWasteRegistered,
[CONTRACT_EVENT_TYPES.PARTICIPANT_REGISTERED]: parseParticipantRegistered,
[CONTRACT_EVENT_TYPES.WASTE_TRANSFERRED]: parseWasteTransferred,
[CONTRACT_EVENT_TYPES.WASTE_CONFIRMED]: parseWasteConfirmed,
[CONTRACT_EVENT_TYPES.TOKENS_REWARDED]: parseTokensRewarded,
[CONTRACT_EVENT_TYPES.WASTE_DEACTIVATED]: parseWasteDeactivated,
[CONTRACT_EVENT_TYPES.WASTE_GRADED]: parseWasteGraded,
[CONTRACT_EVENT_TYPES.PROCESSING_STATUS_CHANGED]: parseProcessingStatusChanged,
[CONTRACT_EVENT_TYPES.WASTE_CONTAMINATED]: parseWasteContaminated,
[CONTRACT_EVENT_TYPES.AUCTION_CREATED]: parseAuctionCreated,
[CONTRACT_EVENT_TYPES.AUCTION_ENDED]: parseAuctionEnded,
[CONTRACT_EVENT_TYPES.CARBON_CREDITS_EARNED]: parseCarbonCreditsEarned,
};

// ---------------------------------------------------------------------------
Expand Down
7 changes: 4 additions & 3 deletions indexer/src/pipeline/transform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
*/

import { WASTE_TYPE_MAP, ROLE_MAP } from '../types';
import { DEFAULT_PARTICIPANT_ROLE, DEFAULT_WASTE_TYPE } from '../constants';
import { ParsedEvent, TransformedEvent } from './types';

// ---------------------------------------------------------------------------
Expand All @@ -35,14 +36,14 @@ export function transformEvent(parsed: ParsedEvent): TransformedEvent {
case 'WasteRegistered':
return {
...parsed,
wasteType: WASTE_TYPE_MAP[parsed.wasteTypeNum] ?? 'Paper',
wasteType: WASTE_TYPE_MAP[parsed.wasteTypeNum] ?? DEFAULT_WASTE_TYPE,
recycler: normalizeAddress(parsed.recycler),
};

case 'ParticipantRegistered':
return {
...parsed,
role: ROLE_MAP[parsed.roleNum] ?? 'Recycler',
role: ROLE_MAP[parsed.roleNum] ?? DEFAULT_PARTICIPANT_ROLE,
address: normalizeAddress(parsed.address),
name: parsed.name.trim(),
};
Expand Down Expand Up @@ -90,7 +91,7 @@ export function transformEvent(parsed: ParsedEvent): TransformedEvent {
case 'CarbonCreditsEarned':
return {
...parsed,
wasteType: WASTE_TYPE_MAP[parsed.wasteTypeNum] ?? 'Paper',
wasteType: WASTE_TYPE_MAP[parsed.wasteTypeNum] ?? DEFAULT_WASTE_TYPE,
participant: normalizeAddress(parsed.participant),
};
}
Expand Down
3 changes: 2 additions & 1 deletion indexer/src/queries/eventQueries.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { getPool } from '../db/client';
import { recordQueryMetric } from '../db/queryOptimizer';
import { QUERY_LIMITS } from '../constants';

export interface EventFilter {
eventType?: string;
Expand All @@ -20,7 +21,7 @@ export interface EventQueryResult {

export async function queryEvents(filter: EventFilter): Promise<EventQueryResult> {
const pool = getPool();
const limit = Math.min(filter.limit ?? 100, 1000);
const limit = Math.min(filter.limit ?? QUERY_LIMITS.MAX, QUERY_LIMITS.MAX);
const offset = filter.offset ?? 0;

let sql = 'SELECT * FROM raw_events WHERE 1=1';
Expand Down
9 changes: 4 additions & 5 deletions indexer/src/queries/optimizedQueries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

import { getPool } from '../db/client';
import { recordQueryMetric } from '../db/queryOptimizer';
import { CACHE, EVENT_PAGINATION } from '../constants';

// ── Types ─────────────────────────────────────────────────────────────────────

Expand Down Expand Up @@ -157,11 +158,11 @@ export interface EventSummary {
* index (created in migration 003) to avoid seq-scans on large tables.
*/
export async function getRecentEvents(
limit = 50,
limit = EVENT_PAGINATION.DEFAULT_RECENT_EVENTS_LIMIT,
offset = 0,
eventType?: string,
): Promise<EventSummary[]> {
const params: unknown[] = [Math.min(limit, 500), offset];
const params: unknown[] = [Math.min(limit, EVENT_PAGINATION.MAX_RECENT_EVENTS_LIMIT), offset];
const typeClause = eventType ? ` AND event_type = $3` : '';
if (eventType) {params.push(eventType);}

Expand All @@ -182,15 +183,13 @@ const metricsCache: { value: SupplyChainStats | null; expiresAt: number } = {
value: null,
expiresAt: 0,
};
const METRICS_TTL_MS = 30_000; // 30 seconds

export async function getCachedSupplyChainStats(): Promise<SupplyChainStats> {
if (metricsCache.value && Date.now() < metricsCache.expiresAt) {
return metricsCache.value;
}
const fresh = await getSupplyChainStats();
metricsCache.value = fresh;
metricsCache.expiresAt = Date.now() + METRICS_TTL_MS;
metricsCache.expiresAt = Date.now() + CACHE.METRICS_TTL_MS;
return fresh;
}

Expand Down
3 changes: 2 additions & 1 deletion indexer/src/queries/participantQueries.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { getPool } from '../db/client';
import { recordQueryMetric } from '../db/queryOptimizer';
import { QUERY_LIMITS } from '../constants';

export type ParticipantRole = 'Recycler' | 'Collector' | 'Manufacturer';

Expand Down Expand Up @@ -56,7 +57,7 @@ export async function queryParticipants(
filter: ParticipantFilter
): Promise<ParticipantQueryResult> {
const pool = getPool();
const limit = Math.min(filter.limit ?? 100, 1000);
const limit = Math.min(filter.limit ?? QUERY_LIMITS.MAX, QUERY_LIMITS.MAX);
const offset = filter.offset ?? 0;

let sql = 'SELECT * FROM participants WHERE 1=1';
Expand Down
19 changes: 10 additions & 9 deletions indexer/src/queries/search.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,28 @@
import { getPool } from '../db/client';
import { ANALYTICS, FULL_TEXT_SEARCH_CONFIG, QUERY_LIMITS } from '../constants';

// --- Full-text search ---

export async function searchParticipants(query: string, limit = 20) {
export async function searchParticipants(query: string, limit = QUERY_LIMITS.DEFAULT) {
const { rows } = await getPool().query(
`SELECT address, role, name, latitude, longitude, registered_at,
ts_rank(search_vector, plainto_tsquery('english', $1)) AS rank
ts_rank(search_vector, plainto_tsquery('${FULL_TEXT_SEARCH_CONFIG}', $1)) AS rank
FROM participants
WHERE search_vector @@ plainto_tsquery('english', $1)
WHERE search_vector @@ plainto_tsquery('${FULL_TEXT_SEARCH_CONFIG}', $1)
ORDER BY rank DESC
LIMIT $2`,
[query, limit]
);
return rows;
}

export async function searchWastes(query: string, limit = 20) {
export async function searchWastes(query: string, limit = QUERY_LIMITS.DEFAULT) {
const { rows } = await getPool().query(
`SELECT w.id, w.recycler_address, w.waste_type, w.weight, w.is_confirmed, w.is_active,
w.registered_at,
ts_rank(w.search_vector, plainto_tsquery('english', $1)) AS rank
ts_rank(w.search_vector, plainto_tsquery('${FULL_TEXT_SEARCH_CONFIG}', $1)) AS rank
FROM wastes w
WHERE w.search_vector @@ plainto_tsquery('english', $1)
WHERE w.search_vector @@ plainto_tsquery('${FULL_TEXT_SEARCH_CONFIG}', $1)
ORDER BY rank DESC
LIMIT $2`,
[query, limit]
Expand All @@ -45,7 +46,7 @@ export async function getWasteStatsByType() {
return rows;
}

export async function getTopRecyclers(limit = 10) {
export async function getTopRecyclers(limit = ANALYTICS.DEFAULT_TOP_RESULTS_LIMIT) {
const { rows } = await getPool().query(
`SELECT p.address, p.name, p.role,
COUNT(w.id) AS waste_count,
Expand Down Expand Up @@ -74,7 +75,7 @@ export async function getRewardSummary() {
return rows;
}

export async function getTransferActivity(days = 30) {
export async function getTransferActivity(days = ANALYTICS.DEFAULT_TRANSFER_ACTIVITY_DAYS) {
const { rows } = await getPool().query(
`SELECT DATE_TRUNC('day', transferred_at) AS day,
COUNT(*) AS transfer_count
Expand All @@ -87,7 +88,7 @@ export async function getTransferActivity(days = 30) {
return rows;
}

export async function getCarbonCreditsByParticipant(limit = 10) {
export async function getCarbonCreditsByParticipant(limit = ANALYTICS.DEFAULT_TOP_RESULTS_LIMIT) {
const { rows } = await getPool().query(
`SELECT participant_address,
SUM(credits) AS total_credits,
Expand Down
26 changes: 26 additions & 0 deletions packages/shared/src/logger.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
export type LogLevel = 'debug' | 'info' | 'warn' | 'error';
interface LogEntry {
timestamp: string;
level: LogLevel;
message: string;
context?: Record<string, unknown>;
error?: Error;
}
export declare class StructuredLogger {
private level;
private enableConsole;
private logs;
constructor(level?: LogLevel, enableConsole?: boolean);
private shouldLog;
private createEntry;
debug(message: string, context?: Record<string, unknown>): void;
info(message: string, context?: Record<string, unknown>): void;
warn(message: string, context?: Record<string, unknown>): void;
error(message: string, error?: Error, context?: Record<string, unknown>): void;
private formatEntry;
getLogs(level?: LogLevel): LogEntry[];
clearLogs(): void;
setLevel(level: LogLevel): void;
}
export {};
//# sourceMappingURL=logger.d.ts.map
1 change: 1 addition & 0 deletions packages/shared/src/logger.d.ts.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

73 changes: 73 additions & 0 deletions packages/shared/src/logger.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions packages/shared/src/logger.js.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.