2
0
mirror of https://github.com/frappe/books.git synced 2024-12-22 19:09:01 +00:00

chore: fix types

This commit is contained in:
18alantom 2022-03-25 15:42:39 +05:30
parent 7514c95883
commit 74bfe7931c
9 changed files with 149 additions and 119 deletions

View File

@ -1,21 +1,23 @@
export const sqliteTypeMap = {
import { KnexColumnType } from './database/types';
export const sqliteTypeMap: Record<string, KnexColumnType> = {
AutoComplete: 'text',
Currency: 'text',
Int: 'integer',
Float: 'float',
Percent: 'float',
Check: 'integer',
Check: 'boolean',
Code: 'text',
Date: 'text',
Datetime: 'text',
Time: 'text',
Date: 'date',
Datetime: 'datetime',
Time: 'time',
Text: 'text',
Data: 'text',
Link: 'text',
DynamicLink: 'text',
Password: 'text',
Select: 'text',
File: 'text',
File: 'binary',
Attach: 'text',
AttachImage: 'text',
Color: 'text',

View File

@ -6,14 +6,14 @@ import {
DuplicateEntryError,
LinkValidationError,
NotFoundError,
ValueError,
ValueError
} from '../../frappe/utils/errors';
import {
Field,
FieldTypeEnum,
RawValue,
SchemaMap,
TargetField,
TargetField
} from '../../schemas/types';
import { sqliteTypeMap } from '../common';
import {
@ -21,11 +21,11 @@ import {
FieldValueMap,
GetAllOptions,
GetQueryBuilderOptions,
QueryFilter,
QueryFilter
} from './types';
export default class DatabaseCore {
knex: Knex;
knex?: Knex;
typeMap = sqliteTypeMap;
dbPath: string;
schemaMap: SchemaMap = {};
@ -39,7 +39,7 @@ export default class DatabaseCore {
filename: this.dbPath,
},
pool: {
afterCreate(conn, done) {
afterCreate(conn: { run: (query: string) => void }, done: () => void) {
conn.run('PRAGMA foreign_keys=ON');
done();
},
@ -61,21 +61,22 @@ export default class DatabaseCore {
}
close() {
this.knex.destroy();
this.knex!.destroy();
}
async commit() {
try {
await this.raw('commit');
} catch (err) {
if (err.type !== CannotCommitError) {
const type = this.#getError(err as Error);
if (type !== CannotCommitError) {
throw err;
}
}
}
async raw(query: string, params: Knex.RawBinding[] = []) {
return await this.knex.raw(query, params);
return await this.knex!.raw(query, params);
}
async migrate() {
@ -104,7 +105,7 @@ export default class DatabaseCore {
let row = [];
try {
const qb = this.knex(schemaName);
const qb = this.knex!(schemaName);
if (name !== undefined) {
qb.where({ name });
}
@ -197,7 +198,7 @@ export default class DatabaseCore {
groupBy,
orderBy = 'creation',
order = 'desc',
}: GetAllOptions = {}): Promise<FieldValueMap[]> {
}: GetAllOptions): Promise<FieldValueMap[]> {
const schema = this.schemaMap[schemaName];
if (!fields) {
fields = ['name', ...(schema.keywordFields ?? [])];
@ -207,7 +208,7 @@ export default class DatabaseCore {
fields = [fields];
}
return (await this.#getQueryBuilder(schemaName, fields, filters, {
return (await this.#getQueryBuilder(schemaName, fields, filters ?? {}, {
offset: start,
limit,
groupBy,
@ -226,7 +227,7 @@ export default class DatabaseCore {
return fieldname;
});
let builder = this.knex('SingleValue');
let builder = this.knex!('SingleValue');
builder = builder.where(fieldnames[0]);
fieldnames.slice(1).forEach(({ fieldname, parent }) => {
@ -252,7 +253,7 @@ export default class DatabaseCore {
}
async rename(schemaName: string, oldName: string, newName: string) {
await this.knex(schemaName)
await this.knex!(schemaName)
.update({ name: newName })
.where('name', oldName);
await this.commit();
@ -282,15 +283,15 @@ export default class DatabaseCore {
}
async #tableExists(schemaName: string) {
return await this.knex.schema.hasTable(schemaName);
return await this.knex!.schema.hasTable(schemaName);
}
async #singleExists(singleSchemaName: string) {
const res = await this.knex('SingleValue')
const res = await this.knex!('SingleValue')
.count('parent as count')
.where('parent', singleSchemaName)
.first();
return res.count > 0;
return (res?.count ?? 0) > 0;
}
async #removeColumns(schemaName: string, targetColumns: string[]) {
@ -298,7 +299,7 @@ export default class DatabaseCore {
}
async #deleteSingleValues(singleSchemaName: string) {
return await this.knex('SingleValue')
return await this.knex!('SingleValue')
.where('parent', singleSchemaName)
.delete();
}
@ -325,9 +326,9 @@ export default class DatabaseCore {
// Alter table hacx for sqlite in case of schema change.
const tempName = `__${schemaName}`;
await this.knex.schema.dropTableIfExists(tempName);
await this.knex!.schema.dropTableIfExists(tempName);
await this.knex.raw('PRAGMA foreign_keys=OFF');
await this.knex!.raw('PRAGMA foreign_keys=OFF');
await this.#createTable(schemaName, tempName);
if (tableRows.length > 200) {
@ -337,27 +338,29 @@ export default class DatabaseCore {
if (rowSlice.length === 0) {
break;
}
await this.knex.batchInsert(tempName, rowSlice);
await this.knex!.batchInsert(tempName, rowSlice);
}
} else {
await this.knex.batchInsert(tempName, tableRows);
await this.knex!.batchInsert(tempName, tableRows);
}
await this.knex.schema.dropTable(schemaName);
await this.knex.schema.renameTable(tempName, schemaName);
await this.knex.raw('PRAGMA foreign_keys=ON');
await this.knex!.schema.dropTable(schemaName);
await this.knex!.schema.renameTable(tempName, schemaName);
await this.knex!.raw('PRAGMA foreign_keys=ON');
}
async #getTableColumns(schemaName: string): Promise<string[]> {
const info = await this.raw(`PRAGMA table_info(${schemaName})`);
return info.map((d) => d.name);
const info: FieldValueMap[] = await this.raw(
`PRAGMA table_info(${schemaName})`
);
return info.map((d) => d.name as string);
}
async #getForeignKeys(schemaName: string): Promise<string[]> {
const foreignKeyList = await this.raw(
const foreignKeyList: FieldValueMap[] = await this.raw(
`PRAGMA foreign_key_list(${schemaName})`
);
return foreignKeyList.map((d) => d.from);
return foreignKeyList.map((d) => d.from as string);
}
#getQueryBuilder(
@ -366,7 +369,7 @@ export default class DatabaseCore {
filters: QueryFilter,
options: GetQueryBuilderOptions
): Knex.QueryBuilder {
const builder = this.knex.select(fields).from(schemaName);
const builder = this.knex!.select(fields).from(schemaName);
this.#applyFiltersToBuilder(builder, filters);
@ -432,9 +435,9 @@ export default class DatabaseCore {
filtersArray.map((filter) => {
const [field, operator, comparisonValue] = filter;
if (operator === '=') {
builder.where(field, comparisonValue);
builder.where(field as string, comparisonValue);
} else {
builder.where(field, operator, comparisonValue);
builder.where(field as string, operator as string, comparisonValue);
}
});
}
@ -445,10 +448,8 @@ export default class DatabaseCore {
const diff: ColumnDiff = { added: [], removed: [] };
for (const field of validFields) {
if (
!tableColumns.includes(field.fieldname) &&
this.typeMap[field.fieldtype]
) {
const hasDbType = this.typeMap.hasOwnProperty(field.fieldtype);
if (!tableColumns.includes(field.fieldname) && hasDbType) {
diff.added.push(field);
}
}
@ -463,7 +464,7 @@ export default class DatabaseCore {
return diff;
}
async #getNewForeignKeys(schemaName): Promise<Field[]> {
async #getNewForeignKeys(schemaName: string): Promise<Field[]> {
const foreignKeys = await this.#getForeignKeys(schemaName);
const newForeignKeys: Field[] = [];
const schema = this.schemaMap[schemaName];
@ -490,7 +491,9 @@ export default class DatabaseCore {
return;
}
const column = table[columnType](field.fieldname);
const column = table[columnType](
field.fieldname
) as Knex.SqlLiteColumnBuilder;
// primary key
if (field.fieldname === 'name') {
@ -525,23 +528,21 @@ export default class DatabaseCore {
const diff: ColumnDiff = await this.#getColumnDiff(schemaName);
const newForeignKeys: Field[] = await this.#getNewForeignKeys(schemaName);
return this.knex.schema
.table(schemaName, (table) => {
if (diff.added.length) {
for (const field of diff.added) {
this.#buildColumnForTable(table, field);
}
return this.knex!.schema.table(schemaName, (table) => {
if (diff.added.length) {
for (const field of diff.added) {
this.#buildColumnForTable(table, field);
}
}
if (diff.removed.length) {
this.#removeColumns(schemaName, diff.removed);
}
})
.then(() => {
if (newForeignKeys.length) {
return this.#addForeignKeys(schemaName, newForeignKeys);
}
});
if (diff.removed.length) {
this.#removeColumns(schemaName, diff.removed);
}
}).then(() => {
if (newForeignKeys.length) {
return this.#addForeignKeys(schemaName, newForeignKeys);
}
});
}
async #createTable(schemaName: string, tableName?: string) {
@ -551,7 +552,7 @@ export default class DatabaseCore {
}
#runCreateTableQuery(schemaName: string, fields: Field[]) {
return this.knex.schema.createTable(schemaName, (table) => {
return this.knex!.schema.createTable(schemaName, (table) => {
for (const field of fields) {
this.#buildColumnForTable(table, field);
}
@ -560,7 +561,7 @@ export default class DatabaseCore {
async #getNonExtantSingleValues(singleSchemaName: string) {
const existingFields = (
await this.knex('SingleValue')
await this.knex!('SingleValue')
.where({ parent: singleSchemaName })
.select('fieldname')
).map(({ fieldname }) => fieldname);
@ -577,11 +578,11 @@ export default class DatabaseCore {
}
async #deleteOne(schemaName: string, name: string) {
return this.knex(schemaName).where('name', name).delete();
return this.knex!(schemaName).where('name', name).delete();
}
#deleteChildren(schemaName: string, parentName: string) {
return this.knex(schemaName).where('parent', parentName).delete();
return this.knex!(schemaName).where('parent', parentName).delete();
}
#runDeleteOtherChildren(
@ -590,7 +591,7 @@ export default class DatabaseCore {
added: string[]
) {
// delete other children
return this.knex(field.target)
return this.knex!(field.target)
.where('parent', parentName)
.andWhere('name', 'not in', added)
.delete();
@ -623,21 +624,21 @@ export default class DatabaseCore {
try {
// copy from old to new table
await this.knex(tempName).insert(this.knex.select().from(schemaName));
await this.knex!(tempName).insert(this.knex!.select().from(schemaName));
} catch (err) {
await this.raw('ROLLBACK');
await this.raw('PRAGMA foreign_keys=ON');
const rows = await this.knex.select().from(schemaName);
const rows = await this.knex!.select().from(schemaName);
await this.prestigeTheTable(schemaName, rows);
return;
}
// drop old table
await this.knex.schema.dropTable(schemaName);
await this.knex!.schema.dropTable(schemaName);
// rename new table
await this.knex.schema.renameTable(tempName, schemaName);
await this.knex!.schema.renameTable(tempName, schemaName);
await this.raw('COMMIT');
await this.raw('PRAGMA foreign_keys=ON');
@ -663,8 +664,7 @@ export default class DatabaseCore {
name: string,
fields: string | string[] = '*'
) {
const fieldValueMap: FieldValueMap = await this.knex
.select(fields)
const fieldValueMap: FieldValueMap = await this.knex!.select(fields)
.from(schemaName)
.where('name', name)
.first();
@ -694,7 +694,7 @@ export default class DatabaseCore {
fieldValueMap.name = getRandomString();
}
const validMap = {};
const validMap: FieldValueMap = {};
for (const { fieldname, fieldtype } of fields) {
if (fieldtype === FieldTypeEnum.Table) {
continue;
@ -703,7 +703,7 @@ export default class DatabaseCore {
validMap[fieldname] = fieldValueMap[fieldname];
}
return this.knex(schemaName).insert(fieldValueMap);
return this.knex!(schemaName).insert(fieldValueMap);
}
async #updateSingleValues(
@ -728,7 +728,7 @@ export default class DatabaseCore {
fieldname: string,
value: RawValue
) {
return await this.knex('SingleValue')
return await this.knex!('SingleValue')
.where({
parent: singleSchemaName,
fieldname,
@ -758,7 +758,7 @@ export default class DatabaseCore {
}
return acc;
}, {});
}, {} as FieldValueMap);
await this.#updateSingleValues(schemaName, defaultValues);
}
@ -767,7 +767,7 @@ export default class DatabaseCore {
async #updateNonExtantSingleValues(schemaName: string) {
const singleValues = await this.#getNonExtantSingleValues(schemaName);
for (const sv of singleValues) {
await this.#updateSingleValue(schemaName, sv.fieldname, sv.value);
await this.#updateSingleValue(schemaName, sv.fieldname, sv.value!);
}
}
@ -775,7 +775,7 @@ export default class DatabaseCore {
const updateMap = { ...fieldValueMap };
delete updateMap.name;
return await this.knex(schemaName)
return await this.knex!(schemaName)
.where('name', fieldValueMap.name as string)
.update(updateMap);
}

View File

@ -6,7 +6,7 @@ import { runPatches } from './runPatch';
import { Patch } from './types';
export class DatabaseManager {
db: DatabaseCore;
db?: DatabaseCore;
constructor() {}
async createNewDatabase(dbPath: string, countryCode: string) {
@ -26,6 +26,10 @@ export class DatabaseManager {
}
async migrate() {
if (this.db === undefined) {
return;
}
const patchesToExecute = await this.#getPatchesToExecute();
const preMigrationPatches = patchesToExecute.filter(
(p) => p.patch.beforeMigrate
@ -40,21 +44,26 @@ export class DatabaseManager {
}
async #getPatchesToExecute(): Promise<Patch[]> {
const query: { name: string }[] = await this.db
.knex('PatchRun')
.select('name');
if (this.db === undefined) {
return [];
}
const query: { name: string }[] = await this.db.knex!('PatchRun').select(
'name'
);
const executedPatches = query.map((q) => q.name);
return patches.filter((p) => !executedPatches.includes(p.name));
}
async #getCountryCode(): Promise<string | undefined> {
if (!this.db) {
if (this.db === undefined) {
return undefined;
}
const query = await this.db
.knex('SingleValue')
.where({ fieldname: 'countryCode', parent: 'SystemSettings' });
const query = await this.db.knex!('SingleValue').where({
fieldname: 'countryCode',
parent: 'SystemSettings',
});
if (query.length > 0) {
return query[0].countryCode as string;
@ -67,7 +76,7 @@ export class DatabaseManager {
try {
fs.unlink(dbPath);
} catch (err) {
if (err.code === 'ENOENT') {
if ((err as NodeJS.ErrnoException).code === 'ENOENT') {
return;
}

View File

@ -27,5 +27,5 @@ async function runPatch(patch: Patch, dm: DatabaseManager): Promise<boolean> {
async function makeEntry(patchName: string, dm: DatabaseManager) {
const defaultFieldValueMap = getDefaultMetaFieldValueMap() as FieldValueMap;
defaultFieldValueMap.name = patchName;
await dm.db.insert('PatchRun', defaultFieldValueMap);
await dm.db!.insert('PatchRun', defaultFieldValueMap);
}

View File

@ -1,19 +1,20 @@
import { Field, RawValue } from '../../schemas/types';
import { DatabaseManager } from './manager';
export type QueryFilter = Record<string, string | string[]>;
export interface GetQueryBuilderOptions {
offset: number;
limit: number;
groupBy: string;
orderBy: string;
order: 'desc' | 'asc';
offset?: number;
limit?: number;
groupBy?: string;
orderBy?: string;
order?: 'desc' | 'asc';
}
export interface GetAllOptions {
schemaName?: string;
schemaName: string;
fields?: string[];
filters?: Record<string, string>;
filters?: QueryFilter;
start?: number;
limit?: number;
groupBy?: string;
@ -31,7 +32,17 @@ export interface Patch {
name: string;
version: string;
patch: {
execute: (DatabaseManager) => Promise<void>;
execute: (dm: DatabaseManager) => Promise<void>;
beforeMigrate?: boolean;
};
}
export type KnexColumnType =
| 'text'
| 'integer'
| 'float'
| 'boolean'
| 'date'
| 'datetime'
| 'time'
| 'binary';

View File

@ -1,15 +1,15 @@
export function getMapFromList<T>(
export function getMapFromList<T, K extends keyof T>(
list: T[],
name: string = 'name'
name: K
): Record<string, T> {
const acc: Record<string, T> = {};
for (const t of list) {
const key = t[name] as string | undefined;
const key = t[name];
if (key === undefined) {
continue;
}
acc[key] = t;
acc[String(key)] = t;
}
return acc;
}

View File

@ -13,8 +13,8 @@ export function getSchemas(countryCode: string = '-'): SchemaMap {
return schemaMap;
}
function addMetaFields(schemaMap: SchemaMap): SchemaMap {
const metaSchemaMap = getMapFromList(metaSchemas);
export function addMetaFields(schemaMap: SchemaMap): SchemaMap {
const metaSchemaMap = getMapFromList(metaSchemas, 'name');
const base = metaSchemaMap.base;
const tree = getCombined(metaSchemaMap.tree, base);
@ -29,15 +29,15 @@ function addMetaFields(schemaMap: SchemaMap): SchemaMap {
}
if (schema.isTree && schema.isSubmittable) {
schema.fields = [...schema.fields, ...submittableTree.fields];
schema.fields = [...schema.fields, ...submittableTree.fields!];
} else if (schema.isTree) {
schema.fields = [...schema.fields, ...tree.fields];
schema.fields = [...schema.fields, ...tree.fields!];
} else if (schema.isSubmittable) {
schema.fields = [...schema.fields, ...submittable.fields];
schema.fields = [...schema.fields, ...submittable.fields!];
} else if (schema.isChild) {
schema.fields = [...schema.fields, ...child.fields];
schema.fields = [...schema.fields, ...child.fields!];
} else {
schema.fields = [...schema.fields, ...base.fields];
schema.fields = [...schema.fields, ...base.fields!];
}
}
@ -45,18 +45,23 @@ function addMetaFields(schemaMap: SchemaMap): SchemaMap {
}
function getCoreSchemas(): SchemaMap {
const rawSchemaMap = getMapFromList(coreSchemas);
const rawSchemaMap = getMapFromList(coreSchemas, 'name');
const coreSchemaMap = getAbstractCombinedSchemas(rawSchemaMap);
return cleanSchemas(coreSchemaMap);
}
function getAppSchemas(countryCode: string): SchemaMap {
const combinedSchemas = getRegionalCombinedSchemas(countryCode);
const appSchemaMap = getMapFromList(appSchemas, 'name');
const regionalSchemaMap = getRegionalSchemaMap(countryCode);
const combinedSchemas = getRegionalCombinedSchemas(
appSchemaMap,
regionalSchemaMap
);
const schemaMap = getAbstractCombinedSchemas(combinedSchemas);
return cleanSchemas(schemaMap);
}
function cleanSchemas(schemaMap: SchemaMap): SchemaMap {
export function cleanSchemas(schemaMap: SchemaMap): SchemaMap {
for (const name in schemaMap) {
const schema = schemaMap[name];
if (schema.isAbstract && !schema.extends) {
@ -97,13 +102,13 @@ function getCombined(
return combined;
}
function getAbstractCombinedSchemas(schemas: SchemaStubMap): SchemaMap {
export function getAbstractCombinedSchemas(schemas: SchemaStubMap): SchemaMap {
const abstractSchemaNames: string[] = Object.keys(schemas).filter(
(n) => schemas[n].isAbstract
);
const extendingSchemaNames: string[] = Object.keys(schemas).filter((n) =>
abstractSchemaNames.includes(schemas[n].extends)
abstractSchemaNames.includes(schemas[n].extends ?? '')
);
const completeSchemas: Schema[] = Object.keys(schemas)
@ -113,11 +118,11 @@ function getAbstractCombinedSchemas(schemas: SchemaStubMap): SchemaMap {
)
.map((n) => schemas[n] as Schema);
const schemaMap = getMapFromList(completeSchemas) as SchemaMap;
const schemaMap = getMapFromList(completeSchemas, 'name') as SchemaMap;
for (const name of extendingSchemaNames) {
const extendingSchema = schemas[name] as Schema;
const abstractSchema = schemas[extendingSchema.extends] as SchemaStub;
const abstractSchema = schemas[extendingSchema.extends!] as SchemaStub;
schemaMap[name] = getCombined(extendingSchema, abstractSchema) as Schema;
}
@ -129,9 +134,10 @@ function getAbstractCombinedSchemas(schemas: SchemaStubMap): SchemaMap {
return schemaMap;
}
function getRegionalCombinedSchemas(countryCode: string): SchemaStubMap {
const regionalSchemaMap = getRegionalSchema(countryCode);
const appSchemaMap = getMapFromList(appSchemas);
export function getRegionalCombinedSchemas(
appSchemaMap: SchemaStubMap,
regionalSchemaMap: SchemaStubMap
): SchemaStubMap {
const combined = { ...appSchemaMap };
for (const name in regionalSchemaMap) {
@ -148,7 +154,7 @@ function getRegionalCombinedSchemas(countryCode: string): SchemaStubMap {
return combined;
}
function getRegionalSchema(countryCode: string): SchemaStubMap {
function getRegionalSchemaMap(countryCode: string): SchemaStubMap {
const countrySchemas = regionalSchemas[countryCode] as
| SchemaStub[]
| undefined;
@ -156,5 +162,5 @@ function getRegionalSchema(countryCode: string): SchemaStubMap {
return {};
}
return getMapFromList(countrySchemas);
return getMapFromList(countrySchemas, 'name');
}

View File

@ -1,6 +1,7 @@
import { SchemaStub } from 'schemas/types';
import IndianSchemas from './in';
/**
* Regional Schemas are exported by country code.
*/
export default { in: IndianSchemas };
export default { in: IndianSchemas } as Record<string, SchemaStub[]>;

View File

@ -10,6 +10,7 @@
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"resolveJsonModule": true,
"sourceMap": true,
"baseUrl": ".",
"types": ["webpack-env"],
@ -23,8 +24,8 @@
"src/**/*.tsx",
"src/**/*.vue",
"tests/**/*.ts",
"tests/**/*.tsx",
"schemas/**/*.ts",
"backend/**/*.ts",
"frappe/**/*.ts",
"models/**/*.ts",