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
4 changes: 3 additions & 1 deletion .config-audit-ignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@

# Vite environment variables are accessed through import.meta.env and may not
# be directly referenced in source code but are used by the build process
# VITE_STELLAR_NETWORK
# Read via `import.meta.env?.VITE_STELLAR_NETWORK` in
# dashboard/src/config/stellarNetwork.ts, which the usage scanner does not match.
VITE_STELLAR_NETWORK
# VITE_EVENTS_API_URL

# Node.js runtime environment variable
Expand Down
48 changes: 48 additions & 0 deletions _bracecheck.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import fs from 'fs';

const src = fs.readFileSync(process.argv[2], 'utf8');

let i = 0;
let line = 1;
const stack = [];
let state = 'code'; // code | line | block | sq | dq | tpl

function report(ch, depth) {
if ('{}()[]'.includes(ch)) {
if ('{(['.includes(ch)) {
stack.push({ ch, line });
} else {
const open = stack.pop();
const match = { '}': '{', ')': '(', ']': '[' }[ch];
if (!open || open.ch !== match) {
console.log(`UNBALANCED at line ${line}: saw '${ch}', expected close for '${open ? open.ch : 'nothing'}' (opened line ${open ? open.line : '-'})`);
}
}
}
}

for (i = 0; i < src.length; i++) {
const c = src[i];
const n = src[i + 1];
if (c === '\n') line++;
if (state === 'code') {
if (c === '/' && n === '/') { state = 'line'; i++; continue; }
if (c === '/' && n === '*') { state = 'block'; i++; continue; }
if (c === '"') { state = 'dq'; continue; }
if (c === "'") { state = 'sq'; continue; }
if (c === '`') { state = 'tpl'; continue; }
report(c);
} else if (state === 'line') {
if (c === '\n') state = 'code';
} else if (state === 'block') {
if (c === '*' && n === '/') { state = 'code'; i++; }
} else if (state === 'dq') {
if (c === '\\') { i++; } else if (c === '"') state = 'code';
} else if (state === 'sq') {
if (c === '\\') { i++; } else if (c === "'") state = 'code';
} else if (state === 'tpl') {
if (c === '\\') { i++; } else if (c === '`') state = 'code';
}
}

console.log(`Remaining open at EOF: ${stack.map((s) => `${s.ch}@${s.line}`).join(', ') || 'none'}`);
152 changes: 152 additions & 0 deletions _bt.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
export interface ContractConfig {
address: string;
events: string[];
/** Optional user ID for per-user notification preference gating */
userId?: string;
}

export interface DiscordConfig {
webhookUrl: string;
webhookId: string;
retryCount?: number;
backoffBaseSeconds?: number;
deduplicationWindowMs?: number;
deduplicationMaxSize?: number;
timeoutMs?: number;
}

export interface RetryQueueConfig {
baseDelayMs?: number;
multiplier?: number;
jitter?: boolean;
maxRetries?: number;
processIntervalMs?: number;
}

export interface WebhookSecret {
id: string;
secret: string;
}

export interface RateLimitConfig {
enabled: boolean;
windowMs: number;
maxRequests: number;
clientOverrides: Record<string, { maxRequests: number; windowMs?: number }>;
}

export interface ApiKey {
key: string;
name?: string;
}

export interface Config {
stellarNetwork: string;
stellarRpcUrl: string;
stellarNetworkPassphrase: string;
contractAddresses: ContractConfig[];
pollIntervalMs: number;
maxReconnectAttempts: number;
reconnectDelayMs: number;
eventsApiPort: number;
eventsApiCorsOrigin: string;
discord?: DiscordConfig;
retryQueue?: RetryQueueConfig;
eventQueue?: EventQueueConfig;
webhookSecrets?: WebhookSecret[];
apiKeys?: ApiKey[];
scheduler?: SchedulerConfig;
retryScheduler?: RetrySchedulerOptions;
databasePath?: string;
rateLimit?: RateLimitConfig;
cleanup?: AppCleanupConfig;
analytics?: AnalyticsConfig;
backfill?: BackfillConfig;
}

export interface SchedulerConfig {
enabled: boolean;
pollIntervalMs: number;
lockTimeoutMs: number;
processorId?: string;
batchSize: number;
timingBufferMs: number;
}

export interface EventQueueConfig {
/** Maximum number of events to process concurrently (default: 1, must be >= 1). */
maxConcurrency?: number;
/** Maximum retry attempts per event (default: 3). */
maxRetries?: number;
/** Base delay in ms for exponential backoff (default: 2000). */
baseDelayMs?: number;
/** How often to poll the queue for due events in ms (default: 1000). */
pollIntervalMs?: number;
}

export interface AppCleanupConfig {
/** How often to run cleanup jobs (ms). */
intervalMs: number;
/** Retain completed/failed/cancelled notifications for this long (ms). */
notificationRetentionMs: number;
/** Retain rate-limit audit rows for this long (ms). */
rateLimitEventRetentionMs: number;
/** Retain in-memory events for this long (ms). */
eventRetentionMs: number;
/** Retain notification execution log rows for this long (ms). */
executionLogRetentionMs: number;
}

export interface RetrySchedulerOptions {
enabled: boolean;
pollIntervalMs: number;
lockTimeoutMs: number;
processorId?: string;
batchSize: number;
baseDelayMs: number;
multiplier: number;
maxDelayMs: number;
jitter: boolean;
}

export interface AnalyticsConfig {
enabled: boolean;
maxRecords: number;
maxBuckets: number;
bucketSizeMs: number;
/** How often to persist summarized snapshots (ms). */
persistIntervalMs: number;
/** How long to retain persisted snapshots (days). */
snapshotRetentionDays: number;
}

export interface BackfillConfig {
/**
* When true, a backfill run is triggered once at startup before the live
* polling loop begins.
*/
enabled: boolean;
/**
* The earliest ledger from which to recover missed events (inclusive).
* Events at or after this ledger that are not yet in processed_events will
* be passed through the normal processing pipeline.
*/
startLedger: number;
/**
* The last ledger to include in the backfill window (inclusive).
* Defaults to the current network-tip ledger when not set.
*/
endLedger?: number;
/**
* Maximum number of Stellar RPC pages to fetch per contract during the
* backfill. Each page holds up to 200 events. Defaults to 50.
*/
maxPages?: number;
/**
* Upper bound on the total number of on-chain events processed per
* contract. Acts as a safety rail against unexpectedly large windows.
* Defaults to 10 000.
*/
maxEventsPerContract?: number;
}

151 changes: 151 additions & 0 deletions _bt2.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
export interface ContractConfig {
address: string;
events: string[];
/** Optional user ID for per-user notification preference gating */
userId?: string;
}

export interface DiscordConfig {
webhookUrl: string;
webhookId: string;
retryCount?: number;
backoffBaseSeconds?: number;
deduplicationWindowMs?: number;
deduplicationMaxSize?: number;
timeoutMs?: number;
}

export interface RetryQueueConfig {
baseDelayMs?: number;
multiplier?: number;
jitter?: boolean;
maxRetries?: number;
processIntervalMs?: number;
}

export interface WebhookSecret {
id: string;
secret: string;
}

export interface RateLimitConfig {
enabled: boolean;
windowMs: number;
maxRequests: number;
clientOverrides: Record<string, { maxRequests: number; windowMs?: number }>;
}

export interface ApiKey {
key: string;
name?: string;
}

export interface Config {
stellarNetwork: string;
stellarRpcUrl: string;
stellarNetworkPassphrase: string;
contractAddresses: ContractConfig[];
pollIntervalMs: number;
maxReconnectAttempts: number;
reconnectDelayMs: number;
eventsApiPort: number;
eventsApiCorsOrigin: string;
discord?: DiscordConfig;
retryQueue?: RetryQueueConfig;
eventQueue?: EventQueueConfig;
webhookSecrets?: WebhookSecret[];
apiKeys?: ApiKey[];
scheduler?: SchedulerConfig;
retryScheduler?: RetrySchedulerOptions;
databasePath?: string;
rateLimit?: RateLimitConfig;
cleanup?: AppCleanupConfig;
analytics?: AnalyticsConfig;
expiration?: ExpirationConfig;
backfill?: BackfillConfig;
}

export interface SchedulerConfig {
enabled: boolean;
pollIntervalMs: number;
lockTimeoutMs: number;
processorId?: string;
batchSize: number;
timingBufferMs: number;
}

export interface EventQueueConfig {
/** Maximum number of events to process concurrently (default: 1, must be >= 1). */
maxConcurrency?: number;
/** Maximum retry attempts per event (default: 3). */
maxRetries?: number;
/** Base delay in ms for exponential backoff (default: 2000). */
baseDelayMs?: number;
/** How often to poll the queue for due events in ms (default: 1000). */
pollIntervalMs?: number;
}

export interface AppCleanupConfig {
/** How often to run cleanup jobs (ms). */
intervalMs: number;
/** Retain completed/failed/cancelled notifications for this long (ms). */
notificationRetentionMs: number;
/** Retain rate-limit audit rows for this long (ms). */
rateLimitEventRetentionMs: number;
/** Retain in-memory events for this long (ms). */
eventRetentionMs: number;
/** Retain notification execution log rows for this long (ms). */
executionLogRetentionMs: number;
}

export interface RetrySchedulerOptions {
enabled: boolean;
pollIntervalMs: number;
lockTimeoutMs: number;
processorId?: string;
batchSize: number;
baseDelayMs: number;
multiplier: number;
maxDelayMs: number;
jitter: boolean;
}

export interface AnalyticsConfig {
enabled: boolean;
maxRecords: number;
maxBuckets: number;
bucketSizeMs: number;
/** How often to persist summarized snapshots (ms). */
persistIntervalMs: number;
/** How long to retain persisted snapshots (days). */
snapshotRetentionDays: number;
}

export interface ExpirationConfig {
/** Default expiration time in milliseconds (default: 24 hours = 86400000). */
defaultExpirationMs: number;
/** Per-event-type expiration times in milliseconds. */
perEventTypeExpiration?: Record<string, number>;
/** Whether expiration checking is enabled (default: true). */
enabled: boolean;
}

/**
* Safety limits for the historical backfill that runs when the listener
* starts without a stored cursor (first boot or after downtime).
*/
export interface BackfillConfig {
/**
* Maximum number of ledgers to replay from the network tip on a cold start.
*
* When the subscriber has no persisted cursor for a contract it would
* normally request events from ledger 1, which can be an arbitrarily large
* range after downtime or a configuration change. This limit caps the
* range to the most recent `maxLedgers` ledgers instead.
*
* Set to `0` to disable the limit and allow full historical replay
* (the previous default behaviour). Default: 10 000.
*/
maxLedgers: number;
}

Loading