@cnstra/persist-postgres 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/CNSPostgresStimulationRepository.ts","../src/migrate.ts"],"sourcesContent":["export {\n CNSPostgresStimulationRepository,\n type TCNSPostgresStimulationRepositoryOptions,\n} from './CNSPostgresStimulationRepository';\nexport {\n ensureSchema,\n persistPostgresSchemaSql,\n assertSafePrefix,\n DEFAULT_PREFIX,\n type IPgQueryable,\n} from './migrate';\n","/**\n * Postgres relational store for the CNStra stimulation/attempt/task resumable model\n * (`ICNSStimulationRepository`). The stimulation's `progress` is NORMALISED: the\n * outstanding frontier into `<prefix>stimulation_frontier` (one row per task) —\n * rebuilt into `TCNSProgress` on read.\n *\n * Reuses the `IPgQueryable` seam so `pg` is an optional peer: inject any pg\n * client/pool, or pass a `connectionString` for the repo to own a Pool. Behaviourally\n * matches `CNSInMemoryStimulationRepository` (listStimulations newest-first, getAttempts\n * by attemptNumber, getTasks by index, delete cascades).\n */\nimport { Pool } from 'pg';\nimport type {\n ICNSStimulationRepository,\n ICNSStimulationRosterQuery,\n TCNSRosterEntry,\n} from '@cnstra/persist';\nimport { computeRosterFacets, encodeRosterCursor } from '@cnstra/persist';\nimport type {\n TCNSStimulationState,\n TCNSStimulationAttempt,\n TCNSStimulationTask,\n TCNSProgress,\n TCNSPlannedTaskJSON,\n TCNSSignalJSON,\n ECNSStimulationStatus,\n ECNSStimulationTaskStatus,\n TCNSStimulationRosterFilter,\n TCNSStimulationRosterRow,\n TCNSStimulationRosterPage,\n TCNSStimulationRosterFacets,\n} from '@cnstra/persist-dto';\nimport {\n DEFAULT_PREFIX,\n assertSafePrefix,\n ensureSchema,\n type IPgQueryable,\n} from './migrate';\n\nexport interface TCNSPostgresStimulationRepositoryOptions {\n connectionString?: string;\n /** Inject an existing pg Pool (transactions supported; not closed by `close()`). */\n pool?: Pool;\n /** Inject any queryable (reads + best-effort non-transactional writes). */\n queryable?: IPgQueryable;\n /** Table prefix (default `cns_`). */\n tablePrefix?: string;\n /** Lazily ensure the schema on first use (default true). */\n autoMigrate?: boolean;\n}\n\nconst j = (v: unknown): string => JSON.stringify(v ?? null);\nconst num = (v: unknown): number => Number(v);\nconst numOrNull = (v: unknown): number | null => (v == null ? null : Number(v));\n\nexport class CNSPostgresStimulationRepository\n implements ICNSStimulationRepository, ICNSStimulationRosterQuery\n{\n private readonly db: IPgQueryable;\n private readonly pool?: Pool;\n private readonly ownsPool: boolean;\n private readonly p: string;\n private readonly autoMigrate: boolean;\n private ready: Promise<void> | undefined;\n\n constructor(opts: TCNSPostgresStimulationRepositoryOptions) {\n if (opts.pool) {\n this.pool = opts.pool;\n this.db = opts.pool;\n this.ownsPool = false;\n } else if (opts.connectionString) {\n this.pool = new Pool({ connectionString: opts.connectionString });\n this.db = this.pool;\n this.ownsPool = true;\n } else if (opts.queryable) {\n this.db = opts.queryable;\n this.ownsPool = false;\n } else {\n throw new Error(\n '[@cnstra/persist-postgres] needs `connectionString`, `pool`, or `queryable`'\n );\n }\n this.p = assertSafePrefix(opts.tablePrefix ?? DEFAULT_PREFIX);\n this.autoMigrate = opts.autoMigrate ?? true;\n }\n\n private ensure(): Promise<void> {\n if (!this.autoMigrate) return Promise.resolve();\n return (this.ready ??= ensureSchema(this.db, this.p));\n }\n\n /** Run `fn` in a transaction when a Pool is available; else best-effort inline. */\n private async tx<T>(fn: (db: IPgQueryable) => Promise<T>): Promise<T> {\n if (!this.pool) return fn(this.db);\n const client = await this.pool.connect();\n try {\n await client.query('BEGIN');\n const out = await fn(client as unknown as IPgQueryable);\n await client.query('COMMIT');\n return out;\n } catch (e) {\n await client.query('ROLLBACK');\n throw e;\n } finally {\n client.release();\n }\n }\n\n // ── write ──\n\n async saveStimulation(s: TCNSStimulationState): Promise<void> {\n await this.ensure();\n const { p } = this;\n await this.tx(async db => {\n await db.query(\n `INSERT INTO ${p}stimulation (stimulation_id, entry, status, scope_name, updated_at)\n VALUES ($1, $2::jsonb, $3, $4, now())\n ON CONFLICT (stimulation_id)\n DO UPDATE SET entry = EXCLUDED.entry, status = EXCLUDED.status,\n scope_name = EXCLUDED.scope_name, updated_at = now()`,\n [s.stimulationId, j(s.entry), s.status, s.scopeName ?? null]\n );\n // Replace the normalised frontier.\n await db.query(\n `DELETE FROM ${p}stimulation_frontier WHERE stimulation_id = $1`,\n [s.stimulationId]\n );\n for (let i = 0; i < s.progress.tasks.length; i++) {\n const t = s.progress.tasks[i];\n await db.query(\n `INSERT INTO ${p}stimulation_frontier\n (stimulation_id, ord, neuron_name, dendrite_collateral_name, input)\n VALUES ($1, $2, $3, $4, $5::jsonb)`,\n [\n s.stimulationId,\n i,\n t.neuronName,\n t.dendriteCollateralName,\n t.input ? j(t.input) : null,\n ]\n );\n }\n });\n }\n\n async saveAttempt(a: TCNSStimulationAttempt): Promise<void> {\n await this.ensure();\n const { p } = this;\n await this.db.query(\n `INSERT INTO ${p}stimulation_attempt\n (stimulation_attempt_id, stimulation_id, attempt_number, status, started_at,\n completed_at, hop_count, has_error, replay_of, entry)\n VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10::jsonb)\n ON CONFLICT (stimulation_attempt_id) DO UPDATE SET\n status = EXCLUDED.status, completed_at = EXCLUDED.completed_at,\n hop_count = EXCLUDED.hop_count, has_error = EXCLUDED.has_error,\n replay_of = EXCLUDED.replay_of, entry = EXCLUDED.entry`,\n [\n a.stimulationAttemptId,\n a.stimulationId,\n a.attemptNumber,\n a.status,\n a.startedAt,\n a.completedAt,\n a.hopCount,\n a.hasError,\n a.replayOf,\n j(a.entry),\n ]\n );\n }\n\n async appendTask(t: TCNSStimulationTask): Promise<void> {\n await this.ensure();\n const { p } = this;\n await this.db.query(\n `INSERT INTO ${p}task\n (stimulation_attempt_id, \"index\", neuron_name, dendrite_collateral_name,\n input_index, output, status, error, started_at, duration)\n VALUES ($1,$2,$3,$4,$5,$6::jsonb,$7,$8,$9,$10)\n ON CONFLICT (stimulation_attempt_id, \"index\") DO NOTHING`,\n [\n t.stimulationAttemptId,\n t.index,\n t.neuronName,\n t.dendriteCollateralName,\n t.inputIndex,\n t.output ? j(t.output) : null,\n t.status,\n t.error,\n t.startedAt,\n t.duration,\n ]\n );\n }\n\n // ── read ──\n\n async getStimulation(\n stimulationId: string\n ): Promise<TCNSStimulationState | undefined> {\n await this.ensure();\n const { p } = this;\n const stimRes = await this.db.query(\n `SELECT entry, status, scope_name FROM ${p}stimulation WHERE stimulation_id = $1`,\n [stimulationId]\n );\n const row = stimRes.rows[0] as\n | {\n entry: TCNSSignalJSON;\n status: ECNSStimulationStatus;\n scope_name: string | null;\n }\n | undefined;\n if (!row) return undefined;\n const frontier = await this.db.query(\n `SELECT ord, neuron_name, dendrite_collateral_name, input\n FROM ${p}stimulation_frontier WHERE stimulation_id = $1 ORDER BY ord`,\n [stimulationId]\n );\n return {\n stimulationId,\n entry: row.entry,\n status: row.status,\n progress: this.buildProgress(frontier.rows as TFrontierRow[]),\n ...(row.scope_name != null ? { scopeName: row.scope_name } : {}),\n };\n }\n\n async listStimulations(filter?: {\n scopeName?: string;\n }): Promise<TCNSStimulationState[]> {\n await this.ensure();\n const { p } = this;\n const scope = filter?.scopeName ?? null;\n const stimsRes = await this.db.query(\n `SELECT stimulation_id, entry, status, scope_name FROM ${p}stimulation\n WHERE ($1::text IS NULL OR scope_name = $1)\n ORDER BY updated_at DESC`,\n [scope]\n );\n const stims = stimsRes.rows as TStimulationRow[];\n if (stims.length === 0) return [];\n const ids = stims.map(r => r.stimulation_id);\n const frontierRes = await this.db.query(\n `SELECT stimulation_id, ord, neuron_name, dendrite_collateral_name, input\n FROM ${p}stimulation_frontier WHERE stimulation_id = ANY($1) ORDER BY stimulation_id, ord`,\n [ids]\n );\n const frontierBy = groupBy(\n frontierRes.rows as TFrontierRow[],\n r => r.stimulation_id\n );\n return stims.map(r => ({\n stimulationId: r.stimulation_id,\n entry: r.entry,\n status: r.status,\n progress: this.buildProgress(\n frontierBy.get(r.stimulation_id) ?? []\n ),\n ...(r.scope_name != null ? { scopeName: r.scope_name } : {}),\n }));\n }\n\n async getAttempts(\n stimulationId: string\n ): Promise<TCNSStimulationAttempt[]> {\n await this.ensure();\n const { p } = this;\n const res = await this.db.query(\n `SELECT * FROM ${p}stimulation_attempt WHERE stimulation_id = $1 ORDER BY attempt_number`,\n [stimulationId]\n );\n return (res.rows as TAttemptRow[]).map(rowToAttempt);\n }\n\n async getTasks(\n stimulationAttemptId: string\n ): Promise<TCNSStimulationTask[]> {\n await this.ensure();\n const { p } = this;\n const res = await this.db.query(\n `SELECT * FROM ${p}task WHERE stimulation_attempt_id = $1 ORDER BY \"index\"`,\n [stimulationAttemptId]\n );\n return (res.rows as TTaskRow[]).map(rowToTask);\n }\n\n // ── roster query capability (filter + keyset + facets pushed into SQL) ──\n\n /**\n * Derive every filtered roster row in one CTE: `startedAt` = MIN(attempt),\n * `completedAt` = the latest attempt's, `hopCount` = SUM, `hasError` from\n * status, and the DISTINCT activated neuron names (for the neurons lens +\n * filter). Newest-first (`startedAt DESC, stimulationId DESC`) so the keyset\n * cursor is stable. `limit` is applied only when paginating (roster); facets\n * pull the whole filtered set.\n */\n private async selectRoster(\n filter: TCNSStimulationRosterFilter,\n opts?: { cursor?: string; limit?: number }\n ): Promise<{ entries: TCNSRosterEntry[]; total: number }> {\n await this.ensure();\n const { p } = this;\n // Decode the keyset cursor → the (startedAt, stimulationId) to page past.\n let curStarted: number | null = null;\n let curId: string | null = null;\n if (opts?.cursor) {\n const [ts, id] = Buffer.from(opts.cursor, 'base64')\n .toString('utf8')\n .split('|');\n curStarted = Number(ts);\n curId = id ?? '';\n }\n const params: unknown[] = [\n filter.scopeName ?? null,\n filter.status ?? null,\n filter.entryCollateralName ?? null,\n filter.neuronName ?? null,\n filter.text ? filter.text.toLowerCase() : null,\n filter.since ?? null,\n filter.until ?? null,\n curStarted,\n curId,\n ];\n const limitClause =\n opts?.limit != null ? ` LIMIT ${Math.max(0, opts.limit | 0)}` : '';\n const sql = `\n WITH agg AS (\n SELECT a.stimulation_id,\n MIN(a.started_at) AS started_at,\n SUM(a.hop_count) AS hop_count,\n (ARRAY_AGG(a.completed_at ORDER BY a.attempt_number DESC))[1] AS last_completed_at\n FROM ${p}stimulation_attempt a\n GROUP BY a.stimulation_id\n ),\n neu AS (\n SELECT ta.stimulation_id,\n ARRAY_AGG(DISTINCT t.neuron_name) AS neurons\n FROM ${p}task t\n JOIN ${p}stimulation_attempt ta\n ON ta.stimulation_attempt_id = t.stimulation_attempt_id\n GROUP BY ta.stimulation_id\n ),\n roster AS (\n SELECT s.stimulation_id, s.entry, s.status, s.scope_name,\n COALESCE(agg.started_at, 0) AS started_at,\n CASE WHEN s.status = 'running' THEN NULL\n ELSE agg.last_completed_at END AS completed_at,\n COALESCE(agg.hop_count, 0) AS hop_count,\n (s.status = 'failed') AS has_error,\n COALESCE(neu.neurons, ARRAY[]::text[]) AS neurons\n FROM ${p}stimulation s\n LEFT JOIN agg ON agg.stimulation_id = s.stimulation_id\n LEFT JOIN neu ON neu.stimulation_id = s.stimulation_id\n )\n SELECT *, COUNT(*) OVER() AS total_count FROM roster\n WHERE ($1::text IS NULL OR scope_name = $1)\n AND ($2::text IS NULL OR status = $2)\n AND ($3::text IS NULL OR entry->>'collateralName' = $3)\n AND ($4::text IS NULL OR $4 = ANY(neurons))\n AND ($5::text IS NULL OR lower(stimulation_id) LIKE '%'||$5||'%')\n AND ($6::bigint IS NULL OR started_at >= $6)\n AND ($7::bigint IS NULL OR started_at <= $7)\n AND ($8::bigint IS NULL OR (started_at, stimulation_id) < ($8, $9))\n ORDER BY started_at DESC, stimulation_id DESC${limitClause}`;\n const res = await this.db.query(sql, params);\n const rows = res.rows as TRosterRow[];\n const total = rows.length ? Number(rows[0].total_count) : 0;\n const entries: TCNSRosterEntry[] = rows.map(r => ({\n row: {\n stimulationId: r.stimulation_id,\n scopeName: r.scope_name ?? undefined,\n entry: r.entry as TCNSSignalJSON,\n status: r.status,\n startedAt: Number(r.started_at),\n completedAt: r.completed_at == null ? null : Number(r.completed_at),\n hopCount: Number(r.hop_count),\n hasError: r.has_error,\n },\n neurons: r.neurons ?? [],\n }));\n return { entries, total };\n }\n\n async queryRoster(\n filter: TCNSStimulationRosterFilter,\n opts: { cursor?: string; limit: number }\n ): Promise<TCNSStimulationRosterPage> {\n const { entries, total } = await this.selectRoster(filter, opts);\n const rows: TCNSStimulationRosterRow[] = entries.map(e => e.row);\n const lastRow = rows[rows.length - 1];\n // A full page implies there may be more; the window `total` is authoritative.\n const hasMore = rows.length >= opts.limit && rows.length < total;\n return {\n rows,\n nextCursor: hasMore && lastRow ? encodeRosterCursor(lastRow) : null,\n total,\n };\n }\n\n async queryFacets(\n filter: TCNSStimulationRosterFilter,\n opts?: { metricPath?: string }\n ): Promise<TCNSStimulationRosterFacets> {\n // Filter is pushed into SQL; the small GROUP-BY tally + histogram reuse the\n // shared reference aggregation so counts match the in-memory store exactly.\n const { entries } = await this.selectRoster(filter);\n return computeRosterFacets(entries, opts);\n }\n\n // ── lifecycle ──\n\n async delete(stimulationId: string): Promise<void> {\n await this.ensure();\n const { p } = this;\n await this.tx(async db => {\n // tasks + attempts have no FK (see migrate.ts) → delete them explicitly,\n // tasks first (they reference attempt ids), then the attempts.\n await db.query(\n `DELETE FROM ${p}task WHERE stimulation_attempt_id IN\n (SELECT stimulation_attempt_id FROM ${p}stimulation_attempt WHERE stimulation_id = $1)`,\n [stimulationId]\n );\n await db.query(\n `DELETE FROM ${p}stimulation_attempt WHERE stimulation_id = $1`,\n [stimulationId]\n );\n // FK ON DELETE CASCADE removes the frontier.\n await db.query(\n `DELETE FROM ${p}stimulation WHERE stimulation_id = $1`,\n [stimulationId]\n );\n });\n }\n\n /**\n * Retention: prune stimulations not updated for `olderThanMs` (with their\n * attempts/tasks/frontier). A run's `updated_at` stops advancing once it\n * settles, so old finished runs age out while an active one stays fresh — the same\n * semantics as the in-memory store's `ttlMs` and the pg-boss checkpoint's\n * `deleteStale`. Run it on a schedule (cron). Returns the number removed.\n */\n async deleteStale(olderThanMs: number): Promise<number> {\n await this.ensure();\n const { p } = this;\n const params = [Math.max(0, Math.floor(olderThanMs))];\n const cond = `updated_at < now() - ($1::bigint * interval '1 millisecond')`;\n const staleIds = `SELECT stimulation_id FROM ${p}stimulation WHERE ${cond}`;\n return this.tx(async db => {\n // tasks + attempts have no FK → delete explicitly (tasks first), then the\n // stimulations (FK cascade removes the frontier).\n await db.query(\n `DELETE FROM ${p}task WHERE stimulation_attempt_id IN\n (SELECT stimulation_attempt_id FROM ${p}stimulation_attempt\n WHERE stimulation_id IN (${staleIds}))`,\n params\n );\n await db.query(\n `DELETE FROM ${p}stimulation_attempt WHERE stimulation_id IN (${staleIds})`,\n params\n );\n const res = await db.query(\n `DELETE FROM ${p}stimulation WHERE ${cond} RETURNING stimulation_id`,\n params\n );\n return res.rows.length;\n });\n }\n\n async close(): Promise<void> {\n if (this.ownsPool && this.pool) await this.pool.end();\n }\n\n // ── helpers ──\n\n private buildProgress(frontier: TFrontierRow[]): TCNSProgress {\n const tasks: TCNSPlannedTaskJSON[] = [...frontier]\n .sort((a, b) => a.ord - b.ord)\n .map(f => ({\n neuronName: f.neuron_name,\n dendriteCollateralName: f.dendrite_collateral_name,\n ...(f.input != null ? { input: f.input } : {}),\n }));\n return { tasks };\n }\n}\n\n// ── row shapes (jsonb columns come back already parsed by node-pg) ──\ntype TStimulationRow = {\n stimulation_id: string;\n entry: TCNSSignalJSON;\n status: ECNSStimulationStatus;\n scope_name: string | null;\n};\ntype TFrontierRow = {\n stimulation_id: string;\n ord: number;\n neuron_name: string;\n dendrite_collateral_name: string;\n input: TCNSSignalJSON | null;\n};\ntype TAttemptRow = {\n stimulation_attempt_id: string;\n stimulation_id: string;\n attempt_number: number;\n status: ECNSStimulationStatus;\n started_at: string | number;\n completed_at: string | number | null;\n hop_count: number;\n has_error: boolean;\n replay_of: string | null;\n entry: TCNSSignalJSON[];\n};\ntype TTaskRow = {\n stimulation_attempt_id: string;\n index: number;\n neuron_name: string;\n dendrite_collateral_name: string;\n input_index: number;\n output: TCNSSignalJSON | null;\n status: ECNSStimulationTaskStatus;\n error: string | null;\n started_at: string | number;\n duration: number | null;\n};\n/** One roster CTE row (see `selectRoster`), with the window total attached. */\ntype TRosterRow = {\n stimulation_id: string;\n entry: unknown;\n status: string;\n scope_name: string | null;\n started_at: string | number;\n completed_at: string | number | null;\n hop_count: string | number;\n has_error: boolean;\n neurons: string[] | null;\n total_count: string | number;\n};\n\nfunction rowToAttempt(a: TAttemptRow): TCNSStimulationAttempt {\n return {\n stimulationAttemptId: a.stimulation_attempt_id,\n stimulationId: a.stimulation_id,\n attemptNumber: num(a.attempt_number),\n status: a.status,\n startedAt: num(a.started_at),\n completedAt: numOrNull(a.completed_at),\n hopCount: num(a.hop_count),\n hasError: Boolean(a.has_error),\n replayOf: a.replay_of ?? null,\n entry: a.entry,\n };\n}\n\nfunction rowToTask(t: TTaskRow): TCNSStimulationTask {\n return {\n stimulationAttemptId: t.stimulation_attempt_id,\n index: num(t.index),\n neuronName: t.neuron_name,\n dendriteCollateralName: t.dendrite_collateral_name,\n inputIndex: num(t.input_index),\n output: t.output ?? null,\n status: t.status,\n error: t.error ?? null,\n startedAt: num(t.started_at),\n duration: numOrNull(t.duration),\n };\n}\n\nfunction groupBy<T, K>(rows: T[], key: (r: T) => K): Map<K, T[]> {\n const m = new Map<K, T[]>();\n for (const r of rows) {\n const k = key(r);\n const b = m.get(k);\n if (b) b.push(r);\n else m.set(k, [r]);\n }\n return m;\n}\n","/**\n * Schema / migrations for the CNStra stimulation/attempt/task relational store.\n *\n * The package OWNS this schema — run it explicitly via `cnstra-persist-postgres migrate`\n * or `ensureSchema(db)`, or let the repository auto-create it on first use. Tables\n * under a configurable prefix (default `cns_`); the stimulation's `progress` is\n * NORMALISED into `stimulation_frontier` (one row per outstanding task).\n */\n\n/** Minimal pg client surface (a `pg` Pool or Client satisfies it). */\nexport interface IPgQueryable {\n query(text: string, params?: unknown[]): Promise<{ rows: unknown[] }>;\n}\n\nexport const DEFAULT_PREFIX = 'cns_';\n\n/** Guard the table prefix (it is interpolated into DDL/DML, not parameterised). */\nexport function assertSafePrefix(prefix: string): string {\n if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(prefix)) {\n throw new Error(\n `[@cnstra/persist-postgres] unsafe table prefix: ${JSON.stringify(prefix)}`\n );\n }\n return prefix;\n}\n\n/** The full CREATE TABLE + CREATE INDEX DDL (idempotent). */\nexport function persistPostgresSchemaSql(prefix: string = DEFAULT_PREFIX): string {\n const p = assertSafePrefix(prefix);\n return `\nCREATE TABLE IF NOT EXISTS ${p}stimulation (\n stimulation_id text PRIMARY KEY,\n entry jsonb NOT NULL,\n status text NOT NULL,\n scope_name text,\n created_at timestamptz NOT NULL DEFAULT now(),\n updated_at timestamptz NOT NULL DEFAULT now()\n);\nCREATE INDEX IF NOT EXISTS ${p}stimulation_updated_idx ON ${p}stimulation (updated_at DESC);\nCREATE INDEX IF NOT EXISTS ${p}stimulation_scope_idx ON ${p}stimulation (scope_name);\n\nCREATE TABLE IF NOT EXISTS ${p}stimulation_frontier (\n stimulation_id text NOT NULL REFERENCES ${p}stimulation(stimulation_id) ON DELETE CASCADE,\n ord int NOT NULL,\n neuron_name text NOT NULL,\n dendrite_collateral_name text NOT NULL,\n input jsonb,\n PRIMARY KEY (stimulation_id, ord)\n);\n\n-- NOTE: no FK on stimulation_id — over the devtools wire the persistor's\n-- saveStimulation and saveAttempt ride SEPARATE async batches processed\n-- concurrently by the server, so an attempt insert can reach Postgres before its\n-- stimulation row's transaction commits; a hard FK would reject it (23503). Same\n-- exposure as ${p}task; delete() removes attempts explicitly instead of cascading.\nCREATE TABLE IF NOT EXISTS ${p}stimulation_attempt (\n stimulation_attempt_id text PRIMARY KEY,\n stimulation_id text NOT NULL,\n attempt_number int NOT NULL,\n status text NOT NULL,\n started_at bigint NOT NULL,\n completed_at bigint,\n hop_count int NOT NULL,\n has_error boolean NOT NULL,\n replay_of text,\n entry jsonb NOT NULL,\n UNIQUE (stimulation_id, attempt_number)\n);\nCREATE INDEX IF NOT EXISTS ${p}stimulation_attempt_idx ON ${p}stimulation_attempt (stimulation_id, attempt_number);\n-- roster query: MIN(started_at)/keyset ordering per run.\nCREATE INDEX IF NOT EXISTS ${p}stimulation_attempt_started_idx ON ${p}stimulation_attempt (stimulation_id, started_at);\n\n-- NOTE: no FK on stimulation_attempt_id — the persistor streams settled tasks\n-- (appendTask) as hops complete, BEFORE the attempt marker is flushed (saveAttempt),\n-- so a hard FK would reject the insert. delete() removes tasks explicitly instead.\nCREATE TABLE IF NOT EXISTS ${p}task (\n stimulation_attempt_id text NOT NULL,\n \"index\" int NOT NULL,\n neuron_name text NOT NULL,\n dendrite_collateral_name text NOT NULL,\n input_index int NOT NULL,\n output jsonb,\n status text NOT NULL,\n error text,\n started_at bigint NOT NULL,\n duration int,\n PRIMARY KEY (stimulation_attempt_id, \"index\")\n);\n-- roster neurons lens: DISTINCT neuron_name per attempt / neuronName filter.\nCREATE INDEX IF NOT EXISTS ${p}task_neuron_idx ON ${p}task (neuron_name);\n`;\n}\n\n/** Idempotently create all tables + indexes. Safe to run repeatedly. */\nexport async function ensureSchema(\n db: IPgQueryable,\n prefix: string = DEFAULT_PREFIX\n): Promise<void> {\n await db.query(persistPostgresSchemaSql(prefix));\n}\n"],"mappings":"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","names":["DEFAULT_PREFIX","assertSafePrefix","prefix","test","Error","JSON","stringify","persistPostgresSchemaSql","p","ensureSchema","db","query","j","v","JSON","stringify","num","Number","numOrNull","CNSPostgresStimulationRepository","opts","db","pool","ownsPool","p","autoMigrate","ready","connectionString","Pool","queryable","Error","assertSafePrefix","tablePrefix","DEFAULT_PREFIX","ensure","Promise","resolve","ensureSchema","tx","fn","client","connect","query","out","e","release","saveStimulation","s","stimulationId","entry","status","scopeName","i","progress","tasks","length","t","neuronName","dendriteCollateralName","input","saveAttempt","a","stimulationAttemptId","attemptNumber","startedAt","completedAt","hopCount","hasError","replayOf","appendTask","index","inputIndex","output","error","duration","getStimulation","stimRes","row","rows","undefined","frontier","buildProgress","scope_name","listStimulations","filter","scope","stimsRes","stims","ids","map","r","stimulation_id","frontierRes","frontierBy","groupBy","_a","get","getAttempts","res","rowToAttempt","getTasks","rowToTask","selectRoster","curStarted","curId","cursor","ts","id","Buffer","from","toString","split","params","entryCollateralName","text","toLowerCase","since","until","limitClause","limit","Math","max","sql","total","total_count","entries","_b","started_at","completed_at","hop_count","has_error","neurons","queryRoster","lastRow","hasMore","nextCursor","encodeRosterCursor","queryFacets","computeRosterFacets","delete","deleteStale","olderThanMs","floor","cond","staleIds","close","end","sort","b","ord","f","neuron_name","dendrite_collateral_name","stimulation_attempt_id","attempt_number","Boolean","replay_of","input_index","key","m","Map","k","push","set"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/CNSPostgresStimulationRepository.ts","../../postgres-schema/src/CNSPostgresSchemaMigrator.ts","../../postgres-schema/src/CNSPostgresSchemaPgDatabase.ts","../../postgres-schema/src/CNSPostgresSchemaPartitionRotation.ts","../src/schema/CNSPostgresStimulationSchema.ts"],"sourcesContent":["export { CNSPostgresStimulationRepository } from './CNSPostgresStimulationRepository';\nexport { CNSPostgresStimulationSchema } from './schema/CNSPostgresStimulationSchema';\nexport type { TCNSPostgresStimulationRepositoryOptions } from './types/TCNSPostgresStimulationRepositoryOptions';\nexport type { TCNSPostgresStimulationMigrateOptions } from './types/TCNSPostgresStimulationMigrateOptions';\nexport type { ICNSPgQueryable } from './interfaces/ICNSPgQueryable';\n","import { Pool } from 'pg';\nimport type {\n ICNSStimulationRepository,\n ICNSStimulationRosterQuery,\n TCNSRosterEntry,\n} from '@cnstra/persist';\nimport { encodeCNSRosterCursor, CNS_HISTOGRAM_BUCKETS } from '@cnstra/persist';\nimport type {\n TCNSStimulationState,\n TCNSStimulationAttempt,\n TCNSStimulationTask,\n TCNSProgress,\n TCNSSignalJSON,\n ECNSStimulationStatus,\n ECNSStimulationTaskStatus,\n TCNSStimulationRosterFilter,\n TCNSStimulationRosterRow,\n TCNSStimulationRosterPage,\n TCNSStimulationRosterFacets,\n} from '@cnstra/persist-dto';\nimport type { ICNSPgQueryable } from './interfaces/ICNSPgQueryable';\nimport { CNSPostgresSchemaPartitionRotation } from '@cnstra/postgres-schema';\nimport type { ICNSPostgresSchemaConnection } from '@cnstra/postgres-schema';\nimport { CNSPostgresStimulationSchema } from './schema/CNSPostgresStimulationSchema';\nimport type { TCNSPostgresStimulationRepositoryOptions } from './types/TCNSPostgresStimulationRepositoryOptions';\n\nconst j = (v: unknown): string => JSON.stringify(v ?? null);\nconst num = (v: unknown): number => Number(v);\nconst numOrNull = (v: unknown): number | null => (v == null ? null : Number(v));\n\nexport class CNSPostgresStimulationRepository\n implements ICNSStimulationRepository, ICNSStimulationRosterQuery\n{\n private static readonly LOCK_STIMULATION = 7;\n private static readonly RETENTION_BATCH = 200;\n\n private readonly db: ICNSPgQueryable;\n private readonly pool?: Pool;\n private readonly ownsPool: boolean;\n private readonly p: string;\n private readonly autoMigrate: boolean;\n private readonly now: () => number;\n private readonly taskPartitions = new CNSPostgresSchemaPartitionRotation({ premakeDays: 7 });\n private ready: Promise<void> | undefined;\n\n constructor(opts: TCNSPostgresStimulationRepositoryOptions) {\n if (opts.pool) {\n this.pool = opts.pool;\n this.db = opts.pool;\n this.ownsPool = false;\n } else if (opts.connectionString) {\n this.pool = new Pool({ connectionString: opts.connectionString });\n this.db = this.pool;\n this.ownsPool = true;\n } else if (opts.queryable) {\n this.db = opts.queryable;\n this.ownsPool = false;\n } else {\n throw new Error(\n '[@cnstra/persist-postgres] needs `connectionString`, `pool`, or `queryable`'\n );\n }\n this.p = CNSPostgresStimulationSchema.assertTablePrefix(opts.tablePrefix ?? CNSPostgresStimulationSchema.DEFAULT_TABLE_PREFIX);\n this.autoMigrate = opts.autoMigrate ?? true;\n if (this.autoMigrate && !this.pool) {\n throw new Error(\n '[@cnstra/persist-postgres] a `queryable` cannot hold the migration transaction: pass `pool` or `connectionString`, or set `autoMigrate: false` and run CNSPostgresStimulationSchema.migrate(pool) yourself'\n );\n }\n this.now = opts.now ?? Date.now;\n }\n\n private schemaReady(): Promise<void> {\n if (!this.autoMigrate) return Promise.resolve();\n this.ready ??= CNSPostgresStimulationSchema.migrate(this.pool as Pool, this.p).catch(error => {\n this.ready = undefined;\n throw error;\n });\n return this.ready;\n }\n\n /** Run `fn` in a transaction when a Pool is available; else best-effort inline. */\n private async tx<T>(fn: (db: ICNSPgQueryable) => Promise<T>): Promise<T> {\n if (!this.pool) return fn(this.db);\n const client = await this.pool.connect();\n try {\n await client.query('BEGIN');\n const out = await fn(client as unknown as ICNSPgQueryable);\n await client.query('COMMIT');\n return out;\n } catch (e) {\n await client.query('ROLLBACK');\n throw e;\n } finally {\n client.release();\n }\n }\n\n // ── write ──\n\n async saveStimulation(s: TCNSStimulationState): Promise<void> {\n await this.schemaReady();\n const { p } = this;\n const now = this.now();\n await this.tx(async db => {\n await this.lockStimulation(db, s.stimulationId);\n const upserted = await db.query(\n `INSERT INTO ${p}stimulation (stimulation_id, entry, status, scope_name, progress, updated_at)\n VALUES ($1, $2::jsonb, $3, $4, $5::jsonb, $6)\n ON CONFLICT (stimulation_id)\n DO UPDATE SET entry = EXCLUDED.entry, status = EXCLUDED.status,\n scope_name = EXCLUDED.scope_name, progress = EXCLUDED.progress, updated_at = EXCLUDED.updated_at\n RETURNING (xmax = 0) AS inserted`,\n [s.stimulationId, j(s.entry), s.status, s.scopeName ?? null, j(s.progress), now]\n );\n if ((upserted.rows[0] as { inserted?: boolean } | undefined)?.inserted !== false) await this.rollUpAttempts(db, s.stimulationId, now);\n });\n }\n\n async saveAttempt(a: TCNSStimulationAttempt): Promise<void> {\n await this.schemaReady();\n const { p } = this;\n await this.tx(async db => {\n await this.lockStimulation(db, a.stimulationId);\n await db.query(\n `INSERT INTO ${p}stimulation_attempt\n (stimulation_attempt_id, stimulation_id, attempt_number, status, started_at,\n completed_at, hop_count, has_error, replay_of, entry)\n VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10::jsonb)\n ON CONFLICT (stimulation_attempt_id) DO UPDATE SET\n status = EXCLUDED.status, completed_at = EXCLUDED.completed_at,\n hop_count = EXCLUDED.hop_count, has_error = EXCLUDED.has_error,\n replay_of = EXCLUDED.replay_of, entry = EXCLUDED.entry`,\n [\n a.stimulationAttemptId,\n a.stimulationId,\n a.attemptNumber,\n a.status,\n a.startedAt,\n a.completedAt,\n a.hopCount,\n a.hasError,\n a.replayOf,\n j(a.entry),\n ]\n );\n await this.rollUpAttempts(db, a.stimulationId, this.now());\n });\n }\n\n private async lockStimulation(db: ICNSPgQueryable, stimulationId: string): Promise<void> {\n await db.query('SELECT pg_advisory_xact_lock($1, hashtext($2))', [\n CNSPostgresStimulationRepository.LOCK_STIMULATION,\n this.p + stimulationId,\n ]);\n }\n\n private async rollUpAttempts(db: ICNSPgQueryable, stimulationId: string, now: number): Promise<void> {\n const { p } = this;\n await db.query(\n `UPDATE ${p}stimulation s\n SET started_at = COALESCE(a.started_at, 0),\n hop_count = COALESCE(a.hop_count, 0),\n last_completed_at = a.last_completed_at,\n updated_at = GREATEST(s.updated_at, $2)\n FROM (\n SELECT MIN(started_at) AS started_at,\n SUM(hop_count) AS hop_count,\n (ARRAY_AGG(completed_at ORDER BY attempt_number DESC))[1] AS last_completed_at\n FROM ${p}stimulation_attempt WHERE stimulation_id = $1\n ) a\n WHERE s.stimulation_id = $1`,\n [stimulationId, now]\n );\n }\n\n async appendTask(t: TCNSStimulationTask): Promise<void> {\n await this.schemaReady();\n const { p } = this;\n await this.db.query(\n `INSERT INTO ${p}stimulation_task\n (stimulation_attempt_id, task_index, neuron_name, dendrite_collateral_name,\n input_index, output, status, error, started_at, duration_ms, activation_context)\n VALUES ($1,$2,$3,$4,$5,$6::jsonb,$7,$8,$9,$10,$11::jsonb)\n ON CONFLICT (stimulation_attempt_id, task_index, started_at) DO NOTHING`,\n [\n t.stimulationAttemptId,\n t.index,\n t.neuronName,\n t.dendriteCollateralName,\n t.inputIndex,\n t.output ? j(t.output) : null,\n t.status,\n t.error,\n t.startedAt,\n t.duration,\n t.activationContext ? j(t.activationContext) : null,\n ]\n );\n }\n\n // ── read ──\n\n async getStimulation(\n stimulationId: string\n ): Promise<TCNSStimulationState | undefined> {\n await this.schemaReady();\n const { p } = this;\n const res = await this.db.query(\n `SELECT stimulation_id, entry, status, scope_name, progress FROM ${p}stimulation WHERE stimulation_id = $1`,\n [stimulationId]\n );\n const row = res.rows[0] as TStimulationRow | undefined;\n return row ? rowToStimulation(row) : undefined;\n }\n\n async listStimulations(filter?: {\n scopeName?: string;\n since?: number;\n }): Promise<TCNSStimulationState[]> {\n await this.schemaReady();\n const { p } = this;\n const scope = filter?.scopeName ?? null;\n // `since` prunes on `updated_at`, the column this already orders by. It\n // is a prune and not the filter: a run's `started_at` is the minimum over\n // its attempts, and a write always follows the start, so this keeps a\n // superset and the caller's predicate still decides. There is no prune\n // for `until` — a run that started before it may have been written after.\n const since = filter?.since ?? null;\n const res = await this.db.query(\n `SELECT stimulation_id, entry, status, scope_name, progress FROM ${p}stimulation\n WHERE ($1::text IS NULL OR scope_name = $1)\n AND ($2::bigint IS NULL OR updated_at >= $2)\n ORDER BY updated_at DESC`,\n [scope, since]\n );\n return (res.rows as TStimulationRow[]).map(rowToStimulation);\n }\n\n async getAttempts(\n stimulationId: string\n ): Promise<TCNSStimulationAttempt[]> {\n await this.schemaReady();\n const { p } = this;\n const res = await this.db.query(\n `SELECT * FROM ${p}stimulation_attempt WHERE stimulation_id = $1 ORDER BY attempt_number`,\n [stimulationId]\n );\n return (res.rows as TAttemptRow[]).map(rowToAttempt);\n }\n\n async getTasks(\n stimulationAttemptId: string\n ): Promise<TCNSStimulationTask[]> {\n await this.schemaReady();\n const { p } = this;\n const res = await this.db.query(\n `SELECT * FROM ${p}stimulation_task WHERE stimulation_attempt_id = $1 ORDER BY task_index`,\n [stimulationAttemptId]\n );\n return (res.rows as TTaskRow[]).map(rowToTask);\n }\n\n /**\n * Attempts of many stimulations in ONE query, so a page of runs costs one\n * round trip rather than one per run.\n */\n async getAttemptsFor(\n stimulationIds: readonly string[]\n ): Promise<TCNSStimulationAttempt[]> {\n if (stimulationIds.length === 0) return [];\n await this.schemaReady();\n const { p } = this;\n const res = await this.db.query(\n `SELECT * FROM ${p}stimulation_attempt\n WHERE stimulation_id = ANY($1)\n ORDER BY stimulation_id, attempt_number`,\n [stimulationIds as string[]]\n );\n return (res.rows as TAttemptRow[]).map(rowToAttempt);\n }\n\n /** Tasks of many attempts in ONE query — see {@link getAttemptsFor}. */\n async getTasksFor(\n stimulationAttemptIds: readonly string[]\n ): Promise<TCNSStimulationTask[]> {\n if (stimulationAttemptIds.length === 0) return [];\n await this.schemaReady();\n const { p } = this;\n const res = await this.db.query(\n `SELECT * FROM ${p}stimulation_task\n WHERE stimulation_attempt_id = ANY($1)\n ORDER BY stimulation_attempt_id, task_index`,\n [stimulationAttemptIds as string[]]\n );\n return (res.rows as TTaskRow[]).map(rowToTask);\n }\n\n // ── roster query capability (filter + keyset + facets pushed into SQL) ──\n\n /**\n * The CTE every roster read stands on. The roster fields are kept on the\n * stimulation row by `saveAttempt` / `saveStimulation`, so the window and the\n * order come from `stimulation(started_at DESC, stimulation_id)`; the total\n * still counts the whole filtered set. Shared so the facet tallies are counted\n * over exactly the set the roster pages through.\n * The task table is read only when `withNeurons` asks for the neuron names.\n * It binds `$1..$7` (the filter); the keyset cursor belongs to the roster's tail.\n */\n private rosterCte(withNeurons: boolean): string {\n const { p } = this;\n const neurons = withNeurons\n ? `COALESCE((\n SELECT ARRAY_AGG(DISTINCT t.neuron_name)\n FROM ${p}stimulation_task t\n JOIN ${p}stimulation_attempt a ON a.stimulation_attempt_id = t.stimulation_attempt_id\n WHERE a.stimulation_id = s.stimulation_id\n ), ARRAY[]::text[])`\n : 'ARRAY[]::text[]';\n return `\n WITH roster AS (\n SELECT s.stimulation_id, s.entry, s.status, s.scope_name, s.started_at,\n CASE WHEN s.status = 'running' THEN NULL ELSE s.last_completed_at END AS completed_at,\n s.hop_count,\n (s.status = 'failed') AS has_error,\n ${neurons} AS neurons\n FROM ${p}stimulation s\n WHERE ($6::bigint IS NULL OR s.started_at >= $6)\n AND ($7::bigint IS NULL OR s.started_at <= $7)\n AND ($1::text IS NULL OR s.scope_name = $1)\n AND ($2::text IS NULL OR s.status = $2)\n AND ($3::text IS NULL OR s.entry->>'collateralName' = $3)\n AND ($5::text IS NULL OR strpos(lower(s.stimulation_id), $5) > 0)\n ),\n filtered AS (\n SELECT *, COUNT(*) OVER() AS total_count FROM roster\n WHERE ($4::text IS NULL OR $4 = ANY(neurons))\n )`;\n }\n\n private async selectRoster(\n filter: TCNSStimulationRosterFilter,\n opts?: { cursor?: string; limit?: number }\n ): Promise<{ entries: TCNSRosterEntry[]; total: number }> {\n await this.schemaReady();\n const { p } = this;\n // Decode the keyset cursor → the (startedAt, stimulationId) to page past.\n let curStarted: number | null = null;\n let curId: string | null = null;\n if (opts?.cursor) {\n // Split on the FIRST separator only. A stimulation id is a key the\n // caller chose — a BullMQ job id, an order number — and one\n // containing `|` would otherwise lose everything after it, sending\n // the page to a run that does not exist.\n const decoded = Buffer.from(opts.cursor, 'base64').toString('utf8');\n const at = decoded.indexOf('|');\n const started = Number(decoded.slice(0, at));\n if (at >= 0 && Number.isFinite(started)) {\n curStarted = started;\n curId = decoded.slice(at + 1);\n }\n }\n const params: unknown[] = [\n filter.scopeName ?? null,\n filter.status ?? null,\n filter.entryCollateralName ?? null,\n filter.neuronName ?? null,\n filter.text ? filter.text.toLowerCase() : null,\n filter.since ?? null,\n filter.until ?? null,\n curStarted,\n curId,\n ];\n const limitClause =\n opts?.limit != null && Number.isFinite(opts.limit) ? ` LIMIT ${Math.max(0, Math.floor(opts.limit))}` : '';\n // The listing discards the neuron array; only a neuron filter reads it.\n const sql = `${this.rosterCte(filter.neuronName != null)}\n -- The count is taken INSIDE \\`filtered\\`, before the keyset predicate\n -- narrows the set. Counting after it made \\`total\\` shrink with every\n -- page, while the in-memory reference reports the whole filtered set —\n -- and the two are contractually identical.\n SELECT * FROM filtered\n WHERE ($8::bigint IS NULL OR (started_at, stimulation_id COLLATE \"C\") < ($8, $9::text COLLATE \"C\"))\n ORDER BY started_at DESC, stimulation_id COLLATE \"C\" DESC${limitClause}`;\n const res = await this.db.query(sql, params);\n const rows = res.rows as TRosterRow[];\n // An empty page carries no row to read the window count from — reporting\n // zero there would tell the panel \"no runs\" for a filter that has plenty.\n // Re-run the SAME query with the cursor dropped rather than maintaining a\n // second hand-written count that can drift from this one.\n let total = rows.length ? Number(rows[0].total_count) : 0;\n if (!rows.length && curStarted !== null) {\n const countRes = await this.db.query(sql, [\n ...params.slice(0, 7),\n null,\n null,\n ]);\n const countRows = countRes.rows as TRosterRow[];\n total = countRows.length ? Number(countRows[0].total_count) : 0;\n }\n const entries: TCNSRosterEntry[] = rows.map(r => ({\n row: {\n stimulationId: r.stimulation_id,\n scopeName: r.scope_name ?? undefined,\n entry: r.entry as TCNSSignalJSON,\n status: r.status,\n startedAt: Number(r.started_at),\n completedAt: r.completed_at == null ? null : Number(r.completed_at),\n hopCount: Number(r.hop_count),\n hasError: r.has_error,\n },\n neurons: r.neurons ?? [],\n }));\n return { entries, total };\n }\n\n async queryRoster(\n filter: TCNSStimulationRosterFilter,\n opts: { cursor?: string; limit: number }\n ): Promise<TCNSStimulationRosterPage> {\n // Ask for one row beyond the page. Its presence is what says \"there is\n // another page\" — `total` cannot answer that any more, now that it counts\n // the whole filtered set and knows nothing about how far this cursor walked.\n const { entries, total } = await this.selectRoster(filter, {\n ...opts,\n limit: opts.limit + 1,\n });\n const hasMore = entries.length > opts.limit;\n const rows: TCNSStimulationRosterRow[] = entries\n .slice(0, opts.limit)\n .map(e => e.row);\n const lastRow = rows[rows.length - 1];\n return {\n rows,\n nextCursor: hasMore && lastRow ? encodeCNSRosterCursor(lastRow) : null,\n total,\n };\n }\n\n async queryFacets(\n filter: TCNSStimulationRosterFilter,\n opts?: { metricPath?: string }\n ): Promise<TCNSStimulationRosterFacets> {\n await this.schemaReady();\n // Four tallies of a few dozen rows. Aggregated here rather than in Node so\n // what crosses the wire is the answer, not the material for it.\n const params: unknown[] = [\n filter.scopeName ?? null,\n filter.status ?? null,\n filter.entryCollateralName ?? null,\n filter.neuronName ?? null,\n filter.text ? filter.text.toLowerCase() : null,\n filter.since ?? null,\n filter.until ?? null,\n // The metric path as a jsonb path array (`a.b` → {a,b}); null when the\n // chart is not summing anything.\n opts?.metricPath ? opts.metricPath.split('.') : null,\n ];\n\n const sql = `${this.rosterCte(true)},\n -- The histogram's bucket width is derived from the set's own span, the\n -- same ${CNS_HISTOGRAM_BUCKETS}-bucket rule the reference implementation\n -- uses. Computed here so the whole thing stays one round trip.\n bounds AS (\n SELECT MIN(started_at) AS mn, MAX(started_at) AS mx FROM filtered WHERE started_at > 0\n ),\n step AS (\n SELECT mn,\n GREATEST(1, CEIL((mx - mn)::numeric / ${CNS_HISTOGRAM_BUCKETS})::bigint) AS size\n FROM bounds\n )\n SELECT 'entry' AS kind, entry->>'collateralName' AS key,\n COUNT(*)::bigint AS cnt,\n NULL::bigint AS ts_bucket, NULL::bigint AS completed,\n NULL::bigint AS failed, NULL::bigint AS running,\n NULL::numeric AS metric\n FROM filtered GROUP BY 2\n UNION ALL\n SELECT 'status', status, COUNT(*)::bigint,\n NULL, NULL, NULL, NULL, NULL\n FROM filtered GROUP BY 2\n UNION ALL\n -- One row per (run, neuron): the neuron rail counts runs that touched\n -- a neuron, which is what UNNEST over the per-run DISTINCT array gives.\n SELECT 'neuron', n, COUNT(*)::bigint,\n NULL, NULL, NULL, NULL, NULL\n FROM filtered, UNNEST(neurons) AS n GROUP BY 2\n UNION ALL\n SELECT 'time', NULL, NULL,\n (step.mn + ((f.started_at - step.mn) / step.size) * step.size)::bigint,\n COUNT(*) FILTER (WHERE f.status = 'completed')::bigint,\n COUNT(*) FILTER (WHERE f.status = 'failed')::bigint,\n COUNT(*) FILTER (WHERE f.status NOT IN ('completed', 'failed'))::bigint,\n -- Only actual JSON numbers are summed, matching the reference's\n -- \\`typeof v === 'number'\\`: a payload field holding \"12\" as a\n -- string is not a metric and must not become one here.\n SUM(\n CASE WHEN $8::text[] IS NOT NULL\n AND jsonb_typeof(f.entry->'payload' #> $8::text[]) = 'number'\n THEN (f.entry->'payload' #> $8::text[])::text::numeric\n END\n )\n FROM filtered f, step\n WHERE f.started_at > 0\n GROUP BY 4`;\n\n const res = await this.db.query(sql, params);\n const rows = res.rows as TFacetRow[];\n\n const tally = (kind: string) =>\n rows\n .filter(r => r.kind === kind && r.key != null)\n .map(r => ({ key: r.key as string, count: Number(r.cnt) }))\n // Deterministic on ties: a facet rail that reshuffles between two\n // refreshes of the same data reads as data changing.\n .sort((a, b) => b.count - a.count || (a.key < b.key ? -1 : 1));\n\n return {\n byEntryCollateral: tally('entry').map(x => ({\n name: x.key,\n count: x.count,\n })),\n byStatus: tally('status').map(x => ({\n status: x.key,\n count: x.count,\n })),\n byNeuron: tally('neuron').map(x => ({ name: x.key, count: x.count })),\n byTime: rows\n .filter(r => r.kind === 'time' && r.ts_bucket != null)\n .map(r => ({\n tsBucket: Number(r.ts_bucket),\n completed: Number(r.completed),\n failed: Number(r.failed),\n running: Number(r.running),\n // Absent rather than 0 when nothing was summed — the reference\n // only sets `metric` once it has a number to put there.\n ...(r.metric == null ? {} : { metric: Number(r.metric) }),\n }))\n .sort((a, b) => a.tsBucket - b.tsBucket),\n };\n }\n\n // ── lifecycle ──\n\n async delete(stimulationId: string): Promise<void> {\n await this.schemaReady();\n const { p } = this;\n await this.tx(async db => {\n await this.lockStimulation(db, stimulationId);\n await db.query(\n `DELETE FROM ${p}stimulation_task WHERE stimulation_attempt_id IN\n (SELECT stimulation_attempt_id FROM ${p}stimulation_attempt WHERE stimulation_id = $1)`,\n [stimulationId]\n );\n await db.query(`DELETE FROM ${p}stimulation_attempt WHERE stimulation_id = $1`, [stimulationId]);\n await db.query(`DELETE FROM ${p}stimulation WHERE stimulation_id = $1`, [stimulationId]);\n });\n }\n\n /**\n * Retention: prune stimulations not updated for `olderThanMs`, with their\n * attempts and tasks, in batches of one transaction each, then the attempts and\n * tasks older than that whose parent row never arrived or is already gone; last,\n * with a pool, drop the daily task partitions that ended before the window and\n * create the next seven.\n * Returns the number of stimulations removed.\n */\n async deleteStale(olderThanMs: number): Promise<number> {\n await this.schemaReady();\n const { p } = this;\n const now = this.now();\n const cutoff = now - Math.max(0, Math.floor(olderThanMs));\n let removedCount = 0;\n let after = '';\n for (;;) {\n const batch = await this.tx(async db => {\n const stale = await db.query(\n `SELECT stimulation_id FROM ${p}stimulation\n WHERE updated_at < $1 AND stimulation_id > $2\n ORDER BY stimulation_id LIMIT $3`,\n [cutoff, after, CNSPostgresStimulationRepository.RETENTION_BATCH]\n );\n const candidates = (stale.rows as { stimulation_id: string }[]).map(row => row.stimulation_id);\n if (candidates.length === 0) return { candidates, removed: 0 };\n await db.query(\n `SELECT pg_advisory_xact_lock($1, hashtext($2 || id)) FROM unnest($3::text[]) AS id ORDER BY id`,\n [CNSPostgresStimulationRepository.LOCK_STIMULATION, p, candidates]\n );\n const removed = await db.query(\n `DELETE FROM ${p}stimulation WHERE stimulation_id = ANY($1) AND updated_at < $2 RETURNING stimulation_id`,\n [candidates, cutoff]\n );\n const ids = (removed.rows as { stimulation_id: string }[]).map(row => row.stimulation_id);\n await db.query(\n `DELETE FROM ${p}stimulation_task t\n USING ${p}stimulation_attempt a\n WHERE a.stimulation_attempt_id = t.stimulation_attempt_id AND a.stimulation_id = ANY($1)`,\n [ids]\n );\n await db.query(`DELETE FROM ${p}stimulation_attempt WHERE stimulation_id = ANY($1)`, [ids]);\n return { candidates, removed: ids.length };\n });\n removedCount += batch.removed;\n if (batch.candidates.length < CNSPostgresStimulationRepository.RETENTION_BATCH) break;\n after = batch.candidates[batch.candidates.length - 1];\n }\n let rotationFailure: { error: unknown } | undefined;\n if (this.pool) {\n try {\n await this.taskPartitions.rotate({ transaction: work => this.tx(db => work(db as unknown as ICNSPostgresSchemaConnection)) }, `${p}stimulation_task`, now, cutoff);\n } catch (error) {\n rotationFailure = { error };\n }\n }\n await this.db.query(\n `DELETE FROM ${p}stimulation_attempt a\n WHERE a.started_at < $1\n AND NOT EXISTS (SELECT 1 FROM ${p}stimulation s WHERE s.stimulation_id = a.stimulation_id)`,\n [cutoff]\n );\n await this.db.query(\n `DELETE FROM ${p}stimulation_task t\n WHERE t.started_at < $1\n AND NOT EXISTS (SELECT 1 FROM ${p}stimulation_attempt a WHERE a.stimulation_attempt_id = t.stimulation_attempt_id)`,\n [cutoff]\n );\n if (rotationFailure) throw rotationFailure.error;\n return removedCount;\n }\n\n async close(): Promise<void> {\n if (this.ownsPool && this.pool) await this.pool.end();\n }\n\n}\n\n// ── row shapes (jsonb columns come back already parsed by node-pg) ──\ntype TStimulationRow = {\n stimulation_id: string;\n entry: TCNSSignalJSON;\n status: ECNSStimulationStatus;\n scope_name: string | null;\n progress: TCNSProgress;\n};\ntype TAttemptRow = {\n stimulation_attempt_id: string;\n stimulation_id: string;\n attempt_number: number;\n status: ECNSStimulationStatus;\n started_at: string | number;\n completed_at: string | number | null;\n hop_count: number;\n has_error: boolean;\n replay_of: string | null;\n entry: TCNSSignalJSON[];\n};\ntype TTaskRow = {\n stimulation_attempt_id: string;\n task_index: number;\n neuron_name: string;\n dendrite_collateral_name: string;\n input_index: number;\n output: TCNSSignalJSON | null;\n status: ECNSStimulationTaskStatus;\n error: string | null;\n started_at: string | number;\n duration_ms: string | number | null;\n activation_context: TCNSStimulationTask['activationContext'] | null;\n};\n/** One row of the facet union: a tally row (`key`+`cnt`) or a histogram bucket. */\ntype TFacetRow = {\n kind: string;\n key: string | null;\n cnt: string | null;\n ts_bucket: string | null;\n completed: string | null;\n failed: string | null;\n running: string | null;\n metric: string | null;\n};\n\n/** One roster CTE row (see `selectRoster`), with the window total attached. */\ntype TRosterRow = {\n stimulation_id: string;\n entry: unknown;\n status: string;\n scope_name: string | null;\n started_at: string | number;\n completed_at: string | number | null;\n hop_count: string | number;\n has_error: boolean;\n neurons: string[] | null;\n total_count: string | number;\n};\n\nfunction rowToAttempt(a: TAttemptRow): TCNSStimulationAttempt {\n return {\n stimulationAttemptId: a.stimulation_attempt_id,\n stimulationId: a.stimulation_id,\n attemptNumber: num(a.attempt_number),\n status: a.status,\n startedAt: num(a.started_at),\n completedAt: numOrNull(a.completed_at),\n hopCount: num(a.hop_count),\n hasError: Boolean(a.has_error),\n replayOf: a.replay_of ?? null,\n entry: a.entry,\n };\n}\n\nfunction rowToTask(t: TTaskRow): TCNSStimulationTask {\n return {\n stimulationAttemptId: t.stimulation_attempt_id,\n index: num(t.task_index),\n neuronName: t.neuron_name,\n dendriteCollateralName: t.dendrite_collateral_name,\n inputIndex: num(t.input_index),\n output: t.output ?? null,\n status: t.status,\n error: t.error ?? null,\n startedAt: num(t.started_at),\n duration: numOrNull(t.duration_ms),\n ...(t.activation_context != null ? { activationContext: t.activation_context } : {}),\n };\n}\n\nfunction rowToStimulation(r: TStimulationRow): TCNSStimulationState {\n return {\n stimulationId: r.stimulation_id,\n entry: r.entry,\n status: r.status,\n progress: r.progress,\n ...(r.scope_name != null ? { scopeName: r.scope_name } : {}),\n };\n}\n","import type { ICNSPostgresSchemaConnection } from './interfaces/ICNSPostgresSchemaConnection';\nimport type { ICNSPostgresSchemaDatabase } from './interfaces/ICNSPostgresSchemaDatabase';\nimport type { ECNSPostgresSchemaStepKind } from './types/ECNSPostgresSchemaStepKind';\nimport type { TCNSPostgresSchemaMigrateOptions } from './types/TCNSPostgresSchemaMigrateOptions';\nimport type { TCNSPostgresSchemaMigratorOptions } from './types/TCNSPostgresSchemaMigratorOptions';\nimport type { TCNSPostgresSchemaStep } from './types/TCNSPostgresSchemaStep';\n\nexport class CNSPostgresSchemaMigrator {\n static readonly LOCK_NAMESPACE = 6;\n static readonly LOCK_TIMEOUT = '5s';\n static readonly MAX_IDENTIFIER_LENGTH = 63;\n\n private readonly steps: readonly TCNSPostgresSchemaStep[];\n\n constructor(private readonly options: TCNSPostgresSchemaMigratorOptions) {\n this.steps = [...options.steps].sort((left, right) => left.step - right.step);\n this.steps.forEach((step, index) => {\n if (step.step !== index + 1) throw new Error(`[${options.name}] schema steps must be numbered 1, 2, 3…; found ${step.step} at position ${index + 1}`);\n });\n if (this.steps[0]?.kind !== 'expand') throw new Error(`[${options.name}] schema step 1 must be an expand step`);\n }\n\n migrationTable(tablePrefix: string): string {\n return `${tablePrefix}${this.options.name}_migration`;\n }\n\n async migrate(database: ICNSPostgresSchemaDatabase, tablePrefix: string, options: TCNSPostgresSchemaMigrateOptions = {}): Promise<void> {\n await database.transaction(async connection => {\n await connection.query('SELECT pg_advisory_xact_lock($1, hashtext($2))', [CNSPostgresSchemaMigrator.LOCK_NAMESPACE, tablePrefix + this.options.name]);\n await connection.query(`SET LOCAL lock_timeout = '${CNSPostgresSchemaMigrator.LOCK_TIMEOUT}'`);\n const applied = await this.readAppliedSteps(connection, tablePrefix);\n if (applied === undefined) await this.assertNoUntrackedTables(connection, tablePrefix);\n const pending = this.pendingSteps(applied ?? new Map(), applied === undefined || options.contract === true);\n if (pending.length > 0) await connection.query(this.sqlFor(tablePrefix, pending, applied === undefined));\n });\n }\n\n async verify(connection: ICNSPostgresSchemaConnection, tablePrefix: string): Promise<void> {\n const applied = await this.readAppliedSteps(connection, tablePrefix);\n if (applied === undefined) {\n throw new Error(`[${this.options.name}] the schema is not installed under prefix \"${tablePrefix}\": run migrate()`);\n }\n const missing = this.steps.filter(step => step.kind === 'expand' && !applied.has(step.step)).map(step => step.step);\n if (missing.length > 0) {\n throw new Error(`[${this.options.name}] schema steps ${missing.join(', ')} are not applied under prefix \"${tablePrefix}\": run migrate()`);\n }\n const unknown = [...applied].filter(([step, kind]) => kind === 'contract' && step > this.steps.length).map(([step]) => step);\n if (unknown.length > 0) {\n throw new Error(`[${this.options.name}] the database has contract steps ${unknown.join(', ')} that this version does not know: upgrade the package`);\n }\n }\n\n migrationSql(tablePrefix: string, appliedSteps: readonly number[] = [], options: TCNSPostgresSchemaMigrateOptions = {}): string {\n const fresh = appliedSteps.length === 0;\n const applied = new Map<number, ECNSPostgresSchemaStepKind>(appliedSteps.map(step => [step, 'expand']));\n return this.sqlFor(tablePrefix, this.pendingSteps(applied, fresh || options.contract === true), fresh);\n }\n\n assertTablePrefix(tablePrefix: string, longestSuffix: string): string {\n const longest = Math.max(longestSuffix.length, `${this.options.name}_migration`.length);\n const limit = CNSPostgresSchemaMigrator.MAX_IDENTIFIER_LENGTH - longest;\n if (!new RegExp(`^[A-Za-z_][A-Za-z0-9_]{0,${limit - 1}}$`).test(tablePrefix)) {\n throw new Error(`[${this.options.name}] unsafe table prefix ${JSON.stringify(tablePrefix)}: letters, digits and _ only, at most ${limit} characters`);\n }\n return tablePrefix;\n }\n\n private pendingSteps(applied: ReadonlyMap<number, ECNSPostgresSchemaStepKind>, withContract: boolean): TCNSPostgresSchemaStep[] {\n return this.steps.filter(step => !applied.has(step.step) && (step.kind === 'expand' || withContract));\n }\n\n private sqlFor(tablePrefix: string, steps: readonly TCNSPostgresSchemaStep[], createMigrationTable: boolean): string {\n const table = this.migrationTable(tablePrefix);\n const parts = createMigrationTable\n ? [`CREATE TABLE ${table} (step integer PRIMARY KEY, kind text NOT NULL CHECK (kind IN ('expand', 'contract')));`]\n : [];\n for (const step of steps) {\n parts.push(step.sql(tablePrefix), `INSERT INTO ${table} (step, kind) VALUES (${step.step}, '${step.kind}');`);\n }\n return parts.join('\\n');\n }\n\n private async readAppliedSteps(connection: ICNSPostgresSchemaConnection, tablePrefix: string): Promise<Map<number, ECNSPostgresSchemaStepKind> | undefined> {\n const table = this.migrationTable(tablePrefix);\n const exists = await connection.query<{ found: string | null }>('SELECT to_regclass($1)::text AS found', [table]);\n if (exists.rows[0]?.found == null) return undefined;\n const rows = await connection.query<{ step: number; kind: ECNSPostgresSchemaStepKind }>(`SELECT step, kind FROM ${table}`);\n return new Map(rows.rows.map(row => [Number(row.step), row.kind]));\n }\n\n private async assertNoUntrackedTables(connection: ICNSPostgresSchemaConnection, tablePrefix: string): Promise<void> {\n const tables = this.options.tables(tablePrefix);\n const found = await connection.query<{ name: string }>(\n 'SELECT name FROM unnest($1::text[]) AS name WHERE to_regclass(name) IS NOT NULL',\n [tables]\n );\n if (found.rows.length > 0) {\n throw new Error(\n `[${this.options.name}] tables ${found.rows.map(row => row.name).join(', ')} exist without ${this.migrationTable(tablePrefix)}: they were created by an earlier unversioned release; drop them or move the data before migrate()`\n );\n }\n }\n}\n","import type { ICNSPostgresSchemaConnection } from './interfaces/ICNSPostgresSchemaConnection';\nimport type { ICNSPostgresSchemaDatabase } from './interfaces/ICNSPostgresSchemaDatabase';\nimport type { ICNSPostgresSchemaPgPool } from './interfaces/ICNSPostgresSchemaPgPool';\n\nexport class CNSPostgresSchemaPgDatabase implements ICNSPostgresSchemaDatabase {\n private constructor(\n private readonly open: () => Promise<{ connection: ICNSPostgresSchemaConnection; release: (error?: Error) => void }>\n ) {}\n\n static fromPool(pool: ICNSPostgresSchemaPgPool): CNSPostgresSchemaPgDatabase {\n return new CNSPostgresSchemaPgDatabase(async () => {\n const client = await pool.connect();\n return { connection: client, release: error => client.release(error) };\n });\n }\n\n async transaction<TResult>(work: (connection: ICNSPostgresSchemaConnection) => Promise<TResult>): Promise<TResult> {\n const { connection, release } = await this.open();\n try {\n await connection.query('BEGIN');\n const result = await work(connection);\n await connection.query('COMMIT');\n release();\n return result;\n } catch (error) {\n const rollback = await connection.query('ROLLBACK').then(\n () => undefined,\n (rollbackError: unknown) => rollbackError\n );\n release(rollback instanceof Error ? rollback : undefined);\n throw error;\n }\n }\n}\n","import type { ICNSPostgresSchemaConnection } from './interfaces/ICNSPostgresSchemaConnection';\nimport type { ICNSPostgresSchemaDatabase } from './interfaces/ICNSPostgresSchemaDatabase';\nimport type { TCNSPostgresSchemaPartitionRotationOptions } from './types/TCNSPostgresSchemaPartitionRotationOptions';\n\ntype TRangePartition = { readonly name: string; readonly from: number; readonly to: number };\n\nexport class CNSPostgresSchemaPartitionRotation {\n static readonly LOCK_NAMESPACE = 8;\n static readonly LOCK_TIMEOUT = '5s';\n static readonly DAY_MS = 24 * 60 * 60 * 1000;\n\n private readonly rotatedFor = new Map<string, string>();\n\n constructor(private readonly options: TCNSPostgresSchemaPartitionRotationOptions) {\n if (!(options.premakeDays >= 1) || !Number.isInteger(options.premakeDays)) {\n throw new Error(`[partition rotation] premakeDays must be a positive integer, got ${options.premakeDays}`);\n }\n }\n\n async rotate(\n database: ICNSPostgresSchemaDatabase,\n table: string,\n now: number,\n dropBefore: number,\n beforeDrop?: (connection: ICNSPostgresSchemaConnection, partition: string) => Promise<void>\n ): Promise<string[]> {\n const today = Math.floor(now / CNSPostgresSchemaPartitionRotation.DAY_MS);\n const days = `${today}:${Math.floor(dropBefore / CNSPostgresSchemaPartitionRotation.DAY_MS)}`;\n if (this.rotatedFor.get(table) === days) return [];\n const dropped = await this.underLock(database, table, async connection => {\n const expired = (await this.readRangePartitions(connection, table)).filter(partition => partition.to <= dropBefore);\n if (beforeDrop) for (const partition of expired) await beforeDrop(connection, partition.name);\n for (const partition of expired) await connection.query(`DROP TABLE ${partition.name}`);\n return expired.map(partition => partition.name);\n });\n await this.underLock(database, table, async connection => {\n const partitions = await this.readRangePartitions(connection, table);\n for (let ahead = 1; ahead <= this.options.premakeDays; ahead++) {\n const from = (today + ahead) * CNSPostgresSchemaPartitionRotation.DAY_MS;\n const to = from + CNSPostgresSchemaPartitionRotation.DAY_MS;\n if (partitions.some(partition => partition.from < to && from < partition.to)) continue;\n const name = `${table}_p${new Date(from).toISOString().slice(0, 10).replace(/-/g, '')}`;\n await connection.query(`CREATE TABLE ${name} (LIKE ${table} INCLUDING DEFAULTS INCLUDING CONSTRAINTS)`);\n await connection.query(`ALTER TABLE ${table} ATTACH PARTITION ${name} FOR VALUES FROM (${from}) TO (${to})`);\n }\n });\n this.rotatedFor.set(table, days);\n return dropped;\n }\n\n private underLock<TResult>(database: ICNSPostgresSchemaDatabase, table: string, work: (connection: ICNSPostgresSchemaConnection) => Promise<TResult>): Promise<TResult> {\n return database.transaction(async connection => {\n await connection.query('SELECT pg_advisory_xact_lock($1, hashtext($2))', [CNSPostgresSchemaPartitionRotation.LOCK_NAMESPACE, table]);\n await connection.query(`SET LOCAL lock_timeout = '${CNSPostgresSchemaPartitionRotation.LOCK_TIMEOUT}'`);\n return work(connection);\n });\n }\n\n private async readRangePartitions(connection: ICNSPostgresSchemaConnection, table: string): Promise<TRangePartition[]> {\n const rows = await connection.query<{ name: string; bound: string }>(\n `SELECT child.oid::regclass::text AS name, pg_get_expr(child.relpartbound, child.oid) AS bound\n FROM pg_inherits\n JOIN pg_class child ON child.oid = pg_inherits.inhrelid\n WHERE pg_inherits.inhparent = $1::regclass`,\n [table]\n );\n const partitions: TRangePartition[] = [];\n for (const row of rows.rows) {\n const range = /^FOR VALUES FROM \\('?(-?[\\d.e+]+)'?\\) TO \\('?(-?[\\d.e+]+)'?\\)$/.exec(row.bound);\n if (range) partitions.push({ name: row.name, from: Number(range[1]), to: Number(range[2]) });\n }\n return partitions;\n }\n}\n","import { CNSPostgresSchemaMigrator, CNSPostgresSchemaPgDatabase } from '@cnstra/postgres-schema';\nimport type { ICNSPostgresSchemaConnection, ICNSPostgresSchemaPgPool } from '@cnstra/postgres-schema';\nimport type { Pool } from 'pg';\nimport type { ICNSPgQueryable } from '../interfaces/ICNSPgQueryable';\nimport type { TCNSPostgresStimulationMigrateOptions } from '../types/TCNSPostgresStimulationMigrateOptions';\n\nexport class CNSPostgresStimulationSchema {\n static readonly DEFAULT_TABLE_PREFIX = 'cns_';\n private static readonly LONGEST_SUFFIX = 'stimulation_attempt_started_idx';\n\n private static readonly migrator = new CNSPostgresSchemaMigrator({\n name: 'stimulation',\n tables: tablePrefix => CNSPostgresStimulationSchema.tables(tablePrefix),\n steps: [{ step: 1, kind: 'expand', sql: tablePrefix => CNSPostgresStimulationSchema.initialSql(tablePrefix) }],\n });\n\n static assertTablePrefix(tablePrefix: string): string {\n return CNSPostgresStimulationSchema.migrator.assertTablePrefix(tablePrefix, CNSPostgresStimulationSchema.LONGEST_SUFFIX);\n }\n\n static tables(tablePrefix: string = CNSPostgresStimulationSchema.DEFAULT_TABLE_PREFIX): string[] {\n const p = CNSPostgresStimulationSchema.assertTablePrefix(tablePrefix);\n return [`${p}stimulation`, `${p}stimulation_attempt`, `${p}stimulation_task`];\n }\n\n static migrationTable(tablePrefix: string = CNSPostgresStimulationSchema.DEFAULT_TABLE_PREFIX): string {\n return CNSPostgresStimulationSchema.migrator.migrationTable(CNSPostgresStimulationSchema.assertTablePrefix(tablePrefix));\n }\n\n static migrationSql(\n tablePrefix: string = CNSPostgresStimulationSchema.DEFAULT_TABLE_PREFIX,\n appliedSteps: readonly number[] = [],\n options?: TCNSPostgresStimulationMigrateOptions\n ): string {\n return CNSPostgresStimulationSchema.migrator.migrationSql(CNSPostgresStimulationSchema.assertTablePrefix(tablePrefix), appliedSteps, options);\n }\n\n static async migrate(\n pool: Pool,\n tablePrefix: string = CNSPostgresStimulationSchema.DEFAULT_TABLE_PREFIX,\n options?: TCNSPostgresStimulationMigrateOptions\n ): Promise<void> {\n await CNSPostgresStimulationSchema.migrator.migrate(\n CNSPostgresSchemaPgDatabase.fromPool(pool as unknown as ICNSPostgresSchemaPgPool),\n CNSPostgresStimulationSchema.assertTablePrefix(tablePrefix),\n options\n );\n }\n\n static async verify(database: ICNSPgQueryable, tablePrefix: string = CNSPostgresStimulationSchema.DEFAULT_TABLE_PREFIX): Promise<void> {\n await CNSPostgresStimulationSchema.migrator.verify(\n database as unknown as ICNSPostgresSchemaConnection,\n CNSPostgresStimulationSchema.assertTablePrefix(tablePrefix)\n );\n }\n\n private static initialSql(p: string): string {\n return `\nCREATE TABLE ${p}stimulation (\n stimulation_id text PRIMARY KEY,\n entry jsonb NOT NULL,\n status text NOT NULL CHECK (status IN ('running', 'completed', 'failed', 'aborted')),\n scope_name text,\n progress jsonb NOT NULL,\n updated_at bigint NOT NULL,\n started_at bigint NOT NULL DEFAULT 0,\n hop_count int NOT NULL DEFAULT 0,\n last_completed_at bigint\n) WITH (fillfactor = 70);\nCREATE INDEX ${p}stimulation_started_idx ON ${p}stimulation (started_at DESC, stimulation_id COLLATE \"C\" DESC);\nCREATE INDEX ${p}stimulation_scope_started_idx ON ${p}stimulation (scope_name, started_at DESC, stimulation_id COLLATE \"C\" DESC);\n\n-- No foreign keys below: attempts and tasks arrive over separate writes, often before their parent row.\nCREATE TABLE ${p}stimulation_attempt (\n stimulation_attempt_id text PRIMARY KEY,\n stimulation_id text NOT NULL,\n attempt_number int NOT NULL,\n status text NOT NULL CHECK (status IN ('running', 'completed', 'failed', 'aborted')),\n started_at bigint NOT NULL,\n completed_at bigint,\n hop_count int NOT NULL,\n has_error boolean NOT NULL,\n replay_of text,\n entry jsonb NOT NULL,\n UNIQUE (stimulation_id, attempt_number)\n);\nCREATE INDEX ${p}stimulation_attempt_started_idx ON ${p}stimulation_attempt (started_at);\n\nCREATE TABLE ${p}stimulation_task (\n stimulation_attempt_id text NOT NULL,\n task_index int NOT NULL,\n neuron_name text NOT NULL,\n dendrite_collateral_name text NOT NULL,\n input_index int NOT NULL,\n output jsonb,\n status text NOT NULL CHECK (status IN ('done', 'failed')),\n error text,\n started_at bigint NOT NULL,\n duration_ms bigint,\n activation_context jsonb,\n PRIMARY KEY (stimulation_attempt_id, task_index, started_at)\n) PARTITION BY RANGE (started_at);\nCREATE TABLE ${p}stimulation_task_default PARTITION OF ${p}stimulation_task DEFAULT;\n`;\n }\n}\n"],"mappings":"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","names":["CNSPostgresSchemaMigrator","options","steps","sort","left","right","step","forEach","index","Error","name","kind","migrationTable","tablePrefix","migrate","database","transaction","connection","query","LOCK_NAMESPACE","LOCK_TIMEOUT","applied","readAppliedSteps","undefined","assertNoUntrackedTables","pending","pendingSteps","Map","contract","length","sqlFor","verify","missing","filter","has","map","join","unknown","migrationSql","appliedSteps","fresh","assertTablePrefix","longestSuffix","longest","Math","max","limit","MAX_IDENTIFIER_LENGTH","RegExp","test","JSON","stringify","withContract","createMigrationTable","table","parts","push","sql","exists","rows","found","row","Number","tables","CNSPostgresSchemaPgDatabase","open","fromPool","pool","client","connect","connection","release","error","transaction","work","query","result","rollback","then","undefined","rollbackError","Error","CNSPostgresSchemaPartitionRotation","options","rotatedFor","Map","premakeDays","Number","isInteger","Error","rotate","database","table","now","dropBefore","beforeDrop","today","Math","floor","DAY_MS","days","get","dropped","underLock","connection","expired","readRangePartitions","filter","partition","to","name","query","map","partitions","ahead","from","some","Date","toISOString","slice","replace","set","work","transaction","LOCK_NAMESPACE","LOCK_TIMEOUT","rows","row","range","exec","bound","push","CNSPostgresStimulationSchema","assertTablePrefix","tablePrefix","migrator","LONGEST_SUFFIX","tables","DEFAULT_TABLE_PREFIX","p","migrationTable","migrationSql","appliedSteps","options","migrate","pool","CNSPostgresSchemaPgDatabase","fromPool","verify","database","initialSql","CNSPostgresSchemaMigrator","name","steps","step","kind","sql","j","v","JSON","stringify","num","Number","numOrNull","CNSPostgresStimulationRepository","opts","db","pool","ownsPool","p","autoMigrate","now","taskPartitions","CNSPostgresSchemaPartitionRotation","premakeDays","ready","connectionString","Pool","queryable","Error","CNSPostgresStimulationSchema","assertTablePrefix","tablePrefix","DEFAULT_TABLE_PREFIX","Date","schemaReady","Promise","resolve","migrate","catch","error","undefined","tx","fn","client","connect","query","out","e","release","saveStimulation","s","lockStimulation","stimulationId","upserted","entry","status","scopeName","progress","rows","inserted","rollUpAttempts","saveAttempt","a","stimulationAttemptId","attemptNumber","startedAt","completedAt","hopCount","hasError","replayOf","LOCK_STIMULATION","appendTask","t","index","neuronName","dendriteCollateralName","inputIndex","output","duration","activationContext","getStimulation","res","row","rowToStimulation","listStimulations","filter","scope","since","map","getAttempts","rowToAttempt","getTasks","rowToTask","getAttemptsFor","stimulationIds","length","getTasksFor","stimulationAttemptIds","rosterCte","withNeurons","neurons","selectRoster","curStarted","curId","cursor","decoded","Buffer","from","toString","at","indexOf","started","slice","isFinite","params","entryCollateralName","text","toLowerCase","until","limitClause","limit","Math","max","floor","sql","total","total_count","countRes","countRows","entries","r","_a","_b","stimulation_id","scope_name","started_at","completed_at","hop_count","has_error","queryRoster","hasMore","lastRow","nextCursor","encodeCNSRosterCursor","queryFacets","metricPath","split","CNS_HISTOGRAM_BUCKETS","tally","kind","key","count","cnt","sort","b","byEntryCollateral","x","name","byStatus","byNeuron","byTime","ts_bucket","tsBucket","completed","failed","running","metric","delete","deleteStale","olderThanMs","cutoff","removedCount","after","batch","stale","RETENTION_BATCH","candidates","removed","ids","rotationFailure","rotate","transaction","work","close","end","stimulation_attempt_id","attempt_number","Boolean","replay_of","task_index","neuron_name","dendrite_collateral_name","input_index","duration_ms","activation_context"]}