feat(reference): add indexed readers and station source
This commit is contained in:
@@ -9,13 +9,14 @@ import type {
|
||||
ReferenceType,
|
||||
} from './types';
|
||||
|
||||
export const SCHEMA_VERSION = 1;
|
||||
export const SCHEMA_VERSION = 3;
|
||||
|
||||
/**
|
||||
* The database is built from scratch and opened read-only by the serving side.
|
||||
* Foreign keys are deliberately explicit even for currently empty future tables.
|
||||
*/
|
||||
export function createReferenceSchema(db: Database): void {
|
||||
db.run(`PRAGMA user_version = ${SCHEMA_VERSION}`);
|
||||
db.run('PRAGMA foreign_keys = ON');
|
||||
db.run('PRAGMA journal_mode = DELETE');
|
||||
db.run('PRAGMA synchronous = FULL');
|
||||
@@ -56,6 +57,13 @@ export function createReferenceSchema(db: Database): void {
|
||||
radius REAL,
|
||||
base_price REAL
|
||||
);
|
||||
CREATE TABLE type_search (
|
||||
type_id INTEGER NOT NULL REFERENCES types(type_id),
|
||||
language TEXT NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
normalized_name TEXT NOT NULL,
|
||||
PRIMARY KEY (type_id, language)
|
||||
);
|
||||
CREATE TABLE market_groups (
|
||||
market_group_id INTEGER PRIMARY KEY,
|
||||
parent_group_id INTEGER REFERENCES market_groups(market_group_id),
|
||||
@@ -161,7 +169,8 @@ export function createReferenceSchema(db: Database): void {
|
||||
owner_id INTEGER,
|
||||
x REAL,
|
||||
y REAL,
|
||||
z REAL
|
||||
z REAL,
|
||||
source_verified INTEGER NOT NULL CHECK (source_verified IN (0, 1))
|
||||
);
|
||||
CREATE TABLE source_provenance (
|
||||
source TEXT PRIMARY KEY,
|
||||
@@ -176,6 +185,8 @@ export function createReferenceSchema(db: Database): void {
|
||||
CREATE INDEX types_group_idx ON types(group_id);
|
||||
CREATE INDEX types_category_idx ON types(category_id);
|
||||
CREATE INDEX types_name_idx ON types(name_json);
|
||||
CREATE INDEX type_search_normalized_name_idx ON type_search(normalized_name);
|
||||
CREATE VIRTUAL TABLE type_search_fts USING fts5(type_id UNINDEXED, language UNINDEXED, name, normalized_name);
|
||||
CREATE INDEX market_groups_parent_idx ON market_groups(parent_group_id);
|
||||
CREATE INDEX activities_blueprint_idx ON activities(blueprint_type_id);
|
||||
CREATE INDEX materials_type_idx ON materials(type_id);
|
||||
@@ -196,6 +207,7 @@ export function createReferenceSchema(db: Database): void {
|
||||
|
||||
const bool = (value: boolean | undefined): number | null => value === undefined ? null : value ? 1 : 0;
|
||||
const json = (value: unknown): string => JSON.stringify(value);
|
||||
const normalizedName = (value: string): string => value.normalize('NFKC').trim().replace(/\s+/g, ' ').toLocaleLowerCase('en-US');
|
||||
const keyId = (key: string, label: string): number => {
|
||||
const id = Number(key);
|
||||
if (!Number.isSafeInteger(id) || id < 0) throw new Error(`${label} key is not a safe integer: ${key}`);
|
||||
@@ -229,6 +241,9 @@ export function insertReferenceData(db: Database, data: ReferenceDataPayload): v
|
||||
type.run(id, row.group_id, row.category_id, json(row.name), row.description === undefined ? null : json(row.description), bool(row.published), bool(row.is_blueprint), bool(row.is_skill), bool(row.is_ore), bool(row.is_repackable), row.volume ?? null, row.packaged_volume ?? null, row.portion_size ?? null, row.mass ?? null, row.capacity ?? null, row.radius ?? null, row.base_price ?? null);
|
||||
}
|
||||
insertTypeMaps(db, data.types);
|
||||
const typeSearch = db.prepare('INSERT INTO type_search(type_id,language,name,normalized_name) VALUES (?,?,?,?)');
|
||||
const typeSearchFts = db.prepare('INSERT INTO type_search_fts(type_id,language,name,normalized_name) VALUES (?,?,?,?)');
|
||||
for (const row of Object.values(data.types)) for (const [language, name] of Object.entries(row.name)) { typeSearch.run(row.type_id, language, name, normalizedName(name)); typeSearchFts.run(row.type_id, language, name, normalizedName(name)); }
|
||||
const marketGroup = db.prepare('INSERT INTO market_groups(market_group_id,parent_group_id,name_json,description_json,icon_id,has_types) VALUES (?,?,?,?,?,?)');
|
||||
for (const [key, value] of Object.entries(data.market_groups ?? {})) {
|
||||
const row = value as Record<string, unknown>;
|
||||
@@ -279,8 +294,16 @@ export function insertUniverseData(db: Database, data: EsiUniversePayload): void
|
||||
}
|
||||
const gate = db.prepare('INSERT INTO gates(system_id,stargate_id,destination_system_id) VALUES (?,?,?)');
|
||||
for (const row of Object.values(data.gates ?? {})) gate.run(row.system_id, row.stargate_id, row.destination_system_id);
|
||||
const station = db.prepare('INSERT INTO stations(station_id,system_id,name,type_id,owner_id,x,y,z) VALUES (?,?,?,?,?,?,?,?)');
|
||||
for (const row of Object.values(data.systems)) for (const stationId of row.stations ?? []) station.run(stationId, row.system_id, null, null, null, null, null, null);
|
||||
const station = db.prepare('INSERT INTO stations(station_id,system_id,name,type_id,owner_id,x,y,z,source_verified) VALUES (?,?,?,?,?,?,?,?,?)');
|
||||
const stationIds = new Set<number>();
|
||||
for (const row of Object.values(data.stations ?? {})) {
|
||||
const position = row.position;
|
||||
station.run(row.station_id, row.system_id, row.name ?? null, row.type_id ?? null, row.owner_id ?? null, position?.x ?? null, position?.y ?? null, position?.z ?? null, 1);
|
||||
stationIds.add(row.station_id);
|
||||
}
|
||||
// ESI system responses expose station IDs but not station metadata. Preserve
|
||||
// those IDs as explicitly unknown rather than fabricating a name or type.
|
||||
for (const row of Object.values(data.systems)) for (const stationId of row.stations ?? []) if (!stationIds.has(stationId)) station.run(stationId, row.system_id, null, null, null, null, null, null, 0);
|
||||
}
|
||||
|
||||
export function insertHoboleaksData(db: Database, data: HoboleaksPayload): void {
|
||||
|
||||
Reference in New Issue
Block a user