-
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: new plugin for SQL database and tests
- Loading branch information
1 parent
4d0b8f7
commit 65b658d
Showing
8 changed files
with
1,628 additions
and
78 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
{ | ||
"name": "@slangroom/db", | ||
"version": "1.28.2", | ||
"dependencies": { | ||
"@slangroom/core": "workspace:*", | ||
"@slangroom/shared": "workspace:*", | ||
"sequelize": "^6.16.0", | ||
"sqlite3": "^5.0.0" | ||
}, | ||
"repository": "https://github.com/dyne/slangroom", | ||
"license": "AGPL-3.0-only", | ||
"type": "module", | ||
"main": "./build/esm/src/index.js", | ||
"types": "./build/esm/src/index.d.ts", | ||
"exports": { | ||
".": { | ||
"import": { | ||
"types": "./build/esm/src/index.d.ts", | ||
"default": "./build/esm/src/index.js" | ||
} | ||
}, | ||
"./*": { | ||
"import": { | ||
"types": "./build/esm/src/*.d.ts", | ||
"default": "./build/esm/src/*.js" | ||
} | ||
} | ||
}, | ||
"publishConfig": { | ||
"access": "public" | ||
}, | ||
"devDependencies": { | ||
"@types/supertest": "^6.0.2" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
// SPDX-FileCopyrightText: 2024 Dyne.org foundation | ||
// | ||
// SPDX-License-Identifier: AGPL-3.0-or-later | ||
|
||
export * from '@slangroom/db/plugin'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
export interface QueryGetRecord { | ||
id: string; | ||
table: string; | ||
database: string; | ||
} | ||
|
||
export interface QuerySaveVar { | ||
varName: string; | ||
varObj: object; | ||
database: string; | ||
table: string; | ||
} | ||
|
||
export interface ObjectLiteral { | ||
[key: string]: any; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,214 @@ | ||
// SPDX-FileCopyrightText: 2024 Dyne.org foundation | ||
// | ||
// SPDX-License-Identifier: AGPL-3.0-or-later | ||
|
||
import { Plugin } from '@slangroom/core'; | ||
import { Jsonable } from '@slangroom/shared'; | ||
import { BindOrReplacements, DataTypes, Model, Sequelize } from "sequelize"; | ||
import { QueryGetRecord, QuerySaveVar } from "./interfaces.js"; | ||
|
||
class Result extends Model { | ||
public result!: string; | ||
} | ||
|
||
export class DbError extends Error { | ||
constructor(e: string) { | ||
super(e) | ||
this.name = 'Slangroom @slangroom/db Error' | ||
} | ||
} | ||
|
||
type DK = string | object | null | undefined; | ||
|
||
function safeJSONParse(o: DK, errorMessage?: string): ({ ok: true, parsed: object } | { ok: false, error: string }) { | ||
const notNull = o ?? {}; | ||
if (typeof notNull === "object") return { ok: true, parsed: notNull }; | ||
try { | ||
return { ok: true, parsed: JSON.parse(notNull) }; | ||
} catch (err) { | ||
return { ok: false, error: errorMessage ?? err }; | ||
} | ||
} | ||
|
||
const p = new Plugin(); | ||
|
||
/** | ||
* @internal | ||
*/ | ||
|
||
/** | ||
* | ||
* @param {string} statement name of the SQL statement | ||
* @param {string} database keyName of the database | ||
*/ | ||
export const execute = p.new('connect', | ||
['statement'], | ||
'execute sql statement', | ||
async (ctx) => { | ||
const statement = ctx.fetch('statement') as string; | ||
const database = ctx.fetchConnect()[0] as string; | ||
try { | ||
const db = new Sequelize(database, { | ||
// disable logging; default: console.log | ||
logging: false | ||
}); | ||
const t = await db.transaction(); | ||
const [o, m] = await db.query(statement, { transaction: t }); | ||
await t.commit(); | ||
const output = { output: o ? o : m } as Jsonable; | ||
db.close(); | ||
return ctx.pass(output); | ||
} catch (error) { | ||
return ctx.fail(new DbError(error.message)); | ||
} | ||
}, | ||
); | ||
|
||
/** | ||
* @internal | ||
*/ | ||
|
||
export const executeParams = p.new('connect', | ||
['statement', 'parameters'], | ||
'execute sql statement with parameters', | ||
async (ctx) => { | ||
const statement = ctx.fetch('statement') as string; | ||
const parameters = ctx.fetch('parameters') as BindOrReplacements; | ||
const database = ctx.fetchConnect()[0] as string; | ||
try { | ||
const db = new Sequelize(database, { logging: false }); | ||
const t = await db.transaction(); | ||
const [o, m] = await db.query(statement, { | ||
transaction: t, | ||
replacements: parameters | ||
}); | ||
await t.commit(); | ||
const output = { output: o ? o : m } as Jsonable; | ||
db.close(); | ||
return ctx.pass(output); | ||
} catch (error) { | ||
return ctx.fail(new DbError(error.message)); | ||
} | ||
}, | ||
); | ||
|
||
/** | ||
* @internal | ||
*/ | ||
/** | ||
* @param {string} record name of the field (row) | ||
* @param {string} table keyName of the table | ||
* @param {string} database keyName of the database | ||
*/ | ||
export const getRecord = p.new('connect', | ||
['record', 'table'], | ||
'read the record of the table', | ||
async (ctx) => { | ||
const record = ctx.fetch('record') as string; | ||
const table = ctx.fetch('table') as string; | ||
const database = ctx.fetchConnect()[0] as string; | ||
|
||
const parse = (o: string) => safeJSONParse(o, `[DATABASE] Error in JSON format "${o}"`) | ||
|
||
// create object(s) with the FOUR values of each GET_RECORD | ||
const dbQueries: QueryGetRecord[] = []; | ||
|
||
dbQueries.push({ | ||
id: record, | ||
table: table, | ||
database: database | ||
}); | ||
try { | ||
var output = {} | ||
for (const query of dbQueries) { | ||
const db = new Sequelize(query.database, { logging: false }); | ||
Result.init( | ||
{ result: DataTypes.TEXT }, | ||
{ | ||
tableName: query.table, | ||
freezeTableName: true, | ||
sequelize: db, | ||
} | ||
); | ||
await Result.sync(); | ||
try { | ||
let result = await Result.findByPk(query.id); | ||
if (result) { | ||
result = result.get({ plain: true }); | ||
// column name is result | ||
const resultData = parse(result!.result); | ||
if (!resultData.ok) return ctx.fail(new DbError(resultData.error)); | ||
output = resultData.parsed; | ||
} else { | ||
return ctx.fail(new DbError(`[DATABASE] | ||
Returned null for id "${query.id}" in table "${query.table}" in db "${query.database}".`)); | ||
} | ||
} catch (e) { | ||
return ctx.fail(new DbError(`[DATABASE] | ||
Something went wrong for id "${query.id}" in table "${query.table}" in db "${query.database}".`)); | ||
} | ||
db.close(); | ||
} | ||
} catch (e) { | ||
return ctx.fail(new DbError(`[DATABASE] Database error: ${e}`)); | ||
} | ||
return ctx.pass(output); | ||
}, | ||
); | ||
|
||
/** | ||
* @internal | ||
*/ | ||
export const saveVar = p.new('connect', | ||
['variable', 'name', 'table'], | ||
'save the variable in the database table', | ||
async (ctx) => { | ||
const elem = ctx.fetch('variable') as object; | ||
const varName = ctx.fetch('name') as string; | ||
const database = ctx.fetchConnect()[0] as string; | ||
const table = ctx.fetch('table') as string; | ||
|
||
const dbQueries: QuerySaveVar[] = []; | ||
|
||
// create object(s) with the THREE values in each GET_RECORD | ||
dbQueries.push({ | ||
varName: varName, | ||
varObj: elem, | ||
database: database, | ||
table: table, | ||
}); | ||
|
||
try { | ||
for (const query of dbQueries) { | ||
|
||
const db = new Sequelize(query.database, { logging: false }); | ||
Result.init( | ||
{ result: DataTypes.TEXT }, | ||
{ | ||
tableName: query.table, | ||
freezeTableName: true, | ||
sequelize: db, | ||
} | ||
); | ||
await Result.sync(); | ||
try { | ||
// column name must be result | ||
await Result.create({ | ||
result: JSON.stringify({ | ||
[query.varName]: query.varObj, | ||
}), | ||
}); | ||
} catch (e) { | ||
return ctx.fail(new DbError(`[DATABASE] | ||
Error in table "${query.table}" in db "${query.database}": ${e}`)); | ||
} | ||
db.close(); | ||
} | ||
} catch (e) { | ||
return ctx.fail(new DbError(`[DATABASE] Database error: ${e}`)); | ||
} | ||
return ctx.pass(null); | ||
}, | ||
); | ||
|
||
export const db = p; |
Oops, something went wrong.