Coder Social home page Coder Social logo

typed-knex's Introduction

Status of this module

This module is deprecated. I'm happy to say that in the last 8 years, a lot of good libraries have been created that help with type safety for sql queries. There is no real need anymore for this library, so it's deprecated.

I recommend the following libraries:

typed-knex

npm version Build Status

Standing on the shoulders of Knex.js, but now everything is typed!

Goals:

  • Be useful for 80% of the use cases, for the other 20% easily switch to lower-level Knex.js.
  • Be as concise a possible.
  • Mirror Knex.js as much a possible, with these exceptions:
    • Don't use this.
    • Be selective on what returns a Promise and what not.
    • Less overloading, which makes typings easier and code completion better.
  • Get the most of the benefits TypeScript provides: type-checking of parameters, typed results, rename refactorings.

Install:

npm install @wwwouter/typed-knex

Make sure experimentalDecorators and emitDecoratorMetadata are turned on in your tsconfig.json:

{
    "compilerOptions": {
        "experimentalDecorators": true,
        "emitDecoratorMetadata": true,
        ...
    },
    ...
}

Tested with Knex.js v2.2.0, TypeScript v4.7.4 and Node.js 14.x, 16.x, 18.x

Important upgrade notice

Because TypeScript 4.1 supports template literal string types, the function syntax is no longer necessary. You can now use strings while maintaining type-safety. The function syntax is removed since version 4.

To help with the upgrade, you can run npx typed-knex -u string-parameters to automatically switch over to the string syntax.

Breaking changes in v4

  • Because TypeScript 4.1 supports template literal string types, the function syntax is no longer necessary. You can now use strings while maintaining type-safety. The function syntax is removed. Run npx typed-knex -u string-parameters to automatically upgrade.
  • .onColumn() is deprecated. Use .on(). Remember that the columns switched eg .onColumns(i=>i.prop1, '=' j=>j.prop2) should become .on("prop2", '=', "prop1"). Run npx typed-knex -u join-on-columns-to-on to automatically upgrade.
  • The use of optional columns (@Column() public nickName?: string;) is deprecated. This was used to signal a nullable column. The correct way to do this is @Column() public nickName: string | null;.

Documentation

Quick example

To reference a column, use the name. Like this .select("name") or this .where("name", "Hejlsberg")

import * as Knex from "knex";
import { TypedKnex } from "@wwwouter/typed-knex";

const knex = Knex({
    client: "pg",
    connection: "postgres://user:pass@localhost:5432/dbname",
});

async function example() {
    const typedKnex = new TypedKnex(knex);

    const query = typedKnex.query(User).innerJoin("category", UserCategory, "id", "=", "categoryId").where("name", "Hejlsberg").select("id", "category.name");

    const oneUser = await query.getSingle();

    console.log(oneUser.id); // Ok
    console.log(oneUser.category.name); // Ok
    console.log(oneUser.name); // Compilation error
}

Define tables

Use the Table decorator to reference a table and use the Column decorator to reference a column.

Use @Column({ primary: true }) for primary key columns.

Use @Column({ name: '[column name]' }) on property with the type of another Table to reference another table.

import { Column, Table } from "@wwwouter/typed-knex";

@Table("userCategories")
export class UserCategory {
    @Column({ primary: true })
    public id: string;
    @Column()
    public name: string;
    @Column()
    public year: number;
}

@Table("users")
export class User {
    @Column({ primary: true })
    public id: string;
    @Column()
    public name: string;
    @Column()
    public categoryId: string;
    @Column({ name: "categoryId" })
    public category: UserCategory;
    @Column()
    public someNullableValue: string | null;
}

Create instance

import * as Knex from "knex";
import { TypedKnex } from "@wwwouter/typed-knex";

const knex = Knex({
    client: "pg",
    connection: "postgres://user:pass@localhost:5432/dbname",
});

const typedKnex = new TypedKnex(knex);

Helper

Querybuilder

General

Getting the results (Promises)

Building the query

getTableName

const tableName = getTableName(User);

// tableName = 'users'

getColumnName

const columnName = getColumnName(User, "id");

// columnName = 'id'

registerBeforeInsertTransform

Hook that is run before doing an insert. Execute this function as soon as possible. For example at the top of index.ts or server.ts.

registerBeforeInsertTransform((item: any, typedQueryBuilder: ITypedQueryBuilder<{}, {}, {}>) => {
    if (typedQueryBuilder.columns.find(column => column.name === 'created_at') && !item.hasOwnProperty('created_at')) {
        item.created_at = new Date();
    }
    if (typedQueryBuilder.columns.find(column => column.name === 'updated_at') && !item.hasOwnProperty('updated_at')) {
        item.updated_at = new Date();
    }
    if (typedQueryBuilder.columns.find(column => column.name === 'id') && !item.hasOwnProperty('id')) {
        item.id = guid();
    }
    return item;
});

registerBeforeUpdateTransform

Hook that is run before doing an update. Execute this function as soon as possible. For example at the top of index.ts or server.ts.

registerBeforeUpdateTransform((item: any, typedQueryBuilder: ITypedQueryBuilder<{}, {}, {}>) => {
    if (typedQueryBuilder.columns.find("name" === 'updated_at') && !item.hasOwnProperty('updated_at')) {
        item.updated_at = new Date();
    }
    return item;
});

query

Use typedKnex.query(Type) to create a query for the table referenced by Type

const query = typedKnex.query(User);

getColumnAlias

Use getColumnAlias to get the underlying alias of a column, to use in a raw function.

const query = typedKnex.query(UserCategory);
query.selectRaw("hash", String, `hashFunction(${query.getColumnAlias("name")})`).select("id");

// select (hashFunction("userCategories"."name")) as "hash", "userCategories"."id" as "id" from "userCategories"

select

https://knexjs.org/guide/query-builder.html#select

typedKnex.query(User).select("id");
typedKnex.query(User).select("id", "name");

where

https://knexjs.org/guide/query-builder.html#where

typedKnex.query(User).where("name", "name");

Or with operator

typedKnex.query(User).where("name", "like", "%user%");

// select * from "users" where "users"."name" like '%user%'

andWhere

typedKnex.query(User).where("name", "name").andWhere("name", "name");
typedKnex.query(User).where("name", "name").andWhere("name", "like", "%na%");

orWhere

typedKnex.query(User).where("name", "name").orWhere("name", "name");
typedKnex.query(User).where("name", "name").orWhere("name", "like", "%na%");

whereNot

https://knexjs.org/guide/query-builder.html#wherenot

typedKnex.query(User).whereNot("name", "name");

whereColumn

To use in subqueries. First parameter is for sub query columns and the third parameter is for columns from the parent query.

typedKnex.query(User).whereNotExists(UserSetting, (subQuery) => {
    subQuery.whereColumn("userId", "=", "id");
});

Use getColumn when nesting

query.whereExists(User, (subQuery1) => {
    subQuery1.whereColumn("status", "=", "status"); // Compares subQuery1 with its parent (query).

    subQuery1.whereExists(User, (subQuery2) => {
        subQuery2.whereColumn(subQuery2.getColumn("status"), "=", query.getColumn("status")); // Compares subQuery2 with the first parent (query)

        subQuery2.whereExists(User, (subQuery3) => {
            subQuery3.whereColumn(subQuery3.getColumn("status"), "=", subQuery1.getColumn("status")); // Compares subQuery3 with the second parent (subQuery1)
        });
    });
});

whereNull

typedKnex.query(User).whereNull("name");

orWhereNull

typedKnex.query(User).whereNull("name").orWhereNull("name");

whereNotNull

typedKnex.query(User).whereNotNull("name");

orWhereNotNull

typedKnex.query(User).whereNotNull("name").orWhereNotNull("name");

orderBy

typedKnex.query(User).orderBy("id");

orderByRaw

await typedKnex.query(User).orderByRaw("SUM(??) DESC", "users.year");

//  select * from "users" order by SUM("users"."year") DESC

innerJoin

typedKnex.query(User).innerJoin("category", UserCategory, "id", "=", "categoryId");

// select * from "users" inner join "userCategories" as "category" on "category"."id" = "users"."categoryId"

innerJoinColumn

typedKnex.query(User).innerJoinColumn("category");

innerJoinTableOnFunction

typedKnex.query(User).innerJoinTableOnFunction("evilTwin", User, (join) => {
    join.on("id", "=", "id").andOn("name", "=", "id").orOn("someValue", "=", "id").onVal("name", "=", "1").andOnVal("name", "=", "2").orOnVal("name", "=", "3").onNull("name");
});

leftOuterJoin

typedKnex.query(User).leftOuterJoin("category", UserCategory, "id", "=", "categoryId");

// select * from "users" left outer join "userCategories" as "category" on "category"."id" = "users"."categoryId"

leftOuterJoinColumn

typedKnex.query(User).leftOuterJoinColumn("category");

leftOuterJoinTableOnFunction

typedKnex.query(User).leftOuterJoinTableOnFunction("evilTwin", User, (join) => {
    join.on("id", "=", "id").andOn("name", "=", "id").orOn("someValue", "=", "id").onVal("name", "=", "1").andOnVal("name", "=", "2").orOnVal("name", "=", "3").onNull("name");
});

selectRaw

typedKnex.query(User).selectRaw("otherId", Number, "select other.id from other");

selectQuery

typedKnex
    .query(UserCategory)
    .select("id")
    .selectQuery("total", Number, User, (subQuery) => {
        subQuery.count("id", "total").whereColumn("categoryId", "=", "id");
    });
select "userCategories"."id" as "id", (select count("users"."id") as "total" from "users" where "users"."categoryId" = "userCategories"."id") as "total" from "userCategories"

findByPrimaryKey

deprecated

const user = await typedKnex.query(User).findByPrimaryKey("id", "d", "name");

whereIn

typedKnex.query(User).whereIn("name", ["user1", "user2"]);

whereNotIn

typedKnex.query(User).whereNotIn("name", ["user1", "user2"]);

orWhereIn

typedKnex.query(User).whereIn("name", ["user1", "user2"]).orWhereIn("name", ["user3", "user4"]);

orWhereNotIn

typedKnex.query(User).whereIn("name", ["user1", "user2"]).orWhereNotIn("name", ["user3", "user4"]);

whereBetween

typedKnex.query(UserCategory).whereBetween("year", [1, 2037]);

whereNotBetween

typedKnex.query(User).whereNotBetween("year", [1, 2037]);

orWhereBetween

typedKnex.query(User).whereBetween("year", [1, 10]).orWhereBetween("year", [100, 1000]);

orWhereNotBetween

typedKnex.query(User).whereBetween("year", [1, 10]).orWhereNotBetween("year", [100, 1000]);

whereExists

typedKnex.query(User).whereExists(UserSetting, (subQuery) => {
    subQuery.whereColumn("userId", "=", "id");
});

orWhereExists

typedKnex.query(User).orWhereExists(UserSetting, (subQuery) => {
    subQuery.whereColumn("userId", "=", "id");
});

whereNotExists

typedKnex.query(User).whereNotExists(UserSetting, (subQuery) => {
    subQuery.whereColumn("userId", "=", "id");
});

orWhereNotExists

typedKnex.query(User).orWhereNotExists(UserSetting, (subQuery) => {
    subQuery.whereColumn("userId", "=", "id");
});

whereParentheses

typedKnex
    .query(User)
    .whereParentheses((sub) => sub.where("id", "1").orWhere("id", "2"))
    .orWhere("name", "Tester");

const queryString = query.toQuery();
console.log(queryString);

Outputs:

select * from "users" where ("users"."id" = '1' or "users"."id" = '2') or "users"."name" = 'Tester'

groupBy

typedKnex.query(User).select("someValue").selectRaw("total", Number, 'SUM("numericValue")').groupBy("someValue");

having

typedKnex.query(User).having("numericValue", ">", 10);

havingNull

typedKnex.query(User).havingNull("numericValue");

havingNotNull

typedKnex.query(User).havingNotNull("numericValue");

havingIn

typedKnex.query(User).havingIn("name", ["user1", "user2"]);

havingNotIn

typedKnex.query(User).havingNotIn("name", ["user1", "user2"]);

havingExists

typedKnex.query(User).havingExists(UserSetting, (subQuery) => {
    subQuery.whereColumn("userId", "=", "id");
});

havingNotExists

typedKnex.query(User).havingNotExists(UserSetting, (subQuery) => {
    subQuery.whereColumn("userId", "=", "id");
});

havingBetween

typedKnex.query(User).havingBetween("numericValue", [1, 10]);

havingNotBetween

typedKnex.query(User).havingNotBetween("numericValue", [1, 10]);

union

typedKnex.query(User).union(User, (subQuery) => {
    subQuery.select("id").where("numericValue", 12);
});

unionAll

typedKnex
    .query(User)
    .select("id")
    .unionAll(User, (subQuery) => {
        subQuery.select("id").where("numericValue", 12);
    });

min

typedKnex.query(User).min("numericValue", "minNumericValue");

count

typedKnex.query(User).count("numericValue", "countNumericValue");

countDistinct

typedKnex.query(User).countDistinct("numericValue", "countDistinctNumericValue");

max

typedKnex.query(User).max("numericValue", "maxNumericValue");

sum

typedKnex.query(User).sum("numericValue", "sumNumericValue");

sumDistinct

typedKnex.query(User).sumDistinct("numericValue", "sumDistinctNumericValue");

avg

typedKnex.query(User).avg("numericValue", "avgNumericValue");

avgDistinct

typedKnex.query(User).avgDistinct("numericValue", "avgDistinctNumericValue");

clearSelect

typedKnex.query(User).select("id").clearSelect().select("name");

clearWhere

typedKnex
    .query(User)
    .where("id", "name")
    .clearWhere()
    .where(("name", "name");

clearOrder

typedKnex
    .query(User)
    .orderBy("id")
    .clearOrder()
    .orderBy(("name");

limit

typedKnex.query(User).limit(10);

offset

typedKnex.query(User).offset(10);

useKnexQueryBuilder

Use useKnexQueryBuilder to get to the underlying Knex.js query builder.

const query = typedKnex.query(User)
    .useKnexQueryBuilder(queryBuilder => queryBuilder.where('somethingelse', 'value'))
    .select("name");
);

getKnexQueryBuilder

Use getKnexQueryBuilder to get to the underlying Knex.js query builder.

const query = typedKnex.query(User).select("name");
const knexQuery = query.getKnexQueryBuilder();
queryBuilder.where("somethingelse", "value");

keepFlat

Use keepFlat to prevent unflattening of the result.

const item = await typedKnex
    .query(User)
    .where("name", 'name')
    .innerJoinColumn("category");
    .select("name", "category.name")
    .getFirst();

// returns { name: 'user name', category: { name: 'category name' }}

const item = await typedKnex
    .query(User)
    .where("name", 'name')
    .innerJoinColumn("category");
    .select("name", "category.name")
    .keepFlat()
    .getFirst();

// returns { name: 'user name', category.name: 'category name' }

toQuery

const query = typedKnex.query(User);

console.log(query.toQuery()); // select * from "users"

getFirstOrNull

Result No item One item Many items
getFirst Error Item First item
getSingle Error Item Error
getFirstOrNull null Item First item
getSingleOrNull null Item Error
getFirstOrUndefined undefined Item First item
getSingleOrUndefined undefined Item Error
const user = await typedKnex.query(User).where("name", "name").getFirstOrNull();

getFirstOrUndefined

Result No item One item Many items
getFirst Error Item First item
getSingle Error Item Error
getFirstOrNull null Item First item
getSingleOrNull null Item Error
getFirstOrUndefined undefined Item First item
getSingleOrUndefined undefined Item Error
const user = await typedKnex.query(User).where("name", "name").getFirstOrUndefined();

getFirst

Result No item One item Many items
getFirst Error Item First item
getSingle Error Item Error
getFirstOrNull null Item First item
getSingleOrNull null Item Error
getFirstOrUndefined undefined Item First item
getSingleOrUndefined undefined Item Error
const user = await typedKnex.query(User).where("name", "name").getFirst();

getSingleOrNull

Result No item One item Many items
getFirst Error Item First item
getSingle Error Item Error
getFirstOrNull null Item First item
getSingleOrNull null Item Error
getFirstOrUndefined undefined Item First item
getSingleOrUndefined undefined Item Error
const user = await typedKnex.query(User).where("name", "name").getSingleOrNull();

getSingleOrUndefined

Result No item One item Many items
getFirst Error Item First item
getSingle Error Item Error
getFirstOrNull null Item First item
getSingleOrNull null Item Error
getFirstOrUndefined undefined Item First item
getSingleOrUndefined undefined Item Error
const user = await typedKnex.query(User).where("name", "name").getSingleOrUndefined();

getSingle

Result No item One item Many items
getFirst Error Item First item
getSingle Error Item Error
getFirstOrNull null Item First item
getSingleOrNull null Item Error
getFirstOrUndefined undefined Item First item
getSingleOrUndefined undefined Item Error
const user = await typedKnex.query(User).where("name", "name").getSingle();

getMany

const users = await typedKnex.query(User).whereNotNull("name").getMany();

getCount

Returns the row count of the query.

const count = await typedKnex.query(User).getCount();

The return type is Promise<number|string>. This follows the Knex.js typing, see the count documentation.

The value of count will, by default, have type of string | number. This may be counter-intuitive but some connectors (eg. postgres) will automatically cast BigInt result to string when javascript's Number type is not large enough for the value.

The return type can be changed by overriding the ITypedQueryBuilder interface.

Declare as number:

declare module "@wwwouter/typed-knex" {
    interface ITypedQueryBuilder<Model, SelectableModel, Row> {
        getCount(): Promise<number>;
    }
}

Declare as BigInt:

declare module "@wwwouter/typed-knex" {
    interface ITypedQueryBuilder<Model, SelectableModel, Row> {
        getCount(): Promise<BigInt>;
    }
}

When using Postgres, pg.types.setTypeParser can be used to automatically convert the values.

To convert to integer, use this code:

pg.types.setTypeParser(20, "text", parseInt);

To convert to bigint, use this code:

pg.types.setTypeParser(20, "text", BigInt);

insertItem

typedKnex.query(User);

insertItemWithReturning

query.insertItemWithReturning({ id: "newId" });

// insert into "users" ("id") values ('newId') returning *
query.insertItemWithReturning({ id: "newId" }, ["id"]);

// insert into "users" ("id") values ('newId') returning "users"."id"

insertItems

typedKnex.query(User);

insertSelect

await typedKnex.query(User);
    .selectRaw('f', String, '\'fixedValue\'')
    .select("name")
    .distinct()
    .whereNotNull("name")
    .insertSelect(UserSetting, "id", "initialValue");

// insert into "userSettings" ("userSettings"."id","userSettings"."initialValue") select distinct ('fixedValue') as "f", "users"."name" as "name" from "users" where "users"."name" is not null

del

typedKnex.query(User);

delByPrimaryKey

deprecated

typedKnex.query(User);

updateItem

typedKnex.query(User);

updateItemWithReturning

query.updateItemWithReturning({ id: "newId" });

// update "users" set "id" = 'newId' returning *
query.updateItemWithReturning({ id: "newId" }, ["id"]);

// update "users" set "id" = 'newId' returning "users"."id"

updateItemByPrimaryKey

deprecated

typedKnex.query(User);

updateItemsByPrimaryKey

deprecated

typedKnex.query(User);

execute

typedKnex.query(User);

whereRaw

typedKnex.query(User);

havingRaw

typedKnex.query(User);

transacting

const typedKnex = new TypedKnex(database);
const transaction = await typedKnex.beginTransaction();
try {
    await typedKnex.query(User).transacting(transaction).insertItem(user1);
    await typedKnex.query(User).transacting(transaction).insertItem(user2);
    await transaction.commit();
} catch (error) {
    await transaction.rollback();
    // handle error
}

truncate

typedKnex.query(User);

distinct

typedKnex.query(User);

distinctOn

typedKnex.query(UserCategory).select("id").distinctOn(["name"]);

// select distinct on ("userCategories"."name") "userCategories"."id" as "id" from "userCategories"

clone

typedKnex.query(User);

groupByRaw

typedKnex.query(User);

Transactions

const typedKnex = new TypedKnex(database);
const transaction = await typedKnex.beginTransaction();
try {
    await typedKnex.query(User).transacting(transaction).insertItem(user1);
    await typedKnex.query(User).transacting(transaction).insertItem(user2);
    await transaction.commit();
} catch (error) {
    await transaction.rollback();
    // handle error
}

validateTables

Use the validateTables function to make sure that the Table's and Column's in TypeScript exist in the database.

import * as Knex from "knex";
import { validateTables } from "@wwwouter/typed-knex";

const knex = Knex({
    client: "pg",
    connection: "postgres://user:pass@localhost:5432/dbname",
});

await validateTables(knex);

Add an array of table names to the validateTables function to only validate those tables.

import * as Knex from "knex";
import { validateTables } from "@wwwouter/typed-knex";

const knex = Knex({
    client: "pg",
    connection: "postgres://user:pass@localhost:5432/dbname",
});

await validateTables(knex, ["users"]);

Test

npm test

Update version

npm version major|minor|patch
update CHANGELOG.md
git commit --amend
npm publish --access=public --otp=CODE
git push

for beta

update version to x.x.x-beta.x
npm publish --access public --tag beta --otp=CODE

typed-knex's People

Contributors

bgilman-nyk avatar dependabot[bot] avatar wwwouter avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

typed-knex's Issues

Feature request: Support for Date columns

Issue type: Feature request
Database system/driver: Postgres
typed-knex version: master

Hiya! I've been working on a project using Knex + TypeScript for some time now, and I've never been happy with the state of my very-messy data access layer. I stumbled into this project today during my routine investigation into the state of TypeScript ORMs and query-builders, and I'm really excited by what you've built so far! After trying to adopt mikro-orm and failing due to the number of assumptions it wants to make about my existing schema, something like this that I can gradually adopt into my Knex-using project seems much more my speed.

I tried adding typed-knex to a few functions, and it seems to work great so far. The only actual blocker I've seen so far is with the supported data types for columns: using a Date type as a column causes a nasty type error.

I've added test cases on a branch here for easy reproducing. On this branch, the error I see is:

test/unit/typedQueryBuilderTests.ts:1267:23 - error TS2769: No overload matches this call.
  Overload 1 of 2, '(selectColumnFunction: (c: TransformPropertiesToFunction<User, []>) => [() => unknown, ((() => unknown) | undefined)?, ((() => unknown) | undefined)?, ((() => unknown) | undefined)?, ((() => unknown) | undefined)?, ((() => unknown) | undefined)?, ... 20 more ...?, ((() => unknown) | undefined)?]): ITypedQueryBuilder<...>', gave the following error.
    Type 'TransformPropertiesToFunction<Date, ["createdAt"]>' is not assignable to type '() => unknown'.
      Type 'TransformPropertiesToFunction<Date, ["createdAt"]>' provides no match for the signature '(): unknown'.
  Overload 2 of 2, '(selectColumnFunction: (c: TransformPropertiesToFunction<User, []>) => () => unknown): ITypedQueryBuilder<User, {}>', gave the following error.
    Type '[TransformPropertiesToFunction<Date, ["createdAt"]>]' is not assignable to type '() => unknown'.
      Type '[TransformPropertiesToFunction<Date, ["createdAt"]>]' provides no match for the signature '(): unknown'.

1267         const query = typedKnex.query(User).select(c => [c.createdAt]);
                           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

  src/typedKnex.ts:543:31
    543         selectColumnFunction: (
                                      ~
    544             c: TransformPropertiesToFunction<Model>
        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    545         ) => () => R1
        ~~~~~~~~~~~~~~~~~~~~~
    The expected type comes from the return type of this signature.


Found 1 error.

I get a similar error in my actual project.

I started trying to investigate the issue, but the innards of the type logic is quite a bit beyond my grasp of TypeScript's type system. My guess is that the type checks assume any object column is a reference to another table, and it can only handle scalars currently. I also assume this probably isn't an easy assumption to change, but I'm curious if it's on your roadmap, or if you've found alternative ways of dealing with dates (e.g. casting to/from strings with knex transforms).

Thanks!

TypeError: Cannot read properties of undefined (reading 'tableName')

Issue type:

[ ] Question
[x] Bug report
[ ] Feature request
[ ] Documentation issue

Database system/driver:

[ ] Postgres
[x] MSSQL
[ ] MySQL
[ ] MariaDB
[ ] SQLite3
[ ] Oracle
[ ] Amazon Redshift

typed-knex version:

[ ] latest
[ ] @next
[x] .4.5.1 (or put your version here)

Error Message

TypeError: Cannot read properties of undefined (reading 'tableName')
      at new TypedQueryBuilder (/Users/User.Name/Documents/code/app-name/server/node_modules/@wwwouter/typed-knex/src/typedKnex.ts:481:54)
      at TypedKnex.query (/Users/User.Name/Documents/code/app-name/server/node_modules/@wwwouter/typed-knex/src/typedKnex.ts:17:16)
      at Object.getFeaturePermissions (/Users/User.Name/Documents/code/app-name/server/.build/repositories/featurePermissionRepository.js:9:56)
      at getFeaturePermissions (/Users/User.Name/Documents/code/app-name/server/.build/services/featurePermissionService.js:30:46)
      at get (/Users/User.Name/Documents/code/app-name/server/.build/controllers/featurePermissionsController.js:13:100)
      at /Users/User.Name/Documents/code/app-name/server/.build/controllers/handler.js:25:28
      at Layer.handle [as handle_request] (/Users/User.Name/Documents/code/app-name/server/.build/node_modules/express/lib/router/layer.js:95:5)
      at next (/Users/User.Name/Documents/code/app-name/server/.build/node_modules/express/lib/router/route.js:137:13)
      at Route.dispatch (/Users/User.Name/Documents/code/app-name/server/.build/node_modules/express/lib/router/route.js:112:3)
      at Layer.handle [as handle_request] (/Users/User.Name/Documents/code/app-name/server/.build/node_modules/express/lib/router/layer.js:95:5)
      at /Users/User.Name/Documents/code/app-name/server/.build/node_modules/express/lib/router/index.js:281:22
      at Function.process_params (/Users/User.Name/Documents/code/app-name/server/.build/node_modules/express/lib/router/index.js:335:12)
      at next (/Users/User.Name/Documents/code/app-name/server/.build/node_modules/express/lib/router/index.js:275:10)
      at /Users/User.Name/Documents/code/app-name/server/node_modules/express-jwt/lib/index.js:131:7
      at /Users/User.Name/Documents/code/app-name/server/node_modules/express-jwt/node_modules/async/lib/async.js:52:16
      at Immediate.<anonymous> (/Users/User.Name/Documents/code/app-name/server/node_modules/express-jwt/node_modules/async/lib/async.js:1206:34)
      at processImmediate (node:internal/timers:464:21)

Steps To Reproduce
The server consistently crashes after hitting a few different endpoints. My team did some digging and found the issue that we are seeing is coming from the package reflect-metadata

If you execute the statement Reflect.getMetadataKeys(User) it returns with the list of metadata tags that are on the class. (So in this instance the result is [Symbol(table)])

But, if you then run the function Reflect.getMetadata(Symbol(table), tableClass) , the response is undefined.

But of course, this is not consistent with every successful call as the response should look like: { tableName: 'users' }

Package.json

{
    "dependencies": {
        "@wwwouter/typed-knex": "^4.5.1",
        "dayjs": "^1.10.7",
        "express": "^4.17.1",
        "express-jwt": "^6.1.0",
        "http-errors": "^1.8.1",
        "knex": "^0.95.15",
        "mock-knex": "^0.4.10",
        "serverless-http": "^2.7.0",
        "slugify": "^1.6.5",
        "tedious": "^14.0.0",
        "uuid": "^8.3.2"
    },
    "engines": {
        "node": ">=14.0"
    },
    "devDependencies": {
        "@jest/globals": "^27.3.1",
        "@serverless/eslint-config": "^4.0.0",
        "@tsconfig/node14": "^1.0.1",
        "@types/express": "^4.17.13",
        "@types/express-jwt": "^6.0.4",
        "@types/http-errors": "^1.8.1",
        "@types/jest": "^27.0.2",
        "@types/jsonwebtoken": "^8.5.8",
        "@types/mock-knex": "^0.4.3",
        "@types/serverless": "^1.78.40",
        "@types/supertest": "^2.0.11",
        "@types/uuid": "^8.3.3",
        "@typescript-eslint/eslint-plugin": "^5.4.0",
        "@typescript-eslint/parser": "^5.4.0",
        "dotenv": "^16.0.0",
        "eslint": "^8.3.0",
        "eslint-config-airbnb-base": "^15.0.0",
        "eslint-config-airbnb-typescript": "^16.0.0",
        "eslint-config-prettier": "^8.3.0",
        "eslint-plugin-import": "^2.25.3",
        "fishery": "^2.0.0",
        "jest": "^27.3.1",
        "jsonwebtoken": "^8.5.1",
        "prettier": "^2.4.1",
        "serverless": "^2.70.0",
        "serverless-dotenv-plugin": "^3.12.2",
        "serverless-offline": "^8.3.1",
        "serverless-plugin-typescript": "^2.1.0",
        "supertest": "^6.1.6",
        "ts-jest": "^27.0.7",
        "ts-node": "^10.3.1",
        "type-fest": "^2.5.1",
        "typescript": "^4.4.4"
    }
}

Update with aliases

Issue type:

[X] Question
[ ] Bug report
[ ] Feature request
[ ] Documentation issue

Database system/driver:

[ ] Postgres
[ ] MSSQL
[X] MySQL
[ ] MariaDB
[ ] SQLite3
[ ] Oracle
[ ] Amazon Redshift

typed-knex version:

[X] latest
[ ] @next
[ ] 0.x.x (or put your version here)

Description:
These are my table definitions:

import { Column, Table } from '@wwwouter/typed-knex';

@Table('company')
export class TableCompany {
  @Column({ name: 'id', primary: true })
  id: number;
  @Column({ name: 'tag' })
  tag: string | null;
  @Column({ name: 'email' })
  email: string | null;
  @Column({ name: 'password' })
  password: string | null;
  @Column({ name: 'created_at' })
  createdAt: Date;
}

@Table('users')
export class TableUsers {
  @Column({ name: 'id', primary: true })
  id: number;
  @Column({ name: 'email' })
  email: string | null;
  @Column({ name: 'password' })
  password: string | null;
  @Column({ name: 'activation_code' })
  activationCode: string | null;
  @Column({ name: 'google_data' })
  googleData: string | null;
  @Column({ name: 'facebook_data' })
  facebookData: string | null;
  @Column({ name: 'company_id' })
  companyId: TableCompany | null;
  @Column({ name: 'created_at' })
  createdAt: Date;
}

I would like to write query:

UPDATE users SET email = ?, activation_code = ? WHERE id = ?

I wrote code like this:

    const newData = new TableUsers();
    newData.email = 'NEW EMAIL';
    newData.activationCode = '543245632';

    await SQLManager.typedKnex
      .query(TableUsers)
      .updateItemByPrimaryKey(1, newData);

Problem is, it creates this query:

update `users` set `email` = ?, `activationCode` = ? where `id` = ?

(and throws error Error: Unknown column 'activationCode' in 'field list'. Problem is activationCode instead of activation_code)

How do I write correct update queries?

findByPrimaryKey Not accepting all columns

Issue type:

[ ] Question
[X] Bug report
[ ] Feature request
[ ] Documentation issue

Database system/driver:

[ ] Postgres
[ ] MSSQL
[X] MySQL
[ ] MariaDB
[ ] SQLite3
[ ] Oracle
[ ] Amazon Redshift

typed-knex version:

[X] latest
[ ] @next
[ ] 0.x.x (or put your version here)

Description:

My code:

const companyData = await SQLManager.typedKnex.query(TableCompany)
      .findByPrimaryKey(companyId, (r) => r.subscription);

My definitions:

@Table('company')
export class TableCompany {
  @Column({ name: 'id', primary: true })
  id: number;
  @Column({ name: 'tag' })
  tag: string | null;
  @Column({ name: 'name' })
  name: string | null;
  @Column({ name: 'email' })
  email: string | null;
  @Column({ name: 'password' })
  password: string | null;
  @Column({ name: 'activation_code' })
  activationCode: string | null;
  @Column({ name: 'resetpass_code' })
  resetpassCode: string | null;
  @Column({ name: 'google_data' })
  googleData: string | null;
  @Column({ name: 'facebook_data' })
  facebookData: string | null;
  @Column({ name: 'subscription' })
  subscription: Date;
  @Column({ name: 'created_at' })
  createdAt: Date;
}

Error on line .findByPrimaryKey(companyId, (r) => r.subscription);, error is: Property 'subscription' does not exist on type 'TransformPropsToFunctionsOnlyLevel1<TableCompany>'.ts(2339)

I noticed that both subscription and createdAt are of type Date and both of them throws this typescript error. rest of properties are working fine (see screenshot).

image

Solution would be to use .select() and .where() - this solution works perfectly with these Date-typed properties.

Where Clause on join functions

Issue type:

[ ] Question
[ ] Bug report
[x ] Feature request
[ ] Documentation issue

Database system/driver:

[ ] Postgres
[x ] MSSQL
[ ] MySQL
[ ] MariaDB
[ ] SQLite3
[ ] Oracle
[ ] Amazon Redshift

typed-knex version:

[ x] latest
[ ] @next
[ ] 0.x.x (or put your version here)

Knex.js version:

Steps to reproduce or a small repository showing the problem:

Given a one to many relationship where I want all rows of table 1 regardless of presence of a table 2 row with a specific value
What I have to do to get what I want:

typedKnex.query(Table1)
.leftOuterJoinJoinTableOnFunction('t2', Table2, (join) => {
  (t1) => t1.c1,
  '='
  (t2) => t2.c1
})
.whereParentheses((sub) => sub
  .whereNotExists(Table2, (subQuery, parentColumn) => {
    subQuery.whereColumn((t2) => t2.c1, '=', parentColumn.c1)
  })
  .orWhere((t1) => t1.t2.c2, '=', 'valueThatUniquelyIdentifiesARow')
);

Here I am essentially having to recode the functionality of a left outer join despite already doing one

The outcome of the code above is:

row1: t1.c1, t2.c2
row2: t1.c1, t2.c2
row3 t1.c1, null

which is what you would get from the below sql code

Select t1.* , t2.*
From Table1 t1
Left Join Table2 t2 ON t1.c1 = t2.c1 And t2.c2 = 'valueThatUniquelyIdentifiesARow'

What I would like to be able to write in typed-knex:

typedKnex.query(Table1)
.innderJoinTableOnFunction('t2', Table2, (join) => 
  join.onColumns(
    (t1) => t1.c1,
    '=',
    (t2) => t2.c1,
    'And',
    (t2) => t2.c1,
    '='
    () => 'valueThatUniquelyIdentifiesARow'
  )
);

Or Something like

typedKnex.query(Table1)
.leftOuterJoinTableOnFunction('t2', Table2, (join) => 
  join.onColumns(
    (t1) => t1.c1,
    '=',
    (t2) => t2.c1,
  )
  .whereColumn((t2) => t2.c2, '=', 'valueThatUniquelyIdentifiesARow'
);

The reason I'd like this change is because doing a where clause outside the join function understandably breaks the left outer join functionality by filtering out Table1 rows that don't have a corresponding Table2 join matching the unique value. It's a bit confusing, and the resulting whereParentheses code is a bit unwieldy. I feel like I'm struggling to communicate what I mean so let me know if you want any clarification.

I don't know how hard this would be to do but it would be a nice update.

On update ?

Issue type:

[x] Question
[ ] Bug report
[ ] Feature request
[ ] Documentation issue

Database system/driver:

[ ] Postgres
[ ] MSSQL
[x] MySQL
[x] MariaDB
[ ] SQLite3
[ ] Oracle
[ ] Amazon Redshift

typed-knex version:

[x] latest
[ ] @next
[ ] 0.x.x (or put your version here)

Knex.js version:
last ?

Hi !
I don't know if it possible but I have a need.
I need to update updated_at's timestamp each time my table is modified.

But, I would be compatible with mysql, mariadb and sqlite3. So 'ON UPDATE ...' doesn't work with sqlite3.

For example, my migration :

export async function up(knex: Knex): Promise<void> {
  await knex.schema.createTable(tableName, (table) => {
    table.uuid('id').notNullable().primary();
    table.string('firstname').notNullable();
    table.string('lastname').notNullable();
    table.string('email').notNullable().unique();
    table.date('birthdate').notNullable();
    table.string('gender', 1).notNullable();
    table.boolean('active').notNullable();
    table.boolean('termsofuse').notNullable();
    table.timestamps(true, true);
  });
}

Do you have any idea, maybe with flags, how to update my table ?
Is typed-knex is the good solution ?

I think I will move from objectionJS to typed-knex !

Thanks for you work !

Optional relations

Issue type:

[X] Question
[ ] Bug report
[X] Feature request
[X] Documentation issue

Database system/driver:

[X] Postgres

typed-knex version:

[X] 2.8.1 (or put your version here)

Knex.js version:
0.20.3

Steps to reproduce or a small repository showing the problem:

Hi Wouter,

I'm (once more) looking into the Typed Knex project. The current state looks quite nice and I like the new short and concise syntax.
During my first test I ran into a problem, which I think is not solvable with the current version, but might just need a better explanation in the docs.

What I'm trying to achieve is to get optional relations working properly. Suppose the following knex migration:

    await knex.schema.createTable('groups', table => {
        table.uuid('id').primary();
        table.string('name');
    });

    await knex.schema.createTable('users', table => {
        table.uuid('id').primary();
        table.string('name');
        table.uuid('groupId')
            .nullable() // <-- Note the 'nullable()'
            .references('id')
            .inTable('groups');
    });

    const group1 = { id: uuidv4(), name: 'group 1' };
    const user1 = { id: uuidv4(), name: 'user 1', groupId: group1.id };
    const user2 = { id: uuidv4(), name: 'user 2', groupId: null };  // <-- this user does not have a group

    await knex.insert(group1).into('groups');
    await knex.insert(user1).into('users');
    await knex.insert(user2).into('users');

The simplest Type Knex implementation of this model would be:

@Entity('groups')
export class Group {
    @Column({ primary: true })
    public id: string;
    @Column()
    public name: string;
}

@Entity('users')
export class User {
    @Column({ primary: true })
    public id: string;
    @Column()
    public name: string;
    @Column({ name: 'groupId' })
    public group: Group;
}

But then, performing queries on this:

const user1 = await typedKnex.query(User)
    .where(i => i.name, 'user 1')
    .leftOuterJoinColumn(i => i.group)
    .select(i => [i.id, i.name, i.group.id, i.group.name])
    .getFirst();
console.log(user1);
/*
{ id: '3c152f1a-c0d5-4343-984c-c829909db133',
  name: 'user 1',
  group:
   { id: '0663bcc3-fddb-4e18-976e-ae90e12fc3c9', name: 'group 1' } }
Ok, that's fine!
*/

const user2 = await typedKnex.query(User)
    .where(i => i.name, 'user 2')
    .leftOuterJoinColumn(i => i.group)
    .select(i => [i.id, i.name, i.group.id, i.group.name])
    .getFirst();
console.log(user2);
/*
{ id: 'c40bb9b6-12b1-4021-a7fb-44b19495e72c',
  name: 'user 2',
  group: { id: null, name: null } }
Hmm....
*/

The result of user2 is really awkward. The fact that user2.group is an object instead of null is strange, and the typing of this object is incorrect because both fields in this object should be non-nullable.

Is there a way around this? The following does not work:

@Column({ name: 'groupId' })
public group: Group | null; // correct typing, but crashes the query builder

@Column({ name: 'groupId' })
public group?: Group; // somewhat correct typing, but crashes the query builder

Thanks!

Some property names are incomplete after a query

Issue type:

[ ] Question
[x] Bug report
[ ] Feature request
[ ] Documentation issue

Database system/driver:

[x] Postgres
[ ] MSSQL
[ ] MySQL
[ ] MariaDB
[ ] SQLite3
[ ] Oracle
[ ] Amazon Redshift

typed-knex version:

[x] latest
[ ] @next
[ ] 0.x.x (or put your version here)

Knex.js version: 0.21.8

Steps to reproduce or a small repository showing the problem:
See https://github.com/voinik/typedKnexBugTest/tree/master

Edit: I've found the culprit. It's not TypedKnex, it's postgres:

NOTICE: identifier "paymentProviderCustomerReference.organization.accessUntilExtended" will be truncated to "paymentProviderCustomerReference.organization.accessUntilExtend"

The same thing happens in Knex.

Do you think you want to fix this problem within TypedKnex, or will you leave it up to the devs to be aware of this postgres behavior?

Select all aliases bug

Issue type:

[X] Question
[X] Bug report
[ ] Feature request
[ ] Documentation issue

Database system/driver:

[ ] Postgres
[ ] MSSQL
[X] MySQL
[ ] MariaDB
[ ] SQLite3
[ ] Oracle
[ ] Amazon Redshift

typed-knex version:

[X] latest
[ ] @next
[ ] 0.x.x (or put your version here)

Description:

My table definition:

@Table('giveaways')
export class TableGiveaways {
  @Column({ name: 'id', primary: true })
  id: number;
  @Column({ name: 'tickets_limit' })
  ticketsLimit: number;
  @Column({ name: 'ticket_price' })
  ticketPrice: number;
  @Column({ name: 'ticket_currency' })
  ticketCurrency: string;
  @Column({ name: 'is_finished' }) isFinished: boolean;
  @Column({ name: 'is_started' })
  isStarted: boolean;
  @Column({ name: 'winner_uuid' })
  winnerUuid: string | null;
  @Column({ name: 'name' })
  name: string;
  @Column({ name: 'item_name' })
  itemName: string;
  @Column({ name: 'item_image' })
  itemImage: string;
  @Column({ name: 'item_url' })
  itemUrl: string;
  @Column({ name: 'background_url' })
  backgroundUrl: string;
  @Column({ name: 'background_brightness' })
  backgroundBrightness: string;
  @Column({ name: 'item_price' })
  itemPrice: number;
  @Column({ name: 'record_url' })
  recordUrl: string | null;
  @Column({ name: 'twitch_url' })
  twitchUrl: string | null;
  @Column({ name: 'start_date' })
  startDate: Date;
  @Column({ name: 'end_date' })
  endDate: Date;
  @Column({ name: 'main_color_50' })
  mainColor_50: string;
  @Column({ name: 'main_color' })
  mainColor: string;
  @Column({ name: 'alt_color' })
  altColor: string;
  @Column({ name: 'light_color' })
  lightColor: string;
  @Column({ name: 'created_at' })
  createdAt: Date;
}

My code:

   const giveaways = await SQLManager.typedKnex
      .query(TableGiveaways)
      .getMany();

  console.log(giveaways);

Response:

{
  id: 2,
  tickets_limit: 1000,
  ticket_price: 999.99,
  ticket_currency: '๐Ÿ’ฐ',
  is_finished: 0,
  is_started: 0,
  winner_uuid: null,
  name: 'Monthly Giveaway (February)',
  item_name: 'StatTrakโ„ข AK-47 Frontside Misty (Field-Tested)',
  item_image: 'https://steamcommunity-a.akamaihd.net/economy/image/-9a81dlWLwJ2UUGcVs_nsVtzdOEdtWwKGZZLQHTxDZ7I56KU0Zwwo4NUX4oFJZEHLbXH5ApeO4YmlhxYQknCRvCo04DEVlxkKgpot7HxfDhjxszJemkV08u_mpSOhcjnI7TDglRc7cF4n-T--Y3nj1H6rhFpYWihcIGUdwc8aFmGqVm6kunogsTvvMyaz3Bn7yghsXiInxPl0AYMMLKjE_A1sw/512fx384f',
  item_url: 'https://steamcommunity.com/profiles/76561198326893249/inventory/#730',
  background_url: 'https://beta.csgonest.eu/assets/55074d13-0a1e-4900-a543-d2785f7f5c27?key=1080p&fallback=no',
  ba....

Problem: If I don't define what columns to select (because I want to select them all), it does not rename fields. For example,is_finished should beisFinished as defined in @Column({ name: 'is_finished' }) isFinished: boolean;

MS Code Freezing with Typed Knex but fine without

Issue type:

[ ] Question
[x ] Bug report
[ ] Feature request
[ ] Documentation issue

Database system/driver:

[x ] MySQL

typed-knex version: 4.4.1
Knex.js version: 0.21.21

Issue:

Hi, this library looks like just what I need but I'm finding MS Code freezes every time I hover over types to get the intellisense. If I comment out the typed-knex query and restart Code it's fine again. As soon as I uncomment it starts slowing down. When I checked Task Manager it looks like either there's a memory leak or it's just going to use a lot more than it should. I gave it a good couple minutes and three attempts to give it time to load up but it didn't sort itself out :(

How to use foreign keys in select queries?

Issue type:

[X] Question
[ ] Bug report
[ ] Feature request
[ ] Documentation issue

Database system/driver:

[ ] Postgres
[ ] MSSQL
[X] MySQL
[ ] MariaDB
[ ] SQLite3
[ ] Oracle
[ ] Amazon Redshift

typed-knex version:

[X] latest
[ ] @next
[ ] 0.x.x (or put your version here)

Description:
These are my table definitions:

import { Column, Table } from '@wwwouter/typed-knex';

@Table('company')
export class TableCompany {
  @Column({ name: 'id', primary: true })
  id: number;
  @Column({ name: 'tag' })
  tag: string | null;
  @Column({ name: 'email' })
  email: string | null;
  @Column({ name: 'password' })
  password: string | null;
  @Column({ name: 'created_at' })
  createdAt: Date;
}

@Table('users')
export class TableUsers {
  @Column({ name: 'id', primary: true })
  id: number;
  @Column({ name: 'email' })
  email: string | null;
  @Column({ name: 'password' })
  password: string | null;
  @Column({ name: 'activation_code' })
  activationCode: string | null;
  @Column({ name: 'google_data' })
  googleData: string | null;
  @Column({ name: 'facebook_data' })
  facebookData: string | null;
  @Column({ name: 'company_id' })
  companyId: TableCompany | null;
  @Column({ name: 'created_at' })
  createdAt: Date;
}

That means inside users table there is company_id which is foreign key to company table (id column).
I want to create this query: (I want to select company_id on purpose, I know it is not needed as I already have it in some varaible)

SELECT id from users WHERE company_id = ?

I writed code like this:

const userData = await SQLManager.typedKnex
      .query(TableUsers)
      .select((r) => [r.id])
      .where((r) => r.companyId.id, "4")
      .limit(1)
      .getFirstOrNull();

Problem is, it creates this query:

select `users`.`id` as `id` from `users` where `companyId`.`id` = ? limit ?

(and throws error Error: Unknown column 'companyId.id' in 'where clause')

How do I write correct queries when my query includes foreign key column?

Double join throwing error

Issue type:

[X] Question
[X] Bug report
[ ] Feature request
[ ] Documentation issue

Database system/driver:

[ ] Postgres
[X] MSSQL
[ ] MySQL
[ ] MariaDB
[ ] SQLite3
[ ] Oracle
[ ] Amazon Redshift

typed-knex version:

[X] latest
[ ] @next
[ ] 0.x.x (or put your version here)

Description:

Class configuration:

@Table('weapons')
export class TableWeapons {
  @Column({ name: 'id', primary: true })
  id: number;
  @Column({ name: 'skin_id' })
  skinId: TableSkins | null;
  @Column({ name: 'skin_id' })
  skinIdPlain: number;
  @Column({ name: 'stattrak' })
  stattrak: boolean;
  @Column({ name: 'souvenir' })
  souvenir: boolean;
  @Column({ name: 'wear' })
  wear: string;
  @Column({ name: 'price' })
  price: number;
  @Column({ name: 'image' })
  image: string;
  @Column({ name: 'big_image' })
  bigImage: string;
}

@Table('skins')
export class TableSkins {
  @Column({ name: 'id', primary: true })
  id: number;
  @Column({ name: 'name' })
  name: string;
  @Column({ name: 'average_price' })
  averagePrice: number | null;
  @Column({ name: 'rarity' })
  rarity: string;
  @Column({ name: 'rarity_color' })
  rarityColor: string;
}

@Table('inventories')
export class TableInventories {
  @Column({ name: 'id', primary: true })
  id: number;
  @Column({ name: 'weapon_id' })
  weaponId: TableWeapons | null;
  @Column({ name: 'weapon_id' })
  weaponIdPlain: number;
  @Column({ name: 'amount' })
  amount: number;
  @Column({ name: 'profile_id' })
  profileId: TableProfiles | null;
  @Column({ name: 'profile_id' })
  profileIdPlain: number;
}

My code:

const invItems = await SQLManager.typedKnex
      .query(TableInventories)
      .select((r) => [r.id, r.weaponIdPlain, r.amount])
      .innerJoinTableOnFunction('weapon', TableWeapons, (join) => {
        join.on(
          (c) => c.id,
          '=',
          (c) => c.weaponIdPlain
        );
      })
      .innerJoinTableOnFunction('skin', TableSkins, (join) => {
        join.on(
          (c) => c.id,
          '=',
          (c) => c.weapon.skinIdPlain
        );
      })
      .where((r) => r.profileIdPlain, profile.id)
      .orderBy((r) => r.skin.name, 'asc')
      .orderBy((r) => r.skin.averagePrice, 'asc')
      .limit(onePage)
      .offset(offset)
      .getMany();

Error:

  error Error: Cannot get column data. Did you set @Column() attribute on TableInventories.skin?
  error     at Object.getColumnInformation (/Users/meldiron/Desktop/csgoNest/Server/node_modules/@wwwouter/typed-knex/src/decorators.ts:146:15)
  error     at TypedQueryBuilder.getColumnNameWithoutAlias (/Users/meldiron/Desktop/csgoNest/Server/node_modules/@wwwouter/typed-knex/src/typedKnex.ts:2135:37)
  error     at TypedQueryBuilder.getColumnNameWithoutAliasFromFunctionOrString (/Users/meldiron/Desktop/csgoNest/Server/node_modules/@wwwouter/typed-knex/src/typedKnex.ts:2054:21)
  error     at TypedQueryBuilder.orderBy (/Users/meldiron/Desktop/csgoNest/Server/node_modules/@wwwouter/typed-knex/src/typedKnex.ts:1301:18)
  error     at getInventory (/Users/meldiron/Desktop/csgoNest/Server/src/api/services/UserService.ts:367:8)
  error     at Object.handleRequest (/Users/meldiron/Desktop/csgoNest/Server/src/api/helpers/utils.ts:9:17)
  error     at getInventory (/Users/meldiron/Desktop/csgoNest/Server/src/api/controllers/userCtrl.ts:56:3) +0ms

If I remove second join (and orderBy, because they use data from second join), it works fine.

EDIT: Actually, I think it is problem with orderBy because if I move the select method and remove orderBy, it works:

const invItems = await SQLManager.typedKnex
      .query(TableInventories)
      .innerJoinTableOnFunction('weapon', TableWeapons, (join) => {
        join.on(
          (c) => c.id,
          '=',
          (c) => c.weaponIdPlain
        );
      })
      .innerJoinTableOnFunction('skin', TableSkins, (join) => {
        join.on(
          (c) => c.id,
          '=',
          (c) => c.weapon.skinIdPlain
        );
      })
      .select((r) => [
        r.id,
        r.weaponIdPlain,
        r.amount,
        r.skin.name,
        r.skin.averagePrice,
      ])
      .where((r) => r.profileIdPlain, profile.id)
      .limit(onePage)
      .offset(offset)
      .getMany();

.orderBy() on timestamp

Issue type:

[X] Question
[X] Bug report
[ ] Feature request
[ ] Documentation issue

Database system/driver:

[ ] Postgres
[X] MSSQL
[ ] MySQL
[ ] MariaDB
[ ] SQLite3
[ ] Oracle
[ ] Amazon Redshift

typed-knex version:

[X] latest
[ ] @next
[ ] 0.x.x (or put your version here)

Description:

Table definition:

@Table('users')
export class TableUsers {
  @Column({ name: 'id', primary: true })
  id: number;
  @Column({ name: 'email' })
  email: string | null;
  @Column({ name: 'created_at' })
  createdAt: Date;
}

Code:

SQLManager.typedKnex.query(TableUsers).orderBy((i) => i.createdAt);

Error (typescript error):

Type 'TransformPropertiesToFunction<Required<Date>, [a: { name: "createdAt"; nullable: true; }]>' is not assignable to type '() => unknown'.
  Type 'TransformPropertiesToFunction<Required<Date>, [a: { name: "createdAt"; nullable: true; }]>' provides no match for the signature '(): unknown'.ts(2322)

(error is on i.createdAt)

If I try to orderBy i.id or i.email it works, it only has problem with date type.

getCount with postgresql returns string, not number

Issue type:

[ ] Question
[x] Bug report
[ ] Feature request
[ ] Documentation issue

Database system/driver:

[x] Postgres
[ ] MSSQL
[ ] MySQL
[ ] MariaDB
[ ] SQLite3
[ ] Oracle
[ ] Amazon Redshift

typed-knex version:

[x] latest
[ ] @next
[ ] 0.x.x (or put your version here)

Knex.js version: 1.0.4

Steps to reproduce or a small repository showing the problem:

Calling getCount with latest typed-knex returns a Promise<string>, instead of the advertised Promise<number>. This seems to be a deliberate design choice by the folks who made node-postgres, as count can be (is?) a bigint.

I'm not sure what currently happens for other implementations, but it would be nice to have a workarounds if you can think of any. Thanks in advance!

Join not working properly

Issue type:

[X] Question
[X] Bug report
[ ] Feature request
[ ] Documentation issue

Database system/driver:

[ ] Postgres
[ ] MSSQL
[X] MySQL
[ ] MariaDB
[ ] SQLite3
[ ] Oracle
[ ] Amazon Redshift

typed-knex version:

[X] latest
[ ] @next
[ ] 0.x.x (or put your version here)

Description:

My definitions:

@Table('static_pages_lang')
export class TableStaticPagesLang {
  @Column({ name: 'id', primary: true })
  id: number;
  @Column({ name: 'page_id' })
  pageId: TableStaticPages;
  @Column({ name: 'page_id' })
  pageIdPlain: number;
  @Column({ name: 'lang_id' })
  langId: string;
  @Column({ name: 'slug' })
  slug: string;
  @Column({ name: 'title' })
  title: string;
  @Column({ name: 'text' })
  text: string;
}

@Table('static_pages')
export class TableStaticPages {
  @Column({ name: 'id', primary: true })
  id: number;
  @Column({ name: 'internal_name' })
  internalName: string;
}

My code:

const staticPages = await SQLManager.typedKnex
      .query(TableStaticPages)
      .leftOuterJoinTableOnFunction('lang', TableStaticPagesLang, (join) => {
        join.on(
          (r2) => r2.pageIdPlain,
          '=',
          (r2) => r2.id
        );
      })
      .select((r) => [r.lang.slug, r.lang.text, r.lang.title])
      .getMany();

    console.log(staticPages);

Result:

  error Error: Unknown column 'lang.pageIdPlain' in 'on clause'
  error     at Packet.asError (/Users/meldiron/Desktop/ren-sk/Server/node_modules/mysql2/lib/packets/packet.js:684:17)
  error     at Query.execute (/Users/meldiron/Desktop/ren-sk/Server/node_modules/mysql2/lib/commands/command.js:28:26)
  error     at Connection.handlePacket (/Users/meldiron/Desktop/ren-sk/Server/node_modules/mysql2/lib/connection.js:449:32)
  error     at PacketParser.onPacket (/Users/meldiron/Desktop/ren-sk/Server/node_modules/mysql2/lib/connection.js:72:12)
  error     at PacketParser.executeStart (/Users/meldiron/Desktop/ren-sk/Server/node_modules/mysql2/lib/packet_parser.js:75:16)
  error     at Socket.<anonymous> (/Users/meldiron/Desktop/ren-sk/Server/node_modules/mysql2/lib/connection.js:79:25)
  error     at Socket.emit (events.js:315:20)
  error     at Socket.EventEmitter.emit (domain.js:483:12)
  error     at addChunk (_stream_readable.js:295:12)
  error     at readableAddChunk (_stream_readable.js:271:9)
  error     at Socket.Readable.push (_stream_readable.js:212:10)
  error     at TCP.onStreamRead (internal/stream_base_commons.js:186:23) +0ms

Generated query:

select `lang`.`slug` as `lang.slug`, `lang`.`text` as `lang.text`, `lang`.`title` as `lang.title` from `static_pages` left outer join `static_pages_lang` as `lang` on `static_pages`.`id` = `lang`.`pageIdPlain`

What I wanted:

select lang.slug, lang.text, lang.title from static_pages left outer join static_pages_lang on static_pages.id = lang.page_id

I guess everything is correct exccept the pageIdPlain needs to page_id as configured using @column. Or am I using it incorrectly?

updateItemWithReturning ignores previous modifications to querybuilder

Issue type:

[ ] Question
[x] Bug report
[ ] Feature request
[ ] Documentation issue

Database system/driver:

[x] Postgres
[ ] MSSQL
[ ] MySQL
[ ] MariaDB
[ ] SQLite3
[ ] Oracle
[ ] Amazon Redshift

typed-knex version:

[x] latest
[ ] @next
[ ] 0.x.x (or put your version here)

Knex.js version: 1.0.4

const payload = { name: 'hello-unique', value: 12 };

handle
            .query(MyTable)
            .where("name", payload.name)
            .updateItemWithReturning({value: payload.value});

Generates the psql query:
update "mytable" set "value" = 12 returning *

Looking at the code, it seems that (at least) updateItemsWithReturning and possibly updateItemsByPrimaryKey don't make use of the this.querybuilder, thereby losing any pre-existing configurations such as the where('name', payload.name) in the previous snippet of code.

Returning values with useKnexQueryBuilder

Issue type:

[x] Question
[ ] Bug report
[ ] Feature request
[ ] Documentation issue

Database system/driver:

[x] Postgres
[ ] MSSQL
[ ] MySQL
[ ] MariaDB
[ ] SQLite3
[ ] Oracle
[ ] Amazon Redshift

typed-knex version:

[x] latest
[ ] @next
[ ] 0.x.x (or put your version here)

Knex.js version: 1.0.4

Steps to reproduce or a small repository showing the problem:
Given that I have a table mytable, with a unique string name and a Numeric(12, 3) counter. How can I have an incrementing update that returns all records that were changed?

I tried using useKnexQueryBuilder, which gives a correct query, but cannot be executed without

@Table('mytable')
export class MyTable {
    @Column({ primary: true }) public id!: number;
    @Column() public name!: string;
    @Column() public counter!: string; // numeric(12, 3)
}

const handle = new TypedKnex(knex({ client: 'pg', connection: <my-db>}));
const q = handle
    .query(MyTable)
    .where('name', 'hello')
    .useKnexQueryBuilder((query) => query.increment('counter', 10))
    .toQuery();
console.log(q);

Gives:

update "mytable" set "counter" = "counter" + 10 where "mytable"."name" = 'hello'

Which is perfect, except for the fact that I still need to run this query. Using execute works, but returns undefined, which is not what I want.
How can I combine useKnexQueryBuilder with something that does not return undefined, but just unknown or any?

Can I use `forUpdate` in `typed-knex` ?

Issue type:

[ ] Question
[ ] Bug report
[ ] Feature request
[x] Documentation issue

Database system/driver:

[ ] Postgres
[ ] MSSQL
[x] MySQL
[ ] MariaDB
[ ] SQLite3
[ ] Oracle
[ ] Amazon Redshift

typed-knex version:

[x] latest
[ ] @next
[ ] 0.x.x (or put your version here)

Knex.js version:

First of all, thank you so much for your work! I love this library!

This issues mainly asks about how the forUpdate function in knex is used in typed-knex!

Joined table is automatically flattened

Issue type:

[x] Question
[x] Bug report
[ ] Feature request
[ ] Documentation issue

Database system/driver:

[x] Postgres
[ ] MSSQL
[ ] MySQL
[ ] MariaDB
[ ] SQLite3
[ ] Oracle
[ ] Amazon Redshift

typed-knex version:

[x] latest
[ ] @next
[ ] 0.x.x (or put your version here)

Knex.js version:
0.21.6

Steps to reproduce or a small repository showing the problem:

I have a schema defined like:

@Table('images')
export class Image {
  @Column({ primary: true })
  public id?: string
  @Column()
  public url?: string
  @Column()
  public mimeType?: string
  @Column()
  public width?: number
  @Column()
  public height?: number
  @Column()
  public fileSize?: number

  @Column()
  public createdAt?: Date
  @Column()
  public updatedAt?: Date
  @Column()
  public deletedAt?: Date | null
}


@Table('users')
export class User {
  @Column({ primary: true })
  public id?: string

  // ... other fields

  @Column({ name: 'imageId' })
  public image?: Image
  @Column()
  public imageId?: string
}

When I do the following query:

typedKnex
  .query(User)
  .where((c) => c.id, 'someid')
  .leftOuterJoinColumn((c) => c.image)
  .getFirstOrNull()
  .then((user) => console.log(user, user.image))

user.image is undefined while user contains flattened fields from image. What am I doing wrong?

Extending Tables

Issue type:

[-] Question
[ ] Bug report
[ ] Feature request
[ ] Documentation issue

Database system/driver:

[x] Postgres
[ ] MSSQL
[ ] MySQL
[ ] MariaDB
[ ] SQLite3
[ ] Oracle
[ ] Amazon Redshift

typed-knex version:

[x] latest
[ ] @next
[ ] 0.x.x (or put your version here)

Is there a way to extend a class so that multiple tables can inherit columns from an abstract class?

Get ID after inserting row

Issue type:

[X] Question
[ ] Bug report
[X] Feature request
[ ] Documentation issue

Database system/driver:

[ ] Postgres
[ ] MSSQL
[X] MySQL
[ ] MariaDB
[ ] SQLite3
[ ] Oracle
[ ] Amazon Redshift

typed-knex version:

[X] latest
[ ] @next
[ ] 0.x.x (or put your version here)

Is it possible to get ID of the last inserted row? Knex itself has .returning('id') but typedKnex always returns void with insertItem so I could not find a way to achieve this.

Support โ€˜unknownโ€™ columns

Issue type:

  • Question

Would it be possible to support columns that are โ€˜unknownโ€™ to typed-knex? That is to say, columns in the db that are not a part of the typed-knex @table decorated entity. Iโ€™m thinking of a nullable column that is automatically managed by a db trigger, that shouldnโ€™t impact any application operation in the first place.

If you are not against such a feature, would you be open to a PR implementing it as an opt-in feature?

Problem with insert

Issue type:

[X] Question
[ ] Bug report
[ ] Feature request
[ ] Documentation issue

Database system/driver:

[ ] Postgres
[ ] MSSQL
[X] MySQL
[ ] MariaDB
[ ] SQLite3
[ ] Oracle
[ ] Amazon Redshift

typed-knex version:

[X latest
[ ] @next
[ ] 0.x.x (or put your version here)

Knex.js version:
Latest

Steps to reproduce or a small repository showing the problem:

Hi !
I want to make a simple insertion with two models.

import { Column, Table } from '@wwwouter/typed-knex';

import { Email, UUID } from '../../ts/types/base.type';

@Table('user')
class UserDB {
  @Column({ primary: true })
  public id!: UUID;

  @Column()
  public firstname!: string;

  @Column()
  public lastname!: string;

  @Column()
  public email!: Email;

  @Column()
  public birthdate!: Date;

  @Column()
  public gender: 'M' | 'F' | 'N' = 'N';

  @Column()
  public created_at!: Date;

  @Column()
  public updated_at!: Date;

  @Column()
  public termsofuse: boolean = true;

  @Column()
  public active: boolean = true;
}

export default UserDB;
import { Column, Table } from '@wwwouter/typed-knex';

import User from './User.db.model';
import { Bcrypt, UUID } from '../../ts/types/base.type';

@Table('password')
class PasswordDB {
  @Column({ primary: true })
  public id!: UUID;

  @Column()
  public password!: Bcrypt;

  @Column({ name: 'user_id' })
  public user!: User;

  @Column()
  public created_at!: Date;

  @Column()
  public updated_at!: Date;
}

export default PasswordDB;

I just run :

await db.query(UserDB).insertItem(userDB);
await db.query(GroupDB).insertItem(groupDB);

userDB and groupDB are instances of UserDB and GroupDB. They are full and there is no misfiled attributes.

And I got :

Error: insert into `group` (`group_name`, `user_id`) values (0, {"gender":"N","termsofuse":true,"active":true,"id":"6dc9f9f9-ae34-4eef-8a33-6f514ec05c6e","firstname":"string","lastname":"string","email":"[email protected]","birthdate":"2020-10-08T13:51:50.489Z","created_at":"2020-10-08 16:11:18","updated_at":"2020-10-08 16:11:18"}) - Column count doesn't match value count at row 1
        at Packet.asError (/........node_modules/mysql2/lib/packets/packet.js:712:17)
        at Query.execute (......../node_modules/mysql2/lib/commands/command.js:28:26)
        at Connection.handlePacket (/.........node_modules/mysql2/lib/connection.js:425:32)
        at PacketParser.onPacket (/h........node_modules/mysql2/lib/connection.js:75:12)
        at PacketParser.executeStart (/.........../node_modules/mysql2/lib/packet_parser.js:75:16)
        at Socket.<anonymous> (/............./node_modules/mysql2/lib/connection.js:82:25)
        at Socket.emit (events.js:314:20)
        at Socket.EventEmitter.emit (domain.js:486:12)
        at addChunk (_stream_readable.js:307:12)
        at readableAddChunk (_stream_readable.js:282:9)

What can I do, to avoid to put the user object instead of just user.id ?

Thanks for your work !

Make Entity first argument optional

Issue type:

[*] Feature request

Database system/driver:

[*] MySQL

typed-knex version:

[*] latest

Knex.js version:

[*] latest

Why this feature request?

Because my Table name & Entity class name are the same.

Plus it improve DX. ๐Ÿ˜„

TypeError when using innerJoinColumn

Issue type:

[x] Question
[x] Bug report
[ ] Feature request
[ ] Documentation issue

Database system/driver:

[x] Postgres
[ ] MSSQL
[ ] MySQL
[ ] MariaDB
[ ] SQLite3
[ ] Oracle
[ ] Amazon Redshift

typed-knex version:

[x] latest
[ ] @next
[ ] 0.x.x (or put your version here)

Table definition

@Table("sites")
export class TableSites {
	@Column()
	public address?: string | null | undefined;
	@Column()
	public country?: string | null | undefined;
	@Column({ primary: true })
	public id: Id;
};

@Table("activities")
export class TableActivities {
	@Column()
	public description?: string | null | undefined;
	@Column({ primary: true })
	public id: Id;
	@Column()
	public siteId?: Id | null | undefined;
	@Column({ name: "siteId" })
	public site?: TableSites | null | undefined;
};

Code

knex.query(TableActivities).select('id', 'siteId').innerJoinColumn('site')

Error

TypeError:
     at Object.getMetadata (node_modules/reflect-metadata/Reflect.js:354:23)
     at Object.getTableMetadata (node_modules/@wwwouter/typed-knex/src/decorators.ts:46:20)
     at TypedQueryBuilder.joinColumn (node_modules/@wwwouter/typed-knex/src/typedKnex.ts:2141:34)
     at TypedQueryBuilder.innerJoinColumn (node_modules/@wwwouter/typed-knex/src/typedKnex.ts:1370:21)

It seems that reflect-metadata couldn't get design:type of propertyKey site, it returns undefined instead of TableSites.
Did I configure Table definition correctly?

Using innerJoinTableOnFunction works perfectly fine.

Table decorator is incorrectly marked as deprecated

Issue type:

[ ] Question
[x] Bug report
[ ] Feature request
[ ] Documentation issue

Database system/driver:

[x] Postgres
[ ] MSSQL
[ ] MySQL
[ ] MariaDB
[ ] SQLite3
[ ] Oracle
[ ] Amazon Redshift

typed-knex version:

[ ] latest
[ ] @next
[x] 4.13.0

Knex.js version:
2.2.0

Typescript version:
4.7.4

Steps to reproduce or a small repository showing the problem:

I have block like this, which i've built following the documentation:

import { Column, Table } from '@wwwouter/typed-knex';

@Table('user')
class User {
    @Column({ primary: true })
    public id: number;
    @Column()
    public username: string;
    @Column()
    public email: string;
    @Column({ name: 'first_name' })
    public firstName: string;
    @Column({ name: 'last_name' })
    public lastName: string;
}

But my vscode keeps showing a warning that the Table decorator is deprecated.
Table is deprecated. use Table.

image

Wrong table alias in "whereColumn" clause with tables from separate PostgreSQL schemas

Issue type:

[ ] Question
[x] Bug report
[ ] Feature request
[ ] Documentation issue

Database system/driver:

[x] Postgres
[ ] MSSQL
[ ] MySQL
[ ] MariaDB
[ ] SQLite3
[ ] Oracle
[ ] Amazon Redshift

typed-knex version:

4.13.1

Knex.js version:

0.19.5

Steps to reproduce or a small repository showing the problem:

import Knex from 'knex';
import { Column, Table, TypedKnex } from '@wwwouter/typed-knex';

@Table('schema1.table1')
class Table1 {
  @Column({ primary: true })
  id: number;
}

@Table('schema2.table2')
class Table2 {
  @Column({ primary: true })
  id: number;

  @Column({ name: 'table1_id' })
  linkedId: number;

  @Column({ name: 'table1_id' })
  linked: Table1;
}

const knex = Knex({
  client: 'pg',
  connection: { /** ... */ }
});

const typedKnex = new TypedKnex(knex)

const query = typedKnex.query(Table1)
  .whereExists(
    Table2,
    (subQuery, t1) => subQuery.whereColumn('linkedId', '=', 'id')
  )
  .select('id')
  .toQuery();

console.log(query);

output:

select "schema1"."table1"."id" as "id"
from "schema1"."table1"
where exists (
    select *
    from "schema2"."table2" as "subquery0$schema2.table2"
    where "subquery0$schema2"."table2"."table1_id" = "schema1"."table1"."id"
    --                       ^ this dot is wrong
)

expected

select "schema1"."table1"."id" as "id"
from "schema1"."table1"
where exists (
    select *
    from "schema2"."table2" as "subquery0$schema2.table2"
    where "subquery0$schema2.table2"."table1_id" = "schema1"."table1"."id"
)

or maybe remove dots from alias?

select "schema1"."table1"."id" as "id"
from "schema1"."table1"
where exists (
    select *
    from "schema2"."table2" as "subquery0$schema2$table2"
    where "subquery0$schema2$table2"."table1_id" = "schema1"."table1"."id"
)

Using andOn inside innerJoinTableOnFunction not translated column according to definition

Issue type:

[X ] Question
[ X] Bug report
[ ] Feature request
[ ] Documentation issue

Database system/driver:

[X ] Postgres
[ ] MSSQL
[ ] MySQL
[ ] MariaDB
[ ] SQLite3
[ ] Oracle
[ ] Amazon Redshift

typed-knex version:

[ X] latest
[ ] @next
[ ] 0.x.x (or put your version here)

Knex.js version:
0.95.12

Query:

   .innerJoinTableOnFunction("sector", RecordTable, (join) => {
        join.andOnVal("group", "=", "MASTER_DATA")
            .andOnVal("key", "=", "SECTOR")
            .andOn("subKey", "=", "budget.sector");
    })

Table Definition:

@Table("records")
export class RecordTable {
    @Column({ primary: true, name: "id" })
    public id!: string;

    @Column({ name: "group" })
    public group!: string;

    @Column({ name: "key" })
    public key!: string;

    @Column({ name: "sub_key" })
    public subKey!: string;

    @Column({ name: "data" })
    public data!: string;
}

The subKey column is suppose to translate into "sector"."sub_key"

Now is generating something like this

INNER JOIN "records" AS "sector" ON "sector"."group" = $1
		AND "sector"."key" = $2
		AND "budget"."sector" = "sector"."subKey"

Postgres error hint:

 hint: 'Perhaps you meant to reference the column "sector.sub_key".',

ValidateTables with multiple databases

Issue type:

  • Question
  • Bug report
  • Feature request
  • Documentation issue

Database system/driver:

  • Postgres
  • MSSQL
  • MySQL
  • MariaDB
  • SQLite3
  • Oracle
  • Amazon Redshift

typed-knex version:

  • latest
  • @next
  • 4.9.1

Knex.js version:

Steps to reproduce or a small repository showing the problem:

I want to use TypedKnex to connect to more than 1 database and validate the tables in each of those databases. The databases do not contain the same tables.

I want to have something like this:

const knex1 = Knex({ client: 'pg', connection: 'someString' });
await validateTables(knex1, [Table1, Table2]);
const knex2 = Knex({ client: 'pg', connection: 'someOtherString' });
await validateTables(knex2, [Table3, Table4]);

When I use validateTables now it will complain that not all the tables exist in both databases.

.singleOrNull without .select does not show | null typing

Issue type:

[ ] Question
[x] Bug report
[ ] Feature request
[ ] Documentation issue

Database system/driver:

[x] Postgres
[ ] MSSQL
[ ] MySQL
[ ] MariaDB
[ ] SQLite3
[ ] Oracle
[ ] Amazon Redshift

typed-knex version:

[x] latest
[ ] @next
[ ] 0.x.x (or put your version here)

Knex.js version: 0.21.8

Steps to reproduce or a small repository showing the problem:
See bug 2 in https://github.com/voinik/typedKnexBugTest/tree/master

In short:

    // Notice the ({..}) | null in the typing if you hover over the variable
    const fullyWrittenSubscription = await typedKnex.query(Subscription)
        .select('createdAt', 'id', 'paymentProviderCustomerReferenceId', 'updatedAt')
        .where('id', subscription1.id)
        .getSingleOrNull();

    // Notice the typing is only RemoveObjectsFrom<Subscription>, and no | null if you hover over the variable
    const shortHandSubscription = await typedKnex.query(Subscription)
        .where('id', subscription1.id)
        .getSingleOrNull();

Select autocomplete dropdown breaks with TypeScript v4.7.x

Issue type:

[ ] Question
[X] Bug report
[ ] Feature request
[ ] Documentation issue

Database system/driver:

[X] Postgres
[ ] MSSQL
[ ] MySQL
[ ] MariaDB
[ ] SQLite3
[ ] Oracle
[ ] Amazon Redshift

typed-knex version:

[ ] latest
[ ] @next
[4.12.0] 0.x.x (or put your version here)

Knex.js version:
2.2.0

Steps to reproduce or a small repository showing the problem:

  1. Type this using any model with more than 1 property in it:
typedKnex.query(User).select('id');
  1. try to add more properties in the select and you'll find the only choices you have in the dropdown are the keys you already have in there, rather than all keys.

I've tested the following versions of TypeScript: 4.4.4, 4.5.4, 4.6.2, 4.6.4. This bug did not appear in these versions. The bug starts appearing in versions 4.7.2 and higher.

Autocomplete not working properly on `findByPrimaryKey`

Issue type:

[X] Question
[X] Bug report
[ ] Feature request
[ ] Documentation issue

Database system/driver:

[ ] Postgres
[ ] MSSQL
[X] MySQL
[ ] MariaDB
[ ] SQLite3
[ ] Oracle
[ ] Amazon Redshift

typed-knex version:

[X] latest
[ ] @next
[ ] 0.x.x (or put your version here)

Description:

Table definition (simplified):

@Table('users')
export class TableUsers {
  @Column({ name: 'id', primary: true })
  id: number;
  @Column({ name: 'settings_email' })
  settingsEmail: string | null;
  @Column({ name: 'settings_full_name' })
  settingsFullName: string | null;
  @Column({ name: 'settings_birth_date' })
  settingsBirthDate: Date | null;
  @Column({ name: 'settings_nickname' })
  settingsNickname: string | null;
  @Column({ name: 'settings_photo' })
  settingsPhoto: string | null;
}

My code:

const userData = await SQLManager.typedKnex
      .query(TableUsers)
      .findByPrimaryKey(userId, (r) => [
        r.settingsBirthDate,
        r.settingsEmail,
        r.settingsFullName,
        r.settingsNickname,
        r.settingsPhoto,
      ]);

Problem: Autocomplete does not work, more info screenshot:
image

If I write it like this, autocomplete work:
image

Composite key considerations

Issue type:

[x] Question

Database system/driver:

[x] Postgres

typed-knex version:

[x] latest
Knex.js version: 1.0.4

Steps to reproduce or a small repository showing the problem:

Very simple question, but how would I represent composite keys in typed-knex?

From a glance at the source code, it seems this is not supported; my more pressing question is whether things will just work out if I don't annotate any columns as primary key for those tables that only have a composite key?

Obviously stuff like findByPrimaryKey won't work in that case, which is totally fine as my code obviously doesn't use that right now ๐Ÿ˜„

Subquery for where in

Issue type:

[ ] Question
[ ] Bug report
[X] Feature request
[ ] Documentation issue

Database system/driver:

[ ] Postgres
[ ] MSSQL
[ ] MySQL
[ ] MariaDB
[ ] SQLite3
[ ] Oracle
[ ] Amazon Redshift

typed-knex version:

[X] latest
[ ] @next
[ ] 0.x.x (or put your version here)

I was looking for subquery for whereIn in the apis but it looks like it's not implemented. There is a whereExist with subquery but why no subquery for where in? Is this a todo or a design decision?

Left join throws error

Issue type:

[x] Question
[x] Bug report
[ ] Feature request
[ ] Documentation issue

Database system/driver:

[ ] Postgres
[ ] MSSQL
[x] MySQL
[ ] MariaDB
[ ] SQLite3
[ ] Oracle
[ ] Amazon Redshift

typed-knex version:

[x] latest (^4.1.0)
[ ] @next
[ ] 0.x.x (or put your version here)

Knex.js version: "^0.16.5",

Steps to reproduce or a small repository showing the problem:

Code:

    const staticPagesP = SQLManager.typedKnex
      .query(TableStaticPages)
      .leftOuterJoinTableOnFunction('lang', TableStaticPagesLang, (join) => {
        join.on('pageIdPlain', '=', 'lang.id');
      })
      .select('lang.slug', 'lang.text', 'lang.title')
      .where('lang.langIdPlain', lang)
      .getMany();

Error:

  error Error: Unknown column 'lang.pageIdPlain' in 'on clause'
  error     at Packet.asError (/Users/meldiron/Desktop/ren-sk/Server/node_modules/mysql2/lib/packets/packet.js:684:17)
  error     at Query.execute (/Users/meldiron/Desktop/ren-sk/Server/node_modules/mysql2/lib/commands/command.js:28:26)
  error     at Connection.handlePacket (/Users/meldiron/Desktop/ren-sk/Server/node_modules/mysql2/lib/connection.js:449:32)
  error     at PacketParser.onPacket (/Users/meldiron/Desktop/ren-sk/Server/node_modules/mysql2/lib/connection.js:72:12)
  error     at PacketParser.executeStart (/Users/meldiron/Desktop/ren-sk/Server/node_modules/mysql2/lib/packet_parser.js:75:16)
  error     at Socket.<anonymous> (/Users/meldiron/Desktop/ren-sk/Server/node_modules/mysql2/lib/connection.js:79:25)
  error     at Socket.emit (events.js:315:20)
  error     at Socket.EventEmitter.emit (domain.js:483:12)
  error     at addChunk (_stream_readable.js:295:12)
  error     at readableAddChunk (_stream_readable.js:271:9)
  error     at Socket.Readable.push (_stream_readable.js:212:10)
  error     at TCP.onStreamRead (internal/stream_base_commons.js:186:23) +0ms

Query:

  knex:query select `lang`.`slug` as `lang.slug`, `lang`.`text` as `lang.text`, `lang`.`title` as `lang.title` from `static_pages` left outer join `static_pages_lang` as `lang` on `lang`.`id` = `lang`.`pageIdPlain` where `lang`.`lang_id` = ? undefined +24ms

From what I can see, it did not translate javascript variable name to mysql column name correctly (from pageIdPlain to page_id)

Schema:

@Table('static_pages')
export class TableStaticPages {
  @Column({ name: 'id', primary: true })
  id: number;
  @Column({ name: 'internal_name' })
  internalName: string;
}

@Table('static_pages_lang')
export class TableStaticPagesLang {
  @Column({ name: 'id', primary: true })
  id: number;
  @Column({ name: 'page_id' })
  pageId: TableStaticPages | null;
  @Column({ name: 'page_id' })
  pageIdPlain: number;
  @Column({ name: 'lang_id' })
  langId: TableLangs | null;
  @Column({ name: 'lang_id' })
  langIdPlain: string;
  @Column({ name: 'slug' })
  slug: string;
  @Column({ name: 'title' })
  title: string;
  @Column({ name: 'text' })
  text: string;
}

Note: This might be miss-use of some function or wrong schema setup by me, I recently migrated from really old version of typed knex to latest. Please let me know.

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.