diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 153494a..331f040 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -2,7 +2,7 @@ name: ci on: push: - branches: [main, v1] + branches: [main] tags: [v*] pull_request: branches: [main] diff --git a/README.md b/README.md index 81e81fa..1c8e469 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ [![npm](https://img.shields.io/npm/v/rado.svg)](https://npmjs.org/package/rado) +[![jsr](https://jsr.io/badges/@rado/rado)](https://jsr.io/@rado/rado) # rado diff --git a/build.ts b/build.ts index 0694c86..d8a23f0 100644 --- a/build.ts +++ b/build.ts @@ -1,5 +1,5 @@ -import {build, type Plugin} from 'esbuild' -import glob from 'glob' +import {type Plugin, build} from 'esbuild' +import {glob} from 'glob' import {readFileSync, writeFileSync} from 'node:fs' const entryPoints = process.env.PROFILE ? '{src,test}/**/*.ts' : 'src/**/*.ts' diff --git a/bun.lockb b/bun.lockb index ec0ae90..5598bfc 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/jsr.json b/jsr.json index ab3de7f..99ff436 100644 --- a/jsr.json +++ b/jsr.json @@ -1,6 +1,6 @@ { "name": "@rado/rado", - "version": "1.0.8", + "version": "1.0.9", "publish": { "include": ["README.md", "LICENSE", "src"] }, diff --git a/package.json b/package.json index 2e83b48..ce17666 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "rado", - "version": "1.0.8", + "version": "1.0.9", "license": "MIT", "type": "module", "scripts": { @@ -26,30 +26,27 @@ }, "files": ["dist"], "devDependencies": { - "@benmerckx/suite": "^0.3.0", "@biomejs/biome": "^1.8.3", "@cloudflare/workers-types": "^4.20230628.0", "@electric-sql/pglite": "^0.1.5", "@sqlite.org/sqlite-wasm": "^3.42.0-build4", - "@types/better-sqlite3": "^5.4.1", - "@types/bun": "^1.1.6", - "@types/glob": "^8.0.0", - "@types/pg": "^8.11.5", - "@types/sql.js": "^1.4.2", - "better-sqlite3": "^11.0.0", - "esbuild": "^0.18.11", - "glob": "^8.0.3", + "@types/better-sqlite3": "^7.6.11", + "@types/bun": "^1.1.8", + "@types/glob": "^8.1.0", + "@types/pg": "^8.11.8", + "@types/sql.js": "^1.4.9", + "better-sqlite3": "^11.2.1", + "esbuild": "^0.23.1", + "glob": "^11.0.0", "madge": "^8.0.0", - "mysql2": "^3.9.7", - "pg": "^8.11.5", - "rimraf": "^4.1.2", - "sade": "^1.8.1", + "mysql2": "^3.11.0", + "pg": "^8.12.0", "speedscope": "^1.15.0", - "sql.js": "^1.8.0", - "sqlite3": "^5.1.6", - "tsx": "^4.7.2", - "typescript": "^5.4.5", - "uvu": "^0.5.6", + "sql.js": "^1.11.0", + "sqlite3": "^5.1.7", + "tsx": "^4.19.0", + "typescript": "^5.5.4", + "@alinea/suite": "^0.4.0", "@db/sqlite": "npm:@jsr/db__sqlite" } } diff --git a/src/core/Column.ts b/src/core/Column.ts index e4f3109..479e835 100644 --- a/src/core/Column.ts +++ b/src/core/Column.ts @@ -1,8 +1,8 @@ import type {DriverSpecs} from './Driver.ts' import {getData, getField, internalData} from './Internal.ts' -import {type Sql, sql} from './Sql.ts' +import {sql, type Sql} from './Sql.ts' import type {Field, FieldData} from './expr/Field.ts' -import {type Input, input} from './expr/Input.ts' +import {input, type Input} from './expr/Input.ts' export interface ColumnData { type: Sql @@ -107,7 +107,9 @@ export interface Columns { export const column: Columns = new Proxy(createColumn as any, { get(target: Record, method: string) { return (target[method] ??= (...args: Array>) => { - while (args.length > 0) if (args.at(-1) === undefined) args.pop() + while (args.length > 0) + if (args.at(-1) === undefined) args.pop() + else break if (args.length === 0) return sql.unsafe(method) return sql`${sql.unsafe(method)}(${sql.join( args.map(sql.inline), diff --git a/src/core/expr/Json.ts b/src/core/expr/Json.ts index c73afec..be828f9 100644 --- a/src/core/expr/Json.ts +++ b/src/core/expr/Json.ts @@ -1,5 +1,5 @@ -import {type HasSql, getSql} from '../Internal.ts' -import {type Sql, sql} from '../Sql.ts' +import {getSql, type HasSql} from '../Internal.ts' +import {sql, type Sql} from '../Sql.ts' import {callFunction} from './Functions.ts' import type {Input} from './Input.ts' @@ -52,8 +52,9 @@ export function jsonExpr(e: HasSql): JsonExpr { export function jsonAggregateArray(...args: Array>) { return callFunction( sql.universal({ + // Once sqlite 3.45+ is more commonplace we can use jsonb_group_array sqlite: sql`json_group_array`, - postgres: sql`json_agg`, + postgres: sql`jsonb_agg`, mysql: sql`json_arrayagg` }), args @@ -63,7 +64,7 @@ export function jsonAggregateArray(...args: Array>) { export function jsonArray(...args: Array>) { return callFunction( sql.universal({ - postgres: sql`json_build_array`, + postgres: sql`jsonb_build_array`, default: sql`json_array` }), args diff --git a/src/driver/better-sqlite3.ts b/src/driver/better-sqlite3.ts index 2dcd0e5..17eb491 100644 --- a/src/driver/better-sqlite3.ts +++ b/src/driver/better-sqlite3.ts @@ -12,7 +12,7 @@ import {execTransaction} from '../sqlite/transactions.ts' class PreparedStatement implements SyncStatement { constructor( - private stmt: Statement>, + private stmt: Statement, private isSelection: boolean ) {} @@ -29,7 +29,8 @@ class PreparedStatement implements SyncStatement { } values(params: Array) { - if (this.isSelection) return this.stmt.raw(true).all(...params) + if (this.isSelection) + return this.stmt.raw(true).all(...params) as Array> this.stmt.run(...params) return [] } diff --git a/src/driver/mysql2.ts b/src/driver/mysql2.ts index a3bd0f2..367db66 100644 --- a/src/driver/mysql2.ts +++ b/src/driver/mysql2.ts @@ -24,14 +24,19 @@ class PreparedStatement implements AsyncStatement { private name?: string ) {} + #transformParam = (param: unknown) => { + if (param instanceof Uint8Array) return Buffer.from(param) + return param + } + all(params: Array): Promise> { return this.client - .query(this.sql, params) + .query(this.sql, params.map(this.#transformParam)) .then(res => res[0] as Array) } async run(params: Array) { - await this.client.query(this.sql, params) + await this.client.query(this.sql, params.map(this.#transformParam)) } get(params: Array): Promise { @@ -40,7 +45,11 @@ class PreparedStatement implements AsyncStatement { values(params: Array): Promise>> { return this.client - .query({sql: this.sql, values: params, rowsAsArray: true}) + .query({ + sql: this.sql, + values: params.map(this.#transformParam), + rowsAsArray: true + }) .then(res => res[0] as Array>) } diff --git a/src/mysql/columns.ts b/src/mysql/columns.ts index a601088..c9b1646 100644 --- a/src/mysql/columns.ts +++ b/src/mysql/columns.ts @@ -1,4 +1,4 @@ -import {type Column, JsonColumn, column} from '../core/Column.ts' +import {JsonColumn, column, type Column} from '../core/Column.ts' type Precision = 0 | 1 | 2 | 3 | 4 | 5 | 6 @@ -32,6 +32,10 @@ export function boolean(name?: string): Column { return column({name, type: column.boolean()}) } +export function blob(name?: string): Column { + return column({name, type: column.blob()}) +} + export function char( name?: string, options?: {length: number} diff --git a/src/postgres/columns.ts b/src/postgres/columns.ts index b70e729..b684298 100644 --- a/src/postgres/columns.ts +++ b/src/postgres/columns.ts @@ -1,4 +1,4 @@ -import {type Column, JsonColumn, column} from '../core/Column.ts' +import {JsonColumn, column, type Column} from '../core/Column.ts' type Precision = 0 | 1 | 2 | 3 | 4 | 5 | 6 type IntervalFields = @@ -121,8 +121,8 @@ export function boolean(name?: string): Column { return column({name, type: column.boolean()}) } -export function blob(name?: string): Column { - return column({name, type: column.blob()}) +export function bytea(name?: string): Column { + return column({name, type: column.bytea()}) } export function text(name?: string): Column { diff --git a/src/universal/columns.ts b/src/universal/columns.ts index fd919bb..3c827e4 100644 --- a/src/universal/columns.ts +++ b/src/universal/columns.ts @@ -1,4 +1,4 @@ -import {type Column, JsonColumn, column} from '../core/Column.ts' +import {JsonColumn, column, type Column} from '../core/Column.ts' import {sql} from '../core/Sql.ts' const idType = sql.universal({ @@ -7,6 +7,21 @@ const idType = sql.universal({ mysql: sql`int not null auto_increment` }) +const blobType = sql.universal({ + postgres: sql`bytea`, + default: sql`blob` +}) + +const numberType = sql.universal({ + mysql: sql`double`, + default: sql`numeric` +}) + +const jsonbType = sql.universal({ + mysql: sql`json`, + default: sql`jsonb` +}) + export function id(name?: string): Column { return column({ name, @@ -19,18 +34,26 @@ export function text(name?: string): Column { return column({name, type: column.text()}) } +export function varchar( + name?: string, + options?: {length: number} +): Column { + return column({name, type: column.varchar(options?.length)}) +} + export function integer(name?: string): Column { return column({name, type: column.integer()}) } +export function number(name?: string): Column { + return column({name, type: numberType, mapFromDriverValue: Number}) +} + export function boolean(name?: string): Column { return column({ name, type: column.boolean(), - mapFromDriverValue(value: unknown): boolean { - if (typeof value === 'number') return value === 1 - return Boolean(value) - } + mapFromDriverValue: Boolean }) } @@ -46,3 +69,20 @@ export function json(name?: string): JsonColumn { } }) } + +export function jsonb(name?: string): JsonColumn { + return new JsonColumn({ + name, + type: jsonbType, + mapToDriverValue(value: T): string { + return JSON.stringify(value) + }, + mapFromDriverValue(value: unknown, {parsesJson}) { + return parsesJson ? value : JSON.parse(value as string) + } + }) +} + +export function blob(name?: string): Column { + return column({name, type: blobType}) +} diff --git a/test/TestDriver.ts b/test/TestDriver.ts index 2b4cd8a..8a30b30 100644 --- a/test/TestDriver.ts +++ b/test/TestDriver.ts @@ -1,7 +1,8 @@ -import {type DefineTest, suite} from '@benmerckx/suite' +import {suite} from '@alinea/suite' import type {Database} from '../src/core/Database.ts' import {testBasic} from './integration/TestBasic.ts' import {testCTE} from './integration/TestCTE.ts' +import {testColumns} from './integration/TestColumns.ts' import {testConstraints} from './integration/TestConstraints.ts' import {testInclude} from './integration/TestInclude.ts' import {testJoins} from './integration/TestJoins.ts' @@ -9,10 +10,7 @@ import {testJson} from './integration/TestJson.ts' import {testMigration} from './integration/TestMigration.ts' import {testPreparedQuery} from './integration/TestPreparedQuery.ts' import {testSubquery} from './integration/TestSubquery.ts' -import { - testGeneratorTransactions, - testTransactions -} from './integration/TestTransactions.ts' +import {testTransactions} from './integration/TestTransactions.ts' export async function testDriver( meta: ImportMeta, @@ -21,20 +19,17 @@ export async function testDriver( ) { const db = await createDb() suite(meta, test => { - const bind = (fn: (db: Database, test: DefineTest) => void) => - fn.bind(null, db, test) + testBasic(db, test) + testColumns(db, test) + testSubquery(db, test) + testPreparedQuery(db, test) + testJoins(db, test) + testJson(db, test) + testTransactions(db, test) + testConstraints(db, test) + testCTE(db, test) + testInclude(db, test) - test('basics', bind(testBasic)) - test('subquery', bind(testSubquery)) - test('prepared queries', bind(testPreparedQuery)) - test('joins', bind(testJoins)) - test('json fields', bind(testJson)) - test('transactions', bind(testTransactions)) - test('generator transactions', bind(testGeneratorTransactions)) - test('constraints and indexes', bind(testConstraints)) - test('recursive cte', bind(testCTE)) - test('include', bind(testInclude)) - - if (supportsDiff) test('migrate', bind(testMigration)) + if (supportsDiff) testMigration(db, test) }) } diff --git a/test/core/Expr.test.ts b/test/core/Expr.test.ts index c00b9b3..bae8d5c 100644 --- a/test/core/Expr.test.ts +++ b/test/core/Expr.test.ts @@ -1,7 +1,7 @@ import * as e from '@/core/expr/Conditions.ts' import {jsonExpr} from '@/core/expr/Json.ts' import {sql} from '@/index.ts' -import {suite} from '@benmerckx/suite' +import {suite} from '@alinea/suite' import {builder, emit} from '../TestUtils.ts' suite(import.meta, test => { diff --git a/test/core/Selection.test.ts b/test/core/Selection.test.ts index 867ddb2..4419a0e 100644 --- a/test/core/Selection.test.ts +++ b/test/core/Selection.test.ts @@ -1,5 +1,5 @@ import {selection, sql} from '@/index.ts' -import {suite} from '@benmerckx/suite' +import {suite} from '@alinea/suite' import {emit} from '../TestUtils.ts' suite(import.meta, test => { diff --git a/test/core/Sql.test.ts b/test/core/Sql.test.ts index 026f151..898b65c 100644 --- a/test/core/Sql.test.ts +++ b/test/core/Sql.test.ts @@ -1,5 +1,5 @@ import {sql} from '@/core/Sql.ts' -import {suite} from '@benmerckx/suite' +import {suite} from '@alinea/suite' import {emit} from '../TestUtils.ts' suite(import.meta, test => { diff --git a/test/core/SqliteFunctions.test.ts b/test/core/SqliteFunctions.test.ts index 80a4012..8ff6674 100644 --- a/test/core/SqliteFunctions.test.ts +++ b/test/core/SqliteFunctions.test.ts @@ -1,7 +1,7 @@ import {table} from '@/index.ts' import {integer} from '@/sqlite/columns.ts' import {bm25, cast} from '@/sqlite/functions.ts' -import {suite} from '@benmerckx/suite' +import {suite} from '@alinea/suite' import {emit} from '../TestUtils.ts' suite(import.meta, test => { diff --git a/test/core/Table.test.ts b/test/core/Table.test.ts index 3c65477..7563c36 100644 --- a/test/core/Table.test.ts +++ b/test/core/Table.test.ts @@ -1,6 +1,6 @@ import {table} from '@/core/Table.ts' import {integer} from '@/sqlite/columns.ts' -import {suite} from '@benmerckx/suite' +import {suite} from '@alinea/suite' import {emit} from '../TestUtils.ts' suite(import.meta, test => { diff --git a/test/integration/TestBasic.ts b/test/integration/TestBasic.ts index 5c8434b..61dc1b2 100644 --- a/test/integration/TestBasic.ts +++ b/test/integration/TestBasic.ts @@ -1,29 +1,31 @@ import {eq, sql, type Database} from '@/index.ts' import {concat} from '@/universal.ts' -import type {DefineTest} from '@benmerckx/suite' +import type {DefineTest} from '@alinea/suite' import {Node} from './schema.ts' -export async function testBasic(db: Database, test: DefineTest) { - try { - await db.create(Node) - const nothing = await db.select().from(Node).get() - test.equal(nothing, null) - await db.insert(Node).values({ - textField: 'hello', - bool: true - }) - const nodes = await db.select().from(Node) - test.equal(nodes, [{id: 1, textField: 'hello', bool: true}]) - await db.update(Node).set({textField: 'world'}).where(eq(Node.id, 1)) - const textField = db.select(Node.textField).from(Node) - test.equal(await textField.get(), 'world') - await db.update(Node).set({ - textField: db.select(sql.value('test')) - }) - test.equal(await textField.get(), 'test') - const abc = await db.select(concat('a', 'b', 'c')).get() - test.equal(abc, 'abc') - } finally { - await db.drop(Node) - } +export function testBasic(db: Database, test: DefineTest) { + test('basic', async () => { + try { + await db.create(Node) + const nothing = await db.select().from(Node).get() + test.equal(nothing, null) + await db.insert(Node).values({ + textField: 'hello', + bool: true + }) + const nodes = await db.select().from(Node) + test.equal(nodes, [{id: 1, textField: 'hello', bool: true}]) + await db.update(Node).set({textField: 'world'}).where(eq(Node.id, 1)) + const textField = db.select(Node.textField).from(Node) + test.equal(await textField.get(), 'world') + await db.update(Node).set({ + textField: db.select(sql.value('test')) + }) + test.equal(await textField.get(), 'test') + const abc = await db.select(concat('a', 'b', 'c')).get() + test.equal(abc, 'abc') + } finally { + await db.drop(Node) + } + }) } diff --git a/test/integration/TestCTE.ts b/test/integration/TestCTE.ts index 431b10e..0fc9a00 100644 --- a/test/integration/TestCTE.ts +++ b/test/integration/TestCTE.ts @@ -1,19 +1,24 @@ import {lte, sql, type Database} from '@/index.ts' -import type {DefineTest} from '@benmerckx/suite' +import type {DefineTest} from '@alinea/suite' -export async function testCTE(db: Database, test: DefineTest) { - const fibonacci = db.$with('fibonacci').as( - db.select({n: sql`1`, next: sql`1`}).unionAll(self => - db - .select({ - n: self.next, - next: sql`${self.n} + ${self.next}` - }) - .from(self) - .where(lte(self.next, 13)) +export function testCTE(db: Database, test: DefineTest) { + test('recursive common table expression', async () => { + const fibonacci = db.$with('fibonacci').as( + db.select({n: sql`1`, next: sql`1`}).unionAll(self => + db + .select({ + n: self.next, + next: sql`${self.n} + ${self.next}` + }) + .from(self) + .where(lte(self.next, 13)) + ) ) - ) - const query = db.withRecursive(fibonacci).select(fibonacci.n).from(fibonacci) - const result = await query.all() - test.equal(result, [1, 1, 2, 3, 5, 8, 13]) + const query = db + .withRecursive(fibonacci) + .select(fibonacci.n) + .from(fibonacci) + const result = await query.all() + test.equal(result, [1, 1, 2, 3, 5, 8, 13]) + }) } diff --git a/test/integration/TestColumns.ts b/test/integration/TestColumns.ts new file mode 100644 index 0000000..6f4c87a --- /dev/null +++ b/test/integration/TestColumns.ts @@ -0,0 +1,110 @@ +import {table, type Database} from '@/index.ts' +import * as column from '@/universal/columns.ts' +import type {DefineTest} from '@alinea/suite' + +export function testColumns(db: Database, test: DefineTest) { + test('id column', async () => { + const TestTable = table('Test', {id: column.id()}) + await db.create(TestTable) + try { + await db.insert(TestTable).values({}) + const row = await db.select().from(TestTable).get() + test.equal(row, {id: 1}) + } finally { + await db.drop(TestTable) + } + }) + + test('varchar column', async () => { + const TestTable = table('Test', { + varchar: column.varchar('varchar', {length: 5}) + }) + await db.create(TestTable) + try { + await db.insert(TestTable).values({varchar: 'hello'}) + const row = await db.select().from(TestTable).get() + test.equal(row, {varchar: 'hello'}) + } finally { + await db.drop(TestTable) + } + }) + + test('text column', async () => { + const TestTable = table('Test', {text: column.text()}) + await db.create(TestTable) + try { + await db.insert(TestTable).values({text: 'hello'}) + const row = await db.select().from(TestTable).get() + test.equal(row, {text: 'hello'}) + } finally { + await db.drop(TestTable) + } + }) + + test('boolean column', async () => { + const TestTable = table('Test', {bool: column.boolean()}) + await db.create(TestTable) + try { + await db.insert(TestTable).values({bool: true}) + const row = await db.select().from(TestTable).get() + test.equal(row, {bool: true}) + } finally { + await db.drop(TestTable) + } + }) + + test('integer column', async () => { + const TestTable = table('Test', {int: column.integer()}) + await db.create(TestTable) + try { + await db.insert(TestTable).values({int: 42}) + const row = await db.select().from(TestTable).get() + test.equal(row, {int: 42}) + } finally { + await db.drop(TestTable) + } + }) + + test('number column', async () => { + const TestTable = table('Test', {number: column.number()}) + await db.create(TestTable) + try { + const values = [ + {number: 4.2}, + {number: Number.MAX_SAFE_INTEGER}, + {number: Number.MIN_SAFE_INTEGER} + ] + await db.insert(TestTable).values(values) + const rows = await db.select().from(TestTable) + test.equal(rows, values) + } finally { + await db.drop(TestTable) + } + }) + + test('json column', async () => { + const TestTable = table('Test', {json: column.json()}) + await db.create(TestTable) + try { + await db.insert(TestTable).values({json: {foo: 'bar'}}) + const row = await db.select().from(TestTable).get() + test.equal(row, {json: {foo: 'bar'}}) + } finally { + await db.drop(TestTable) + } + }) + + test('blob column', async () => { + const encoder = new TextEncoder() + const decoder = new TextDecoder() + const TestTable = table('Test', {blob: column.blob().notNull()}) + await db.create(TestTable) + try { + await db.insert(TestTable).values({blob: encoder.encode('hello')}) + const row = await db.select().from(TestTable).get() + test.equal(decoder.decode(row!.blob), 'hello') + } finally { + await db.drop(TestTable) + } + }) +} diff --git a/test/integration/TestConstraints.ts b/test/integration/TestConstraints.ts index a68bf23..c214ff4 100644 --- a/test/integration/TestConstraints.ts +++ b/test/integration/TestConstraints.ts @@ -1,25 +1,27 @@ import type {Database} from '@/index.ts' -import type {DefineTest} from '@benmerckx/suite' +import type {DefineTest} from '@alinea/suite' import {TableA, TableB} from './schema.ts' -export async function testConstraints(db: Database, test: DefineTest) { - try { - await db.create(TableA, TableB) - await db.insert(TableA).values({}) - await db.insert(TableB).values({ - isUnique: 1, - hasRef: 1, - colA: 1, - colB: 1 - }) - const [row] = await db.select().from(TableB) - test.equal(row, { - isUnique: 1, - hasRef: 1, - colA: 1, - colB: 1 - }) - } finally { - await db.drop(TableB, TableA) - } +export function testConstraints(db: Database, test: DefineTest) { + test('constraints', async () => { + try { + await db.create(TableA, TableB) + await db.insert(TableA).values({}) + await db.insert(TableB).values({ + isUnique: 1, + hasRef: 1, + colA: 1, + colB: 1 + }) + const [row] = await db.select().from(TableB) + test.equal(row, { + isUnique: 1, + hasRef: 1, + colA: 1, + colB: 1 + }) + } finally { + await db.drop(TableB, TableA) + } + }) } diff --git a/test/integration/TestInclude.ts b/test/integration/TestInclude.ts index b01f938..93cabce 100644 --- a/test/integration/TestInclude.ts +++ b/test/integration/TestInclude.ts @@ -1,91 +1,97 @@ import {eq, include, table, type Database} from '@/index.ts' import {id, integer, lastInsertId, text} from '@/universal.ts' -import type {DefineTest} from '@benmerckx/suite' +import type {DefineTest} from '@alinea/suite' -export async function testInclude(db: Database, test: DefineTest) { - const User = table('User', { - id: id(), - name: text().notNull() - }) - const Post = table('Post', { - id: id(), - userId: integer().notNull(), - title: text().notNull() - }) - await db.create(User, Post) - await db.insert(User).values({name: 'Bob'}) - const user1 = await db.select(lastInsertId()).get() - await db.insert(Post).values({userId: user1!, title: 'Post 1'}) - await db.insert(Post).values({userId: user1!, title: 'Post 2'}) - const posts = include(db.select().from(Post).where(eq(Post.userId, User.id))) - const result = await db - .select({...User, posts}) - .from(User) - .where(eq(User.id, user1)) - .get() - test.equal(result, { - id: user1, - name: 'Bob', - posts: [ - {id: 1, userId: user1, title: 'Post 1'}, - {id: 2, userId: user1, title: 'Post 2'} - ] - }) - const emptyOne = await db - .select({ - empty: include.one(db.select().from(User).where(eq(User.id, 42))) +export function testInclude(db: Database, test: DefineTest) { + test('include', async () => { + const User = table('User', { + id: id(), + name: text().notNull() }) - .get() - test.equal(emptyOne, {empty: null}) - const postsWithUser = await db - .select({ - ...Post, - user: include.one(db.select().from(User).where(eq(User.id, Post.userId))) - }) - .from(Post) - test.equal(postsWithUser, [ - { - id: 1, - userId: user1, - title: 'Post 1', - user: {id: user1, name: 'Bob'} - }, - { - id: 2, - userId: user1, - title: 'Post 2', - user: {id: user1, name: 'Bob'} - } - ]) - - const emptyResult = await db.select({ - empty: include(db.select().from(User).where(eq(User.id, 42))) - }) - test.equal(emptyResult, [{empty: []}]) - - const nestedResult = await db - .select({ - user: include.one( - db - .select({ - ...User, - posts: include( - db.select().from(Post).where(eq(Post.userId, User.id)) - ) - }) - .from(User) - ) + const Post = table('Post', { + id: id(), + userId: integer().notNull(), + title: text().notNull() }) - .get() - test.equal(nestedResult, { - user: { + await db.create(User, Post) + await db.insert(User).values({name: 'Bob'}) + const user1 = await db.select(lastInsertId()).get() + await db.insert(Post).values({userId: user1!, title: 'Post 1'}) + await db.insert(Post).values({userId: user1!, title: 'Post 2'}) + const posts = include( + db.select().from(Post).where(eq(Post.userId, User.id)) + ) + const result = await db + .select({...User, posts}) + .from(User) + .where(eq(User.id, user1)) + .get() + test.equal(result, { id: user1, name: 'Bob', posts: [ {id: 1, userId: user1, title: 'Post 1'}, {id: 2, userId: user1, title: 'Post 2'} ] - } + }) + const emptyOne = await db + .select({ + empty: include.one(db.select().from(User).where(eq(User.id, 42))) + }) + .get() + test.equal(emptyOne, {empty: null}) + const postsWithUser = await db + .select({ + ...Post, + user: include.one( + db.select().from(User).where(eq(User.id, Post.userId)) + ) + }) + .from(Post) + test.equal(postsWithUser, [ + { + id: 1, + userId: user1, + title: 'Post 1', + user: {id: user1, name: 'Bob'} + }, + { + id: 2, + userId: user1, + title: 'Post 2', + user: {id: user1, name: 'Bob'} + } + ]) + + const emptyResult = await db.select({ + empty: include(db.select().from(User).where(eq(User.id, 42))) + }) + test.equal(emptyResult, [{empty: []}]) + + const nestedResult = await db + .select({ + user: include.one( + db + .select({ + ...User, + posts: include( + db.select().from(Post).where(eq(Post.userId, User.id)) + ) + }) + .from(User) + ) + }) + .get() + test.equal(nestedResult, { + user: { + id: user1, + name: 'Bob', + posts: [ + {id: 1, userId: user1, title: 'Post 1'}, + {id: 2, userId: user1, title: 'Post 2'} + ] + } + }) + await db.drop(User, Post) }) - await db.drop(User, Post) } diff --git a/test/integration/TestJoins.ts b/test/integration/TestJoins.ts index e57cf07..5e0f9f4 100644 --- a/test/integration/TestJoins.ts +++ b/test/integration/TestJoins.ts @@ -1,65 +1,67 @@ import {eq, type Database} from '@/index.ts' import {lastInsertId} from '@/universal.ts' -import type {DefineTest} from '@benmerckx/suite' +import type {DefineTest} from '@alinea/suite' import {Post, User} from './schema.ts' -export async function testJoins(db: Database, test: DefineTest) { - try { - await db.create(User, Post) - await db.insert(User).values({name: 'Bob'}) - const user1 = await db.select(lastInsertId()).get() - await db.insert(User).values({name: 'Mario'}) - const user2 = await db.select(lastInsertId()).get() - await db.insert(Post).values({userId: user1!, title: 'Post 1'}) - const post1 = await db.select(lastInsertId()).get() - await db.insert(Post).values({userId: user1!, title: 'Post 2'}) - const post2 = await db.select(lastInsertId()).get() - const posts = await db.select().from(Post) - test.equal(posts, [ - {id: post1, userId: user1, title: 'Post 1'}, - {id: post2, userId: user1, title: 'Post 2'} - ]) - const userAndPosts = await db - .select() - .from(User) - .innerJoin(Post, eq(Post.userId, User.id)) - .where(eq(User.id, user1)) - test.equal(userAndPosts, [ - { - User: {id: user1, name: 'Bob'}, - Post: {id: post1, userId: user1, title: 'Post 1'} - }, - { - User: {id: user1, name: 'Bob'}, - Post: {id: post2, userId: user1, title: 'Post 2'} - } - ]) +export function testJoins(db: Database, test: DefineTest) { + test('joins', async () => { + try { + await db.create(User, Post) + await db.insert(User).values({name: 'Bob'}) + const user1 = await db.select(lastInsertId()).get() + await db.insert(User).values({name: 'Mario'}) + const user2 = await db.select(lastInsertId()).get() + await db.insert(Post).values({userId: user1!, title: 'Post 1'}) + const post1 = await db.select(lastInsertId()).get() + await db.insert(Post).values({userId: user1!, title: 'Post 2'}) + const post2 = await db.select(lastInsertId()).get() + const posts = await db.select().from(Post) + test.equal(posts, [ + {id: post1, userId: user1, title: 'Post 1'}, + {id: post2, userId: user1, title: 'Post 2'} + ]) + const userAndPosts = await db + .select() + .from(User) + .innerJoin(Post, eq(Post.userId, User.id)) + .where(eq(User.id, user1)) + test.equal(userAndPosts, [ + { + User: {id: user1, name: 'Bob'}, + Post: {id: post1, userId: user1, title: 'Post 1'} + }, + { + User: {id: user1, name: 'Bob'}, + Post: {id: post2, userId: user1, title: 'Post 2'} + } + ]) - const noPosts = await db - .select() - .from(User) - .leftJoin(Post, eq(Post.userId, 42)) - .where(eq(User.id, user1)) - test.equal(noPosts, [ - { - User: {id: user1, name: 'Bob'}, - Post: null - } - ]) + const noPosts = await db + .select() + .from(User) + .leftJoin(Post, eq(Post.userId, 42)) + .where(eq(User.id, user1)) + test.equal(noPosts, [ + { + User: {id: user1, name: 'Bob'}, + Post: null + } + ]) - const rightJoin = await db - .select() - .from(Post) - .rightJoin(User, eq(User.id, Post.userId)) - .where(eq(User.id, user2)) + const rightJoin = await db + .select() + .from(Post) + .rightJoin(User, eq(User.id, Post.userId)) + .where(eq(User.id, user2)) - test.equal(rightJoin, [ - { - Post: null, - User: {id: 2, name: 'Mario'} - } - ]) - } finally { - await db.drop(User, Post) - } + test.equal(rightJoin, [ + { + Post: null, + User: {id: 2, name: 'Mario'} + } + ]) + } finally { + await db.drop(User, Post) + } + }) } diff --git a/test/integration/TestJson.ts b/test/integration/TestJson.ts index 476bbcb..96e73a5 100644 --- a/test/integration/TestJson.ts +++ b/test/integration/TestJson.ts @@ -1,23 +1,28 @@ -import {type Database, eq} from '@/index.ts' -import type {DefineTest} from '@benmerckx/suite' +import {eq, type Database} from '@/index.ts' +import type {DefineTest} from '@alinea/suite' import {WithJson} from './schema.ts' -export async function testJson(db: Database, test: DefineTest) { - await db.create(WithJson) - try { - const data = {str: 'string', sub: {field: 'value'}, arr: [1, 2, 3]} - await db.insert(WithJson).values({data}) - const [row] = await db - .select({ - id: WithJson.id, - str: WithJson.data.str, - sub: WithJson.data.sub, - arr: WithJson.data.arr - }) - .from(WithJson) - .where(eq(WithJson.data.sub.field, 'value'), eq(WithJson.data.arr[0], 1)) - test.equal(row, {id: 1, ...data}) - } finally { - await db.drop(WithJson) - } +export function testJson(db: Database, test: DefineTest) { + test('json', async () => { + await db.create(WithJson) + try { + const data = {str: 'string', sub: {field: 'value'}, arr: [1, 2, 3]} + await db.insert(WithJson).values({data}) + const [row] = await db + .select({ + id: WithJson.id, + str: WithJson.data.str, + sub: WithJson.data.sub, + arr: WithJson.data.arr + }) + .from(WithJson) + .where( + eq(WithJson.data.sub.field, 'value'), + eq(WithJson.data.arr[0], 1) + ) + test.equal(row, {id: 1, ...data}) + } finally { + await db.drop(WithJson) + } + }) } diff --git a/test/integration/TestMigration.ts b/test/integration/TestMigration.ts index 1709361..109036d 100644 --- a/test/integration/TestMigration.ts +++ b/test/integration/TestMigration.ts @@ -1,28 +1,30 @@ import {table, type Database} from '@/index.ts' import {id, text} from '@/universal.ts' -import type {DefineTest} from '@benmerckx/suite' +import type {DefineTest} from '@alinea/suite' -export async function testMigration(db: Database, test: DefineTest) { - const TableA = table('Table', { - id: id(), - fieldA: text(), - removeMe: text() - }) +export function testMigration(db: Database, test: DefineTest) { + test('migration', async () => { + const TableA = table('Table', { + id: id(), + fieldA: text(), + removeMe: text() + }) - await db.create(TableA) - await db.insert(TableA).values({fieldA: 'hello', removeMe: 'world'}) + await db.create(TableA) + await db.insert(TableA).values({fieldA: 'hello', removeMe: 'world'}) - const node = await db.select().from(TableA).get() - test.equal(node, {id: 1, fieldA: 'hello', removeMe: 'world'}) + const node = await db.select().from(TableA).get() + test.equal(node, {id: 1, fieldA: 'hello', removeMe: 'world'}) - const TableB = table('Table', { - id: id(), - fieldB: text('fieldA'), - extraColumn: text() - }) + const TableB = table('Table', { + id: id(), + fieldB: text('fieldA'), + extraColumn: text() + }) - await db.migrate(TableB) - const newNode = await db.select().from(TableB).get() - test.equal(newNode, {id: 1, fieldB: 'hello', extraColumn: null}) - await db.drop(TableB) + await db.migrate(TableB) + const newNode = await db.select().from(TableB).get() + test.equal(newNode, {id: 1, fieldB: 'hello', extraColumn: null}) + await db.drop(TableB) + }) } diff --git a/test/integration/TestPreparedQuery.ts b/test/integration/TestPreparedQuery.ts index ff8c5c8..fc54933 100644 --- a/test/integration/TestPreparedQuery.ts +++ b/test/integration/TestPreparedQuery.ts @@ -1,22 +1,24 @@ import {eq, sql, type Database} from '@/index.ts' -import type {DefineTest} from '@benmerckx/suite' +import type {DefineTest} from '@alinea/suite' import {Node} from './schema.ts' -export async function testPreparedQuery(db: Database, test: DefineTest) { - try { - await db.create(Node) - await db.insert(Node).values({ - textField: 'hello', - bool: true - }) - const query = db - .select() - .from(Node) - .where(eq(Node.textField, sql.placeholder('text'))) - .prepare<{text: string}>('prepared') - const rows = await query.execute({text: 'hello'}) - test.equal(rows, [{id: 1, textField: 'hello', bool: true}]) - } finally { - await db.drop(Node) - } +export function testPreparedQuery(db: Database, test: DefineTest) { + test('prepared queries', async () => { + try { + await db.create(Node) + await db.insert(Node).values({ + textField: 'hello', + bool: true + }) + const query = db + .select() + .from(Node) + .where(eq(Node.textField, sql.placeholder('text'))) + .prepare<{text: string}>('prepared') + const rows = await query.execute({text: 'hello'}) + test.equal(rows, [{id: 1, textField: 'hello', bool: true}]) + } finally { + await db.drop(Node) + } + }) } diff --git a/test/integration/TestSubquery.ts b/test/integration/TestSubquery.ts index d61d749..ac654d8 100644 --- a/test/integration/TestSubquery.ts +++ b/test/integration/TestSubquery.ts @@ -1,22 +1,24 @@ -import {type Database, exists, inArray, sql} from '@/index.ts' -import type {DefineTest} from '@benmerckx/suite' +import {exists, inArray, sql, type Database} from '@/index.ts' +import type {DefineTest} from '@alinea/suite' -export async function testSubquery(db: Database, test: DefineTest) { - const inner = db.select(sql`1`.as('number')) - const named = inner.as('named') - const result = await db - .select({ - contains: inArray(1, inner), - exists: exists(inner), - value: inner, - named: named +export function testSubquery(db: Database, test: DefineTest) { + test('subquery', async () => { + const inner = db.select(sql`1`.as('number')) + const named = inner.as('named') + const result = await db + .select({ + contains: inArray(1, inner), + exists: exists(inner), + value: inner, + named: named + }) + .from(named) + .get() + test.equal(result, { + contains: true, + exists: true, + value: 1, + named: 1 }) - .from(named) - .get() - test.equal(result, { - contains: true, - exists: true, - value: 1, - named: 1 }) } diff --git a/test/integration/TestTransactions.ts b/test/integration/TestTransactions.ts index c6162aa..0d91dd3 100644 --- a/test/integration/TestTransactions.ts +++ b/test/integration/TestTransactions.ts @@ -1,56 +1,55 @@ import {count, type Database} from '@/index.ts' -import type {DefineTest} from '@benmerckx/suite' +import type {DefineTest} from '@alinea/suite' import {txGenerator} from '../../src/universal.ts' import {Node} from './schema.ts' -export async function testTransactions(db: Database, test: DefineTest) { - try { - await db.create(Node) - await Promise.allSettled([ - db.transaction(async tx => { - await tx.insert(Node).values({ +export function testTransactions(db: Database, test: DefineTest) { + test('transactions', async () => { + try { + await db.create(Node) + await Promise.allSettled([ + db.transaction(async tx => { + await tx.insert(Node).values({ + textField: 'hello', + bool: true + }) + const nodes = await tx.select().from(Node) + test.equal(nodes, [{id: 1, textField: 'hello', bool: true}]) + tx.rollback() + }), + db.transaction(async tx => { + await tx.insert(Node).values({ + textField: 'hello1', + bool: true + }) + const nodes = await tx.select(count()).from(Node).get() + test.equal(nodes, 1) + tx.rollback() + }) + ]) + } catch (err) { + test.equal((err).message, 'Rollback') + const nodes = await db.select().from(Node) + test.equal(nodes, []) + } finally { + await db.drop(Node) + } + }) + + test('generator transactions', async () => { + const result = await db.transaction( + txGenerator(function* (tx) { + yield* tx.create(Node) + yield* tx.insert(Node).values({ textField: 'hello', bool: true }) - const nodes = await tx.select().from(Node) + const nodes = yield* tx.select().from(Node) test.equal(nodes, [{id: 1, textField: 'hello', bool: true}]) - tx.rollback() - }), - db.transaction(async tx => { - await tx.insert(Node).values({ - textField: 'hello1', - bool: true - }) - const nodes = await tx.select(count()).from(Node).get() - test.equal(nodes, 1) - tx.rollback() - }) - ]) - } catch (err) { - test.equal((err).message, 'Rollback') - const nodes = await db.select().from(Node) - test.equal(nodes, []) - } finally { - await db.drop(Node) - } -} - -export async function testGeneratorTransactions( - db: Database, - test: DefineTest -) { - const result = await db.transaction( - txGenerator(function* (tx) { - yield* tx.create(Node) - yield* tx.insert(Node).values({ - textField: 'hello', - bool: true + yield* tx.drop(Node) + return 1 }) - const nodes = yield* tx.select().from(Node) - test.equal(nodes, [{id: 1, textField: 'hello', bool: true}]) - yield* tx.drop(Node) - return 1 - }) - ) - test.equal(result, 1) + ) + test.equal(result, 1) + }) } diff --git a/test/query/Delete.test.ts b/test/query/Delete.test.ts index e945dd3..cc18c64 100644 --- a/test/query/Delete.test.ts +++ b/test/query/Delete.test.ts @@ -1,7 +1,7 @@ import {eq, table} from '@/index.ts' import {QueryBuilder} from '@/postgres/builder.ts' import {integer} from '@/sqlite/columns.ts' -import {suite} from '@benmerckx/suite' +import {suite} from '@alinea/suite' import {emit} from '../TestUtils.ts' suite(import.meta, test => { diff --git a/test/query/Insert.test.ts b/test/query/Insert.test.ts index 755496e..92a4803 100644 --- a/test/query/Insert.test.ts +++ b/test/query/Insert.test.ts @@ -2,7 +2,7 @@ import type {Builder} from '@/core/Builder.ts' import type {IsPostgres} from '@/core/MetaData.ts' import {eq, table} from '@/index.ts' import {integer} from '@/sqlite/columns.ts' -import {suite} from '@benmerckx/suite' +import {suite} from '@alinea/suite' import {builder, emit} from '../TestUtils.ts' suite(import.meta, test => { diff --git a/test/query/Select.test.ts b/test/query/Select.test.ts index 4ef7648..ee9cc44 100644 --- a/test/query/Select.test.ts +++ b/test/query/Select.test.ts @@ -1,6 +1,6 @@ import {alias, eq, table} from '@/index.ts' import {integer, text} from '@/universal.ts' -import {suite} from '@benmerckx/suite' +import {suite} from '@alinea/suite' import {builder, emit} from '../TestUtils.ts' suite(import.meta, test => { diff --git a/test/query/Union.test.ts b/test/query/Union.test.ts index 9c00ef1..beef477 100644 --- a/test/query/Union.test.ts +++ b/test/query/Union.test.ts @@ -1,6 +1,5 @@ import type {IsPostgres} from '@/core/MetaData.ts' import { - type Builder, except, exceptAll, intersect, @@ -9,10 +8,11 @@ import { sql, table, union, - unionAll + unionAll, + type Builder } from '@/index.ts' import {integer} from '@/sqlite/columns.ts' -import {suite} from '@benmerckx/suite' +import {suite} from '@alinea/suite' import {builder, emit} from '../TestUtils.ts' const pg = builder as Builder diff --git a/test/query/Update.test.ts b/test/query/Update.test.ts index 77b5c41..1e60680 100644 --- a/test/query/Update.test.ts +++ b/test/query/Update.test.ts @@ -1,7 +1,7 @@ import {sql, table} from '@/index.ts' import {QueryBuilder} from '@/postgres/builder.ts' import {integer} from '@/sqlite/columns.ts' -import {suite} from '@benmerckx/suite' +import {suite} from '@alinea/suite' import {builder, emit} from '../TestUtils.ts' suite(import.meta, test => {