Skip to content

Commit

Permalink
fixes
Browse files Browse the repository at this point in the history
Signed-off-by: Jim Ezesinachi <ezesinachijim@gmail.com>
  • Loading branch information
jimezesinachi committed Oct 15, 2024
1 parent 43d03a2 commit 903cbc1
Show file tree
Hide file tree
Showing 5 changed files with 78 additions and 47 deletions.
94 changes: 68 additions & 26 deletions packages/backend/src/QueryEngine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,29 +13,37 @@ import { SynthqlError } from './SynthqlError';

export interface QueryEngineProps<DB> {
/**
* The database connection string e.g. `postgresql://user:password@localhost:5432/db`.
* The database connection string.
*
* If you use this option, SynthQL will create a conection pool for you internally.
* e.g. `postgresql://user:password@localhost:5432/db`.
*
* If you use this option, SynthQL will create
* a conection pool for you internally.
*/
url?: string;
/**
* The name of the database schema to
* execute your SynthQL queries against.
*
* e.g `public`
* e.g. `public`
*/
schema?: string;
/**
* An optional SQL statement that will be sent before every SynthQL query.
* An optional SQL statement that will
* be sent before every SynthQL query.
*
* e.g.:
*
* e.g `SELECT version();`
* ```sql
* SELECT version();
* ```
*/
prependSql?: string;
/**
* A list of middlewares that you want to be used
* to transform any matching queries before execution
* A list of middlewares that you want to be used to
* transform any matching queries, before execution.
*
* e.g:
* e.g.:
*
* ```ts
* // Create type/interface for context
Expand All @@ -46,10 +54,11 @@ export interface QueryEngineProps<DB> {
* email: string;
* roles: UserRole[];
* isActive: boolean;
* }
* };
*
* // Create middleware
* const restrictPaymentsByCustomer = middleware<Query<DB, 'payment'>, Session>({
* const restrictPaymentsByCustomer =
* middleware<Query<DB, 'payment'>, Session>({
* predicate: ({ query, context }) =>
* query.from === 'payment' &&
* context.roles.includes('user') &&
Expand All @@ -69,7 +78,7 @@ export interface QueryEngineProps<DB> {
* A list of providers that you want to be used
* to execute your SynthQL queries against.
*
* e.g:
* e.g.:
*
* ```ts
* const films = [{
Expand All @@ -82,17 +91,21 @@ export interface QueryEngineProps<DB> {
*
* const filmProvider = {
* table: 'film',
* execute: async ({ film_id: filmIds }): Promise<{ film_id: number }[]> => {
* return films.filter((f) => filmIds.includes(f.film_id));
* execute: async ({ film_id: filmIds }):
* Promise<{ film_id: number }[]> => {
* return films.filter((f) =>
* filmIds.includes(f.film_id));
* },
* };
* ```
*/
providers?: Array<QueryProvider<DB, Table<DB>>>;
/**
* The connection pool to which the executor will send SQL queries to.
* The connection pool to which the
* executor will send SQL queries to.
*
* You can use this instead of passing a connection string.
* You can use this instead of
* passing a connection string.
*/
pool?: Pool;

Expand Down Expand Up @@ -139,11 +152,11 @@ export class QueryEngine<DB> {
TContext,
>(
query: TQuery,
context?: TContext,
opts?: {
context?: TContext;
/**
* The name of the database schema to execute
* your SynthQL query against
* The name of the database schema to
* execute your SynthQL query against
*
* e.g `public`
*/
Expand All @@ -161,12 +174,12 @@ export class QueryEngine<DB> {
if (
middleware.predicate({
query,
context: context,
context: opts?.context,
})
) {
transformedQuery = middleware.transformQuery({
query: transformedQuery,
context: context,
context: opts?.context,
});
}
}
Expand All @@ -190,19 +203,49 @@ export class QueryEngine<DB> {
TContext,
>(
query: TQuery,
context?: TContext,
opts?: {
/**
* The name of the database schema to execute
* your SynthQL query against
* When using middlewares (via the `QueryEngine` options),
* pass the data that should be used to transform
* the query, via this option
*
* e.g `public`
* e.g.:
*
* ```ts
* // Create type/interface for context
* type UserRole = 'user' | 'admin' | 'super';
*
* interface Session {
* id: number;
* email: string;
* roles: UserRole[];
* isActive: boolean;
* };
*
* // Create context
* // This would usually be an object generated from a server
* // request handler (e.g a parsed cookie/token)
* const context: Session = {
* id: 1,
* email: 'user@example.com',
* roles: ['user', 'admin', 'super'],
* isActive: true,
* };
* ```
*/
context?: TContext;
/**
* The name of the database schema to
* execute your SynthQL query against
*
* e.g. `public`
*/
schema?: string;
},
): Promise<QueryResult<DB, TQuery>> {
return collectLast(
this.execute(query, context, {
this.execute(query, {
context: opts?.context,
schema: opts?.schema ?? this.schema,
returnLastOnly: true,
}),
Expand Down Expand Up @@ -235,7 +278,6 @@ export class QueryEngine<DB> {

try {
const result = await this.pool.query(explainQuery, params);

return result.rows[0]['QUERY PLAN'][0];
} catch (err) {
throw SynthqlError.createSqlExecutionError({
Expand Down
4 changes: 2 additions & 2 deletions packages/backend/src/execution/middleware.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ describe('createExpressSynthqlHandler', async () => {
const queryEngine = createQueryEngine([restrictPaymentsByCustomer]);

// Create context
// This would be an object generated from a server
// This would usually be an object generated from a server
// request handler (e.g a parsed cookie/token)
const context: Session = {
id: 1,
Expand All @@ -51,7 +51,7 @@ describe('createExpressSynthqlHandler', async () => {
})
.one();

const result = await queryEngine.executeAndWait(q, context);
const result = await queryEngine.executeAndWait(q, { context });

const resultFromQueryWithContextManuallyAdded =
await queryEngine.executeAndWait(queryWithContextManuallyAdded);
Expand Down
20 changes: 6 additions & 14 deletions packages/backend/src/tests/benchmarks/bench.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,9 @@ describe('Benchmark tests', () => {
const q = from('actor').where({ actor_id: 1 }).one();

await collectLast(
queryEngine.execute(
q,
{},
{
returnLastOnly: true,
},
),
queryEngine.execute(q, {
returnLastOnly: true,
}),
);
})
.add(
Expand Down Expand Up @@ -67,13 +63,9 @@ describe('Benchmark tests', () => {
.one();

await collectLast(
queryEngine.execute(
q,
{},
{
returnLastOnly: true,
},
),
queryEngine.execute(q, {
returnLastOnly: true,
}),
);
},
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ async function tryExecuteQuery<DB>(
query: any,
returnLastOnly: boolean,
) {
return queryEngine.execute(query, {}, { returnLastOnly });
return queryEngine.execute(query, { returnLastOnly });
}

async function writeBody(
Expand Down
5 changes: 1 addition & 4 deletions packages/handler-next/src/createNextSynthqlHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ async function tryExecuteQuery<DB>(
query: any,
returnLastOnly: boolean,
) {
return queryEngine.execute(query, {}, { returnLastOnly });
return queryEngine.execute(query, { returnLastOnly });
}

async function writeBody(
Expand All @@ -109,7 +109,6 @@ async function writeBody(
return new NextResponse(stream, {
// This is a streaming request, so albeit
// counterintuitively, we always need to return 2xx

status: 200,
headers: { 'Content-Type': 'application/x-ndjson' },
});
Expand All @@ -130,15 +129,13 @@ async function writeBody(
// The `e` can be of any type, but in case its an error,
// we want to preserve the stack trace and any other
// information that might be useful for debugging

const error = SynthqlError.createResponseStreamingError({
error: e,
query,
});

// We need to catch errors here and write them to the streaming response
// We can't throw them because that would break the stream

return new NextResponse(
JSON.stringify({
type: error.type,
Expand Down

0 comments on commit 903cbc1

Please sign in to comment.