Skip to content

Commit

Permalink
Merge pull request #30 from CRBroughton/28-add-more-data-types
Browse files Browse the repository at this point in the history
28 add more data types
  • Loading branch information
CRBroughton authored Mar 15, 2024
2 parents bbf807c + e7a53c4 commit 34c7d6b
Show file tree
Hide file tree
Showing 11 changed files with 127 additions and 18 deletions.
5 changes: 5 additions & 0 deletions .changeset/purple-snakes-drive.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@crbroughton/sibyl": patch
---

Add new datatypes to Sibyl - bool, real, varchar, text and blob
5 changes: 5 additions & 0 deletions .changeset/slimy-timers-walk.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@crbroughton/sibyl": major
---

Introducing new SibylResponse type - acts as a wrapper to convert types from TS to database types
13 changes: 11 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ interface tableRowType {
name: string
sex: string
job: string
hasReadTheReadme: boolean
}

interface secondRowType {
Expand Down Expand Up @@ -70,7 +71,8 @@ createTable('firstTable', { // inferred table name and entry
id: 'int', // only allows for known data types ('int', 'char', 'blob')
job: 'char',
name: 'char',
sex: 'char'
sex: 'char',
hasReadTheReadme: 'bool'
})
```

Expand All @@ -88,6 +90,7 @@ const result = Create('firstTable', { // returns the resulting entry
name: 'Craig',
sex: 'male',
job: 'Software Engineer',
hasReadTheReadme: true,
})
```

Expand All @@ -96,13 +99,14 @@ const result = Create('firstTable', { // returns the resulting entry
To insert new entries into the database, you can use the `Insert` function:

```typescript
let insertions: tableRowType[] = []
let insertions: SibylResponse<tableRowType>[] = []
for (let index = 0; index < 1000; index++) {
insertions.push({
id: faker.number.int(),
name: faker.person.firstName(),
sex: faker.person.sex(),
job: faker.person.jobTitle(),
hasReadTheReadme: true,
})
}
// execute the provided instruction - Data will now be in the DB
Expand All @@ -126,6 +130,11 @@ selection.value = Select('firstTable', {
})
```

Sibyl also offers a custom type the `SibylResponse` type; This type can be helpful
when wanting to convert data types to TypeScript types; At the moment the custom type
only support boolean conversions from `boolean` to `0 | 1`. It's recommended to use
this type as a wrapper, if you're ever using boolean values.

## Development

To install dependencies:
Expand Down
7 changes: 4 additions & 3 deletions playground/src/components/DemoTable.vue
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,14 @@ import { Button } from '@/components/ui/button'
import { All, Order, Select as SelectOrders, Create } from '@/sibyl'
import { computed, onMounted, ref } from 'vue'
import { faker } from '@faker-js/faker'
import type { SibylResponse } from '@crbroughton/sibyl'
const results = ref<Order[] | undefined>([])
const results = ref<SibylResponse<Order>[] | undefined>([])
const currencies = ref<string[]>([])
const selectedCurrency = ref('')
onMounted(() => {
results.value = All('orders')
function getCurrencies(orders: Order[]): string[] {
function getCurrencies(orders: SibylResponse<Order>[]): string[] {
currencies.value.push('All')
orders.forEach(order => {
Expand Down Expand Up @@ -81,6 +81,7 @@ function createNewEntry() {
product: faker.commerce.product(),
currency: faker.finance.currency().name,
price: faker.commerce.price(),
booleanTest: false,
})
results.value = All('orders')
}
Expand Down
3 changes: 3 additions & 0 deletions playground/src/sibyl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export interface Order {
product: string
currency: string
price: string
booleanTest: boolean
}

interface Tables {
Expand All @@ -26,6 +27,7 @@ createTable('orders', {
product: 'char',
id: 'int',
price: 'char',
booleanTest: 'bool',
})

const insertions: Order[] = []
Expand All @@ -35,6 +37,7 @@ for (let index = 0; index < 1000; index++) {
product: faker.commerce.product(),
currency: faker.finance.currency().name,
price: faker.commerce.price(),
booleanTest: false,
})
}
Insert('orders', insertions)
Expand Down
19 changes: 11 additions & 8 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import type { Database } from 'sql.js'
import { buildSelectQuery, convertCreateTableStatement, convertToObjects, formatInsertStatement, objectToWhereClause } from './sibylLib'
import { buildSelectQuery, convertBooleanValues, convertCreateTableStatement, convertToObjects, formatInsertStatement, objectToWhereClause } from './sibylLib'
import type { DeleteArgs, SelectArgs } from './types'

export default async function Sibyl<T extends Record<string, any>>(db: Database) {
type MappedTable<T> = {
[Key in keyof T]:
T[Key] extends number ? 'int' :
T[Key] extends string ? 'char' :
'blob'
T[Key] extends boolean ? 'bool' :
T[Key] extends number ? 'int' | 'real' :
T[Key] extends string ? 'varchar' | 'char' :
T[Key] extends Date ? 'text' | 'int' | 'real' :
T[Key] extends Blob ? 'blob' :
null
}

type TableKeys = keyof T
Expand All @@ -27,10 +30,10 @@ function Select<K extends TableKeys>(table: K, args: SelectArgs<AccessTable<K>>)
const record = db.exec(query)

if (record[0]) {
return convertToObjects<AccessTable<K>>({
return convertBooleanValues(convertToObjects<AccessTable<K>>({
columns: record[0].columns,
values: record[0].values,
})
}))
}

return undefined
Expand All @@ -53,10 +56,10 @@ function All<K extends TableKeys>(table: K) {
const record = db.exec(`SELECT * from ${String(table)}`)

if (record[0]) {
return convertToObjects<AccessTable<K>>({
return convertBooleanValues(convertToObjects<AccessTable<K>>({
columns: record[0].columns,
values: record[0].values,
})
}))
}

return undefined
Expand Down
24 changes: 20 additions & 4 deletions src/sibylLib.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { DataStructure, SelectArgs } from './types'
import type { DataStructure, SelectArgs, SibylResponse } from './types'

export function formatInsertStatement<T extends Record<string, any>>(table: string, structs: T[]) {
const sortedStructs = sortKeys(structs)
Expand Down Expand Up @@ -33,9 +33,12 @@ export function sortKeys<T extends { [key: string]: any }>(arr: T[]): T[] {
export function objectToWhereClause<T>(obj: Partial<T>): string {
const clauses = []
for (const key in obj) {
// eslint-disable-next-line no-prototype-builtins
if (obj.hasOwnProperty(key))
clauses.push(`${key} = '${obj[key]}'`)
if (Object.prototype.hasOwnProperty.call(obj, key)) {
if (obj[key] === true || obj[key] === false)
clauses.push(`${key} = '${Number(obj[key])}'`)
else
clauses.push(`${key} = '${obj[key]}'`)
}
}
return clauses.join(' AND ')
}
Expand All @@ -52,6 +55,19 @@ export function convertToObjects<T>(data: DataStructure): T[] {
return result
}

export function convertBooleanValues<T>(arr: T[]) {
return arr.map((obj) => {
const convertedObj = {} as SibylResponse<T>
for (const key in obj) {
if (typeof obj[key] === 'boolean')
convertedObj[key as keyof T] = obj[key] ? 1 : 0 as any
else
convertedObj[key as keyof T] = obj[key] as SibylResponse<T>[keyof T]
}
return convertedObj
})
}

export function buildSelectQuery<T>(table: string, args: SelectArgs<T>) {
let query = `SELECT * from ${table} WHERE ${objectToWhereClause(args.where)}`

Expand Down
45 changes: 45 additions & 0 deletions src/tests/convertBooleanValues.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { describe, expect, it } from 'vitest'
import { convertBooleanValues } from '../sibylLib'

interface TableRow {
id: number
location: string
name: string
booleanTest: boolean
}

describe('convertBooleanValues tests', () => {
it('converts any boolean values in the array to either 0 or 1', async () => {
const actual = convertBooleanValues<TableRow>([
{
id: 1,
booleanTest: false,
location: 'Brighton',
name: 'Craig',
},
{
id: 2,
booleanTest: true,
location: 'Leeds',
name: 'Craig',
},
])

const expectation = [
{
id: 1,
booleanTest: 0,
location: 'Brighton',
name: 'Craig',
},
{
id: 2,
booleanTest: 1,
location: 'Leeds',
name: 'Craig',
},
]

expect(actual).toStrictEqual(expectation)
})
})
5 changes: 4 additions & 1 deletion src/tests/create.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ interface TableRow {
id: number
location: string
name: string
booleanTest: boolean
}

interface Tables {
Expand All @@ -26,17 +27,19 @@ describe('create tests', () => {
id: 'int',
location: 'char',
name: 'char',
booleanTest: 'bool',
})
const actual = Create('first', {
name: 'Craig',
id: 2344,
location: 'Brighton',
booleanTest: true,
})

const expectation = {
id: 2344,
location: 'Brighton',
name: 'Craig',
booleanTest: 1,
}
expect(actual).toStrictEqual(expectation)
})
Expand Down
13 changes: 13 additions & 0 deletions src/tests/objectToWhereClause.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ interface TableRow {
id: number
name: string
location: string
booleanTest: boolean
}

describe('objectToWhereClause tests', () => {
Expand All @@ -26,6 +27,18 @@ describe('objectToWhereClause tests', () => {

const expectation = 'id = \'1\' AND location = \'Brighton\' AND name = \'Craig\''

expect(actual).toStrictEqual(expectation)
})
it('converts 0 and 1 booleans back into booleans when inserting into the database', async () => {
const actual = objectToWhereClause<TableRow>({
id: 1,
location: 'Brighton',
name: 'Craig',
booleanTest: false,
})

const expectation = 'id = \'1\' AND location = \'Brighton\' AND name = \'Craig\' AND booleanTest = \'0\''

expect(actual).toStrictEqual(expectation)
})
})
6 changes: 6 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
export type SibylResponse<T> = {
[Key in keyof T]:
T[Key] extends boolean ? 0 | 1 :
T[Key]
}

export interface SelectArgs<T> {
where: Partial<T>
offset?: number
Expand Down

0 comments on commit 34c7d6b

Please sign in to comment.