nexus-ai-pro 2.1.0 → 2.2.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/API_STABILITY.md +38 -0
- package/CHANGELOG.md +145 -1
- package/README.md +13 -8
- package/dist/cache/memory-cache.d.ts +8 -0
- package/dist/cache/memory-cache.js +9 -0
- package/dist/cache/semantic-cache.d.ts +6 -3
- package/dist/cache/semantic-cache.js +5 -2
- package/dist/cli/args.js +3 -0
- package/dist/cli/db.d.ts +16 -4
- package/dist/cli/db.js +169 -22
- package/dist/cli/doctor.d.ts +17 -0
- package/dist/cli/doctor.js +53 -0
- package/dist/cli.js +12 -1
- package/dist/core/lifecycle.js +11 -6
- package/dist/core/nexus.d.ts +6 -0
- package/dist/core/nexus.js +103 -22
- package/dist/doctor/index.d.ts +90 -0
- package/dist/doctor/index.js +237 -0
- package/dist/graph/functional.d.ts +80 -7
- package/dist/graph/functional.js +138 -35
- package/dist/graph/graph.d.ts +15 -5
- package/dist/graph/graph.js +72 -20
- package/dist/images/evals.js +4 -2
- package/dist/operations/adapters.d.ts +52 -4
- package/dist/operations/adapters.js +194 -7
- package/dist/operations/runner.js +1 -0
- package/dist/operations/stats.js +8 -1
- package/dist/operations/store.d.ts +5 -1
- package/dist/operations/store.js +7 -0
- package/dist/ops/circuit-breaker.d.ts +52 -0
- package/dist/ops/circuit-breaker.js +4 -0
- package/dist/ops/circuit-store.d.ts +13 -2
- package/dist/ops/circuit-store.js +68 -0
- package/dist/ops/circuit-sync.d.ts +17 -0
- package/dist/ops/circuit-sync.js +86 -0
- package/dist/ops/rate-limiter.d.ts +2 -0
- package/dist/ops/rate-limiter.js +2 -0
- package/dist/postgres/circuits.d.ts +21 -4
- package/dist/postgres/circuits.js +65 -4
- package/dist/postgres/client.d.ts +0 -2
- package/dist/postgres/client.js +0 -4
- package/dist/postgres/evaluate.d.ts +16 -4
- package/dist/postgres/evaluate.js +17 -5
- package/dist/postgres/index.d.ts +32 -11
- package/dist/postgres/index.js +65 -31
- package/dist/postgres/migrations.d.ts +60 -0
- package/dist/postgres/migrations.js +121 -0
- package/dist/postgres/operations.d.ts +14 -3
- package/dist/postgres/operations.js +45 -15
- package/dist/postgres/prompts.d.ts +11 -2
- package/dist/postgres/prompts.js +14 -3
- package/dist/postgres/rollups.d.ts +35 -0
- package/dist/postgres/rollups.js +129 -0
- package/dist/postgres/store.d.ts +11 -2
- package/dist/postgres/store.js +14 -3
- package/dist/postgres/traces.d.ts +13 -3
- package/dist/postgres/traces.js +25 -3
- package/dist/postgres/vectors.d.ts +11 -2
- package/dist/postgres/vectors.js +14 -3
- package/dist/router/auto-router.d.ts +34 -7
- package/dist/router/auto-router.js +201 -68
- package/dist/server/assistant.d.ts +4 -0
- package/dist/server/assistant.js +8 -0
- package/dist/server/errors.d.ts +12 -0
- package/dist/server/errors.js +7 -0
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.js +1 -1
- package/dist/server/runs.d.ts +24 -1
- package/dist/server/runs.js +90 -14
- package/dist/sqlite/index.d.ts +16 -3
- package/dist/sqlite/index.js +16 -3
- package/dist/sqlite/migrations.d.ts +26 -0
- package/dist/sqlite/migrations.js +63 -0
- package/dist/sqlite/operations.d.ts +14 -3
- package/dist/sqlite/operations.js +42 -15
- package/dist/sqlite/store.d.ts +9 -2
- package/dist/sqlite/store.js +13 -3
- package/dist/sqlite/vectors.d.ts +11 -2
- package/dist/sqlite/vectors.js +13 -2
- package/dist/store/tenant.d.ts +12 -0
- package/dist/store/tenant.js +29 -0
- package/dist/tenancy/index.d.ts +113 -0
- package/dist/tenancy/index.js +233 -0
- package/dist/tracing/instrument.js +2 -0
- package/dist/types/config.d.ts +24 -2
- package/dist/types/graph.d.ts +11 -1
- package/dist/types/messages.d.ts +5 -0
- package/dist/types/operations.d.ts +6 -0
- package/dist/types/response.d.ts +20 -0
- package/dist/types/server.d.ts +10 -1
- package/dist/utils/schema-migrations.d.ts +97 -0
- package/dist/utils/schema-migrations.js +91 -0
- package/dist/utils/signals.d.ts +17 -0
- package/dist/utils/signals.js +26 -0
- package/dist/utils/tenant.d.ts +14 -0
- package/dist/utils/tenant.js +16 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist-cjs/cache/memory-cache.d.ts +8 -0
- package/dist-cjs/cache/memory-cache.js +9 -0
- package/dist-cjs/cache/semantic-cache.d.ts +6 -3
- package/dist-cjs/cache/semantic-cache.js +5 -2
- package/dist-cjs/cli/args.js +3 -0
- package/dist-cjs/cli/db.d.ts +16 -4
- package/dist-cjs/cli/db.js +200 -20
- package/dist-cjs/cli/doctor.d.ts +17 -0
- package/dist-cjs/cli/doctor.js +56 -0
- package/dist-cjs/core/lifecycle.js +11 -6
- package/dist-cjs/core/nexus.d.ts +6 -0
- package/dist-cjs/core/nexus.js +103 -22
- package/dist-cjs/doctor/index.d.ts +90 -0
- package/dist-cjs/doctor/index.js +276 -0
- package/dist-cjs/graph/functional.d.ts +80 -7
- package/dist-cjs/graph/functional.js +137 -34
- package/dist-cjs/graph/graph.d.ts +15 -5
- package/dist-cjs/graph/graph.js +72 -20
- package/dist-cjs/images/evals.js +4 -2
- package/dist-cjs/operations/adapters.d.ts +52 -4
- package/dist-cjs/operations/adapters.js +193 -6
- package/dist-cjs/operations/runner.js +1 -0
- package/dist-cjs/operations/stats.js +8 -1
- package/dist-cjs/operations/store.d.ts +5 -1
- package/dist-cjs/operations/store.js +7 -0
- package/dist-cjs/ops/circuit-breaker.d.ts +52 -0
- package/dist-cjs/ops/circuit-breaker.js +4 -0
- package/dist-cjs/ops/circuit-store.d.ts +13 -2
- package/dist-cjs/ops/circuit-store.js +68 -0
- package/dist-cjs/ops/circuit-sync.d.ts +17 -0
- package/dist-cjs/ops/circuit-sync.js +87 -0
- package/dist-cjs/ops/rate-limiter.d.ts +2 -0
- package/dist-cjs/ops/rate-limiter.js +2 -0
- package/dist-cjs/postgres/circuits.d.ts +21 -4
- package/dist-cjs/postgres/circuits.js +98 -3
- package/dist-cjs/postgres/client.d.ts +0 -2
- package/dist-cjs/postgres/client.js +0 -5
- package/dist-cjs/postgres/evaluate.d.ts +16 -4
- package/dist-cjs/postgres/evaluate.js +50 -4
- package/dist-cjs/postgres/index.d.ts +32 -11
- package/dist-cjs/postgres/index.js +69 -20
- package/dist-cjs/postgres/migrations.d.ts +60 -0
- package/dist-cjs/postgres/migrations.js +129 -0
- package/dist-cjs/postgres/operations.d.ts +14 -3
- package/dist-cjs/postgres/operations.js +78 -14
- package/dist-cjs/postgres/prompts.d.ts +11 -2
- package/dist-cjs/postgres/prompts.js +47 -2
- package/dist-cjs/postgres/rollups.d.ts +35 -0
- package/dist-cjs/postgres/rollups.js +168 -0
- package/dist-cjs/postgres/store.d.ts +11 -2
- package/dist-cjs/postgres/store.js +47 -2
- package/dist-cjs/postgres/traces.d.ts +13 -3
- package/dist-cjs/postgres/traces.js +58 -2
- package/dist-cjs/postgres/vectors.d.ts +11 -2
- package/dist-cjs/postgres/vectors.js +47 -2
- package/dist-cjs/router/auto-router.d.ts +34 -7
- package/dist-cjs/router/auto-router.js +201 -68
- package/dist-cjs/server/assistant.d.ts +4 -0
- package/dist-cjs/server/assistant.js +8 -0
- package/dist-cjs/server/errors.d.ts +12 -0
- package/dist-cjs/server/errors.js +9 -1
- package/dist-cjs/server/index.d.ts +1 -1
- package/dist-cjs/server/index.js +2 -1
- package/dist-cjs/server/runs.d.ts +24 -1
- package/dist-cjs/server/runs.js +89 -13
- package/dist-cjs/sqlite/index.d.ts +16 -3
- package/dist-cjs/sqlite/index.js +31 -10
- package/dist-cjs/sqlite/migrations.d.ts +26 -0
- package/dist-cjs/sqlite/migrations.js +71 -0
- package/dist-cjs/sqlite/operations.d.ts +14 -3
- package/dist-cjs/sqlite/operations.js +76 -15
- package/dist-cjs/sqlite/store.d.ts +9 -2
- package/dist-cjs/sqlite/store.js +47 -3
- package/dist-cjs/sqlite/vectors.d.ts +11 -2
- package/dist-cjs/sqlite/vectors.js +47 -2
- package/dist-cjs/store/tenant.d.ts +12 -0
- package/dist-cjs/store/tenant.js +33 -0
- package/dist-cjs/tenancy/index.d.ts +113 -0
- package/dist-cjs/tenancy/index.js +250 -0
- package/dist-cjs/tracing/instrument.js +2 -0
- package/dist-cjs/types/config.d.ts +24 -2
- package/dist-cjs/types/graph.d.ts +11 -1
- package/dist-cjs/types/messages.d.ts +5 -0
- package/dist-cjs/types/operations.d.ts +6 -0
- package/dist-cjs/types/response.d.ts +20 -0
- package/dist-cjs/types/server.d.ts +10 -1
- package/dist-cjs/utils/schema-migrations.d.ts +97 -0
- package/dist-cjs/utils/schema-migrations.js +102 -0
- package/dist-cjs/utils/signals.d.ts +17 -0
- package/dist-cjs/utils/signals.js +29 -0
- package/dist-cjs/utils/tenant.d.ts +14 -0
- package/dist-cjs/utils/tenant.js +22 -0
- package/dist-cjs/version.d.ts +1 -1
- package/dist-cjs/version.js +1 -1
- package/llms.txt +4 -3
- package/package.json +71 -2
package/dist/postgres/index.js
CHANGED
|
@@ -1,35 +1,69 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
|
|
1
|
+
import { MIGRATIONS_TABLE, migrationChecksum, sqlLiteral } from '../utils/schema-migrations.js';
|
|
2
|
+
import { circuitStoreMigrations } from './circuits.js';
|
|
3
|
+
import { quoteTable } from './client.js';
|
|
4
|
+
import { evaluationStoreMigrations } from './evaluate.js';
|
|
5
|
+
import { migrationsTableStatements } from './migrations.js';
|
|
6
|
+
import { operationStoreMigrations } from './operations.js';
|
|
7
|
+
import { promptStoreMigrations } from './prompts.js';
|
|
8
|
+
import { rollupStoreMigrations } from './rollups.js';
|
|
9
|
+
import { storeMigrations } from './store.js';
|
|
10
|
+
import { traceStoreMigrations } from './traces.js';
|
|
11
|
+
import { vectorStoreMigrations } from './vectors.js';
|
|
12
|
+
export { migrationChecksum, SchemaMigrationError, } from '../utils/schema-migrations.js';
|
|
13
|
+
export { circuitStoreMigration, circuitStoreMigrations, PostgresCircuitStateStore, } from './circuits.js';
|
|
9
14
|
export { fromPostgresJs } from './client.js';
|
|
10
|
-
export { evaluationStoreMigration, PostgresDatasetStore, PostgresExperimentStore, } from './evaluate.js';
|
|
11
|
-
export {
|
|
12
|
-
export {
|
|
13
|
-
export {
|
|
14
|
-
export {
|
|
15
|
-
export {
|
|
15
|
+
export { evaluationStoreMigration, evaluationStoreMigrations, PostgresDatasetStore, PostgresExperimentStore, } from './evaluate.js';
|
|
16
|
+
export { applyPostgresMigrations, postgresMigrationStatus, } from './migrations.js';
|
|
17
|
+
export { operationStoreMigration, operationStoreMigrations, PostgresOperationStore, } from './operations.js';
|
|
18
|
+
export { PostgresPromptStore, promptStoreMigration, promptStoreMigrations, } from './prompts.js';
|
|
19
|
+
export { PostgresRollupStore, rollupStoreMigration, rollupStoreMigrations, } from './rollups.js';
|
|
20
|
+
export { PostgresStore, storeMigration, storeMigrations } from './store.js';
|
|
21
|
+
export { PostgresTraceStore, traceStoreMigration, traceStoreMigrations, } from './traces.js';
|
|
22
|
+
export { PostgresVectorStore, vectorStoreMigration, vectorStoreMigrations, } from './vectors.js';
|
|
23
|
+
export const POSTGRES_ADAPTERS = [
|
|
24
|
+
'operations',
|
|
25
|
+
'store',
|
|
26
|
+
'traces',
|
|
27
|
+
'evaluation',
|
|
28
|
+
'circuits',
|
|
29
|
+
'prompts',
|
|
30
|
+
'rollups',
|
|
31
|
+
'vectors',
|
|
32
|
+
];
|
|
33
|
+
export function postgresMigrations(options = {}) {
|
|
34
|
+
const adapters = new Set(options.adapters ?? POSTGRES_ADAPTERS.filter((adapter) => adapter !== 'vectors'));
|
|
35
|
+
const vectorDimensions = options.vectorDimensions;
|
|
36
|
+
const migrationsOf = {
|
|
37
|
+
operations: () => operationStoreMigrations(),
|
|
38
|
+
store: () => storeMigrations(vectorDimensions ? { vectorDimensions } : {}),
|
|
39
|
+
traces: () => traceStoreMigrations(),
|
|
40
|
+
evaluation: () => evaluationStoreMigrations(),
|
|
41
|
+
circuits: () => circuitStoreMigrations(),
|
|
42
|
+
prompts: () => promptStoreMigrations(),
|
|
43
|
+
rollups: () => rollupStoreMigrations(),
|
|
44
|
+
vectors: () => {
|
|
45
|
+
if (vectorDimensions === undefined)
|
|
46
|
+
throw new RangeError('The vectors adapter needs vectorDimensions');
|
|
47
|
+
return vectorStoreMigrations({ dimensions: vectorDimensions });
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
return POSTGRES_ADAPTERS.filter((adapter) => adapters.has(adapter)).flatMap((adapter) => migrationsOf[adapter]());
|
|
51
|
+
}
|
|
16
52
|
export function postgresMigration(options = {}) {
|
|
17
|
-
const
|
|
18
|
-
const statements = [
|
|
19
|
-
|
|
20
|
-
...(
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
53
|
+
const migrations = postgresMigrations(options);
|
|
54
|
+
const statements = migrations.flatMap((migration) => [...migration.statements]);
|
|
55
|
+
if (options.record) {
|
|
56
|
+
statements.push(...migrationsTableStatements());
|
|
57
|
+
for (const migration of migrations) {
|
|
58
|
+
statements.push(`INSERT INTO ${quoteTable(MIGRATIONS_TABLE)} (component, version, name, checksum, applied_at) VALUES (${[
|
|
59
|
+
migration.component,
|
|
60
|
+
String(migration.version),
|
|
61
|
+
migration.name,
|
|
62
|
+
migrationChecksum(migration),
|
|
63
|
+
]
|
|
64
|
+
.map((value, index) => (index === 1 ? value : sqlLiteral(value)))
|
|
65
|
+
.join(', ')}, to_char(now() AT TIME ZONE 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"')) ON CONFLICT (component, version) DO NOTHING`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
29
68
|
return `${statements.join(';\n\n')};\n`;
|
|
30
69
|
}
|
|
31
|
-
function vectorMigrationFor(dimensions) {
|
|
32
|
-
if (dimensions === undefined)
|
|
33
|
-
throw new RangeError('The vectors adapter needs vectorDimensions');
|
|
34
|
-
return vectorStoreMigration({ dimensions });
|
|
35
|
-
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { type MigrationResult, type MigrationStatus, type SchemaMigration } from '../utils/schema-migrations.js';
|
|
2
|
+
import { type PostgresLikeClient } from './client.js';
|
|
3
|
+
export { type AppliedMigration, type MigrationResult, type MigrationStatus, migrationChecksum, type SchemaMigration, SchemaMigrationError, } from '../utils/schema-migrations.js';
|
|
4
|
+
/** Options for applying or inspecting Postgres migrations. */
|
|
5
|
+
export interface PostgresMigrateOptions {
|
|
6
|
+
/** Plans without writing anything, not even the table that records migrations. */
|
|
7
|
+
dryRun?: boolean;
|
|
8
|
+
/** The table that records applied migrations, optionally schema-qualified. Defaults to `nexus_schema_migrations`. */
|
|
9
|
+
table?: string;
|
|
10
|
+
/**
|
|
11
|
+
* Runs one migration's statements, and the row recording it, in one transaction on one connection.
|
|
12
|
+
* Without it, statements run one at a time and the record is written last: every bundled statement
|
|
13
|
+
* is idempotent, so an interrupted migration runs again on the next call. Pass it for a pool, where
|
|
14
|
+
* `BEGIN` and `COMMIT` would otherwise reach different connections:
|
|
15
|
+
*
|
|
16
|
+
* ```ts
|
|
17
|
+
* transaction: async (run) => {
|
|
18
|
+
* const connection = await pool.connect();
|
|
19
|
+
* try {
|
|
20
|
+
* await connection.query('BEGIN');
|
|
21
|
+
* const result = await run(connection);
|
|
22
|
+
* await connection.query('COMMIT');
|
|
23
|
+
* return result;
|
|
24
|
+
* } catch (error) {
|
|
25
|
+
* await connection.query('ROLLBACK');
|
|
26
|
+
* throw error;
|
|
27
|
+
* } finally {
|
|
28
|
+
* connection.release();
|
|
29
|
+
* }
|
|
30
|
+
* }
|
|
31
|
+
* ```
|
|
32
|
+
*
|
|
33
|
+
* PGlite needs only `transaction: (run) => db.transaction(run)`.
|
|
34
|
+
*/
|
|
35
|
+
transaction?: <T>(run: (client: PostgresLikeClient) => Promise<T>) => Promise<T>;
|
|
36
|
+
/**
|
|
37
|
+
* How long one migrator holds the lock before another may take it over, renewed before each
|
|
38
|
+
* migration. Defaults to 10 minutes, longer than any bundled migration takes.
|
|
39
|
+
*/
|
|
40
|
+
lockTtlMs?: number;
|
|
41
|
+
/** How long to wait for another migrator to finish before giving up. Defaults to 2 minutes. */
|
|
42
|
+
lockTimeoutMs?: number;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Where a Postgres database stands against a list of migrations. Reads only: on a database that has
|
|
46
|
+
* never been migrated, everything is pending.
|
|
47
|
+
*/
|
|
48
|
+
export declare function postgresMigrationStatus(client: PostgresLikeClient, migrations: readonly SchemaMigration[], options?: Pick<PostgresMigrateOptions, 'table'>): Promise<MigrationStatus>;
|
|
49
|
+
/**
|
|
50
|
+
* Applies the pending migrations in order, under a lock, recording each with a checksum.
|
|
51
|
+
*
|
|
52
|
+
* The lock is a row with a lease rather than a session advisory lock, so it holds through a
|
|
53
|
+
* connection pool and serverless drivers alike: a second migrator waits, and one that died is taken
|
|
54
|
+
* over once its lease lapses. A migration whose statements changed after it ran is refused before
|
|
55
|
+
* anything is written. Migrations a newer release recorded are left alone, so an older worker
|
|
56
|
+
* starting mid-rollout does nothing.
|
|
57
|
+
*/
|
|
58
|
+
export declare function applyPostgresMigrations(client: PostgresLikeClient, migrations: readonly SchemaMigration[], options?: PostgresMigrateOptions): Promise<MigrationResult>;
|
|
59
|
+
/** The statements that create the migrations table and its lock, which `migrate()` runs first. */
|
|
60
|
+
export declare function migrationsTableStatements(table?: string): string[];
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { assertUnchanged, delay, label, MIGRATIONS_TABLE, migrationChecksum, planMigrations, SchemaMigrationError, } from '../utils/schema-migrations.js';
|
|
2
|
+
import { quoteTable } from './client.js';
|
|
3
|
+
export { migrationChecksum, SchemaMigrationError, } from '../utils/schema-migrations.js';
|
|
4
|
+
const LOCK_NAME = 'migrate';
|
|
5
|
+
const DB_NOW = '(extract(epoch from clock_timestamp()) * 1000)';
|
|
6
|
+
export async function postgresMigrationStatus(client, migrations, options = {}) {
|
|
7
|
+
return planMigrations(migrations, await readApplied(client, options.table ?? MIGRATIONS_TABLE));
|
|
8
|
+
}
|
|
9
|
+
export async function applyPostgresMigrations(client, migrations, options = {}) {
|
|
10
|
+
const tableName = options.table ?? MIGRATIONS_TABLE;
|
|
11
|
+
if (options.dryRun) {
|
|
12
|
+
const status = await postgresMigrationStatus(client, migrations, options);
|
|
13
|
+
assertUnchanged(status);
|
|
14
|
+
return { applied: status.pending, statements: status.pending.flatMap((m) => [...m.statements]), dryRun: true };
|
|
15
|
+
}
|
|
16
|
+
planMigrations(migrations, []);
|
|
17
|
+
await bootstrap(client, tableName);
|
|
18
|
+
const owner = `migrator-${globalThis.crypto.randomUUID()}`;
|
|
19
|
+
const lockTtlMs = options.lockTtlMs ?? 600_000;
|
|
20
|
+
await acquire(client, tableName, owner, lockTtlMs, options.lockTimeoutMs ?? 120_000);
|
|
21
|
+
try {
|
|
22
|
+
const status = planMigrations(migrations, await readApplied(client, tableName));
|
|
23
|
+
assertUnchanged(status);
|
|
24
|
+
const statements = [];
|
|
25
|
+
for (const migration of status.pending) {
|
|
26
|
+
if (!(await claim(client, tableName, owner, lockTtlMs))) {
|
|
27
|
+
throw new SchemaMigrationError('MIGRATION_LOCKED', `The migration lock lapsed before ${label(migration)}`);
|
|
28
|
+
}
|
|
29
|
+
const apply = async (connection) => {
|
|
30
|
+
for (const statement of migration.statements)
|
|
31
|
+
await connection.query(statement);
|
|
32
|
+
await connection.query(`INSERT INTO ${quoteTable(tableName)} (component, version, name, checksum, applied_at)
|
|
33
|
+
VALUES ($1, $2, $3, $4, $5) ON CONFLICT (component, version) DO NOTHING`, [
|
|
34
|
+
migration.component,
|
|
35
|
+
migration.version,
|
|
36
|
+
migration.name,
|
|
37
|
+
migrationChecksum(migration),
|
|
38
|
+
new Date().toISOString(),
|
|
39
|
+
]);
|
|
40
|
+
};
|
|
41
|
+
if (options.transaction)
|
|
42
|
+
await options.transaction(apply);
|
|
43
|
+
else
|
|
44
|
+
await apply(client);
|
|
45
|
+
statements.push(...migration.statements);
|
|
46
|
+
}
|
|
47
|
+
return { applied: status.pending, statements, dryRun: false };
|
|
48
|
+
}
|
|
49
|
+
finally {
|
|
50
|
+
await client
|
|
51
|
+
.query(`DELETE FROM ${lockTable(tableName)} WHERE name = $1 AND owner = $2`, [LOCK_NAME, owner])
|
|
52
|
+
.catch(() => undefined);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
export function migrationsTableStatements(table = MIGRATIONS_TABLE) {
|
|
56
|
+
return [
|
|
57
|
+
`CREATE TABLE IF NOT EXISTS ${quoteTable(table)} (
|
|
58
|
+
component text NOT NULL,
|
|
59
|
+
version integer NOT NULL,
|
|
60
|
+
name text NOT NULL,
|
|
61
|
+
checksum text NOT NULL,
|
|
62
|
+
applied_at text COLLATE "C" NOT NULL,
|
|
63
|
+
PRIMARY KEY (component, version)
|
|
64
|
+
)`,
|
|
65
|
+
`CREATE TABLE IF NOT EXISTS ${lockTable(table)} (
|
|
66
|
+
name text PRIMARY KEY,
|
|
67
|
+
owner text NOT NULL,
|
|
68
|
+
expires_at double precision NOT NULL
|
|
69
|
+
)`,
|
|
70
|
+
];
|
|
71
|
+
}
|
|
72
|
+
async function bootstrap(client, table) {
|
|
73
|
+
for (const statement of migrationsTableStatements(table)) {
|
|
74
|
+
try {
|
|
75
|
+
await client.query(statement);
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
const code = error?.code;
|
|
79
|
+
if (code !== '23505' && code !== '42P07')
|
|
80
|
+
throw error;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
async function readApplied(client, table) {
|
|
85
|
+
const { rows: exists } = await client.query('SELECT to_regclass($1) IS NOT NULL AS present', [quoteTable(table)]);
|
|
86
|
+
if (!exists[0]?.present)
|
|
87
|
+
return [];
|
|
88
|
+
const { rows } = await client.query(`SELECT component, version, name, checksum, applied_at FROM ${quoteTable(table)} ORDER BY applied_at, component, version`);
|
|
89
|
+
return rows.map((row) => {
|
|
90
|
+
const value = row;
|
|
91
|
+
return {
|
|
92
|
+
component: String(value.component),
|
|
93
|
+
version: Number(value.version),
|
|
94
|
+
name: String(value.name),
|
|
95
|
+
checksum: String(value.checksum),
|
|
96
|
+
appliedAt: String(value.applied_at),
|
|
97
|
+
};
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
async function acquire(client, table, owner, ttlMs, timeoutMs) {
|
|
101
|
+
const deadline = Date.now() + timeoutMs;
|
|
102
|
+
let wait = 50;
|
|
103
|
+
while (!(await claim(client, table, owner, ttlMs))) {
|
|
104
|
+
if (Date.now() >= deadline) {
|
|
105
|
+
throw new SchemaMigrationError('MIGRATION_LOCKED', `Another migrator held the lock for ${timeoutMs} ms. It is released when that migrator finishes, or when its lease lapses.`);
|
|
106
|
+
}
|
|
107
|
+
await delay(wait);
|
|
108
|
+
wait = Math.min(wait * 2, 1_000);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
async function claim(client, table, owner, ttlMs) {
|
|
112
|
+
const lock = lockTable(table);
|
|
113
|
+
const { rows } = await client.query(`INSERT INTO ${lock} (name, owner, expires_at) VALUES ($1, $2, ${DB_NOW} + $3::double precision)
|
|
114
|
+
ON CONFLICT (name) DO UPDATE SET owner = EXCLUDED.owner, expires_at = EXCLUDED.expires_at
|
|
115
|
+
WHERE ${lock}.owner = EXCLUDED.owner OR ${lock}.expires_at < ${DB_NOW}
|
|
116
|
+
RETURNING owner`, [LOCK_NAME, owner, ttlMs]);
|
|
117
|
+
return rows.length > 0;
|
|
118
|
+
}
|
|
119
|
+
function lockTable(table) {
|
|
120
|
+
return quoteTable(`${table}_lock`);
|
|
121
|
+
}
|
|
@@ -1,12 +1,20 @@
|
|
|
1
1
|
import type { OperationRecord, OperationStore, OperationStoreFilter, OperationStoreStats } from '../types/operations.js';
|
|
2
|
+
import type { MigrationResult, SchemaMigration } from '../utils/schema-migrations.js';
|
|
2
3
|
import { type PostgresLikeClient } from './client.js';
|
|
4
|
+
import type { PostgresMigrateOptions } from './migrations.js';
|
|
3
5
|
/** Options for the Postgres operation store. */
|
|
4
6
|
export interface PostgresOperationStoreOptions {
|
|
5
7
|
/** Table name, optionally schema-qualified. Defaults to `nexus_operations`. */
|
|
6
8
|
table?: string;
|
|
7
9
|
}
|
|
8
|
-
/** The schema, as statements
|
|
10
|
+
/** The schema, as statements: every migration's, in order. For `nexus db sql` or the application's own tooling. */
|
|
9
11
|
export declare function operationStoreMigration(options?: PostgresOperationStoreOptions): string[];
|
|
12
|
+
/**
|
|
13
|
+
* The versioned schema, which `migrate()` and `nexus db migrate` apply. Version 1 is the 2.0 table;
|
|
14
|
+
* version 2 indexes queued work by age, so a claim reads the oldest few rows instead of sorting the
|
|
15
|
+
* queue; version 3 indexes queued work by tenant.
|
|
16
|
+
*/
|
|
17
|
+
export declare function operationStoreMigrations(options?: PostgresOperationStoreOptions): SchemaMigration[];
|
|
10
18
|
/**
|
|
11
19
|
* Operation records in Postgres.
|
|
12
20
|
*
|
|
@@ -25,8 +33,11 @@ export declare class PostgresOperationStore<TResult = unknown> implements Operat
|
|
|
25
33
|
private readonly options;
|
|
26
34
|
private readonly table;
|
|
27
35
|
constructor(client: PostgresLikeClient, options?: PostgresOperationStoreOptions);
|
|
28
|
-
/**
|
|
29
|
-
|
|
36
|
+
/**
|
|
37
|
+
* Applies this store's pending migrations, recorded in `nexus_schema_migrations`, under a lock.
|
|
38
|
+
* Never runs implicitly. Safe on a database an older release created: what exists is kept.
|
|
39
|
+
*/
|
|
40
|
+
migrate(options?: PostgresMigrateOptions): Promise<MigrationResult>;
|
|
30
41
|
/**
|
|
31
42
|
* Inserts a new record. Throws `OperationDuplicateError` when its idempotency key is already
|
|
32
43
|
* claimed. Refuses records carrying raw bytes.
|
|
@@ -1,12 +1,37 @@
|
|
|
1
1
|
import { OperationDuplicateError } from '../operations/errors.js';
|
|
2
2
|
import { assertSerializableRecord } from '../operations/serialization.js';
|
|
3
3
|
import { TERMINAL_OPERATION_STATUSES } from '../types/operations.js';
|
|
4
|
-
import { fromJson, isUniqueViolation, quoteDerived, quoteTable
|
|
4
|
+
import { fromJson, isUniqueViolation, quoteDerived, quoteTable } from './client.js';
|
|
5
5
|
const DEFAULT_TABLE = 'nexus_operations';
|
|
6
6
|
const TERMINAL = TERMINAL_OPERATION_STATUSES.map((status) => `'${status}'`).join(', ');
|
|
7
7
|
export function operationStoreMigration(options = {}) {
|
|
8
|
+
return operationStoreMigrations(options).flatMap((migration) => [...migration.statements]);
|
|
9
|
+
}
|
|
10
|
+
export function operationStoreMigrations(options = {}) {
|
|
8
11
|
const name = options.table ?? DEFAULT_TABLE;
|
|
9
12
|
const table = quoteTable(name);
|
|
13
|
+
const component = `operations:${name}`;
|
|
14
|
+
return [
|
|
15
|
+
{ component, version: 1, name: 'create the operations table', statements: createStatements(name, table) },
|
|
16
|
+
{
|
|
17
|
+
component,
|
|
18
|
+
version: 2,
|
|
19
|
+
name: 'index queued work by age',
|
|
20
|
+
statements: [
|
|
21
|
+
`CREATE INDEX IF NOT EXISTS ${quoteDerived(name, 'queued')} ON ${table} (created_at, id) WHERE status = 'queued'`,
|
|
22
|
+
],
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
component,
|
|
26
|
+
version: 3,
|
|
27
|
+
name: 'index queued work by tenant',
|
|
28
|
+
statements: [
|
|
29
|
+
`CREATE INDEX IF NOT EXISTS ${quoteDerived(name, 'tenant_queued')} ON ${table} ((doc ->> 'tenantId'), created_at, id) WHERE status = 'queued'`,
|
|
30
|
+
],
|
|
31
|
+
},
|
|
32
|
+
];
|
|
33
|
+
}
|
|
34
|
+
function createStatements(name, table) {
|
|
10
35
|
return [
|
|
11
36
|
`CREATE TABLE IF NOT EXISTS ${table} (
|
|
12
37
|
id text PRIMARY KEY,
|
|
@@ -32,8 +57,9 @@ export class PostgresOperationStore {
|
|
|
32
57
|
this.options = options;
|
|
33
58
|
this.table = quoteTable(options.table ?? DEFAULT_TABLE);
|
|
34
59
|
}
|
|
35
|
-
async migrate() {
|
|
36
|
-
await
|
|
60
|
+
async migrate(options = {}) {
|
|
61
|
+
const { applyPostgresMigrations } = await import('./migrations.js');
|
|
62
|
+
return applyPostgresMigrations(this.client, operationStoreMigrations(this.options), options);
|
|
37
63
|
}
|
|
38
64
|
async create(record) {
|
|
39
65
|
assertSerializableRecord(record);
|
|
@@ -95,29 +121,21 @@ export class PostgresOperationStore {
|
|
|
95
121
|
}
|
|
96
122
|
async listQueued(limit, filter) {
|
|
97
123
|
const params = [new Date().toISOString(), limit];
|
|
98
|
-
|
|
99
|
-
if (filter?.kindPrefix) {
|
|
100
|
-
params.push(filter.kindPrefix);
|
|
101
|
-
kind = ' AND substr(kind, 1, char_length($3)) = $3';
|
|
102
|
-
}
|
|
124
|
+
const narrowed = filterSql(filter, params);
|
|
103
125
|
const { rows } = await this.client.query(`SELECT doc::text AS doc FROM ${this.table}
|
|
104
|
-
WHERE status = 'queued' AND (lease_expires_at IS NULL OR lease_expires_at <= $1)${
|
|
126
|
+
WHERE status = 'queued' AND (lease_expires_at IS NULL OR lease_expires_at <= $1)${narrowed}
|
|
105
127
|
ORDER BY created_at, id
|
|
106
128
|
LIMIT $2`, params);
|
|
107
129
|
return rows.map((row) => fromJson(row.doc));
|
|
108
130
|
}
|
|
109
131
|
async stats(now, filter) {
|
|
110
132
|
const params = [now];
|
|
111
|
-
|
|
112
|
-
if (filter?.kindPrefix) {
|
|
113
|
-
params.push(filter.kindPrefix);
|
|
114
|
-
kind = ' AND substr(kind, 1, char_length($2)) = $2';
|
|
115
|
-
}
|
|
133
|
+
const narrowed = filterSql(filter, params);
|
|
116
134
|
const { rows } = await this.client.query(`SELECT status, count(*)::int AS count,
|
|
117
135
|
min(created_at) FILTER (WHERE status = 'queued') AS oldest,
|
|
118
136
|
count(*) FILTER (WHERE status = 'running' AND (lease_expires_at IS NULL OR lease_expires_at <= $1))::int AS lapsed
|
|
119
137
|
FROM ${this.table}
|
|
120
|
-
WHERE status NOT IN (${TERMINAL})${
|
|
138
|
+
WHERE status NOT IN (${TERMINAL})${narrowed}
|
|
121
139
|
GROUP BY status`, params);
|
|
122
140
|
return statsFromRows(rows);
|
|
123
141
|
}
|
|
@@ -139,6 +157,18 @@ export class PostgresOperationStore {
|
|
|
139
157
|
];
|
|
140
158
|
}
|
|
141
159
|
}
|
|
160
|
+
function filterSql(filter, params) {
|
|
161
|
+
let sql = '';
|
|
162
|
+
if (filter?.kindPrefix) {
|
|
163
|
+
params.push(filter.kindPrefix);
|
|
164
|
+
sql += ` AND substr(kind, 1, char_length($${params.length})) = $${params.length}`;
|
|
165
|
+
}
|
|
166
|
+
if (filter?.tenantId !== undefined) {
|
|
167
|
+
params.push(filter.tenantId);
|
|
168
|
+
sql += ` AND doc ->> 'tenantId' = $${params.length}`;
|
|
169
|
+
}
|
|
170
|
+
return sql;
|
|
171
|
+
}
|
|
142
172
|
function statsFromRows(rows) {
|
|
143
173
|
const byStatus = {};
|
|
144
174
|
let oldestQueuedAt;
|
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
import type { PromptHistoryEntry, PromptLabel, PromptStore, PromptVersion } from '../types/prompts.js';
|
|
2
|
+
import type { MigrationResult, SchemaMigration } from '../utils/schema-migrations.js';
|
|
2
3
|
import { type PostgresLikeClient } from './client.js';
|
|
4
|
+
import type { PostgresMigrateOptions } from './migrations.js';
|
|
3
5
|
/** Options for the Postgres prompt store. */
|
|
4
6
|
export interface PostgresPromptStoreOptions {
|
|
5
7
|
/** Table name prefix, optionally schema-qualified. Defaults to `nexus_prompt`, for `nexus_prompt_versions` and so on. */
|
|
6
8
|
table?: string;
|
|
7
9
|
}
|
|
10
|
+
/**
|
|
11
|
+
* The versioned schema, which `migrate()` and `nexus db migrate` apply. Version 1 is the 2.0 schema.
|
|
12
|
+
*/
|
|
13
|
+
export declare function promptStoreMigrations(options?: PostgresPromptStoreOptions): SchemaMigration[];
|
|
8
14
|
/** The schema for the prompt store, as statements. */
|
|
9
15
|
export declare function promptStoreMigration(options?: PostgresPromptStoreOptions): string[];
|
|
10
16
|
/**
|
|
@@ -20,8 +26,11 @@ export declare class PostgresPromptStore implements PromptStore {
|
|
|
20
26
|
private readonly labels;
|
|
21
27
|
private readonly history;
|
|
22
28
|
constructor(client: PostgresLikeClient, options?: PostgresPromptStoreOptions);
|
|
23
|
-
/**
|
|
24
|
-
|
|
29
|
+
/**
|
|
30
|
+
* Applies this store's pending migrations, recorded in `nexus_schema_migrations`, under a lock.
|
|
31
|
+
* Never runs implicitly. Safe on a database an older release created: what exists is kept.
|
|
32
|
+
*/
|
|
33
|
+
migrate(options?: PostgresMigrateOptions): Promise<MigrationResult>;
|
|
25
34
|
/** Stores a version, unless one with the same content version exists. */
|
|
26
35
|
saveVersion(version: PromptVersion): Promise<void>;
|
|
27
36
|
/** Reads a version. */
|
package/dist/postgres/prompts.js
CHANGED
|
@@ -1,8 +1,18 @@
|
|
|
1
|
-
import { fromJson, quoteDerived, quoteTable
|
|
1
|
+
import { fromJson, quoteDerived, quoteTable } from './client.js';
|
|
2
2
|
function tables(options) {
|
|
3
3
|
const base = options.table ?? 'nexus_prompt';
|
|
4
4
|
return { base, versions: `${base}_versions`, labels: `${base}_labels`, history: `${base}_history` };
|
|
5
5
|
}
|
|
6
|
+
export function promptStoreMigrations(options = {}) {
|
|
7
|
+
return [
|
|
8
|
+
{
|
|
9
|
+
component: `prompts:${tables(options).versions}`,
|
|
10
|
+
version: 1,
|
|
11
|
+
name: 'create the prompt tables',
|
|
12
|
+
statements: promptStoreMigration(options),
|
|
13
|
+
},
|
|
14
|
+
];
|
|
15
|
+
}
|
|
6
16
|
export function promptStoreMigration(options = {}) {
|
|
7
17
|
const names = tables(options);
|
|
8
18
|
return [
|
|
@@ -44,8 +54,9 @@ export class PostgresPromptStore {
|
|
|
44
54
|
this.labels = quoteTable(names.labels);
|
|
45
55
|
this.history = quoteTable(names.history);
|
|
46
56
|
}
|
|
47
|
-
async migrate() {
|
|
48
|
-
await
|
|
57
|
+
async migrate(options = {}) {
|
|
58
|
+
const { applyPostgresMigrations } = await import('./migrations.js');
|
|
59
|
+
return applyPostgresMigrations(this.client, promptStoreMigrations(this.options), options);
|
|
49
60
|
}
|
|
50
61
|
async saveVersion(version) {
|
|
51
62
|
await this.client.query(`INSERT INTO ${this.versions} (name, version, created_at, doc) VALUES ($1, $2, $3, $4::jsonb) ON CONFLICT DO NOTHING`, [version.name, version.version, version.createdAt, JSON.stringify(version)]);
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { type RollupKey, type RollupQuery, type RollupRow, type RollupStore, type RollupTotals } from '../tracing/rollups.js';
|
|
2
|
+
import type { MigrationResult, SchemaMigration } from '../utils/schema-migrations.js';
|
|
3
|
+
import { type PostgresLikeClient } from './client.js';
|
|
4
|
+
import type { PostgresMigrateOptions } from './migrations.js';
|
|
5
|
+
/** Options for the Postgres rollup store. */
|
|
6
|
+
export interface PostgresRollupStoreOptions {
|
|
7
|
+
/** Table name, optionally schema-qualified. Defaults to `nexus_rollups`. */
|
|
8
|
+
table?: string;
|
|
9
|
+
}
|
|
10
|
+
/** The schema, as statements: every migration's, in order. */
|
|
11
|
+
export declare function rollupStoreMigration(options?: PostgresRollupStoreOptions): string[];
|
|
12
|
+
/** The versioned schema, which `migrate()` and `nexus db migrate` apply. */
|
|
13
|
+
export declare function rollupStoreMigrations(options?: PostgresRollupStoreOptions): SchemaMigration[];
|
|
14
|
+
/**
|
|
15
|
+
* Rollup rows in Postgres, so a dashboard's totals live beside its traces and survive a restart.
|
|
16
|
+
*
|
|
17
|
+
* Adding to a row is one `INSERT … ON CONFLICT DO UPDATE` that sums every total, latency buckets
|
|
18
|
+
* included, so two workers finishing runs in the same hour both count. A query reads only the rows
|
|
19
|
+
* of its range: a month of one tenant is a few hundred rows, however many runs it held.
|
|
20
|
+
*/
|
|
21
|
+
export declare class PostgresRollupStore implements RollupStore {
|
|
22
|
+
private readonly client;
|
|
23
|
+
private readonly options;
|
|
24
|
+
private readonly table;
|
|
25
|
+
constructor(client: PostgresLikeClient, options?: PostgresRollupStoreOptions);
|
|
26
|
+
/**
|
|
27
|
+
* Applies this store's pending migrations, recorded in `nexus_schema_migrations`, under a lock.
|
|
28
|
+
* Never runs implicitly.
|
|
29
|
+
*/
|
|
30
|
+
migrate(options?: PostgresMigrateOptions): Promise<MigrationResult>;
|
|
31
|
+
/** Adds totals to the row for `key`, creating it, in one statement. */
|
|
32
|
+
add(key: RollupKey, totals: RollupTotals): Promise<void>;
|
|
33
|
+
/** Rows matching a query, oldest hour first, filtered as the in-memory store filters them. */
|
|
34
|
+
query(query?: RollupQuery): Promise<RollupRow[]>;
|
|
35
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { ROLLUP_LATENCY_BOUNDS_MS, } from '../tracing/rollups.js';
|
|
2
|
+
import { quoteDerived, quoteTable } from './client.js';
|
|
3
|
+
import { SqlParams } from './sql.js';
|
|
4
|
+
const DEFAULT_TABLE = 'nexus_rollups';
|
|
5
|
+
export function rollupStoreMigration(options = {}) {
|
|
6
|
+
return rollupStoreMigrations(options).flatMap((migration) => [...migration.statements]);
|
|
7
|
+
}
|
|
8
|
+
export function rollupStoreMigrations(options = {}) {
|
|
9
|
+
const name = options.table ?? DEFAULT_TABLE;
|
|
10
|
+
const table = quoteTable(name);
|
|
11
|
+
return [
|
|
12
|
+
{
|
|
13
|
+
component: `rollups:${name}`,
|
|
14
|
+
version: 1,
|
|
15
|
+
name: 'create the rollups table',
|
|
16
|
+
statements: [
|
|
17
|
+
`CREATE TABLE IF NOT EXISTS ${table} (
|
|
18
|
+
hour text COLLATE "C" NOT NULL,
|
|
19
|
+
kind text NOT NULL,
|
|
20
|
+
name text NOT NULL,
|
|
21
|
+
model text NOT NULL,
|
|
22
|
+
provider text NOT NULL,
|
|
23
|
+
tenant text NOT NULL,
|
|
24
|
+
runs bigint NOT NULL,
|
|
25
|
+
errors bigint NOT NULL,
|
|
26
|
+
cost double precision NOT NULL,
|
|
27
|
+
input_tokens bigint NOT NULL,
|
|
28
|
+
output_tokens bigint NOT NULL,
|
|
29
|
+
latency bigint[] NOT NULL,
|
|
30
|
+
PRIMARY KEY (hour, kind, name, model, provider, tenant)
|
|
31
|
+
)`,
|
|
32
|
+
`CREATE INDEX IF NOT EXISTS ${quoteDerived(name, 'tenant_hour')} ON ${table} (tenant, hour)`,
|
|
33
|
+
],
|
|
34
|
+
},
|
|
35
|
+
];
|
|
36
|
+
}
|
|
37
|
+
export class PostgresRollupStore {
|
|
38
|
+
client;
|
|
39
|
+
options;
|
|
40
|
+
table;
|
|
41
|
+
constructor(client, options = {}) {
|
|
42
|
+
this.client = client;
|
|
43
|
+
this.options = options;
|
|
44
|
+
this.table = quoteTable(options.table ?? DEFAULT_TABLE);
|
|
45
|
+
}
|
|
46
|
+
async migrate(options = {}) {
|
|
47
|
+
const { applyPostgresMigrations } = await import('./migrations.js');
|
|
48
|
+
return applyPostgresMigrations(this.client, rollupStoreMigrations(this.options), options);
|
|
49
|
+
}
|
|
50
|
+
async add(key, totals) {
|
|
51
|
+
const t = this.table;
|
|
52
|
+
await this.client.query(`INSERT INTO ${t} (hour, kind, name, model, provider, tenant, runs, errors, cost, input_tokens, output_tokens, latency)
|
|
53
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7::bigint, $8::bigint, $9::double precision, $10::bigint, $11::bigint, $12::bigint[])
|
|
54
|
+
ON CONFLICT (hour, kind, name, model, provider, tenant) DO UPDATE SET
|
|
55
|
+
runs = ${t}.runs + EXCLUDED.runs,
|
|
56
|
+
errors = ${t}.errors + EXCLUDED.errors,
|
|
57
|
+
cost = ${t}.cost + EXCLUDED.cost,
|
|
58
|
+
input_tokens = ${t}.input_tokens + EXCLUDED.input_tokens,
|
|
59
|
+
output_tokens = ${t}.output_tokens + EXCLUDED.output_tokens,
|
|
60
|
+
latency = ARRAY(
|
|
61
|
+
SELECT coalesce(stored, 0) + coalesce(added, 0)
|
|
62
|
+
FROM unnest(${t}.latency, EXCLUDED.latency) WITH ORDINALITY AS bucket(stored, added, position)
|
|
63
|
+
ORDER BY position
|
|
64
|
+
)`, [
|
|
65
|
+
key.hour,
|
|
66
|
+
key.kind,
|
|
67
|
+
key.name,
|
|
68
|
+
key.model,
|
|
69
|
+
key.provider,
|
|
70
|
+
key.tenant,
|
|
71
|
+
totals.runs,
|
|
72
|
+
totals.errors,
|
|
73
|
+
totals.cost,
|
|
74
|
+
totals.inputTokens,
|
|
75
|
+
totals.outputTokens,
|
|
76
|
+
`{${bucketsOf(totals.latency).join(',')}}`,
|
|
77
|
+
]);
|
|
78
|
+
}
|
|
79
|
+
async query(query = {}) {
|
|
80
|
+
const params = new SqlParams();
|
|
81
|
+
const where = [];
|
|
82
|
+
if (query.since)
|
|
83
|
+
where.push(`hour >= ${params.add(hourOf(query.since))}`);
|
|
84
|
+
if (query.until)
|
|
85
|
+
where.push(`hour <= ${params.add(query.until)}`);
|
|
86
|
+
if (query.kind !== undefined) {
|
|
87
|
+
const kinds = Array.isArray(query.kind) ? query.kind : [query.kind];
|
|
88
|
+
where.push(kinds.length ? `kind IN (${kinds.map((kind) => params.add(kind)).join(', ')})` : 'FALSE');
|
|
89
|
+
}
|
|
90
|
+
if (query.model !== undefined)
|
|
91
|
+
where.push(`model = ${params.add(query.model)}`);
|
|
92
|
+
if (query.provider !== undefined)
|
|
93
|
+
where.push(`provider = ${params.add(query.provider)}`);
|
|
94
|
+
if (query.tenant !== undefined)
|
|
95
|
+
where.push(`tenant = ${params.add(query.tenant)}`);
|
|
96
|
+
if (query.name !== undefined)
|
|
97
|
+
where.push(`name = ${params.add(query.name)}`);
|
|
98
|
+
const { rows } = await this.client.query(`SELECT hour, kind, name, model, provider, tenant, runs::text AS runs, errors::text AS errors, cost,
|
|
99
|
+
input_tokens::text AS input_tokens, output_tokens::text AS output_tokens, latency::text AS latency
|
|
100
|
+
FROM ${this.table}
|
|
101
|
+
${where.length ? `WHERE ${where.join(' AND ')}` : ''}
|
|
102
|
+
ORDER BY hour, kind, name, model, provider, tenant`, params.values);
|
|
103
|
+
return rows.map((raw) => {
|
|
104
|
+
const row = raw;
|
|
105
|
+
return {
|
|
106
|
+
hour: String(row.hour),
|
|
107
|
+
kind: row.kind,
|
|
108
|
+
name: String(row.name),
|
|
109
|
+
model: String(row.model),
|
|
110
|
+
provider: String(row.provider),
|
|
111
|
+
tenant: String(row.tenant),
|
|
112
|
+
runs: Number(row.runs),
|
|
113
|
+
errors: Number(row.errors),
|
|
114
|
+
cost: Number(row.cost),
|
|
115
|
+
inputTokens: Number(row.input_tokens),
|
|
116
|
+
outputTokens: Number(row.output_tokens),
|
|
117
|
+
latency: String(row.latency).replace(/[{}]/g, '').split(',').filter(Boolean).map(Number),
|
|
118
|
+
};
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
function bucketsOf(latency) {
|
|
123
|
+
return Array.from({ length: ROLLUP_LATENCY_BOUNDS_MS.length + 1 }, (_, index) => Math.round(latency[index] ?? 0));
|
|
124
|
+
}
|
|
125
|
+
function hourOf(iso) {
|
|
126
|
+
const date = new Date(iso);
|
|
127
|
+
date.setUTCMinutes(0, 0, 0);
|
|
128
|
+
return date.toISOString();
|
|
129
|
+
}
|