Giter Site home page Giter Site logo

Comments (5)

Newbie012 avatar Newbie012 commented on May 18, 2024

In the original issue, I wrote my suggestion from my phone, so I was unable to check if I wrote it properly. This is how it should be:

import postgres from "postgres";

const sql = postgres();

type A = { id: number; firstName: string; type: string; accessory: string };
const a = [{ id: 1, firstName: 'b', type: 'c', accessory: 'd' }] as const satisfies readonly A[];

export async function query() {

  return await sql<A[]>`
    SELECT * FROM animals
  `;
}

as you can see in the playground

Now that we covered the original issue, I'll address this feature request.

While I understand how it can make life easier in some cases (prior to satisfies syntax), it would be unwise to do so.

Although, if you are certain that the database will return a type literal, you could enforce it via CREATE TYPE name AS ENUM and SafeQL will return a union of type literal (introduced in 0.0.25:

CREATE TYPE mood AS ENUM ('sad', 'ok', 'happy');

CREATE TABLE person (
    ...,
    mood mood NOT NULL
);
sql<{ mood: "sad" | "ok" | "happy" }[]>`SELECT mood FROM person`;

Hopefully, these solutions will cover your needs.

from safeql.

Eprince-hub avatar Eprince-hub commented on May 18, 2024

Thanks for the quick reply.
I understand the suggested solution above, but the downside of doing something like this is the inflexibility, and the main purpose of using literal type is not to enforce the database to return some constant value like ('sad', 'ok', 'happy', ...)

CREATE TYPE mood AS ENUM ('sad', 'ok', 'happy');

CREATE TABLE person (
    ...,
    mood mood NOT NULL
);

The insertion below will fail unless I somehow have to go and add it first to the ENUM type, which denies the flexibility/safety and SafeQL easy usage doctrine that the users would get if SafeQL somehow supports literal internally.

insert into person
  (mood)
VALUES
  ('yes');

Literal type is important and mostly very helpful when we are consigned about type safety and expressiveness. It also helps to reduce the risk of errors, which is why most developers use it for database seeding, and having a setup like the above will not achieve any flexibility/efficiency while still keeping the benefit of using the literal and SafeQL

Imagine when more data needs to be added to the database via seeding

Having a setup like below makes it more efficient and less complicated

const testData = [{ firstName: 'b', type: 'c', accessory: 'd' }] as const;

// I can add a new object to the `testData` without having to do anything else
const testData = [{ firstName: 'b', type: 'c', accessory: 'd' }, { firstName: 'y', type: 'k', accessory: 'p' }] as const;

Another very important use case for literal types is the ability to refer to known ids from another table that may change over time. For example, let's consider a quiz application where we have a Quiz table and a Topic table. Topics are added to the database via the seeder, and their ids may change over time as new topics are added or removed. By defining a literal type topicId for the id property of the Topic, we can ensure that only valid Topic ids are referenced when creating a quiz.
e.g

const topics = [
  { id: 1, name: 'History' },
  { id: 2, name: 'Math' },
  { id: 3, name: 'Science' }
] as const;

type TopicId = typeof topics[number]['id']; // his defines a literal type `TopicId` that can only take on the values of the id property of the objects in the topics array.

const quiz1: Quiz = { id: 1, topicId: 2 }; // No error because `topicId` is a valid id in the `topics` array
const quiz2: Quiz = { id: 2, topicId: 4 }; // This will result in a type error since 4 is not a valid `topicId`

This ensures that only valid Topic ids are referenced when creating a quiz, making the code more maintainable and less error-prone. If we need to add new topics via the seeder, we can do so without worrying about changing anything in the codebase that references the Topic ids.

from safeql.

Newbie012 avatar Newbie012 commented on May 18, 2024

I didn't recommend changing your code to ENUMs (the solution I wrote at the start of my comment is more suitable for your use case). The reason I mentioned ENUMs was due to the fact that SafeQL will return a union of literal strings on enum types, which will cause a conflict with your suggestion (more about that later in the comment).

While I understand your reasons not to use ENUMs, I'm still not sure what's wrong with the solution I wrote above using satisfies:

import postgres from "postgres";

const sql = postgres();

type A = { id: number; firstName: string; type: string; accessory: string };
const a = [{ id: 1, firstName: 'b', type: 'c', accessory: 'd' }] as const satisfies readonly A[];

export async function query() {

  return await sql<A[]>`
    SELECT * FROM animals
  `;
}

Looking at the original code feels like it's the opposite direction of how SafeQL should be used. SafeQL is the one who's responsible to determine what would be the returned type of the raw query, not the other way around. In fact, if the original code would not throw any errors, this would've been a serious bug, since we can't assume that the query returns only these literal types since there's no guarantee that it will stay like that in a different context/part of code. You could potentially add manually a new record with values that differs from the literal types that you've provided:

const d = [
  { id: 1, firstName: 'b', type: 'c', accessory: 'd' }
] as const;

type G = typeof d[number];

export async function query() {
  await sql`insert into animals (first_name, type, accessory) values ('unexpected', 'unexpected', 'unexpected')`;

  return await sql<G[]>`
    SELECT * FROM animals
  `;
}

// ReturnType<typeof query>: {id: 1, firstName: 'b', type: 'c', accessory: 'd' }[]
// Runtime: [{id: 1, firstName: 'b', type: 'c', accessory: 'd' }, {id: 2, firstName: 'unexpected', type: 'unexpected', accessory: 'unexpected' }]

While the return type of query function will be readonly {id: 1, firstName: 'b', type: 'c', accessory: 'd' }[], you will get a different result in runtime:

[
  {id: 1, firstName: 'b', type: 'c', accessory: 'd' },
  {id: 2, firstName: 'unexpected', type: 'unexpected', accessory: 'unexpected' },
  // There's no guarantees that there won't be more records in the future
]

from safeql.

Eprince-hub avatar Eprince-hub commented on May 18, 2024

The solution you provided, using satisfies, is okay, but in my case, it defeats why we used the literal type in the first place. Consider the cases below

  1. My last example from my comment above where we want a certain table to accept only an Ids from another table.
// Using satisfies
type A = { id: number; firstName: string; type: string; accessory: string};
const a = [{ id: 1, firstName: 'b', type: 'c', accessory: 'd'}] as const satisfies readonly A[];

type AId = A['id']
const aId1:AId = 2 // no error because it would accept just any number

// Using `as const`
const b = [{ id: 1, firstName: 'b', type: 'c', accessory: 'd' }] as const
type B = typeof b[number]

type BId = B['id']
const bId1:BId = 2 // error because it will accept just the ids of b
  1. If the properties of the original object change, (add more or remove ), we will need to dig through our codebase and update the type everywhere it is being used
type A1 = { id: number; firstName: string; type: string; accessory: string};
const a1 = [{ id: 1, firstName: 'b', type: 'c', accessory: 'd', newProperty: ''}] as const satisfies readonly A1[]; // must be updated everywhere A1 is used

const b1 = [{ id: 1, firstName: 'b', type: 'c', accessory: 'd', newProperty: '' }] as const // Update only in one place, and B1 will have all the properties already
type B1 = typeof b1[number]
  1. The satisfies solution seems like creating an extra type that we don't necessarily need to if we are using the as const pattern.

I have the errors shown in this playground

from safeql.

Newbie012 avatar Newbie012 commented on May 18, 2024

You could do instead:

type A = { id: number; firstName: string; type: string; accessory: string};
const a = [{ id: 1, firstName: 'b', type: 'c', accessory: 'd'}] as const satisfies readonly A[];
type ReadonlyA = (typeof a)[number];

But the main issue is, as I mentioned, you're trying to enforce a different type than what the actual return type of the query. The best thing is to let SafeQL do its thing and use your other types separately. If you want, you could suppress SafeQL by either a comment or by doing something like:

const untypedSql = sql;

untypedSql<PlaceYourDesiredType>`...`

I'm sorry but I can't accept this feature request as it's not the intended way of using SafeQL.

from safeql.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.