Skip to content

Commit

Permalink
Refactorings and Bug Fixes
Browse files Browse the repository at this point in the history
No major new features; mostly tidying things a bit:
- Move all file-based stuff from PackageDB to PackageLoader
- Create PackageInfo and ResourceInfo types and use them as inputs and outputs of API functions
- Rename PackageDB functions
- Fix bug in function that finds resources
- Remove separate test script for PackageDB
- Update manual test script for PackageLoader
  • Loading branch information
cmoesel committed Apr 5, 2024
1 parent 5c757dc commit ed72ce4
Show file tree
Hide file tree
Showing 7 changed files with 318 additions and 275 deletions.
208 changes: 74 additions & 134 deletions src/PackageDB.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
import path from 'path';
import util from 'util';
import fs from 'fs-extra';
import { Database, Statement } from 'sql.js';
import { PackageStats } from './PackageStats';
import { InvalidPackageError } from './errors/InvalidPackageError';
import { InvalidResourceError } from './errors/InvalidResourceError';
import { PackageInfo } from './PackageInfo';
import { ResourceInfo } from './ResourceInfo';

const CREATE_PACKAGE_TABLE =
'CREATE TABLE package (rowId INTEGER PRIMARY KEY, name char, version char, packagePath char, packageJsonPath char);';
'CREATE TABLE package (rowId INTEGER PRIMARY KEY, name char, version char, packagePath char, packageJSONPath char);';
const INSERT_PACKAGE =
'INSERT INTO package (name, version, packagePath, packageJsonPath) VALUES (:name, :version, :packagePath, :packageJsonPath)';
'INSERT INTO package (name, version, packagePath, packageJSONPath) VALUES (:name, :version, :packagePath, :packageJSONPath)';
const RESOURCE_PROPERTIES = [
'resourceType',
'id',
Expand All @@ -22,15 +20,15 @@ const RESOURCE_PROPERTIES = [
'sdBaseDefinition',
'packageName',
'packageVersion',
'path'
'resourcePath'
];
const CREATE_RESOURCE_TABLE = `CREATE TABLE resource (rowId INTEGER PRIMARY KEY, ${RESOURCE_PROPERTIES.map(
p => `${p} char`
).join(', ')});`;
const INSERT_RESOURCE = `INSERT INTO resource (${RESOURCE_PROPERTIES.join(
', '
)}) VALUES (${RESOURCE_PROPERTIES.map(p => `:${p}`).join(', ')})`;
const FIND_PACKAGE = 'SELECT * FROM package WHERE name = :name and version = :version';
const FIND_PACKAGE = 'SELECT * FROM package WHERE name = :name and version = :version LIMIT 1';

export class PackageDB {
private insertPackageStmt: Statement;
Expand All @@ -51,152 +49,95 @@ export class PackageDB {
this.db.exec('VACUUM');
}

async registerPackageAtPath(
packagePath: string,
overrideName?: string,
overrideVersion?: string,
registerResources = true
) {
// Check that we have a valid package
const packageContentDir = path.join(packagePath, 'package');
try {
if (!(await fs.stat(packageContentDir)).isDirectory()) {
throw new Error(); // will be caught directly below
}
} catch (e) {
throw new InvalidPackageError(
packagePath,
`${packageContentDir} does not exist or is not a directory`
);
}

// Load the package.json file
const packageJSONPath = path.join(packageContentDir, 'package.json');
let packageJSON = null;
try {
packageJSON = await fs.readJSON(packageJSONPath);
} catch {
throw new InvalidPackageError(
packagePath,
`${packageJSONPath} does not exist or is not a valid JSON file`
);
savePackageInfo(info: PackageInfo) {
const binding: any = {
':name': info.name,
':version': info.version
};
if (info.packagePath) {
binding[':packagePath'] = info.packagePath;
}

// Get the name and version from the package.json file (or use overrides if applicable)
const name = overrideName ?? packageJSON.name;
if (name == null) {
throw new InvalidPackageError(packagePath, `${packageJSONPath} is missing the name property`);
}
const version = overrideVersion ?? packageJSON.version;
if (version == null) {
throw new InvalidPackageError(
packagePath,
`${packageJSONPath} is missing the version property`
);
if (info.packageJSONPath) {
binding[':packageJSONPath'] = info.packageJSONPath;
}

// Register the package information
this.registerPackageInfo(name, version, packagePath, packageJSONPath);

// Register the package's resources (if indicated)
if (registerResources) {
const files = await fs.readdir(packageContentDir);
await Promise.all(
files.map(async f => {
const filePath = path.join(packageContentDir, f);
if (/\.json$/i.test(filePath)) {
try {
await this.registerResourceAtPath(filePath, name, version);
} catch (e) {
// swallow this error because some JSON files will not be resources
}
}
})
);
}

return this.getPackageStats(name, version);
this.insertPackageStmt.run(binding);
}

registerPackageInfo(
packageName: string,
packageVersion: string,
packagePath?: string,
packageJSONPath?: string
) {
saveResourceInfo(info: ResourceInfo) {
const binding: any = {
':name': packageName,
':version': packageVersion
':resourceType': info.resourceType
};
if (packagePath) {
binding[':packagePath'] = packagePath;
if (info.id) {
binding[':id'] = info.id;
}
if (packageJSONPath) {
binding[':packageJsonPath'] = packageJSONPath;
if (info.url) {
binding[':url'] = info.url;
}
this.insertPackageStmt.run(binding);
}

async registerResourceAtPath(filePath: string, packageName?: string, packageVersion?: string) {
let resourceJSON: any;
try {
resourceJSON = await fs.readJSON(filePath);
} catch (e) {
throw new InvalidResourceError(filePath, 'invalid FHIR resource file');
if (info.name) {
binding[':name'] = info.name;
}
this.registerResourceInfo(resourceJSON, filePath, packageName, packageVersion);
}

registerResourceInfo(
resourceJSON: any,
resourcePath: string,
packageName?: string,
packageVersion?: string
) {
const { resourceType, id, url, name, version } = resourceJSON;
if (typeof resourceType !== 'string' || resourceType === '') {
throw new InvalidResourceError(resourcePath, 'resource does not specify its resourceType');
}
const preparedData: any = {
':resourceType': resourceType,
':id': typeof id === 'string' ? id : null,
':url': typeof url === 'string' ? url : null,
':name': typeof name === 'string' ? name : null,
':version': typeof version === 'string' ? version : null,
':sdKind': resourceType === 'StructureDefinition' ? resourceJSON.kind ?? null : null,
':sdDerivation':
resourceType === 'StructureDefinition' ? resourceJSON.derivation ?? null : null,
':sdType': resourceType === 'StructureDefinition' ? resourceJSON.type ?? null : null,
':sdBaseDefinition':
resourceType === 'StructureDefinition' ? resourceJSON.baseDefinition ?? null : null,
':packageName': packageName ?? null,
':packageVersion': packageVersion ?? null,
':path': path.resolve(resourcePath)
};
this.insertResourceStmt.run(preparedData);
}

findResources(key: string) {
const res = this.db.exec(
'SELECT * FROM resource WHERE id = :key OR name = :key OR url = :key',
{ ':key': key }
);
return res.length && res[0].values.length ? res[0].values[0] : [];
if (info.version) {
binding[':version'] = info.version;
}
if (info.sdKind) {
binding[':sdKind'] = info.sdKind;
}
if (info.sdDerivation) {
binding[':sdDerivation'] = info.sdDerivation;
}
if (info.sdType) {
binding[':sdType'] = info.sdType;
}
if (info.sdBaseDefinition) {
binding[':sdBaseDefinition'] = info.sdBaseDefinition;
}
if (info.packageName) {
binding[':packageName'] = info.packageName;
}
if (info.packageVersion) {
binding[':packageVersion'] = info.packageVersion;
}
if (info.resourcePath) {
binding[':resourcePath'] = info.resourcePath;
}
this.insertResourceStmt.run(binding);
}

findPackage(name: string, version: string) {
findPackageInfo(name: string, version: string): PackageInfo {
try {
this.findPackageStmt.bind({ ':name': name, ':version': version });
if (this.findPackageStmt.step()) {
return this.findPackageStmt.getAsObject();
return this.findPackageStmt.getAsObject() as PackageInfo;
}
} finally {
this.findPackageStmt.reset();
}
}

findResourceInfos(key: string): ResourceInfo[] {
// TODO: Upgrade to class-level property once query and parameters are sorted out
const stmt = this.db.prepare(
'SELECT * FROM resource WHERE id = :key OR name = :key OR url = :key'
);
stmt.bind({ ':key': key });
const results: ResourceInfo[] = [];
while (stmt.step()) {
results.push(stmt.getAsObject() as ResourceInfo);
}
stmt.free();
return results;
}

findResourceInfo(key: string): ResourceInfo {
// TODO: Make this more sophisticate if/when it makes sense
const results = this.findResourceInfos(key);
if (results.length > 0) {
return results[0];
}
}

getPackageStats(name: string, version: string): PackageStats {
const pkg = this.findPackage(name, version);
const pkg = this.findPackageInfo(name, version);
if (pkg == null) {
return;
}
Expand All @@ -207,7 +148,6 @@ export class PackageDB {
return {
name,
version,
packageJSON: (pkg.packageJsonPath as string) ?? null,
resourceCount: count
};
}
Expand Down
6 changes: 6 additions & 0 deletions src/PackageInfo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export type PackageInfo = {
name: string;
version: string;
packagePath?: string;
packageJSONPath?: string;
};
Loading

0 comments on commit ed72ce4

Please sign in to comment.