diff --git a/scripts/deduce-category-implications.ts b/scripts/deduce-category-implications.ts index 60b98d3d..3bc2d64d 100644 --- a/scripts/deduce-category-implications.ts +++ b/scripts/deduce-category-implications.ts @@ -1,19 +1,27 @@ -import { type Client } from '@libsql/client' +import type { Client } from '@libsql/client' import { are_equal_sets } from './shared' -import dotenv from 'dotenv' - -dotenv.config({ quiet: true }) +/** + * Deduces implications from given ones. + */ export async function deduce_category_implications(db: Client) { await clear_deduced_category_implications(db) await create_dualized_category_implications(db) await create_self_dual_category_implications(db) } +/** + * Clears all deduced implications. This is done as a first step. + */ async function clear_deduced_category_implications(db: Client) { await db.execute(`DELETE FROM implications WHERE is_deduced = TRUE`) } +/** + * Dualizes all implications by dualizing the involved properties + * (in case they have a dual). For example, if P ===> Q holds, + * then P^op ===> Q^op holds as well. + */ async function create_dualized_category_implications(db: Client) { const res = await db.execute(` SELECT @@ -91,6 +99,10 @@ async function create_dualized_category_implications(db: Client) { console.info(`Dualized ${dualizable_implications.length} category implications`) } +/** + * Creates all trivial implications of the form + * self-dual + P ===> P^op + */ async function create_self_dual_category_implications(db: Client) { const { rows } = await db.execute(` INSERT INTO implication_input ( diff --git a/scripts/deduce-category-properties.ts b/scripts/deduce-category-properties.ts index 00a54e6c..ccfa2ae3 100644 --- a/scripts/deduce-category-properties.ts +++ b/scripts/deduce-category-properties.ts @@ -1,5 +1,4 @@ import type { Transaction, Client } from '@libsql/client' -import dotenv from 'dotenv' import { get_assumption_string, get_conclusion_string, @@ -7,14 +6,16 @@ import { NormalizedCategoryImplication, } from './shared' -dotenv.config({ quiet: true }) - type CategoryMeta = { id: string name: string dual_category_id: string | null } +/** + * Deduce properties of categories from given ones + * by using the list of implications. + */ export async function deduce_category_properties(db: Client) { const tx = await db.transaction() @@ -30,7 +31,19 @@ export async function deduce_category_properties(db: Client) { } for (const category of categories) { + const allowed = + category.dual_category_id !== null && + category.name.toLowerCase().startsWith('dual') // prevent circular deduction + + if (!allowed) continue + await deduce_dual_category_properties(tx, category) + await deduce_satisfied_category_properties(tx, category.id, implications, { + check_conflicts: false, + }) + await deduce_unsatisfied_category_properties(tx, category.id, implications, { + check_conflicts: false, + }) } await tx.commit() @@ -41,6 +54,20 @@ export async function deduce_category_properties(db: Client) { } } +/** + * Implications have the form: + * + * P_1 + ... + P_n ----> Q_1 + ... + Q_m + * + * or + * + * P_1 + ... + P_n <---> Q_1 + ... + Q_m + * + * This function decomposes them into normalized implications, + * which have the form: + * + * P_1 + ... + P_n ----> Q + */ async function get_normalized_category_implications( tx: Transaction, ): Promise { @@ -101,6 +128,9 @@ async function get_normalized_category_implications( return implications } +/** + * Returns the list of categories saved in the database. + */ async function get_categories(tx: Transaction) { const res = await tx.execute(` SELECT id, name, dual_category_id @@ -109,6 +139,10 @@ async function get_categories(tx: Transaction) { return res.rows as unknown as CategoryMeta[] } +/** + * Clears all the deduced properties. + * This runs before the deduction starts. + */ async function delete_deduced_category_properties(tx: Transaction, category_id: string) { await tx.execute({ sql: ` @@ -119,26 +153,38 @@ async function delete_deduced_category_properties(tx: Transaction, category_id: }) } -async function deduce_satisfied_category_properties( +/** + * Returns the list of properties that are satisfied or unsatisfied + * for a given category. + */ +async function get_decided_properties( tx: Transaction, category_id: string, - implications: NormalizedCategoryImplication[], + value: boolean, ) { - const satisfied_res = await tx.execute({ + const res = await tx.execute({ sql: ` SELECT property_id FROM category_property_assignments - WHERE - category_id = ? - AND is_satisfied = TRUE - AND is_deduced = FALSE + WHERE category_id = ? AND is_satisfied = ? `, - args: [category_id], + args: [category_id, value], }) - const satisfied_props = new Set( - satisfied_res.rows.map((row) => row.property_id) as string[], - ) as Set + return new Set(res.rows.map((row) => row.property_id) as string[]) +} + +/** + * Deduce satisfied properties for a given category from given ones + * by using the list of normalized implications. + */ +async function deduce_satisfied_category_properties( + tx: Transaction, + category_id: string, + implications: NormalizedCategoryImplication[], + options: { check_conflicts: boolean } = { check_conflicts: true }, +) { + const satisfied_props = await get_decided_properties(tx, category_id, true) const deduced_satisfied_props: string[] = [] const reasons: Record = {} @@ -175,13 +221,14 @@ async function deduce_satisfied_category_properties( values.push(category_id, id, reasons[id], i + 1) } - const insert_sql = ` - INSERT INTO category_property_assignments ( - category_id, property_id, is_satisfied, reason, position, is_deduced - ) - VALUES - ${value_fragments.join(',\n')} - ` + const insert_sql = options.check_conflicts + ? `INSERT INTO category_property_assignments + (category_id, property_id, is_satisfied, reason, position, is_deduced) + VALUES ${value_fragments.join(',\n')}` + : `INSERT INTO category_property_assignments + (category_id, property_id, is_satisfied, reason, position, is_deduced) + VALUES ${value_fragments.join(',\n')} + ON CONFLICT (category_id, property_id) DO NOTHING` await tx.execute({ sql: insert_sql, args: values }) } @@ -191,39 +238,18 @@ async function deduce_satisfied_category_properties( ) } +/** + * Deduce unsatisfied properties for a given category from given ones + * by using the satisfied properties and the list of normalized implications. + */ async function deduce_unsatisfied_category_properties( tx: Transaction, category_id: string, implications: NormalizedCategoryImplication[], + options: { check_conflicts: boolean } = { check_conflicts: true }, ) { - const satisfied_res = await tx.execute({ - sql: ` - SELECT property_id - FROM category_property_assignments - WHERE category_id = ? AND is_satisfied = TRUE - `, - args: [category_id], - }) - - const satisfied_props = new Set( - satisfied_res.rows.map((row) => row.property_id) as string[], - ) - - const unsatisfied_res = await tx.execute({ - sql: ` - SELECT property_id - FROM category_property_assignments - WHERE - category_id = ? - AND is_satisfied = FALSE - AND is_deduced = FALSE - `, - args: [category_id], - }) - - const unsatisfied_props = new Set( - unsatisfied_res.rows.map((row) => row.property_id) as string[], - ) + const satisfied_props = await get_decided_properties(tx, category_id, true) + const unsatisfied_props = await get_decided_properties(tx, category_id, false) const deduced_unsatisfied_props: string[] = [] const reasons: Record = {} @@ -278,12 +304,14 @@ async function deduce_unsatisfied_category_properties( values.push(category_id, id, reasons[id], i + 1) } - const insert_query = ` - INSERT INTO category_property_assignments ( - category_id, property_id, is_satisfied, reason, position, is_deduced - ) - VALUES - ${value_fragments.join(',\n')}` + const insert_query = options.check_conflicts + ? `INSERT INTO category_property_assignments + (category_id, property_id, is_satisfied, reason, position, is_deduced) + VALUES ${value_fragments.join(',\n')}` + : `INSERT INTO category_property_assignments + (category_id, property_id, is_satisfied, reason, position, is_deduced) + VALUES ${value_fragments.join(',\n')} + ON CONFLICT (category_id, property_id) DO NOTHING` await tx.execute({ sql: insert_query, args: values }) } @@ -293,13 +321,11 @@ async function deduce_unsatisfied_category_properties( ) } +/** + * Assign dual properties to dual categories: + * If C has property P, then C^op has property P^op (if defined). + */ async function deduce_dual_category_properties(tx: Transaction, category: CategoryMeta) { - const allowed = - category.dual_category_id !== null && - category.name.toLowerCase().startsWith('dual') // prevent circular deduction - - if (!allowed) return - const res = await tx.execute({ sql: ` INSERT OR REPLACE INTO category_property_assignments @@ -318,7 +344,9 @@ async function deduce_dual_category_properties(tx: Transaction, category: Catego FROM category_property_assignments a INNER JOIN properties p ON p.id = a.property_id INNER JOIN relations r ON r.relation= p.relation - WHERE a.category_id = ? AND p.dual_property_id IS NOT NULL + WHERE + a.category_id = ? + AND p.dual_property_id IS NOT NULL ORDER BY lower(p.dual_property_id) `, args: [category.id, category.dual_category_id], diff --git a/scripts/deduce-functor-implications.ts b/scripts/deduce-functor-implications.ts index c5a523ba..d36efc0e 100644 --- a/scripts/deduce-functor-implications.ts +++ b/scripts/deduce-functor-implications.ts @@ -1,20 +1,29 @@ -import { type Client } from '@libsql/client' +import type { Client } from '@libsql/client' import { are_equal_sets } from './shared' -import dotenv from 'dotenv' - -dotenv.config({ quiet: true }) // TODO: remove code duplication with category implication deduction script +/** + * Deduces functor implications from given ones. + */ export async function deduce_functor_implications(db: Client) { await clear_deduced_functor_implications(db) await create_dualized_functor_implications(db) } +/** + * Clears all deduced functor implications. This is done as a first step. + */ async function clear_deduced_functor_implications(db: Client) { await db.execute(`DELETE FROM functor_implications WHERE is_deduced = TRUE`) } +/** + * Dualizes all functor implications by dualizing the involved properties + * (in case they have a dual). For example, if P ===> Q holds, + * then P^op ===> Q^op holds as well. The assumptions of source and target + * categories (if any) need to be dualized as well. + */ async function create_dualized_functor_implications(db: Client) { const res = await db.execute(` SELECT diff --git a/scripts/deduce-functor-properties.ts b/scripts/deduce-functor-properties.ts index 081e2697..dfd12bcb 100644 --- a/scripts/deduce-functor-properties.ts +++ b/scripts/deduce-functor-properties.ts @@ -1,5 +1,4 @@ import type { Transaction, Client } from '@libsql/client' -import dotenv from 'dotenv' import { get_assumption_string, get_conclusion_string, @@ -7,8 +6,6 @@ import { NormalizedFunctorImplication, } from './shared' -dotenv.config({ quiet: true }) - type FunctorMeta = { id: string name: string @@ -21,6 +18,10 @@ type FunctorMeta = { // TODO: remove code duplication with category deduction script // (not quite the same because we need source and target assumptions) +/** + * Deduce properties of functor from given ones + * by using the list of functor implications. + */ export async function deduce_functor_properties(db: Client) { const tx = await db.transaction() @@ -43,6 +44,20 @@ export async function deduce_functor_properties(db: Client) { } } +/** + * Implications have the form: + * + * P_1 + ... + P_n ----> Q_1 + ... + Q_m + * + * or + * + * P_1 + ... + P_n <---> Q_1 + ... + Q_m + * + * This function decomposes them into normalized implications, + * which have the form: + * + * P_1 + ... + P_n ----> Q + */ async function get_normalized_functor_implications( tx: Transaction, ): Promise { @@ -113,6 +128,10 @@ async function get_normalized_functor_implications( return implications } +/** + * Returns the list of functors saved in the database along with + * the satisfied properties of their source and target category. + */ async function get_functors(tx: Transaction) { const res = await tx.execute(` SELECT @@ -148,6 +167,10 @@ async function get_functors(tx: Transaction) { })) as FunctorMeta[] } +/** + * Clears all the deduced functor properties. + * This runs before the deduction starts. + */ async function delete_deduced_functor_properties(tx: Transaction, functor: FunctorMeta) { await tx.execute({ sql: ` @@ -158,26 +181,37 @@ async function delete_deduced_functor_properties(tx: Transaction, functor: Funct }) } -async function deduce_satisfied_functor_properties( +/** + * Returns the list of properties that are satisfied or unsatisfied + * for a given functor. + */ +async function get_decided_functor_properties( tx: Transaction, - functor: FunctorMeta, - implications: NormalizedFunctorImplication[], + functor_id: string, + value: boolean, ) { - const satisfied_res = await tx.execute({ + const res = await tx.execute({ sql: ` SELECT property_id FROM functor_property_assignments - WHERE - functor_id = ? - AND is_satisfied = TRUE - AND is_deduced = FALSE + WHERE functor_id = ? AND is_satisfied = ? `, - args: [functor.id], + args: [functor_id, value], }) - const satisfied_props = new Set( - satisfied_res.rows.map((row) => row.property_id) as string[], - ) as Set + return new Set(res.rows.map((row) => row.property_id) as string[]) +} + +/** + * Deduce satisfied properties for a given functor from given ones + * by using the list of normalized implications. + */ +async function deduce_satisfied_functor_properties( + tx: Transaction, + functor: FunctorMeta, + implications: NormalizedFunctorImplication[], +) { + const satisfied_props = await get_decided_functor_properties(tx, functor.id, true) const deduced_satisfied_props: string[] = [] const reasons: Record = {} @@ -232,39 +266,17 @@ async function deduce_satisfied_functor_properties( ) } +/** + * Deduce unsatisfied properties for a given functor from given ones + * by using the satisfied properties and the list of normalized implications. + */ async function deduce_unsatisfied_functor_properties( tx: Transaction, functor: FunctorMeta, implications: NormalizedFunctorImplication[], ) { - const satisfied_res = await tx.execute({ - sql: ` - SELECT property_id - FROM functor_property_assignments - WHERE functor_id = ? AND is_satisfied = TRUE - `, - args: [functor.id], - }) - - const satisfied_props = new Set( - satisfied_res.rows.map((row) => row.property_id) as string[], - ) - - const unsatisfied_res = await tx.execute({ - sql: ` - SELECT property_id - FROM functor_property_assignments - WHERE - functor_id = ? - AND is_satisfied = FALSE - AND is_deduced = FALSE - `, - args: [functor.id], - }) - - const unsatisfied_props = new Set( - unsatisfied_res.rows.map((row) => row.property_id) as string[], - ) + const satisfied_props = await get_decided_functor_properties(tx, functor.id, true) + const unsatisfied_props = await get_decided_functor_properties(tx, functor.id, false) const deduced_unsatisfied_props: string[] = [] const reasons: Record = {} diff --git a/scripts/deduce-special-morphisms.ts b/scripts/deduce-special-morphisms.ts index 41197f1f..170d998e 100644 --- a/scripts/deduce-special-morphisms.ts +++ b/scripts/deduce-special-morphisms.ts @@ -1,12 +1,17 @@ -import { Client } from '@libsql/client' +import type { Client } from '@libsql/client' + +// TODO: deduce further morphisms, +// e.g. isomorphisms = bijective morphisms in algebraic categories, +// e.g. regular monomorphisms = same as monomorphisms in mono-regular categories export async function deduce_special_morphisms(db: Client) { await deduce_special_morphisms_of_dual_categories(db) - // TODO: deduce further morphisms, - // e.g. isomorphisms = bijective morphisms in algebraic categories, - // e.g. regular monomorphisms = same as monomorphisms in mono-regular categories, } +/** + * Deduce special morphisms in dual categories. + * For example, monomorphisms in C describe epimorphisms in C^op. + */ async function deduce_special_morphisms_of_dual_categories(db: Client) { const res = await db.execute(` INSERT INTO special_morphisms (category_id, type, description, reason) diff --git a/scripts/deduce-special-objects.ts b/scripts/deduce-special-objects.ts index b5341238..7d4f4d87 100644 --- a/scripts/deduce-special-objects.ts +++ b/scripts/deduce-special-objects.ts @@ -1,9 +1,13 @@ -import { Client } from '@libsql/client' +import type { Client } from '@libsql/client' export async function deduce_special_objects(db: Client) { await deduce_special_objects_of_dual_categories(db) } +/** + * Deduce special objects in dual categories. + * For example, initial objects in C describe the terminal objects in C^op. + */ async function deduce_special_objects_of_dual_categories(db: Client) { const res = await db.execute(` INSERT INTO special_objects (category_id, type, description) diff --git a/scripts/deduce.ts b/scripts/deduce.ts index ce7c7838..ce3d846e 100644 --- a/scripts/deduce.ts +++ b/scripts/deduce.ts @@ -1,31 +1,27 @@ -import { createClient } from '@libsql/client' -import dotenv from 'dotenv' import { deduce_category_implications } from './deduce-category-implications' import { deduce_category_properties } from './deduce-category-properties' import { deduce_functor_implications } from './deduce-functor-implications' import { deduce_functor_properties } from './deduce-functor-properties' import { deduce_special_objects } from './deduce-special-objects' import { deduce_special_morphisms } from './deduce-special-morphisms' +import { get_client } from './shared' -dotenv.config({ quiet: true }) +/** + * Makes deductions for categories and functors. + */ +async function deduce() { + const db = get_client() -const DB_URL = process.env.DB_URL -const DB_AUTH_TOKEN = process.env.DB_AUTH_TOKEN + await db.execute('PRAGMA foreign_keys = ON') -if (!DB_URL) throw new Error('No DB_URL found') + await deduce_category_implications(db) + await deduce_category_properties(db) -const db = createClient({ - url: DB_URL, - authToken: DB_AUTH_TOKEN, -}) + await deduce_special_objects(db) + await deduce_special_morphisms(db) -await db.execute('PRAGMA foreign_keys = ON') + await deduce_functor_implications(db) + await deduce_functor_properties(db) +} -await deduce_category_implications(db) -await deduce_category_properties(db) - -await deduce_special_objects(db) -await deduce_special_morphisms(db) - -await deduce_functor_implications(db) -await deduce_functor_properties(db) +await deduce() diff --git a/scripts/migrate.ts b/scripts/migrate.ts index c992a589..e8a49b7d 100644 --- a/scripts/migrate.ts +++ b/scripts/migrate.ts @@ -1,21 +1,40 @@ -import { createClient } from '@libsql/client' +import { type Client, createClient } from '@libsql/client' import fs from 'node:fs/promises' import path from 'node:path' import dotenv from 'dotenv' +import { get_client } from './shared' dotenv.config({ quiet: true }) -const DB_VISITS_URL = process.env.DB_VISITS_URL -const DB_VISITS_AUTH_TOKEN = process.env.DB_VISITS_AUTH_TOKEN +await migrate() -if (!DB_VISITS_URL) throw new Error('No DB_VISITS_URL found') +/** + * Creates the tables, indexes, triggers, and views. + */ +async function migrate() { + await create_visits_table() -const db_visits = createClient({ - url: DB_VISITS_URL, - authToken: DB_VISITS_AUTH_TOKEN, -}) + const db = get_client() + await db.execute('PRAGMA foreign_keys = ON') + await create_migrations_table(db) + await apply_migrations(db) +} + +/** + * Creates the visits table. It is stored in a separate database. + */ +async function create_visits_table() { + const DB_VISITS_URL = process.env.DB_VISITS_URL + const DB_VISITS_AUTH_TOKEN = process.env.DB_VISITS_AUTH_TOKEN + + if (!DB_VISITS_URL) throw new Error('No DB_VISITS_URL found') + + const db_visits = createClient({ + url: DB_VISITS_URL, + authToken: DB_VISITS_AUTH_TOKEN, + }) -await db_visits.execute(` + await db_visits.execute(` CREATE TABLE IF NOT EXISTS visits ( id INTEGER PRIMARY KEY, created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, @@ -25,63 +44,63 @@ await db_visits.execute(` ) `) -console.info('Created visits table') - -const DB_URL = process.env.DB_URL -const DB_AUTH_TOKEN = process.env.DB_AUTH_TOKEN - -if (!DB_URL) throw new Error('No DB_URL found') - -const db = createClient({ - url: DB_URL, - authToken: DB_AUTH_TOKEN, -}) - -await db.execute('PRAGMA foreign_keys = ON') + console.info('Created visits table') +} -await db.execute(` - CREATE TABLE IF NOT EXISTS migrations ( - file TEXT PRIMARY KEY, - applied_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP - ) -`) +/** + * Creates the migration table that records + * which migrations have already been applied. + */ +async function create_migrations_table(db: Client) { + await db.execute(` + CREATE TABLE IF NOT EXISTS migrations ( + file TEXT PRIMARY KEY, + applied_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP + ) + `) +} -const { rows } = await db.execute('SELECT file FROM migrations') -const applied_migrations = new Set(rows.map((row) => row.file) as string[]) +/** + * Applies all migrations that have not been applied yet. + */ +async function apply_migrations(db: Client) { + const { rows } = await db.execute('SELECT file FROM migrations') + const applied_migrations = new Set(rows.map((row) => row.file) as string[]) -const migrations_folder = path.join(process.cwd(), 'database', 'migrations') -const unsorted_files = await fs.readdir(migrations_folder, 'utf8') -const files = unsorted_files.filter((f) => f.endsWith('.sql')).sort() + const migrations_folder = path.join(process.cwd(), 'database', 'migrations') + const unsorted_files = await fs.readdir(migrations_folder, 'utf8') + const files = unsorted_files.filter((f) => f.endsWith('.sql')).sort() -const invalid_file = files.find((file) => !file.match(/^\d{3}_/)) -if (invalid_file) throw new Error(`Invalid file name: ${invalid_file}`) + const invalid_file = files.find((file) => !file.match(/^\d{3}_/)) + if (invalid_file) throw new Error(`Invalid file name: ${invalid_file}`) -const all_done = files.every((file) => applied_migrations.has(file)) + const all_done = files.every((file) => applied_migrations.has(file)) -if (all_done) { - console.info('No migrations need to be applied') - process.exit(0) -} + if (all_done) { + console.info('No migrations need to be applied') + process.exit(0) + } -for (const file of files) { - if (applied_migrations.has(file)) continue - - const sql = await fs.readFile(path.join(migrations_folder, file), 'utf8') - - const tx = await db.transaction() - try { - await tx.executeMultiple(sql) - await tx.execute({ - sql: 'INSERT INTO migrations (file) VALUES (?)', - args: [file], - }) - await tx.commit() - console.info(`Applied migration: ${file}`) - } catch (err) { - console.error(`Failed migration: ${file}`, err) - await tx.rollback() - process.exit(1) + for (const file of files) { + if (applied_migrations.has(file)) continue + + const sql = await fs.readFile(path.join(migrations_folder, file), 'utf8') + + const tx = await db.transaction() + try { + await tx.executeMultiple(sql) + await tx.execute({ + sql: 'INSERT INTO migrations (file) VALUES (?)', + args: [file], + }) + await tx.commit() + console.info(`Applied migration: ${file}`) + } catch (err) { + console.error(`Failed migration: ${file}`, err) + await tx.rollback() + process.exit(1) + } } -} -console.info('Applied all migrations') + console.info('Applied all migrations') +} diff --git a/scripts/seed.ts b/scripts/seed.ts index 29a7315e..3f50b304 100644 --- a/scripts/seed.ts +++ b/scripts/seed.ts @@ -1,56 +1,55 @@ -import { createClient } from '@libsql/client' import fs from 'node:fs/promises' import path from 'node:path' -import dotenv from 'dotenv' - -dotenv.config({ quiet: true }) - -const DB_URL = process.env.DB_URL -const DB_AUTH_TOKEN = process.env.DB_AUTH_TOKEN +import { get_client } from './shared' + +/** + * Seeds the data recorded in SQL files into the database. + */ +async function seed() { + const db = get_client() + const data_folder = path.join(process.cwd(), 'database', 'data') + + const subfolders = (await fs.readdir(data_folder, { withFileTypes: true })) + .filter((f) => f.isDirectory()) + .map((f) => f.name) + .sort() -if (!DB_URL) throw new Error('No DB_URL found') + const invalid_folder = subfolders.find((f) => !f.match(/^\d{3}_/)) + if (invalid_folder) throw new Error(`Invalid folder name: ${invalid_folder}`) -const db = createClient({ - url: DB_URL, - authToken: DB_AUTH_TOKEN, -}) + for (const folder of subfolders) { + const folder_path = path.join(data_folder, folder) + const files = (await fs.readdir(folder_path, { withFileTypes: true })) + .filter((f) => f.isFile() && f.name.endsWith('.sql')) + .map((f) => path.join(folder_path, f.name)) + .sort() -const data_folder = path.join(process.cwd(), 'database', 'data') + const tx = await db.transaction() -const subfolders = (await fs.readdir(data_folder, { withFileTypes: true })) - .filter((f) => f.isDirectory()) - .map((f) => f.name) - .sort() + try { + for (const file of files) { + const base = path.basename(file) + const is_valid = base?.match(/^[A-Za-z0-9_.,\-()]+$/) + if (!is_valid) { + throw new Error(`Invalid file name: ${base}`) + } + const sql = await fs.readFile(file, 'utf8') -const invalid_folder = subfolders.find((f) => !f.match(/^\d{3}_/)) -if (invalid_folder) throw new Error(`Invalid folder name: ${invalid_folder}`) + await tx.executeMultiple(sql) -for (const folder of subfolders) { - const folder_path = path.join(data_folder, folder) - const files = (await fs.readdir(folder_path, { withFileTypes: true })) - .filter((f) => f.isFile() && f.name.endsWith('.sql')) - .map((f) => path.join(folder_path, f.name)) - .sort() + const operation = file.includes('clear') ? 'Clear data' : 'Insert data' + console.info(`${operation}: ${base}`) + } - for (const file of files) { - const base = path.basename(file) - const is_valid = base?.match(/^[A-Za-z0-9_.,\-()]+$/) - if (!is_valid) { - throw new Error(`Invalid file name: ${base}`) - } - const sql = await fs.readFile(file, 'utf8') - const tx = await db.transaction() - try { - await tx.executeMultiple(sql) await tx.commit() - const operation = file.includes('clear') ? 'Clear data' : 'Insert data' - console.info(`${operation}: ${base}`) } catch (err) { - console.error(`Failed to process ${file}`, err) + console.error(`Failed to seed ${folder}`, err) await tx.rollback() process.exit(1) } } + + console.info('Inserted all data') } -console.info('Inserted all data') +await seed() diff --git a/scripts/shared.ts b/scripts/shared.ts index 883a13c1..eb574485 100644 --- a/scripts/shared.ts +++ b/scripts/shared.ts @@ -1,3 +1,8 @@ +import { createClient } from '@libsql/client' +import dotenv from 'dotenv' + +dotenv.config({ quiet: true }) + export function are_equal_sets(a: Set, b: Set) { return a.size === b.size && [...a].every((el) => b.has(el)) } @@ -9,6 +14,15 @@ export function is_subset(a: Set, b: Set, exception?: T) { return true } +export function get_client() { + const DB_URL = process.env.DB_URL + const DB_AUTH_TOKEN = process.env.DB_AUTH_TOKEN + + if (!DB_URL) throw new Error('No DB_URL found') + + return createClient({ url: DB_URL, authToken: DB_AUTH_TOKEN }) +} + type NormalizedImplication = { id: string assumptions: Set diff --git a/scripts/test.ts b/scripts/test.ts index eb63f950..1ca438f9 100644 --- a/scripts/test.ts +++ b/scripts/test.ts @@ -3,26 +3,16 @@ * It checks that the data behaves as expected. * If not, an error is thrown, which must be fixed. */ + import Set_expected from './expected-data/Set.json' import Ab_expected from './expected-data/Ab.json' import Top_expected from './expected-data/Top.json' import decided_categories from './expected-data/decided-categories.json' -import { createClient } from '@libsql/client' -import dotenv from 'dotenv' - -dotenv.config({ quiet: true }) - -const DB_URL = process.env.DB_URL -const DB_AUTH_TOKEN = process.env.DB_AUTH_TOKEN - -if (!DB_URL) throw new Error('No DB_URL found') +import { get_client } from './shared' -const db = createClient({ - url: DB_URL, - authToken: DB_AUTH_TOKEN, -}) +const db = get_client() -execute_tests() +await execute_tests() /** * The main test function verifying that the data behaves as expected.