Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/public-kings-like.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@slonik/sql-tag": major
"slonik": major
---

Replaced `any` output types (e.g. for `sql.unknown`) with `unknown`
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2215,18 +2215,18 @@ Produces:
) => QuerySqlToken;
```

Creates a query with Zod `any` type. The result of such a query has TypeScript type `any`.
Creates a query with Zod `unknown` type. The result of such a query has TypeScript type `unknown`.

```ts
const result = await connection.one(sql.unsafe`
SELECT foo
FROM bar
`);

// `result` type is `any`
// `result` type is `unknown`
```

`sql.unsafe` is effectively a shortcut to `sql.type(z.any())`.
`sql.unsafe` is effectively a shortcut to `sql.type(z.unknown())`.

`sql.unsafe` is as a convenience method for development. Your production code must not use `sql.unsafe`. Instead,

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { createPool, SchemaValidationError, sql } from 'slonik';
import type { DatabasePool, Interceptor } from 'slonik';
import { afterAll, beforeAll, beforeEach, describe, expect, it } from 'vitest';
import { z } from 'zod';
import { ColumnIdentifiers } from '../types.js';

const POSTGRES_DSN =
// eslint-disable-next-line n/no-process-env
Expand Down Expand Up @@ -599,7 +600,7 @@ describe('createConnectionLoaderClass (with validation)', () => {
const loader = new UnsafePersonConnectionLoader(pool);

const result = await loader.load({
orderBy: ({ uid }) => [[uid, 'ASC']],
orderBy: ({ uid }: ColumnIdentifiers<{ uid: string }>) => [[uid, 'ASC']],
});

expect(getNodeIds(result.edges)).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
Expand Down
57 changes: 46 additions & 11 deletions packages/slonik/src/helpers.test/createIntegrationTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -583,9 +583,14 @@ export const createIntegrationTests = (

const error = await t.throwsAsync(
pool.connect(async (connection) => {
const connectionPid = await connection.oneFirst(sql.unsafe`
SELECT pg_backend_pid()
`);
const connectionPid = await connection.oneFirst(
sql.type(
z.object({
pg_backend_pid: z.int()
})
)`
SELECT pg_backend_pid()
`);

setTimeout(() => {
void pool.query(
Expand All @@ -609,7 +614,12 @@ export const createIntegrationTests = (

const error = await t.throwsAsync(
pool.connect(async (connection) => {
const connectionPid = await connection.oneFirst(sql.unsafe`
const connectionPid = await connection.oneFirst(
sql.type(
z.object({
pg_backend_pid: z.int()
})
)`
SELECT pg_backend_pid()
`);

Expand Down Expand Up @@ -699,7 +709,12 @@ export const createIntegrationTests = (
)
`);

const result = await pool.oneFirst(sql.unsafe`
const result = await pool.oneFirst(
sql.type(
z.object({
payload: z.instanceof(Buffer),
})
)`
SELECT payload
FROM person
`);
Expand Down Expand Up @@ -779,13 +794,23 @@ export const createIntegrationTests = (
driverFactory,
});

const firstPersonId = await pool.oneFirst(sql.unsafe`
const firstPersonId = await pool.oneFirst(
sql.type(
z.object({
id: z.string(),
})
)`
INSERT INTO person (name)
VALUES ('foo')
RETURNING id
`);

const secondPersonId = await pool.oneFirst(sql.unsafe`
const secondPersonId = await pool.oneFirst(
sql.type(
z.object({
id: z.string(),
})
)`
INSERT INTO person (name)
VALUES ('bar')
RETURNING id
Expand Down Expand Up @@ -1297,8 +1322,13 @@ export const createIntegrationTests = (
const error = await t.throwsAsync(
pool.connect(async (connection) => {
// connection pid
const connectionPid = await connection.oneFirst(sql.unsafe`
SELECT pg_backend_pid();
const connectionPid = await connection.oneFirst(
sql.type(
z.object({
pg_backend_pid: z.int()
})
)`
SELECT pg_backend_pid()
`);

setTimeout(() => {
Expand All @@ -1324,8 +1354,13 @@ export const createIntegrationTests = (
const error = await t.throwsAsync(
pool.connect(async (connection) => {
// connection pid
const connectionPid = await connection.oneFirst(sql.unsafe`
SELECT pg_backend_pid();
const connectionPid = await connection.oneFirst(
sql.type(
z.object({
pg_backend_pid: z.int()
})
)`
SELECT pg_backend_pid()
`);

setTimeout(() => {
Expand Down
53 changes: 43 additions & 10 deletions packages/slonik/src/helpers.test/createPoolTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import type { TestFn } from 'ava';
import { randomUUID } from 'node:crypto';
import { setTimeout as delay } from 'node:timers/promises';
import * as sinon from 'sinon';
import { z } from 'zod';

export const createPoolTests = (
test: TestFn<TestContextType>,
Expand Down Expand Up @@ -389,15 +390,23 @@ export const createPoolTests = (
let firstConnectionPid: number | undefined;

await pool.connect(async (connection) => {
firstConnectionPid = await connection.oneFirst(sql.unsafe`
firstConnectionPid = await connection.oneFirst(sql.type(
z.object({
pg_backend_pid: z.int(),
})
)`
SELECT pg_backend_pid();
`);
});

let secondConnectionPid: number | undefined;

await pool.connect(async (connection) => {
secondConnectionPid = await connection.oneFirst(sql.unsafe`
secondConnectionPid = await connection.oneFirst(sql.type(
z.object({
pg_backend_pid: z.int(),
})
)`
SELECT pg_backend_pid();
`);
});
Expand All @@ -414,15 +423,23 @@ export const createPoolTests = (
let firstConnectionPid: number | undefined;

await pool.transaction(async (transaction) => {
firstConnectionPid = await transaction.oneFirst(sql.unsafe`
firstConnectionPid = await transaction.oneFirst(sql.type(
z.object({
pg_backend_pid: z.int(),
})
)`
SELECT pg_backend_pid();
`);
});

let secondConnectionPid: number | undefined;

await pool.transaction(async (transaction) => {
secondConnectionPid = await transaction.oneFirst(sql.unsafe`
secondConnectionPid = await transaction.oneFirst(sql.type(
z.object({
pg_backend_pid: z.int(),
})
)`
SELECT pg_backend_pid();
`);
});
Expand Down Expand Up @@ -723,14 +740,22 @@ export const createPoolTests = (
maximumPoolSize: 1,
});

const firstConnectionPid = await pool.oneFirst(sql.unsafe`
const firstConnectionPid = await pool.oneFirst(sql.type(
z.object({
pg_backend_pid: z.int(),
})
)`
SELECT pg_backend_pid();
`);

// Confirm that the same connection is re-used.
await t.is(
t.is(
firstConnectionPid,
await pool.oneFirst(sql.unsafe`
await pool.oneFirst(sql.type(
z.object({
pg_backend_pid: z.int(),
})
)`
SELECT pg_backend_pid();
`),
);
Expand Down Expand Up @@ -773,14 +798,22 @@ export const createPoolTests = (
maximumPoolSize: 1,
});

const firstConnectionPid = await pool.oneFirst(sql.unsafe`
const firstConnectionPid = await pool.oneFirst(sql.type(
z.object({
pg_backend_pid: z.int(),
})
)`
SELECT pg_backend_pid();
`);

// Confirm that the same connection is re-used.
await t.is(
t.is(
firstConnectionPid,
await pool.oneFirst(sql.unsafe`
await pool.oneFirst(sql.type(
z.object({
pg_backend_pid: z.int(),
})
)`
SELECT pg_backend_pid();
`),
);
Expand Down
16 changes: 8 additions & 8 deletions packages/slonik/src/types.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export const queryMethods = async (): Promise<void> => {

// any
const any = await client.any(sql.unsafe``);
expectTypeOf(any).toEqualTypeOf<readonly any[]>();
expectTypeOf(any).toEqualTypeOf<readonly unknown[]>();

const anyZodTypedQuery = await client.any(sql.type(ZodRow)``);
expectTypeOf(anyZodTypedQuery).toEqualTypeOf<readonly Row[]>();
Expand All @@ -48,7 +48,7 @@ export const queryMethods = async (): Promise<void> => {

// anyFirst
const anyFirst = await client.anyFirst(sql.unsafe``);
expectTypeOf(anyFirst).toEqualTypeOf<readonly any[]>();
expectTypeOf(anyFirst).toEqualTypeOf<readonly unknown[]>();

const anyFirstZodTypedQuery = await client.anyFirst(sql.type(ZodRow)``);
expectTypeOf(anyFirstZodTypedQuery).toEqualTypeOf<
Expand All @@ -57,14 +57,14 @@ export const queryMethods = async (): Promise<void> => {

// many
const many = await client.many(sql.unsafe``);
expectTypeOf(many).toEqualTypeOf<readonly any[]>();
expectTypeOf(many).toEqualTypeOf<readonly unknown[]>();

const manyZodTypedQuery = await client.many(sql.type(ZodRow)``);
expectTypeOf(manyZodTypedQuery).toEqualTypeOf<readonly Row[]>();

// manyFirst
const manyFirst = await client.manyFirst(sql.unsafe``);
expectTypeOf(manyFirst).toEqualTypeOf<readonly any[]>();
expectTypeOf(manyFirst).toEqualTypeOf<readonly unknown[]>();

const manyFirstZodTypedQuery = await client.manyFirst(sql.type(ZodRow)``);
expectTypeOf(manyFirstZodTypedQuery).toEqualTypeOf<
Expand All @@ -73,14 +73,14 @@ export const queryMethods = async (): Promise<void> => {

// maybeOne
const maybeOne = await client.maybeOne(sql.unsafe``);
expectTypeOf(maybeOne).toEqualTypeOf<any>();
expectTypeOf(maybeOne).toEqualTypeOf<unknown>();

const maybeOneZodTypedQuery = await client.maybeOne(sql.type(ZodRow)``);
expectTypeOf(maybeOneZodTypedQuery).toEqualTypeOf<null | Row>();

// maybeOneFirst
const maybeOneFirst = await client.maybeOneFirst(sql.unsafe``);
expectTypeOf(maybeOneFirst).toEqualTypeOf<any>();
expectTypeOf(maybeOneFirst).toEqualTypeOf<unknown>();

const maybeOneFirstZodTypedQuery = await client.maybeOneFirst(
sql.type(ZodRow)``,
Expand All @@ -91,14 +91,14 @@ export const queryMethods = async (): Promise<void> => {

// one
const one = await client.one(sql.unsafe``);
expectTypeOf(one).toEqualTypeOf<any>();
expectTypeOf(one).toEqualTypeOf<unknown>();

const oneZodTypedQuery = await client.one(sql.type(ZodRow)``);
expectTypeOf(oneZodTypedQuery).toEqualTypeOf<Row>();

// oneFirst
const oneFirst = await client.oneFirst(sql.unsafe``);
expectTypeOf(oneFirst).toEqualTypeOf<any>();
expectTypeOf(oneFirst).toEqualTypeOf<unknown>();

const oneFirstZodTypedQuery = await client.oneFirst(sql.type(ZodRow)``);
expectTypeOf(oneFirstZodTypedQuery).toEqualTypeOf<boolean | string>();
Expand Down
26 changes: 10 additions & 16 deletions packages/slonik/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -426,14 +426,16 @@ type PoolState = {
};

type PoolStateName = 'ACTIVE' | 'ENDED' | 'ENDING';

// This helper is used in Query*FirstFunction types to extract the first column's type from the schema
type InferFirstSchemaOutput<T extends StandardSchemaV1> =
keyof StandardSchemaV1.InferOutput<T> extends never
? unknown
: StandardSchemaV1.InferOutput<T>[keyof StandardSchemaV1.InferOutput<T>];
type QueryAnyFirstFunction = <T extends StandardSchemaV1>(
sql: QuerySqlToken<T>,
values?: PrimitiveValueExpression[],
) => Promise<
ReadonlyArray<
StandardSchemaV1.InferOutput<T>[keyof StandardSchemaV1.InferOutput<T>]
>
>;
) => Promise<ReadonlyArray<InferFirstSchemaOutput<T>>>;
type QueryAnyFunction = <T extends StandardSchemaV1>(
sql: QuerySqlToken<T>,
values?: PrimitiveValueExpression[],
Expand All @@ -445,31 +447,23 @@ type QueryExistsFunction = <T extends StandardSchemaV1>(
type QueryManyFirstFunction = <T extends StandardSchemaV1>(
sql: QuerySqlToken<T>,
values?: PrimitiveValueExpression[],
) => Promise<
ReadonlyArray<
StandardSchemaV1.InferOutput<T>[keyof StandardSchemaV1.InferOutput<T>]
>
>;
) => Promise<ReadonlyArray<InferFirstSchemaOutput<T>>>;
type QueryManyFunction = <T extends StandardSchemaV1>(
sql: QuerySqlToken<T>,
values?: PrimitiveValueExpression[],
) => Promise<ReadonlyArray<StandardSchemaV1.InferOutput<T>>>;
type QueryMaybeOneFirstFunction = <T extends StandardSchemaV1>(
sql: QuerySqlToken<T>,
values?: PrimitiveValueExpression[],
) => Promise<
null | StandardSchemaV1.InferOutput<T>[keyof StandardSchemaV1.InferOutput<T>]
>;
) => Promise<null | InferFirstSchemaOutput<T>>;
type QueryMaybeOneFunction = <T extends StandardSchemaV1>(
sql: QuerySqlToken<T>,
values?: PrimitiveValueExpression[],
) => Promise<null | StandardSchemaV1.InferOutput<T>>;
type QueryOneFirstFunction = <T extends StandardSchemaV1>(
sql: QuerySqlToken<T>,
values?: PrimitiveValueExpression[],
) => Promise<
StandardSchemaV1.InferOutput<T>[keyof StandardSchemaV1.InferOutput<T>]
>;
) => Promise<InferFirstSchemaOutput<T>>;
type QueryOneFunction = <T extends StandardSchemaV1>(
sql: QuerySqlToken<T>,
values?: PrimitiveValueExpression[],
Expand Down
2 changes: 1 addition & 1 deletion packages/sql-tag/src/factories/createSqlTag.ts
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ export const createSqlTag = <
unsafe: (parts, ...args) => {
return Object.freeze({
...createFragment(parts, args),
parser: z.any(),
parser: z.unknown(),
type: QueryToken,
});
},
Expand Down
Loading