diff --git a/packages/backend/src/QueryEngine.ts b/packages/backend/src/QueryEngine.ts index a09c496c..e7b0b49b 100644 --- a/packages/backend/src/QueryEngine.ts +++ b/packages/backend/src/QueryEngine.ts @@ -1,40 +1,84 @@ import { Pool } from 'pg'; import { Query, QueryResult, Table } from '@synthql/queries'; -import { composeQuery } from './execution/executors/PgExecutor/composeQuery'; import { QueryPlan, collectLast } from '.'; +import { QueryExecutor } from './execution/types'; import { QueryProvider } from './QueryProvider'; import { execute } from './execution/execute'; -import { QueryExecutor } from './execution/types'; -import { QueryProviderExecutor } from './execution/executors/QueryProviderExecutor'; +import { Middleware } from './execution/middleware'; import { PgExecutor } from './execution/executors/PgExecutor'; +import { QueryProviderExecutor } from './execution/executors/QueryProviderExecutor'; +import { composeQuery } from './execution/executors/PgExecutor/composeQuery'; import { generateLast } from './util/generators/generateLast'; import { SynthqlError } from './SynthqlError'; export interface QueryEngineProps { /** - * 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. + * + * e.g.: + * + * ```ts + * // Create type/interface for context + * type UserRole = 'user' | 'admin' | 'super'; + * + * interface Session { + * id: number; + * email: string; + * roles: UserRole[]; + * isActive: boolean; + * }; + * + * // Create middleware + * const restrictPaymentsByCustomer = + * middleware, Session>({ + * predicate: ({ query, context }) => + * query.from === 'payment' && + * context.roles.includes('user') && + * context.isActive, + * transformQuery: ({ query, context }) => ({ + * ...query, + * where: { + * ...query.where, + * customer_id: context.id, + * }, + * }), + * }); + * ``` + */ + middlewares?: Array>; /** * A list of providers that you want to be used * to execute your SynthQL queries against. * - * e.g: + * e.g.: * * ```ts * const films = [{ @@ -47,17 +91,21 @@ export interface QueryEngineProps { * * 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>>; /** - * 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; @@ -71,7 +119,8 @@ export class QueryEngine { private pool: Pool; private schema: string; private prependSql?: string; - private executors: Array = []; + private middlewares: Array; + private executors: Array; constructor(config: QueryEngineProps) { this.schema = config.schema ?? 'public'; @@ -82,6 +131,7 @@ export class QueryEngine { connectionString: config.url, max: 10, }); + this.middlewares = config.middlewares ?? []; const qpe = new QueryProviderExecutor(config.providers ?? []); this.executors = [ @@ -96,12 +146,17 @@ export class QueryEngine { ]; } - execute, TQuery extends Query>( + execute< + TTable extends Table, + TQuery extends Query, + TContext, + >( query: TQuery, 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` */ @@ -113,7 +168,23 @@ export class QueryEngine { returnLastOnly?: boolean; }, ): AsyncGenerator> { - const gen = execute(query, { + let transformedQuery: any = query; + + for (const middleware of this.middlewares) { + if ( + middleware.predicate({ + query, + context: opts?.context, + }) + ) { + transformedQuery = middleware.transformQuery({ + query: transformedQuery, + context: opts?.context, + }); + } + } + + const gen = execute(transformedQuery as TQuery, { executors: this.executors, defaultSchema: opts?.schema ?? this.schema, prependSql: this.prependSql, @@ -129,30 +200,59 @@ export class QueryEngine { async executeAndWait< TTable extends Table, TQuery extends Query, + TContext, >( query: TQuery, 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> { - return await collectLast( - generateLast( - execute(query, { - executors: this.executors, - defaultSchema: opts?.schema ?? this.schema, - prependSql: this.prependSql, - }), - ), + return collectLast( + this.execute(query, { + context: opts?.context, + schema: opts?.schema ?? this.schema, + returnLastOnly: true, + }), ); } - compile(query: T extends Query ? T : never): { + compile(query: T extends Query ? T : never): { sql: string; params: any[]; } { diff --git a/packages/backend/src/execution/middleware.test.ts b/packages/backend/src/execution/middleware.test.ts new file mode 100644 index 00000000..7d4a408e --- /dev/null +++ b/packages/backend/src/execution/middleware.test.ts @@ -0,0 +1,61 @@ +import { test, describe, expect } from 'vitest'; +import { DB, from } from '../tests/generated'; +import { Query } from '@synthql/queries'; +import { middleware } from './middleware'; +import { createQueryEngine } from '../tests/queryEngine'; + +// Create type/interface for context +type UserRole = 'user' | 'admin' | 'super'; +interface Session { + id: number; + email: string; + roles: UserRole[]; + isActive: boolean; +} + +// Create middleware +const restrictPaymentsByCustomer = middleware, Session>({ + predicate: ({ query, context }) => + query?.from === 'payment' && + context?.roles?.includes('user') && + context?.isActive, + transformQuery: ({ query, context }) => ({ + ...query, + where: { + ...query.where, + customer_id: context.id, + }, + }), +}); + +describe('createExpressSynthqlHandler', async () => { + test('1', async () => { + const queryEngine = createQueryEngine([restrictPaymentsByCustomer]); + + // 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, + }; + + // Create base query + const q = from('payment').one(); + + const queryWithContextManuallyAdded = from('payment') + .where({ + customer_id: context.id, + }) + .one(); + + const result = await queryEngine.executeAndWait(q, { context }); + + const resultFromQueryWithContextManuallyAdded = + await queryEngine.executeAndWait(queryWithContextManuallyAdded); + + expect(result).toEqual(resultFromQueryWithContextManuallyAdded); + }); +}); diff --git a/packages/backend/src/execution/middleware.ts b/packages/backend/src/execution/middleware.ts new file mode 100644 index 00000000..b87a94fb --- /dev/null +++ b/packages/backend/src/execution/middleware.ts @@ -0,0 +1,41 @@ +export interface Middleware { + predicate: ({ + query, + context, + }: { + query: TQuery; + context: TContext; + }) => boolean; + transformQuery: ({ + query, + context, + }: { + query: TQuery; + context: TContext; + }) => TQuery; +} + +export function middleware({ + predicate, + transformQuery, +}: { + predicate: ({ + query, + context, + }: { + query: TQuery; + context: TContext; + }) => boolean; + transformQuery: ({ + query, + context, + }: { + query: TQuery; + context: TContext; + }) => TQuery; +}): Middleware { + return { + predicate, + transformQuery, + }; +} diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts index 27575d5e..d5253623 100644 --- a/packages/backend/src/index.ts +++ b/packages/backend/src/index.ts @@ -1,5 +1,6 @@ export { collectLast } from './util/generators/collectLast'; export { composeQuery } from './execution/executors/PgExecutor/composeQuery'; +export { middleware } from './execution/middleware'; export type * from './types/QueryPlan'; export * from './QueryEngine'; export * from './SynthqlError'; diff --git a/packages/backend/src/tests/benchmarks/bench.test.ts b/packages/backend/src/tests/benchmarks/bench.test.ts index 196ffe3a..26469328 100644 --- a/packages/backend/src/tests/benchmarks/bench.test.ts +++ b/packages/backend/src/tests/benchmarks/bench.test.ts @@ -1,14 +1,16 @@ -import { col } from '@synthql/queries'; import { describe, test } from 'vitest'; +import { col } from '@synthql/queries'; import { collectLast } from '../..'; import { from } from '../generated'; -import { queryEngine } from '../queryEngine'; +import { createQueryEngine } from '../queryEngine'; import Benchmark from 'benchmark'; import fs from 'fs'; import path from 'path'; describe('Benchmark tests', () => { test(`Find matching rows`, async () => { + const queryEngine = createQueryEngine(); + const suite = new Benchmark.Suite(); const lines: Array = []; diff --git a/packages/backend/src/tests/e2e/select.test.ts b/packages/backend/src/tests/e2e/select.test.ts index 2740d4af..daeed02c 100644 --- a/packages/backend/src/tests/e2e/select.test.ts +++ b/packages/backend/src/tests/e2e/select.test.ts @@ -4,7 +4,9 @@ import { collectLast } from '../..'; import { DB } from '../generated'; import { sql } from '../postgres'; import { findActorById, findCityById, from, movie } from '../queries'; -import { queryEngine } from '../queryEngine'; +import { createQueryEngine } from '../queryEngine'; + +const queryEngine = createQueryEngine(); describe('select', () => { function run, T extends Query>( diff --git a/packages/backend/src/tests/propertyBased/properties/cardinality.test.ts b/packages/backend/src/tests/propertyBased/properties/cardinality.test.ts index e842600d..dde675e1 100644 --- a/packages/backend/src/tests/propertyBased/properties/cardinality.test.ts +++ b/packages/backend/src/tests/propertyBased/properties/cardinality.test.ts @@ -1,9 +1,10 @@ import { test } from '@fast-check/vitest'; import { ArbitraryQueryBuilder } from '../arbitraries/ArbitraryQueryBuilder'; -import { queryEngine } from '../../queryEngine'; +import { createQueryEngine } from '../../queryEngine'; import { describe, expect } from 'vitest'; const queryBuilder = ArbitraryQueryBuilder.fromPagila(); +const queryEngine = createQueryEngine(); describe('No results', () => { const numRuns = 1000; diff --git a/packages/backend/src/tests/propertyBased/properties/cardinalityMany.test.ts b/packages/backend/src/tests/propertyBased/properties/cardinalityMany.test.ts index ecdd0166..f1df7436 100644 --- a/packages/backend/src/tests/propertyBased/properties/cardinalityMany.test.ts +++ b/packages/backend/src/tests/propertyBased/properties/cardinalityMany.test.ts @@ -2,10 +2,12 @@ import { it } from '@fast-check/vitest'; import { describe, expect } from 'vitest'; import { Query } from '@synthql/queries'; import { DB, schema } from '../../generated'; -import { pool, queryEngine } from '../../queryEngine'; +import { pool, createQueryEngine } from '../../queryEngine'; import { arbitraryQuery } from '../arbitraries/arbitraryQuery'; import { getTableRowsByTableName } from '../getTableRowsByTableName'; +const queryEngine = createQueryEngine(); + describe('cardinalityMany', async () => { const validWhereArbitraryQuery = arbitraryQuery({ schema, diff --git a/packages/backend/src/tests/propertyBased/properties/cardinalityMaybe.test.ts b/packages/backend/src/tests/propertyBased/properties/cardinalityMaybe.test.ts index ac59bcc2..babc86c3 100644 --- a/packages/backend/src/tests/propertyBased/properties/cardinalityMaybe.test.ts +++ b/packages/backend/src/tests/propertyBased/properties/cardinalityMaybe.test.ts @@ -2,10 +2,12 @@ import { it } from '@fast-check/vitest'; import { describe, expect } from 'vitest'; import { Query } from '@synthql/queries'; import { DB, schema } from '../../generated'; -import { pool, queryEngine } from '../../queryEngine'; +import { pool, createQueryEngine } from '../../queryEngine'; import { arbitraryQuery } from '../arbitraries/arbitraryQuery'; import { getTableRowsByTableName } from '../getTableRowsByTableName'; +const queryEngine = createQueryEngine(); + describe('cardinalityMaybe', async () => { const validWhereArbitraryQuery = arbitraryQuery({ schema, diff --git a/packages/backend/src/tests/propertyBased/properties/cardinalityOne.test.ts b/packages/backend/src/tests/propertyBased/properties/cardinalityOne.test.ts index 867a8160..9a85708d 100644 --- a/packages/backend/src/tests/propertyBased/properties/cardinalityOne.test.ts +++ b/packages/backend/src/tests/propertyBased/properties/cardinalityOne.test.ts @@ -2,11 +2,13 @@ import { it } from '@fast-check/vitest'; import { describe, expect } from 'vitest'; import { Query } from '@synthql/queries'; import { DB, schema } from '../../generated'; -import { pool, queryEngine } from '../../queryEngine'; +import { pool, createQueryEngine } from '../../queryEngine'; import { arbitraryQuery } from '../arbitraries/arbitraryQuery'; import { getTableRowsByTableName } from '../getTableRowsByTableName'; import { SynthqlError } from '../../../SynthqlError'; +const queryEngine = createQueryEngine(); + describe('cardinalityOne', async () => { const validWhereArbitraryQuery = arbitraryQuery({ schema, diff --git a/packages/backend/src/tests/propertyBased/properties/defer.test.ts b/packages/backend/src/tests/propertyBased/properties/defer.test.ts index bdcbea43..f64740ae 100644 --- a/packages/backend/src/tests/propertyBased/properties/defer.test.ts +++ b/packages/backend/src/tests/propertyBased/properties/defer.test.ts @@ -1,7 +1,7 @@ import { describe } from 'node:test'; import { ArbitraryQueryBuilder } from '../arbitraries/ArbitraryQueryBuilder'; import { test } from '@fast-check/vitest'; -import { queryEngine } from '../../queryEngine'; +import { createQueryEngine } from '../../queryEngine'; import { expect } from 'vitest'; import { queryHeight } from '../../util/queryHeight'; import { flattenDeferredQueryResult } from '../../util/flattenDeferredQueryResults'; @@ -9,6 +9,7 @@ import { Query } from '@synthql/queries'; import { DB } from '../../generated'; const queryBuilder = ArbitraryQueryBuilder.fromPagila(); +const queryEngine = createQueryEngine(); describe('property based tests for defer', () => { const numRuns = 100; diff --git a/packages/backend/src/tests/queryEngine.ts b/packages/backend/src/tests/queryEngine.ts index 81019625..9bbcdd33 100644 --- a/packages/backend/src/tests/queryEngine.ts +++ b/packages/backend/src/tests/queryEngine.ts @@ -1,6 +1,7 @@ import dotenv from 'dotenv'; import { Pool } from 'pg'; import { QueryEngine } from '../QueryEngine'; +import { Middleware } from '../execution/middleware'; import { DB } from './generated'; dotenv.config(); @@ -10,7 +11,10 @@ export const pool = new Pool({ 'postgres://postgres:postgres@localhost:5432/postgres', }); -export const queryEngine = new QueryEngine({ - pool, - schema: 'public', -}); +export function createQueryEngine(middlewares?: Array>) { + return new QueryEngine({ + pool, + schema: 'public', + middlewares, + }); +} diff --git a/packages/docs/static/reference/assets/navigation.js b/packages/docs/static/reference/assets/navigation.js index 4b21da91..1b40451e 100644 --- a/packages/docs/static/reference/assets/navigation.js +++ b/packages/docs/static/reference/assets/navigation.js @@ -1 +1 @@ -window.navigationData = "data:application/octet-stream;base64,H4sIAAAAAAAAA5WXUU/bMBCA/0ueyxhssK1vpVQa2wRdW22aEELGuVIL10ltBzWa9t+nOGnjxM7ZvJLPn8++83G9/5to2OtknDwR+gIiPVWSJqMkJ3qTjJNtlhYc1Gnz8VFJ+m6jtzwZJS9MpMn4fJTQDeOpBJGM74+ynwXIciaemYBWRjlRqiezwK747Pzzv9HRtyyF3uz4TMpM4kKbxIzWxnOZ5aq1MqFBrgkdjtQs6N3DxWVfPudExFkrEtPRjHOg+gdRuhWuC0E1y0TXZ6Fd4+XHjnCbZwrM5mFjyzrKB1vKmb94KGeewrFWbohIOcgT2OcSlPJbGuixgaJLkUogGmb1qqY6vtYu39F92yAK9EYO5xKw1/ihKiL6RLew10MH0WXu07pLeju9//Lp7MLOibtiAbsC7AqM3qtZGdqyvmbsdG6ajrsOLUYTxISWmcqBDqSn/R6dnGcQIIkGX9A93QFFQ9wVIBkMvInmY3Rw04wXW3ENa29fsm1HEutLR6hqiCA1A38b9YrbNVFbvMGMCpd0A1sSlNUYJlqRJw4xV3kAMdlElNdX/ZdlSwwQekATUfZaulfkaeVelwk84DJMrKtzW8M6964c4xUTRJZ3OaY7MLGu6i1mTiv1GWsy5J0SmTJBONNoQiwsaDQVjsoMEedZlTma3ZaK8/0ivIgQGixkvIY1SAnpAlTBnX85trRLhrw3gvIiRaNskJDpW8YEXn81EfIEH2zUa63HSCLJFrQ7Dji6IxnlDSfBwkLGBazxazNAyLKEasrFNDUR8gRbXFR/+70BiVoMEGWZclKosKvGgp3t5nay+PN4N58tJqu7xbLVvhLJqpP1+luP7/o/dGa1jPsmHNtGM478BGFqqGb9tj6PqntFNmT0lFpHlFebhTQGQiS7oR9ZtsRAJ2foJCiBDM2p5lP0FNgMyNNMmD/45pdW2IXRyaomTZq+g9PTHOUBDFWxGgi3reFW3WX7F9qp4CaKucxeWeqvPyfkA4wW3+A9+MQOjqjVW8Q92FNbrbhQ0ESBO1suSuZkDHd6S8y8gYf/O5IR9qYSAAA=" \ No newline at end of file +window.navigationData = "data:application/octet-stream;base64,H4sIAAAAAAAAE5WXbU/bMBCA/0s+lzHYYFu/lVJpbBN0Ldo0IYSMcyUWjpPaDmuF9t+nOmnjxM7ZfK0fP3658+V695po2OhknDwS+gwiPVaSJqOkJDpLxklepBUHddwMPihJ32U658koeWYiTcano4RmjKcSRDK+O8h+ViC3M/HEBLQyyolSPZkFdsUnp5//jQ6+5VbobM1nUhYSF9okZrQWnsuiVK2VCQ1yRejwTs2E3j2cnfflc05EnHVHYjpacA5U/yBKt8JVJahmhej6LLRrPP/YEeZlocAsHja2LKLMWZpy+EskhIQt6eju7T1y5s9FypknD62ZGREpB3kEm1KCUn5LAz00UHRmUwlEw6ye1STb19rlO7hvGUSB3sj+XAI2Gj/Ujog+0TVs9NBB9Lb0ad0pvZXef/l0cmbHxJ2xgHUFdkJHr9XMDC1ZXzN2OjdMh1WHJqMBYkLLQpVAB8LTjkcH5wkESKK9j6qn26PoFtcVSAYDb6IZjN7ctOBVLi5h5S1ztu1AYmXuAO3qK0jNwF+VveJ2TtQSbzCjwiXNICdBWY1holvyyCHmKvcgJpuI7eVF/2XZEgOEHtBEbHtfCK/I82XwuszGAy7DxLo6tzWsc+/KMV4wQeT2psR0eybWtXuLhVNKfcaaDHmnRKZMEM40GhALCxpNhqMyQ8R5brclGt2WivP9IryKEBosZLyEFUgJ6QJUxZ1Pji3tkiHvlaC8StFdNkjI9K1gAs+/mgh5gg826rXWXSmRJAfttgOO7kBGecNBsLCQcQEr/NoMELIsYdc0Y5qaCHmCJS6qvv3OQKIWA0RZppxUKuyqsWBlu7qeLP483Mxni8ntzWLZal+IZLuT9epbj+/6P3R6tYL7OhzbRguO/P1gaihn/bY+j6p7STZk9KRaR1TuFgtpDIRI1kP/2WyJgY5O0E5QAhnqU81QdBfYNMjTQpgffP1LK+zCaGdVkyZM38GpaY5yD4ayWA1st83hVt1l+xfayeBmF3NZvLDUn3/OlvcwmnyD9+ATOziiVm8R92BPbrXiSkGzC9zZclEyJ2K405ti5g3c/wcPd/4t9RIAAA==" \ No newline at end of file diff --git a/packages/docs/static/reference/assets/search.js b/packages/docs/static/reference/assets/search.js index 38c7824d..a7f5a95d 100644 --- a/packages/docs/static/reference/assets/search.js +++ b/packages/docs/static/reference/assets/search.js @@ -1 +1 @@ -window.searchData = "data:application/octet-stream;base64,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"; \ No newline at end of file +window.searchData = "data:application/octet-stream;base64,H4sIAAAAAAAAE71dW4/cuI7+Kwv3eazUWL673ybJADtnF2eySXAOFo2g4VSpOz7jclXbrkx6g/z3heQbqSJ9K3deJsmUKH7iR1ISZVvfreL4V2nd3n23/kzzvXXrbKw8OUjr1vqc7P6U+f6XsthZG+tcZNatdTjuz5ksf2l+uy+L3fZLdcisjbXLkrKUpXVrWT82l73tspTsaZelM3r5kuT7TBav5LdTIcuS7LFpc9+0WdB7Lr9Vg12rBjP6TfOqOJYnuaN77X+e0efTWRappC3Q/Dajt0ImDDj9y2hPgdcTfcwyuav+OymrrrOHc76r0mOOHQe0JDrfWKekkHlleCKj83A6lvJ/zrJ4HlXaN12k1ffdoFN8f189n+RclTfo/3R9jMLAPaFB9wCF7fSGKZ+y1+c028tiDYxb1N0VcNsxM6iT8+NB5pXcLyKURH7R5arogSse0v0+k38lxZhX9A2XuKHj906oQb3LkrzTmOaVLB6SnZGpu4ZLNCKGfvsmd2c1pv/4mB7kPL03nfB9IzzORT9EBpD6LU/zxyV4WtkV4XyQVZXmj+VMJEBsGYjB7DQPw5y81HfRjYAxzMcifXyUxVzDALEVnWWhn6wHYgGAxcovMsZv+WOay3fF8TSNDdD+6vyhtC3ReVMLTrQAHCE3Qe6+yEOyDEsnux6cUyFPMt9/eFpoHiS/Hqx+rlrmKje4gzXtdfya7qfmE8JcvfiKoI7HpfTVkutByY6Pj1MT3QWaXvg6QE5EJZ4OU9M5B2hJrvEF2Hge87IqzrvqWMxSeYMFZ5lgkmdMAnHhEVW5f5WWr05F+jWp5BVYjMw3CQ2R8VbDQ6S+aRaiU95quKjcNwkYl/NWQyb1OvpYzMQFxdZB5dheZKCaF943vdDSOKMw/Jrv/5Wk1SIovewqiNT+Mc1mWqUXWophcD8wB8O2+fOVmL0vaHozOhqoWKwDcFvOXwSZIEfqFKekSA4zo4+F23W2MmIjME5ZkuZzI6IVWjwJgiXAh+e8+vKU/VYUIxMybLioRILir5BJJd8mVfI5KeWbY55LXZNZAONmrK9xKyEbDCL+8JR15ZLlWKle1kT5rpuDVwA70NmamN/L8nTMS/mhKmRySPPH5YjZrtbE+/fymL9LivIqpEQna2J8kxT7NE+ytHpejpHoZDHGJRsBAtfMnQADBi/eirkoaol1tI+uAy7VT570p+g/yLJMHudC6KUu1q1p/kUWaSX316DaHfdzITUii60CD7G09/9Wn1o2zf+zPnAkjhKoU86BHoYnUOpYFQZ6HAq/D6R/yG8Vg085CXFMeikxDU93EDtxRTtP/c3IStZEQfQzy0rv5dNZglPRyWgbweU2M51sgMFLD+uAcbJr4HqUuSySijo0M47H25bDWo0jd+6kmlAHD893x2xYDTyFJ3XoVf2IFt3mSj1PzEkp1KPbvBKLNE079ea03TxNOekGqmvp+r/ckuOhOI5ZlsWwbYRnAaH2Vjjm8Xl1Hd8Qz4SnDhbYf0jPWI6DQx18kgBZe4LG6Rae8kRAKdWTIrP0dyLrIPjriyxmmXzbSqyjP8132Xk/D0Evsw6GLD2k80hoJdbRf3x4KOU8AJ3IOgh2/d5kFgwstxIbyf/NA9EIrKP9sTieT6/nAehl1sHwJSm/zALQCKyjXf8xR3sjsFi7Odv8mj9/TD5n8q18GMABWv2EmcfUNmf+geNhd2jZ+ZCX8wFse8lZQCax8Pb1MKC3r5dYnqN7wuBXUje2nGmbLFHn9ppe//6PX9//7/0f7357/+vHP95/6DR+TYpUDQdrNZuvMdjXaZ4Uz3+c1M7iaG5nkXLUcE3VE5Suoe7NpBkMtFpFqQ6+IX26wXqq/plk56FAAa3WU/pxODX2jdZQ+fvoerBpsYayvx/TfNBD6wZrqKofXVM7YlldFJYu5teu4c/aVGGFs3dX/cCmFGbnYNguBDK27dgvQ6PlVsbydSSoB+C0otcjIv31vSzP2ejGpG61RpC8lQ+yKOR+VC9uuIbq9/JhMBPo339CNPZ65gRhjZ7xr78VgwvpC43b+1pgolritYErh6wBNN2+ErORbPWAuUQ0stQcAbNt5RdAGtuDjy0mxqB1HayP7TgtNGhcWngVTPBZ7w/sE84QW91qSdziEOKfp2a03dyPPEYNh96MZcrcOUGzNruzguK9LHdFelL15zn6gdjE6B2GcSqOJ1lUKf2QNoeil1oFRCGfzmkh93MgtDKrAEj2+1SZNMneLbIHJb8KsL/t5cMsJPeNwALFU98DGlQ957ymEa7H+DI4tvf3ab6X3+ZNeBCY0dGENe+1EKeugwdQjsw4q1p0u45hRyAvTJfDyHGnLwl/WZodRo/6fEnwS9LzMHTQ40sCfzgWh6RaD3bX30uCvnYuGh4C0/uLZpu0ytZMN013Lwk5reRhRaO33a0M+UUnyhp0+885K94h9I0pfsq8hAcwvdY1dRRjbl9+XHeabft7Scc/pHl6OB/WQ913+KKwk28rw+46fEnYMl8Tc9PbyoBhWeDi5JjBfM3Z8eyEgE9uJ0f52KnxjAUnRjB3UTkGZPrSEeOYuTwcgzF1EYhBzFrojUFYsjLCcBavfghoMDDqY7q3E/bqfcurg8N8rmGaxps5DzWAcV237GBAzKkWgC7aIVyRNYYBTV8sXKJabSc4AnFmfM8GOjXgR2DOygCzQS5JCSOAF+eIKeDJpDEZ8E+ZT7G26RNqP5zrZ1QDw9wpdRTKgiBcNKmOApkdZAum1VEQVwXRlRMrBY4MkgXQepGfGziG3gUhBAY7+HA7Om+dCwt1sSq4+hnyq8ChLlYFpx+xvwob7GFVaPk5y65CBjpYl9DyXZEekuL5v+TzckpxJ9cCNB8y+TD2ukfdYI3nWcYeqV3tedp/jbxAon9fTdGbLDmXo+rqVle+epaWzDN79DtZZvOrteMnkjily59LAl+tK2Xz5iOhrv9Qbt9sWF//0V1SW1n3oa0FY5VSabQde8lvRHPavjw8SfdF6/m6L+KfUV+7ca/6asVg0E1f75pPwg0O2Wh7ld7eW94c80p+o76aTPlW03qBseHDQbRmMB1cDHqxXqNcuj8d03y+yhsgOaLbEB9at8uy+j1PF6DBwosBgbchSpqS/l2Ii6hvms4I+k8bq34C4va79VUWpdqM3VrO1t3G1sZ6SGW2V9+Eb19x2h0P6uPJ1qfmt3/Knf5+2O1d3eQX29rc2Rsv2trxp0+bu1ZA/3/9P3QrYW3uBNFKoFaOtblzNq699aNo425cb+sEArV3UHsXtveo9i5q71mbO59A4aFWvrW5C4hWPmoVWJu7kGgVoFahtbmLiFYhahVZm7uYaBWhVrGyob1x/K2IPdQuxrZWphektQ1SNCsO1RDzIpTZhUs1xIQIZW/hUQ0xE0KZXPgNZRsRNCxiGcyLUAR0LTciJGUwS0LRISgGBCZKhKx1BSZLKFYERZfAfAlNWLjxnG3sGl1ixhzFi0NFkYMZcxQvDkWtY0SSDiWKWgcz5ugYcjduuLVtB7fElDmKDcejhuNgohzFgeOTLTE9jiLBoYLNwfQ4igOHtKWD6XEUCQ7FuIPpcRQHDsWjg9lxFQeuTel2MT2uYFG6mB/XYc3uGjnOZc3uYoJcjzW7iwlyFQsu5UYu5sdVLLiUG7mYH1eR4FIZwsX0uIoEl8oQLqbH5dOdi/nxND9kXsf0eIoDl/I2D7PjKQpcKrt7mBxPMeBSzuYZM48iwKWczcPUeIoAj0oFHqbGUwR4FIcepsZTBHgUhx6mxlMEeBSHHqbGU/b3KA49zIzPR46PqfEVAR7FoY+p8RUBXrBx4q1nzPU+5sZXDHgUiT7mxlcMeBSJvrEw0NxQJPqYG18x4FMk+pgbn591fEyOryjwKbp9TI6vKPApun1MTqAY8Cm6A8xNoBjwKboDzE2gCPApEgNMTaAI8APKLwLMTeCxBgowOYGiwCdzb2Cs3AJeO6Yn4OkJMD1BxGvH/ASan4hsiQkKNUEx1TLEDIWKh4CMsxBTFCoiAsqNQkxRqHgIKDcKMUOhoiFwSd2YoVAvrCk/CjFBoWIhoPwoNFbXioSASughpidUHARULggxO6GiICBX7JicyGZdI8LkRJocksYIkxMpCkIqbUSYnEhREFIsRpicSDEQUixGmJtIMRBSySDC3ER630ORGGFuIsVASJEYGZufiHWgCJMTKQpCMnAjzE6sKAgpvmNMTqwYCCm+Y8xN7LAhFmNyYk0ONUvEmJxYURBRdMeYnNjnd36YnThgZ8cY0xPrnakgx4P5iRUJEeVDsbE/VRxElA/F5gZVkRCR20Tb2KLaioaIcqP6J9jU4bdwtrFPtRUVEZU56p9gU34Kqn+DbRUdEb3ls42dqR2w3lz/BttqtshtrG3sTu2I3VnUv8G28UBbgzNdO6BdS1zUFTRpMdmvWVrQFYTYVpWAOAyNtgZruogQC7qtQZsuHsQO3dagTRcNYrK+YdYT6oIC7Q1mSWGopmAWFXTtgI5HYdYVdPWAo8KgTRcQGCqM4oLQNQSGCsesCDk8FUaJQehCQkyGu1FjEM4Aa0aVQehaQkymBqPMIHQ1ISbD3Sg0CGeANKPUIHRFgSHCqDYIXVSIqSlJGPUGoasKMRntRsFB6LJCTBajjIqDcOv0SM03wjXLeLqOZ5M1RKPqIHRtgTGCUXcQurzAeKNRehC6wsB4o1F9ELrIwHijUYAQus7AuJhRgxBNEYIskRpVCKGLDfQ6TxiFCKHrDYyTGbUI4fE7XmGUI4SuOjA288wKrMfbzChKCF17YGxm1CWEV2dIMpsapQlR1yaYsRm86SIE4ztGgULoOgRnB4M3XYlg7GBUKYQuRjB2MAoVwq/jjUx7RqlC1LUK2g5GtUL49XqEzHu+WTGvV45k4jNqFsKveSMzlFG2EE3dgkxRRuFC6PqEsMkcZdQuhM9vjoVRvhC6SsGstYwKhqhLGHRsGkUMoWsVQpB50qhjCF2tEPRZi1HJEHUpgz5uMWoZQlcsBH3iEpgnHJo3QVaIhVHQEHVFQ5DOY5Q0hC5csB0bzOnahRCkpxl1DRHWB1SkpxmVDaHrF0LQLmEUN0RYU0e6mlHfELqMIchDF2GUOEToDYGoudPnql9lUcn97/X56t1d91T9d6t57fxW2O1p73crtm6//9hYIqz/dJt/+82/A6/5s/l3ZNd/xn4jZzvtX5qWagXa/KWRUUuW+i+eUH/50Z/oqn+pgfTPfMKnYnvEsdMjVivnpt/2L27UKmhh+aym/Hn/GXYdAmNEDi/1VH+MEAgCTJHHCzaPRQJBAQTdEcG9eqa7lw2iXjaMOdn2suELzALZkRFHF6/3ssBONiepPxR4f2w+T4goDF0wan+wA/UhGiDoA8FwRLD9gCIQ94B4wIij7+MCawPMYeNsUcT10d4wD8wNrM1J6QscehEPEOxzXqW/HN/L+GCIAa9I33ae6XvRe1noT7yk/l4RsGkAbNpmEJs1rpY3HFktznrjeKOydF5QS5u+F58L364XQxhGg8tFU/eSE/CLGIxftLmHi4q6AzMJh8Dy8QBp50P+tf56HJANgSyvtrm9rZdzgbVc1o/rq8YvcgccMysKLqoBakHqcJsJxGdh61seQEDK+hIeECJgFB7Lmu5m31yStesuybrozQXj8tiA070115M0jz19aW+uAMhAXz6X4+q+/l0e81N9E9LlAEFoeKytdTfqVgsejw8c3B+2VHONZfmUyfbWq0tcIJ/xEau7K5orqcr2SqrLzgA2j009urNhUMC5PM7m++Y7hEXzwUKQ/2ACsTmnNFNHDJOX4LWCd42AyhgKt+nDsdvFTLuGYanvH0EEyUzAXkMuE9YvdsMcaMMcyIoV2OZgSmZXWt2dmiDWAOcu50CNXJLv/9IXYAJxYHWXc5nOU9AwfTjZDEoayxYX+LzLEd3dUAjkQIr2WLOC+46gbC/KSbafjwEuCR3Z4ZaV9a0YIEMAmEHjeAHnd/0lOaADADbgwkd/eP8zXloBRkLOFYiU5hi22VjcUOuP7QONYNESct6jvpcFIhWQH7ebGMGZB75nBCMM6BXsrNDdlQEAg9AMmzQRcw5hXDgEGQKOyAmXp/p1mj8l3i7YMKn47MBLvVg49W9gwLwEF8EBR1ZaFvIBr/sF2qwEPPZm/tMYzAGgwA9Y1uuP6oBggpw5nHv++5jmxl4F2CvmxlrfgwF4BpEbcv7R3GQCpIBd2XTfXaEOAgisOx2O0e6bJcAk0BVcLul3V+SByQKkGJ+zSX9ZNaIPTrPcGNE912CcgHmnyWwu50Tdp2XAeCM4Xi6z6RaQFTDYkLNudxUZtBKYZgbEBtZ6IFv4nMdedlG0l8KBnoBnsTHfvx4I0wW0mc9FW3snDrAaiPSQGz6OtAiqsjlVzfVnYGzAKwLOo9qrkMGMDPIBW8Q6ZQlaAjiAEYdzICWUGyEqQmjGZtrhlu+nIy4GOEC4Lbyx4dqv+lEXwBnbGpvL0cIU7OCyUrQ1Qqdd7LptjdBz27+w42tecsLBDfhg0zN8nw46G/DuAUmUon0gFDSQgyG9z7K+gxpiBgnU4UYLZJVh8aBBXDoco/xMHMGqL5cIa3HDkQVcWnJ+oCWJ/RVI4TE36v7VIDiXApOzcqimFMFZ2+aAXiw0IrhGsDnDNokyzfE0LGCUCzZ59V9ZADECV0aiDQSnDRa3q5q3ZW62ZtJ+whr4CtxDNF27Xam+LZfbHCHt5XUgAkC+DdqSH1tTgN8DgPMDtDRb3SxlVaX5I67VwXTPuYORxly48mVN95R9PqfZ3ly1QpfgROuJdNe+KQfFIdx27S5CdsR1T5e1KdCNx7lWI3zqXhyFOGBEBCxbQwtoGPtBm7bZLeIF5xGcy+x2MmN3itSZh3oGBOQfzg5VipdiaOxdCLHCFcYdQ1d1WLEifXw05iZUiGHtVJr14Bgaip0fTCkPzmZNfMfdAVxbUhJtwDpt5LutQbw2KXhtumHX6OpFUJhi4FacW0qdS1m2r6xDr4IL+4Czbi9Mxhg0WMg51EXtPAKwY46e5u5MkP4AqWFrVvasBXzVA7oxWtZxSUzL7prPJ8DhwnmGXrp+2lin9CQztfC4vfv048f/A+Ram0RemQAA"; \ No newline at end of file diff --git a/packages/handler-next/src/createNextSynthqlHandler.ts b/packages/handler-next/src/createNextSynthqlHandler.ts index fbc165a0..db85be1d 100644 --- a/packages/handler-next/src/createNextSynthqlHandler.ts +++ b/packages/handler-next/src/createNextSynthqlHandler.ts @@ -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' }, }); @@ -130,7 +129,6 @@ 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, @@ -138,7 +136,6 @@ async function writeBody( // 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, diff --git a/packages/introspect/src/tests/pg_types.test.ts b/packages/introspect/src/tests/pg_types.test.ts index 96f82994..7bc3e4ba 100644 --- a/packages/introspect/src/tests/pg_types.test.ts +++ b/packages/introspect/src/tests/pg_types.test.ts @@ -1,9 +1,8 @@ import { describe, expect, test } from 'vitest'; import { Pool, types } from 'pg'; import Ajv2020 from 'ajv/dist/2020'; -import { generate } from '../generate'; import { JSONSchema } from '@apidevtools/json-schema-ref-parser'; -import { AnyDB, AnyTable, Schema } from '@synthql/queries'; +import { generate } from '../generate'; // Database connection configuration const client = new Pool({ @@ -12,8 +11,9 @@ const client = new Pool({ }); // Use the OIDs imported from pg.types to set custom type parsers -// We're replicating the bheaviour here so it matches the -// adjustments we made in wellKnownDefs for values of these types +// We're replicating the behaviour in the PgExecutor +// here so it matches the adjustments we made in +// wellKnownDefs for values of these types types.setTypeParser(types.builtins.DATE, (value) => value); types.setTypeParser(types.builtins.TIMESTAMP, (value) => value); types.setTypeParser(types.builtins.TIMESTAMPTZ, (value) => value);