diff --git a/README.md b/README.md index 19329c1..0b96a00 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,10 @@ npm start # indexer + read API # or run separately: npm run index # indexer only npm run serve # read API only + +# indexer flags (override the matching env var; see --help): +node index.js --hoses profiles --no-legacy # run a single hose, no legacy fallback +node src/indexer.js --help ``` ## Read API diff --git a/index.js b/index.js index 4b0039c..f343fbb 100644 --- a/index.js +++ b/index.js @@ -1,6 +1,8 @@ // Entry point: run the read API and the relay indexer in one process. import { startServer } from './src/server.js'; -import { runIndexer } from './src/indexer.js'; +import { runIndexer, parseArgs, USAGE } from './src/indexer.js'; + +if (parseArgs().help) { console.log(USAGE); process.exit(0); } await startServer(); -await runIndexer(); +await runIndexer(); // honours --hoses / --legacy / --no-legacy (override env) diff --git a/src/indexer.js b/src/indexer.js index 31089fa..457ddf2 100644 --- a/src/indexer.js +++ b/src/indexer.js @@ -42,6 +42,38 @@ export function planIngest(allHoses = ALL_HOSES, env = process.env, legacyKindsA const SUB_ID = 'beacon'; const RECONNECT_MS = 3000; +export const USAGE = `beacon indexer — firehose into MongoDB + +Usage: node index.js [flags] (or: node src/indexer.js [flags]) + +Flags (override the matching env var): + --hoses comma list of hose names to run (env HOSES; default: all) + --no-legacy disable the raw-upsert fallback for un-migrated kinds (INDEX_LEGACY_KINDS=0) + --legacy force the legacy fallback on + -h, --help show this help + +Env: MONGODB_URI, MONGO_DB, RELAYS, PORT — see .env.example`; + +/** + * Parse indexer CLI flags into an env-overlay (only keys the user passed). + * Merged over process.env by runIndexer so flags win and env is the fallback. + */ +export function parseArgs(argv = process.argv.slice(2)) { + const out = {}; + for (let i = 0; i < argv.length; i++) { + const a = argv[i]; + if (a === '-h' || a === '--help') out.help = true; + else if (a === '--no-legacy') out.INDEX_LEGACY_KINDS = '0'; + else if (a === '--legacy') out.INDEX_LEGACY_KINDS = '1'; + else if (a === '--hoses' || a.startsWith('--hoses=')) { + const next = argv[i + 1]; + out.HOSES = a.includes('=') ? a.slice(a.indexOf('=') + 1) + : (next && !next.startsWith('--') ? argv[++i] : ''); + } + } + return out; +} + function connectRelay(url, kinds, onEvent) { let ws, closed = false, timer; const open = () => { @@ -63,7 +95,9 @@ function connectRelay(url, kinds, onEvent) { export async function runIndexer() { const db = await connect(); - const { hoses, hoseFor, kinds, legacy, unknown } = planIngest(); + // CLI flags override env; env (process.env / .env) is the fallback. + const env = { ...process.env, ...parseArgs() }; + const { hoses, hoseFor, kinds, legacy, unknown } = planIngest(ALL_HOSES, env); if (unknown.length) console.warn(`[beacon] unknown hose(s) in HOSES, ignored: ${unknown.join(', ')}`); if (!kinds.length) { console.warn('[beacon] no hoses enabled and no legacy kinds — nothing to index'); return { stop() {} }; } for (const h of hoses) await h.ensureIndexes(db); @@ -87,4 +121,7 @@ export async function runIndexer() { return { stop }; } -if (import.meta.url === `file://${process.argv[1]}`) runIndexer(); +if (import.meta.url === `file://${process.argv[1]}`) { + if (parseArgs().help) { console.log(USAGE); process.exit(0); } + runIndexer(); +} diff --git a/test/indexer-args.test.js b/test/indexer-args.test.js new file mode 100644 index 0000000..fd690bb --- /dev/null +++ b/test/indexer-args.test.js @@ -0,0 +1,50 @@ +// CLI flag parsing for the indexer. parseArgs produces an env-overlay that +// runIndexer merges over process.env (flags win, env is the fallback). +import test from 'node:test'; +import assert from 'node:assert/strict'; +import { parseArgs, planIngest } from '../src/indexer.js'; + +const profiles = { name: 'profiles', kinds: [0] }; +const follows = { name: 'follows', kinds: [3] }; +const ALL = [profiles, follows]; + +test('no flags -> empty overlay', () => { + assert.deepEqual(parseArgs([]), {}); +}); + +test('--hoses with a space-separated value', () => { + assert.deepEqual(parseArgs(['--hoses', 'profiles,follows']), { HOSES: 'profiles,follows' }); +}); + +test('--hoses=value form', () => { + assert.deepEqual(parseArgs(['--hoses=profiles']), { HOSES: 'profiles' }); +}); + +test('--hoses with no value -> empty string (planIngest treats as default)', () => { + assert.deepEqual(parseArgs(['--hoses']), { HOSES: '' }); + assert.deepEqual(parseArgs(['--hoses', '--no-legacy']), { HOSES: '', INDEX_LEGACY_KINDS: '0' }); +}); + +test('--no-legacy and --legacy map to INDEX_LEGACY_KINDS', () => { + assert.deepEqual(parseArgs(['--no-legacy']), { INDEX_LEGACY_KINDS: '0' }); + assert.deepEqual(parseArgs(['--legacy']), { INDEX_LEGACY_KINDS: '1' }); +}); + +test('--help / -h set help', () => { + assert.equal(parseArgs(['--help']).help, true); + assert.equal(parseArgs(['-h']).help, true); +}); + +test('flags compose', () => { + assert.deepEqual(parseArgs(['--hoses', 'profiles', '--no-legacy']), + { HOSES: 'profiles', INDEX_LEGACY_KINDS: '0' }); +}); + +test('flags override env when merged (the runIndexer contract)', () => { + const env = { HOSES: 'profiles,follows', INDEX_LEGACY_KINDS: '1' }; + const merged = { ...env, ...parseArgs(['--hoses', 'profiles', '--no-legacy']) }; + const p = planIngest(ALL, merged, []); + assert.deepEqual(p.hoses.map((h) => h.name), ['profiles']); // flag won over env + assert.equal(p.legacy, false); + assert.deepEqual(p.kinds, [0]); +});