rename klo to ktx

This commit is contained in:
Andrey Avtomonov 2026-05-10 23:51:24 +02:00
parent 1a42152e6f
commit 3ce510b55b
704 changed files with 10205 additions and 10255 deletions

View file

@ -6,17 +6,17 @@ import { join } from 'node:path';
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
import {
createSqliteLiveDatabaseIntrospection,
isKloSqliteConnectionConfig,
KloSqliteScanConnector,
isKtxSqliteConnectionConfig,
KtxSqliteScanConnector,
sqliteDatabasePathFromConfig,
} from './index.js';
describe('KloSqliteScanConnector', () => {
describe('KtxSqliteScanConnector', () => {
let tempDir: string;
let dbPath: string;
beforeEach(async () => {
tempDir = await mkdtemp(join(tmpdir(), 'klo-connector-sqlite-'));
tempDir = await mkdtemp(join(tmpdir(), 'ktx-connector-sqlite-'));
dbPath = join(tempDir, 'warehouse.db');
const db = new Database(dbPath);
db.exec(`
@ -47,14 +47,14 @@ describe('KloSqliteScanConnector', () => {
});
it('resolves SQLite path configuration safely', () => {
const originalDatabaseUrl = process.env.KLO_SQLITE_TEST_URL;
const originalDatabaseUrl = process.env.KTX_SQLITE_TEST_URL;
const pointerPath = join(tempDir, 'sqlite-path.txt');
process.env.KLO_SQLITE_TEST_URL = `sqlite:${dbPath}`;
process.env.KTX_SQLITE_TEST_URL = `sqlite:${dbPath}`;
writeFileSync(pointerPath, dbPath, 'utf-8');
try {
expect(isKloSqliteConnectionConfig({ driver: 'sqlite', path: 'warehouse.db', readonly: true })).toBe(true);
expect(isKloSqliteConnectionConfig({ driver: 'postgres', url: 'env:DATABASE_URL', readonly: true })).toBe(
expect(isKtxSqliteConnectionConfig({ driver: 'sqlite', path: 'warehouse.db', readonly: true })).toBe(true);
expect(isKtxSqliteConnectionConfig({ driver: 'postgres', url: 'env:DATABASE_URL', readonly: true })).toBe(
false,
);
expect(
@ -68,7 +68,7 @@ describe('KloSqliteScanConnector', () => {
sqliteDatabasePathFromConfig({
connectionId: 'warehouse',
projectDir: tempDir,
connection: { driver: 'sqlite', url: 'env:KLO_SQLITE_TEST_URL', readonly: true },
connection: { driver: 'sqlite', url: 'env:KTX_SQLITE_TEST_URL', readonly: true },
}),
).toBe(dbPath);
expect(
@ -94,15 +94,15 @@ describe('KloSqliteScanConnector', () => {
).toThrow('Native SQLite connector requires connections.warehouse.readonly: true');
} finally {
if (originalDatabaseUrl === undefined) {
delete process.env.KLO_SQLITE_TEST_URL;
delete process.env.KTX_SQLITE_TEST_URL;
} else {
process.env.KLO_SQLITE_TEST_URL = originalDatabaseUrl;
process.env.KTX_SQLITE_TEST_URL = originalDatabaseUrl;
}
}
});
it('introspects schema, primary keys, row counts, views, and foreign keys', async () => {
const connector = new KloSqliteScanConnector({
const connector = new KtxSqliteScanConnector({
connectionId: 'warehouse',
connection: { driver: 'sqlite', path: dbPath, readonly: true },
now: () => new Date('2026-04-29T10:00:00.000Z'),
@ -149,7 +149,7 @@ describe('KloSqliteScanConnector', () => {
});
it('runs samples, distinct values, statistics, and read-only SQL', async () => {
const connector = new KloSqliteScanConnector({
const connector = new KtxSqliteScanConnector({
connectionId: 'warehouse',
connection: { driver: 'sqlite', path: dbPath, readonly: true },
});

View file

@ -3,28 +3,28 @@ import { existsSync, readFileSync, statSync } from 'node:fs';
import { homedir } from 'node:os';
import { isAbsolute, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
import { assertReadOnlySql, limitSqlForExecution, normalizeQueryRows } from '@klo/context/connections';
import { assertReadOnlySql, limitSqlForExecution, normalizeQueryRows } from '@ktx/context/connections';
import {
createKloConnectorCapabilities,
type KloColumnSampleInput,
type KloColumnSampleResult,
type KloColumnStatsInput,
type KloColumnStatsResult,
type KloQueryResult,
type KloReadOnlyQueryInput,
type KloScanConnector,
type KloScanContext,
type KloScanInput,
type KloSchemaForeignKey,
type KloSchemaSnapshot,
type KloSchemaTable,
type KloTableRef,
type KloTableSampleInput,
type KloTableSampleResult,
} from '@klo/context/scan';
import { KloSqliteDialect } from './dialect.js';
createKtxConnectorCapabilities,
type KtxColumnSampleInput,
type KtxColumnSampleResult,
type KtxColumnStatsInput,
type KtxColumnStatsResult,
type KtxQueryResult,
type KtxReadOnlyQueryInput,
type KtxScanConnector,
type KtxScanContext,
type KtxScanInput,
type KtxSchemaForeignKey,
type KtxSchemaSnapshot,
type KtxSchemaTable,
type KtxTableRef,
type KtxTableSampleInput,
type KtxTableSampleResult,
} from '@ktx/context/scan';
import { KtxSqliteDialect } from './dialect.js';
export interface KloSqliteConnectionConfig {
export interface KtxSqliteConnectionConfig {
driver?: string;
path?: string;
url?: string;
@ -36,24 +36,24 @@ export interface KloSqliteConnectionConfig {
export interface SqliteDatabasePathInput {
connectionId: string;
projectDir?: string;
connection: KloSqliteConnectionConfig | undefined;
connection: KtxSqliteConnectionConfig | undefined;
}
export interface KloSqliteScanConnectorOptions extends SqliteDatabasePathInput {
export interface KtxSqliteScanConnectorOptions extends SqliteDatabasePathInput {
now?: () => Date;
}
export interface KloSqliteReadOnlyQueryInput extends KloReadOnlyQueryInput {
export interface KtxSqliteReadOnlyQueryInput extends KtxReadOnlyQueryInput {
params?: Record<string, unknown> | unknown[];
}
export interface KloSqliteColumnDistinctValuesOptions {
export interface KtxSqliteColumnDistinctValuesOptions {
maxCardinality: number;
limit: number;
sampleSize?: number;
}
export interface KloSqliteColumnDistinctValuesResult {
export interface KtxSqliteColumnDistinctValuesResult {
values: string[] | null;
cardinality: number;
}
@ -81,14 +81,14 @@ interface SqliteForeignKeyRow {
}
function stringConfigValue(
connection: KloSqliteConnectionConfig | undefined,
key: keyof KloSqliteConnectionConfig,
connection: KtxSqliteConnectionConfig | undefined,
key: keyof KtxSqliteConnectionConfig,
): string | undefined {
const value = connection?.[key];
return typeof value === 'string' && value.trim().length > 0 ? resolveStringReference(key, value.trim()) : undefined;
}
function resolveStringReference(key: keyof KloSqliteConnectionConfig, value: string): string {
function resolveStringReference(key: keyof KtxSqliteConnectionConfig, value: string): string {
if (value.startsWith('env:')) {
return process.env[value.slice('env:'.length)] ?? '';
}
@ -135,13 +135,13 @@ function stripLeadingSqlComments(sql: string): string {
return sql.slice(index);
}
export function isKloSqliteConnectionConfig(connection: KloSqliteConnectionConfig | undefined): boolean {
export function isKtxSqliteConnectionConfig(connection: KtxSqliteConnectionConfig | undefined): boolean {
const driver = String(connection?.driver ?? '').toLowerCase();
return driver === 'sqlite' || driver === 'sqlite3';
}
export function sqliteDatabasePathFromConfig(input: SqliteDatabasePathInput): string {
if (!isKloSqliteConnectionConfig(input.connection)) {
if (!isKtxSqliteConnectionConfig(input.connection)) {
throw new Error(`Native SQLite connector cannot run driver "${input.connection?.driver ?? 'unknown'}"`);
}
if (input.connection?.readonly !== true) {
@ -157,10 +157,10 @@ export function sqliteDatabasePathFromConfig(input: SqliteDatabasePathInput): st
return isAbsolute(configuredPath) ? configuredPath : resolve(input.projectDir ?? process.cwd(), configuredPath);
}
export class KloSqliteScanConnector implements KloScanConnector {
export class KtxSqliteScanConnector implements KtxScanConnector {
readonly id: string;
readonly driver = 'sqlite' as const;
readonly capabilities = createKloConnectorCapabilities({
readonly capabilities = createKtxConnectorCapabilities({
tableSampling: true,
columnSampling: true,
columnStats: false,
@ -173,10 +173,10 @@ export class KloSqliteScanConnector implements KloScanConnector {
private readonly connectionId: string;
private readonly dbPath: string;
private readonly now: () => Date;
private readonly dialect = new KloSqliteDialect();
private readonly dialect = new KtxSqliteDialect();
private db: Database.Database | null = null;
constructor(options: KloSqliteScanConnectorOptions) {
constructor(options: KtxSqliteScanConnectorOptions) {
this.connectionId = options.connectionId;
this.dbPath = sqliteDatabasePathFromConfig(options);
this.now = options.now ?? (() => new Date());
@ -195,7 +195,7 @@ export class KloSqliteScanConnector implements KloScanConnector {
}
}
async introspect(input: KloScanInput, _ctx: KloScanContext): Promise<KloSchemaSnapshot> {
async introspect(input: KtxScanInput, _ctx: KtxScanContext): Promise<KtxSchemaSnapshot> {
this.assertConnection(input.connectionId);
const database = this.database();
const rawTables = database
@ -220,13 +220,13 @@ export class KloSqliteScanConnector implements KloScanConnector {
};
}
async sampleTable(input: KloTableSampleInput, _ctx: KloScanContext): Promise<KloTableSampleResult> {
async sampleTable(input: KtxTableSampleInput, _ctx: KtxScanContext): Promise<KtxTableSampleResult> {
this.assertConnection(input.connectionId);
const result = this.query(this.dialect.generateSampleQuery(this.qTableName(input.table), input.limit, input.columns));
return { headers: result.headers, rows: result.rows, totalRows: result.totalRows };
}
async sampleColumn(input: KloColumnSampleInput, _ctx: KloScanContext): Promise<KloColumnSampleResult> {
async sampleColumn(input: KtxColumnSampleInput, _ctx: KtxScanContext): Promise<KtxColumnSampleResult> {
this.assertConnection(input.connectionId);
const result = this.query(
this.dialect.generateColumnSampleQuery(this.qTableName(input.table), input.column, input.limit),
@ -235,21 +235,21 @@ export class KloSqliteScanConnector implements KloScanConnector {
return { values, nullCount: null, distinctCount: null };
}
async columnStats(_input: KloColumnStatsInput, _ctx: KloScanContext): Promise<KloColumnStatsResult | null> {
async columnStats(_input: KtxColumnStatsInput, _ctx: KtxScanContext): Promise<KtxColumnStatsResult | null> {
return null;
}
async executeReadOnly(input: KloSqliteReadOnlyQueryInput, _ctx: KloScanContext): Promise<KloQueryResult> {
async executeReadOnly(input: KtxSqliteReadOnlyQueryInput, _ctx: KtxScanContext): Promise<KtxQueryResult> {
this.assertConnection(input.connectionId);
const result = this.query(limitSqlForExecution(stripLeadingSqlComments(input.sql), input.maxRows), input.params);
return { ...result, rowCount: result.rows.length };
}
async getColumnDistinctValues(
table: KloTableRef,
table: KtxTableRef,
columnName: string,
options: KloSqliteColumnDistinctValuesOptions,
): Promise<KloSqliteColumnDistinctValuesResult | null> {
options: KtxSqliteColumnDistinctValuesOptions,
): Promise<KtxSqliteColumnDistinctValuesResult | null> {
const sampleSize = options.sampleSize ?? 10000;
const tableName = this.qTableName(table);
const quotedColumn = this.dialect.quoteIdentifier(columnName);
@ -281,7 +281,7 @@ export class KloSqliteScanConnector implements KloScanConnector {
return Number(result.rows[0]?.[0] ?? 0);
}
qTableName(table: Pick<KloTableRef, 'name'>): string {
qTableName(table: Pick<KtxTableRef, 'name'>): string {
return this.dialect.formatTableName(table);
}
@ -303,7 +303,7 @@ export class KloSqliteScanConnector implements KloScanConnector {
return this.db;
}
private query(sql: string, params?: Record<string, unknown> | unknown[]): Omit<KloQueryResult, 'rowCount'> {
private query(sql: string, params?: Record<string, unknown> | unknown[]): Omit<KtxQueryResult, 'rowCount'> {
const statement = this.database().prepare(assertReadOnlySql(sql));
const rows = (params ? statement.all(params) : statement.all()) as unknown[];
return {
@ -313,7 +313,7 @@ export class KloSqliteScanConnector implements KloScanConnector {
};
}
private readTable(database: Database.Database, table: SqliteMasterRow): KloSchemaTable {
private readTable(database: Database.Database, table: SqliteMasterRow): KtxSchemaTable {
const columns = database
.prepare(`PRAGMA table_info(${this.dialect.quoteIdentifier(table.name)})`)
.all() as SqliteTableInfoRow[];
@ -350,7 +350,7 @@ export class KloSqliteScanConnector implements KloScanConnector {
};
}
private mapForeignKeys(rows: SqliteForeignKeyRow[]): KloSchemaForeignKey[] {
private mapForeignKeys(rows: SqliteForeignKeyRow[]): KtxSchemaForeignKey[] {
return rows
.sort((a, b) => a.id - b.id || a.seq - b.seq)
.map((row) => ({
@ -365,7 +365,7 @@ export class KloSqliteScanConnector implements KloScanConnector {
private assertConnection(connectionId: string): void {
if (connectionId !== this.connectionId) {
throw new Error(`KLO SQLite connector ${this.id} cannot serve connection ${connectionId}`);
throw new Error(`KTX SQLite connector ${this.id} cannot serve connection ${connectionId}`);
}
}
}

View file

@ -1,8 +1,8 @@
import { describe, expect, it } from 'vitest';
import { KloSqliteDialect } from './dialect.js';
import { KtxSqliteDialect } from './dialect.js';
describe('KloSqliteDialect', () => {
const dialect = new KloSqliteDialect();
describe('KtxSqliteDialect', () => {
const dialect = new KtxSqliteDialect();
it('quotes identifiers and formats single-file SQLite table names', () => {
expect(dialect.quoteIdentifier('orders')).toBe('"orders"');
@ -10,7 +10,7 @@ describe('KloSqliteDialect', () => {
expect(dialect.formatTableName({ catalog: 'ignored', db: 'ignored', name: 'orders' })).toBe('"orders"');
});
it('maps native SQLite types to KLO dimension types', () => {
it('maps native SQLite types to KTX dimension types', () => {
expect(dialect.mapToDimensionType('INTEGER')).toBe('number');
expect(dialect.mapToDimensionType('numeric(10,2)')).toBe('number');
expect(dialect.mapToDimensionType('timestamp')).toBe('time');

View file

@ -1,11 +1,11 @@
import type { KloSchemaDimensionType, KloTableRef } from '@klo/context/scan';
import type { KtxSchemaDimensionType, KtxTableRef } from '@ktx/context/scan';
type SqliteTableNameRef = Pick<KloTableRef, 'name'> & Partial<Pick<KloTableRef, 'catalog' | 'db'>>;
type SqliteTableNameRef = Pick<KtxTableRef, 'name'> & Partial<Pick<KtxTableRef, 'catalog' | 'db'>>;
export class KloSqliteDialect {
export class KtxSqliteDialect {
readonly type = 'sqlite';
private readonly typeMappings: Record<string, KloSchemaDimensionType> = {
private readonly typeMappings: Record<string, KtxSchemaDimensionType> = {
DATETIME: 'time',
DATE: 'time',
TIMESTAMP: 'time',
@ -36,7 +36,7 @@ export class KloSqliteDialect {
return nativeType;
}
mapToDimensionType(nativeType: string): KloSchemaDimensionType {
mapToDimensionType(nativeType: string): KtxSchemaDimensionType {
if (!nativeType) {
return 'string';
}

View file

@ -1,13 +1,13 @@
export { KloSqliteDialect } from './dialect.js';
export { KtxSqliteDialect } from './dialect.js';
export {
isKloSqliteConnectionConfig,
KloSqliteScanConnector,
isKtxSqliteConnectionConfig,
KtxSqliteScanConnector,
sqliteDatabasePathFromConfig,
type KloSqliteColumnDistinctValuesOptions,
type KloSqliteColumnDistinctValuesResult,
type KloSqliteConnectionConfig,
type KloSqliteReadOnlyQueryInput,
type KloSqliteScanConnectorOptions,
type KtxSqliteColumnDistinctValuesOptions,
type KtxSqliteColumnDistinctValuesResult,
type KtxSqliteConnectionConfig,
type KtxSqliteReadOnlyQueryInput,
type KtxSqliteScanConnectorOptions,
type SqliteDatabasePathInput,
} from './connector.js';
export {

View file

@ -1,10 +1,10 @@
import type { LiveDatabaseIntrospectionPort } from '@klo/context/ingest';
import type { KloProjectConnectionConfig } from '@klo/context/project';
import { KloSqliteScanConnector, type KloSqliteConnectionConfig } from './connector.js';
import type { LiveDatabaseIntrospectionPort } from '@ktx/context/ingest';
import type { KtxProjectConnectionConfig } from '@ktx/context/project';
import { KtxSqliteScanConnector, type KtxSqliteConnectionConfig } from './connector.js';
export interface CreateSqliteLiveDatabaseIntrospectionOptions {
projectDir?: string;
connections: Record<string, KloProjectConnectionConfig>;
connections: Record<string, KtxProjectConnectionConfig>;
now?: () => Date;
}
@ -13,8 +13,8 @@ export function createSqliteLiveDatabaseIntrospection(
): LiveDatabaseIntrospectionPort {
return {
async extractSchema(connectionId: string) {
const connection = options.connections[connectionId] as KloSqliteConnectionConfig | undefined;
const connector = new KloSqliteScanConnector({
const connection = options.connections[connectionId] as KtxSqliteConnectionConfig | undefined;
const connector = new KtxSqliteScanConnector({
connectionId,
connection,
projectDir: options.projectDir,

View file

@ -1,13 +1,13 @@
import { describe, expect, it } from 'vitest';
describe('@klo/connector-sqlite package exports', () => {
describe('@ktx/connector-sqlite package exports', () => {
it('exports the native SQLite scan connector surface', async () => {
const connector = await import('./index.js');
expect(connector.KloSqliteDialect).toBeTypeOf('function');
expect(connector.KloSqliteScanConnector).toBeTypeOf('function');
expect(connector.KtxSqliteDialect).toBeTypeOf('function');
expect(connector.KtxSqliteScanConnector).toBeTypeOf('function');
expect(connector.createSqliteLiveDatabaseIntrospection).toBeTypeOf('function');
expect(connector.isKloSqliteConnectionConfig).toBeTypeOf('function');
expect(connector.isKtxSqliteConnectionConfig).toBeTypeOf('function');
expect(connector.sqliteDatabasePathFromConfig).toBeTypeOf('function');
});
});