decho
decho
Explore posts from servers
KKysely
Created by decho on 7/1/2024 in #help
How to infer an aggregated column with `filterWhere` clause as nullable?
Greetings. I am joining from another table using leftJoin, then grouping rows by id, and finally using jsonAgg(jsonBuildObject(...)) with a filterWhere method chained on top of this expression as seen in the demo. The problem is that the inferred type by kysely is incorrect, because it thinks the json aggregate function will always be an array, however, if there are no matching rows from the other table it will be NULL because of the filter where clause. I know I can use the .$castTo<{...}>() helper and manually set the type, but that can become problematic, especially when dealing with larger objects. Is there any workaround for this problem? I thought filterWhere would be smart enough to make the column nullable, or maybe I'm not smart enough myself and I'm missing something 🙂 https://kyse.link/COF71
5 replies
KKysely
Created by decho on 6/11/2024 in #help
Question about ColumnTypes and dates.
I have a simple yet confusing problem. In postgres I have a table with a column of type DATE (yyyy-mm-dd).
CREATE TABLE IF NOT EXISTS person(
id INT NOT NULL,
birthday DATE NOT NULL
);
CREATE TABLE IF NOT EXISTS person(
id INT NOT NULL,
birthday DATE NOT NULL
);
Kysely's PostgresDialect is using the node-postgres/pg driver, which transforms date columns as javascript Date objects when it returns rows, however, you can use strings and/or Date objects as query parameters, both are valid ways to query the database. This works both in node and when you write raw SQL:
-- both are valid queries
SELECT * FROM person WHERE birthday < '2020-01-01';
SELECT * FROM person WHERE birthday < NOW();
-- both are valid queries
SELECT * FROM person WHERE birthday < '2020-01-01';
SELECT * FROM person WHERE birthday < NOW();
The problem is that I'm not sure how to define my database types to account for that. If I create the following type I will get type error if I use string in my .where clauses:
export interface PersonTable {
id: Generated<number>;
birthday: ColumnType<Date, Date, Date>;
}

// ❌ will give a type error because birthday is a string
await db.selectFrom('person').where('birthday', '=', '2020-01-01');
export interface PersonTable {
id: Generated<number>;
birthday: ColumnType<Date, Date, Date>;
}

// ❌ will give a type error because birthday is a string
await db.selectFrom('person').where('birthday', '=', '2020-01-01');
and, if I change the ColumnType to be a union of Date | string as acceptable values for ColumnType -> SelectType, then the inferred result for birthday in all queries will also be the same union, but we obviously know node-postgres only returns JS Date objects:
export interface PersonTable {
id: Generated<number>;
birthday: ColumnType<Date | string, Date | string, Date | string>;
}

// ❌ res.birthday will be inferred as Date | string instead of just Date
const res = await db.selectFrom('person').selectAll().executeTakeFirstOrThrow();
export interface PersonTable {
id: Generated<number>;
birthday: ColumnType<Date | string, Date | string, Date | string>;
}

// ❌ res.birthday will be inferred as Date | string instead of just Date
const res = await db.selectFrom('person').selectAll().executeTakeFirstOrThrow();
So my question is, is there any way to deal with this problem? Here is a kyse.link as well: https://kyse.link/2CJ6I In a certain way, the SelectType in ColumnType is more like select type (how you can select) and also a return type (how the result is inferred).
10 replies
KKysely
Created by decho on 6/2/2024 in #help
Creating a jsonAgg helper funtcion that returns a context aware `AggregateFunctionBuilder`
Greetings. I am wondering if it's possible to create a helper that does exactly what eb.fn.jsonAgg does but for JSONB. - I have tried using the helpers/postgres/jsonArrayFrom, but that is functionally not the same because it creates a subquery. - I have tried creating a simple helper, but it's not type/context aware, and it doesn't support method chaining (.distinct() for example):
function jsonbAgg<O>(
expr: Expression<O>,
): RawBuilder<Simplify<O>[]> {
return sql`jsonb_agg(${expr})`;
}
function jsonbAgg<O>(
expr: Expression<O>,
): RawBuilder<Simplify<O>[]> {
return sql`jsonb_agg(${expr})`;
}
- Lastly, I have tried recreating the jsonAgg function from the source code and that almost works, however it's not aware of the query context, and you can for example aggregate on a completely unrelated table, since it uses the entire DB type as first generic parameter (<DB extends MyDB, TB extends keyof DB....> Example of the problem demonstrated: https://kyse.link/7zHaq --- I have since refactored my code, but this might not always be an easy option in the future, so I was curious to know if there is a way around this problem.
2 replies
TtRPC
Created by decho on 12/24/2023 in #❓-help
Incorrect type inference with discriminated unions.
Hey folks, I am having a really weird issue. Here is a minimal reproducible example: https://tsplay.dev/WJB1kW Basically I have a function that returns an object of the Difference, which is a union of 3 other types:
interface DifferenceCreate {
type: "CREATE";
value: any;
}

interface DifferenceRemove {
type: "REMOVE";
oldValue: any;
}

interface DifferenceChange {
type: "CHANGE";
value: any;
oldValue: any;
}

export type Difference = DifferenceCreate | DifferenceRemove | DifferenceChange;

function myFunction(): Difference {
return { type: 'CREATE', value: 1 }
}
interface DifferenceCreate {
type: "CREATE";
value: any;
}

interface DifferenceRemove {
type: "REMOVE";
oldValue: any;
}

interface DifferenceChange {
type: "CHANGE";
value: any;
oldValue: any;
}

export type Difference = DifferenceCreate | DifferenceRemove | DifferenceChange;

function myFunction(): Difference {
return { type: 'CREATE', value: 1 }
}
I have created a router and a procedure that returns the result of myFunction. I then call this function on the client like this:
const test:Difference = await client.test.query();
const test:Difference = await client.test.query();
However, I am getting a type error:
Type '{ type: "CREATE"; value?: any; } | { type: "REMOVE"; oldValue?: any; } | { type: "CHANGE"; value?: any; oldValue?: any; }' is not assignable to type 'Difference'.
Type '{ type: "CREATE"; value?: any; }' is not assignable to type 'Difference'.
Type '{ type: "CREATE"; value?: any; }' is not assignable to type 'DifferenceCreate'.
Property 'value' is optional in type '{ type: "CREATE"; value?: any; }' but required in type 'DifferenceCreate'.
Type '{ type: "CREATE"; value?: any; } | { type: "REMOVE"; oldValue?: any; } | { type: "CHANGE"; value?: any; oldValue?: any; }' is not assignable to type 'Difference'.
Type '{ type: "CREATE"; value?: any; }' is not assignable to type 'Difference'.
Type '{ type: "CREATE"; value?: any; }' is not assignable to type 'DifferenceCreate'.
Property 'value' is optional in type '{ type: "CREATE"; value?: any; }' but required in type 'DifferenceCreate'.
Any idea how to solve this issue?
14 replies
DIAdiscord.js - Imagine an app
Created by decho on 8/14/2023 in #djs-questions
Getting "Unknown interaction" error even with interaction.deferReply()
Hey folks, I am getting a strange error with this code:
class StandingsInteraction {
public async interaction(interaction: TypedCommands['standings']) {
// interaction is ChatInputCommandInteraction
await interaction.deferReply();

const { data, expired } = await standingsCache.getData();

if (!expired) {
return await interaction.editReply({
content: data // data is a string here
});
}

const res = await interaction.editReply({
files: [{
attachment: data, // data is a Buffer here
name: 'la-liga-standings.png'
}]
});

standingsCache.revalidate(res);
}
}
class StandingsInteraction {
public async interaction(interaction: TypedCommands['standings']) {
// interaction is ChatInputCommandInteraction
await interaction.deferReply();

const { data, expired } = await standingsCache.getData();

if (!expired) {
return await interaction.editReply({
content: data // data is a string here
});
}

const res = await interaction.editReply({
files: [{
attachment: data, // data is a Buffer here
name: 'la-liga-standings.png'
}]
});

standingsCache.revalidate(res);
}
}
The problem is that I am randomly getting this error, and I can't figure out why since I am deferring the reply:
DiscordAPIError[10062]: Unknown interaction
at handleErrors (/home/node_modules/@discordjs/rest/dist/index.js:687:13)
at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
at async BurstHandler.runRequest (/home/node_modules/@discordjs/rest/dist/index.js:786:23)
at async _REST.request (/home/node_modules/@discordjs/rest/dist/index.js:1218:22)
at async ChatInputCommandInteraction.deferReply (/home/node_modules/discord.js/src/structures/interfaces/InteractionResponses.js:69:5)
at async StandingsInteraction.interaction (file:///home/apps/discord/out/commands/standings.js:5:5)
at async interactionController (file:///home/apps/discord/out/commands/_controller.js:31:7)
at async Client.<anonymous> (file:///home/apps/discord/out/index.js:13:5) {
requestBody: { files: undefined, json: { type: 5, data: [Object] } },
rawError: { message: 'Unknown interaction', code: 10062 },
code: 10062,
status: 404,
method: 'POST',
url: 'https://discord.com/api/v10/interactions/.../callback'
}
DiscordAPIError[10062]: Unknown interaction
at handleErrors (/home/node_modules/@discordjs/rest/dist/index.js:687:13)
at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
at async BurstHandler.runRequest (/home/node_modules/@discordjs/rest/dist/index.js:786:23)
at async _REST.request (/home/node_modules/@discordjs/rest/dist/index.js:1218:22)
at async ChatInputCommandInteraction.deferReply (/home/node_modules/discord.js/src/structures/interfaces/InteractionResponses.js:69:5)
at async StandingsInteraction.interaction (file:///home/apps/discord/out/commands/standings.js:5:5)
at async interactionController (file:///home/apps/discord/out/commands/_controller.js:31:7)
at async Client.<anonymous> (file:///home/apps/discord/out/index.js:13:5) {
requestBody: { files: undefined, json: { type: 5, data: [Object] } },
rawError: { message: 'Unknown interaction', code: 10062 },
code: 10062,
status: 404,
method: 'POST',
url: 'https://discord.com/api/v10/interactions/.../callback'
}
I searched online for the problem but without much success.
15 replies
DIAdiscord.js - Imagine an app
Created by decho on 7/31/2023 in #djs-questions
Looking for information about "managed" emojis.
Hello. I am developing a bot for my server, and this bot needs certain emojis to be always available, in case the server loses boost status. Since discord doesn't provide a way to re-order you emojis, if you want to achieve that you have to delete all server emojis and reupload them in correct order (bot emojis being uploaded first). Either way, while doing a research on this topic I stumbled upon the concept of "managed" emojis, but the documentation about it seems very limited, I can't find much info online about it either. This is what ChatGPT told me about them:
Managed emojis are custom emojis that are added by a bot or an integration, such as Twitch or YouTube. They cannot be deleted or edited by the server owner or anyone with the Manage Emoji permission.
Based on this, my understanding is that I can have emojis that are related to my bot rather than a specific server, which would be ideal for my case. However, I can't seem to find any information on how to upload/use these so called "managed" emojis, so this is what I'm asking for. Any info would be appreciated, thanks!
8 replies
KKysely
Created by decho on 7/27/2023 in #help
Modifying the return type of a query
I have a question, hopefully it's not a silly one. So I have generated types for my DB (via kanel-kysely), one of which is for a materialized view, and as such all columns of that view are generated as nullable. (I think that's just some Postgres weirdness, not related to kysely or kanel). I also have a function that runs a query and selects all from that view, and I am wondering if there is any way to modify that in kysely land so that the returned type has all of these columns as non-nullable. Here is a super basic example: https://kyse.link/?p=s&i=EBBWymS9HA2FBdR6X73o Basically I'm asking if there is a way to change the query so that all nulls from the column types are excluded.
6 replies
KKysely
Created by decho on 7/11/2023 in #help
Ways to work with materialized views
Hey guys, I just wanted to ask before I embark on a journey on solving this problem on my own, if someone has found a good way to work with and implement materialized views with Kysely? I am currently using kysely-codegen but unfortunately it does not generate types for materialized views. kysely.introspect also does not return any data for materialized views either. My ultimate goal is to put a complex query in a materialized view (my leaderboard https://discord.com/channels/890118421587578920/1125059913551458406) and be able to do 3 things: 1. query the materialized view itself with kysely 2. use kysely to query the view itself SELECT * FROM my_materialized_view WHERE column = something 3. have types for it all 3 issues are solved if you have a tool that generates types for the materialized view (I think). --- So far I am thinking of two approaches. First one to be to just write my own types generator by querying pg_catalog and stuff, similar to kysely-codgen. Gonna take a while. Second solution would be to write a simple query in kysely which produces the result of the materialized view, wrap that in a function (which never gets called), and then write another function that returns raw query with type assertions.
import { sql } from 'kysely';

async function materializedQuery() {
// this func never gets called, just using it to create a type
return await db.selectFrom('countries').selectAll().execute();
}

async function materializedView() {
return await sql`select * from my_materialized_view`.execute(db) as unknown as ReturnType<typeof materializedQuery>;
}

/**
function materializedView(): Promise<{
id: number;
name: string;
}[]>
*/
import { sql } from 'kysely';

async function materializedQuery() {
// this func never gets called, just using it to create a type
return await db.selectFrom('countries').selectAll().execute();
}

async function materializedView() {
return await sql`select * from my_materialized_view`.execute(db) as unknown as ReturnType<typeof materializedQuery>;
}

/**
function materializedView(): Promise<{
id: number;
name: string;
}[]>
*/
This is obviously very hacky, and won't even solve problem #2. So I was just wondering if anyone faced this problem before and has any tips. Cheers!
31 replies
KKysely
Created by decho on 7/6/2023 in #help
Unable to compile project with the TypeScript compiler (tsc) v.5.1.6
92 replies
KKysely
Created by decho on 7/2/2023 in #help
Can't get filterWhere to work with joins from another table.
Hey everyone. I am trying to recreate the following SQL (simplified demo):
SELECT
"users"."id",
COUNT(*) FILTER (
WHERE
"predictions"."userId" = "users.id"
) AS "apperances"
FROM
"users"
INNER JOIN "predictions" ON "predictions"."userId" = "users"."id"
INNER JOIN "matches" ON "matchId" = "predictions"."matchId"
SELECT
"users"."id",
COUNT(*) FILTER (
WHERE
"predictions"."userId" = "users.id"
) AS "apperances"
FROM
"users"
INNER JOIN "predictions" ON "predictions"."userId" = "users"."id"
INNER JOIN "matches" ON "matchId" = "predictions"."matchId"
await kysely
.selectFrom('users')
.innerJoin('predictions', 'predictions.userId', 'users.id')
.innerJoin('matches', 'matchId', 'predictions.matchId')
.select('users.id')
.select((eb) => [
eb.fn.countAll().filterWhere('predictions.userId', '=', 'users.id').as('apperances')
])
.execute();
await kysely
.selectFrom('users')
.innerJoin('predictions', 'predictions.userId', 'users.id')
.innerJoin('matches', 'matchId', 'predictions.matchId')
.select('users.id')
.select((eb) => [
eb.fn.countAll().filterWhere('predictions.userId', '=', 'users.id').as('apperances')
])
.execute();
the problem occurs in filterWhere's last parameter and I am getting a type error. Does anyone know how to fix this or if there is a workaround? I am not 100% sure why this error appears, as far as I'm concerned the SQL I am trying to generate is completely valid. Here is a full kyse.link demo: https://kyse.link/?p=s&i=GQxCID0h0CK3Jq2Ki7FB
5 replies
KKysely
Created by decho on 6/30/2023 in #query-showcase
CTE with exists() and case()
Hey guys, just wrote this one and I thought I'd share.
WITH
"ongoing" AS (
SELECT
EXISTS (
SELECT
FROM
"matches"
WHERE
"kickOff" BETWEEN NOW() - INTERVAL '2 hours' AND NOW()
) AS "is_ongoing"
FROM
"matches"
LIMIT
$1
)
SELECT
"matches".*
FROM
"matches",
"ongoing"
WHERE
"kickOff" > NOW()
AND CASE
WHEN "ongoing"."is_ongoing" = TRUE THEN FALSE
ELSE TRUE
END
ORDER BY
"kickOff" ASC
LIMIT
$2
WITH
"ongoing" AS (
SELECT
EXISTS (
SELECT
FROM
"matches"
WHERE
"kickOff" BETWEEN NOW() - INTERVAL '2 hours' AND NOW()
) AS "is_ongoing"
FROM
"matches"
LIMIT
$1
)
SELECT
"matches".*
FROM
"matches",
"ongoing"
WHERE
"kickOff" > NOW()
AND CASE
WHEN "ongoing"."is_ongoing" = TRUE THEN FALSE
ELSE TRUE
END
ORDER BY
"kickOff" ASC
LIMIT
$2
https://kyse.link/?p=s&i=weqpZStlVZ7TONs1D3UR 1. Create a CTE that checks if there is an ongoing match and returns a boolean value. 2. Find the closest match in a future date. 3. Only return it if there is no ongoing match. EDIT: Updated version: https://kyse.link/?p=s&i=r8bpSEIqQVS92bvZxqsn
5 replies
KKysely
Created by decho on 4/18/2023 in #help
Shorthand way to pass the entire update object inside of doUpdateSet when updating multiple values.
Greetings! Thanks for developing Kysely, some of the stuff that it does actually blows my mind. I only started using it a few days ago, and I love it so far. Anyway, I have a quick and simple question. I am trying to update (upsert ) multiple values into a table like this:
const countries = [
{ id: 1, name: 'UK', continent: 'Europe' },
{ id: 2, name: 'France', continent: 'Europe' }
];

await db.insertInto('countries')
.values(countries)
.onConflict(oc => oc
.column('id')
.doUpdateSet(eb => ({
name: eb.ref('excluded.name'),
continent: eb.ref('excluded.continent')
}))
)
.execute();
const countries = [
{ id: 1, name: 'UK', continent: 'Europe' },
{ id: 2, name: 'France', continent: 'Europe' }
];

await db.insertInto('countries')
.values(countries)
.onConflict(oc => oc
.column('id')
.doUpdateSet(eb => ({
name: eb.ref('excluded.name'),
continent: eb.ref('excluded.continent')
}))
)
.execute();
This all works as expected, however, imagine that my countries had tons of keys besides id, name and continent. It would become really tedious and error prone if I had to write down my_column: eb.ref('excluded.my_coulmn') inside of the doUpdateSet method. This is not an issue if you are upserting a single object at a time (like a single country instead of an entire array), because then you can just reference that object inside the doUpdateSet, but I am working with an array of objects (countries). So my question is if there is a workaround/solution for this problem. Cheers!
62 replies
DIAdiscord.js - Imagine an app
Created by decho on 7/26/2022 in #djs-questions
Creating types for slash command options.
Hello guys. I have been search the Internet about this, with little to no success, so I thought I'd ask here. Is it possible to provide custom types for my slash command options. For example, I can restrict the interaction and command name and commandNames respectively, and also the type like this:
import type { ApplicationCommandType, ChatInputApplicationCommandData, CommandInteraction } from 'discord.js';

type ISlashChatCommandNames = 'foo' | 'bar';

export interface ISlashChatCommandData extends ChatInputApplicationCommandData {
name: ISlashChatCommandNames;
type: ApplicationCommandType.ChatInput,
}

export interface ISlashChatCommandInteraction extends CommandInteraction {
commandName: ISlashChatCommandNames;
commandType: ApplicationCommandType.ChatInput,
}
import type { ApplicationCommandType, ChatInputApplicationCommandData, CommandInteraction } from 'discord.js';

type ISlashChatCommandNames = 'foo' | 'bar';

export interface ISlashChatCommandData extends ChatInputApplicationCommandData {
name: ISlashChatCommandNames;
type: ApplicationCommandType.ChatInput,
}

export interface ISlashChatCommandInteraction extends CommandInteraction {
commandName: ISlashChatCommandNames;
commandType: ApplicationCommandType.ChatInput,
}
ISlashChatCommandData is used for the slash command builder andISlashChatCommandInteraction is for when the users are interacting with the bot.
15 replies