New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
dynamicPick ts result type from execute #86
Comments
Hi, Wow, this is very complex in TS; I don't know if there is a clean way to implement this, considering the number of transformations the library does internally. In any case, I found a way for you to do this 😁 First, some points:
Then, here is how your code looks like with the changes I made: import {MySqlConnection} from 'ts-sql-query/connections/MySqlConnection';
import {Table} from 'ts-sql-query/Table';
import {SelectedValues} from 'ts-sql-query/extras/types';
import {extractColumnsFrom} from 'ts-sql-query/extras/utils';
import {dynamicPickPaths, expandTypeFromDynamicPickPaths} from 'ts-sql-query/dynamicCondition';
class Connection extends MySqlConnection<'Connection'> {
allowEmptyString = true;
}
class StoreTable extends Table<Connection, 'StoreTable'> {
id = this.autogeneratedPrimaryKey('id', 'int');
name = this.column('name', 'string');
address = this.column('address', 'string');
foo = this.optionalColumn('foo', 'double');
bar = this.column('bar', 'boolean');
someField = this.column('someField', 'string');
someBool = this.column('someBool', 'boolean');
constructor() {
super('store');
}
}
export const storeTable = new StoreTable();
const EXTRACTED_COLUMNS_FROM_STORE_TABLE = extractColumnsFrom(storeTable);
const AVAILABLE_STORE_FIELD = ['name', 'address', 'foo', 'someBool'] as const;
type AvailableStoreField = typeof AVAILABLE_STORE_FIELD[number];
type SomeStoreClassGetStoreResult<TField extends AvailableStoreField> = Pick<
SelectedValues<StoreTable>,
TField
>;
class SomeStoreClass {
constructor(private getConnection: () => Connection, private storeId: number) {}
async getStore<TField extends AvailableStoreField>(
fields: TField[]
): Promise<SomeStoreClassGetStoreResult<TField>> {
const connection = this.getConnection();
const pick = dynamicPickPaths(EXTRACTED_COLUMNS_FROM_STORE_TABLE, fields)
const store = await connection
.selectFrom(storeTable)
.select(pick)
.where(storeTable.id.equals(this.storeId))
.executeSelectNoneOrOne();
if (!store) {
throw new Error(`store #${this.storeId} not found`);
}
return expandTypeFromDynamicPickPaths(EXTRACTED_COLUMNS_FROM_STORE_TABLE, fields, store);
}
}
const someStoreClass = new SomeStoreClass({} as any, 1);
/**
* I want to use it like this, so that there are only those fields that I specified
* and that the type is correct (without undefined for required columns)
*/
someStoreClass.getStore(['address', 'foo']).then((store) => {
/**
* store type => SomeStoreClassGetStoreResult<"address" | "foo">
* store => {address: string; foo?: number | undefined}
*/
}) Please, test this solution and let me know if that satisfies and works for you. I'm also interested in understanding why you want to use this picking feature; several people asked me about this feature, but only some of them had already needed it. I will be waiting for your feedback. |
…nd implement `expandTypeFromDynamicPickPaths` utility function to rectify the query output when the list of fields to pick is a generic type. #86
Released ts-sql-query 1.49.0 with the changes commented here. |
Hi Nice try)) but there are problems. const store = await connection
.selectFrom(storeTable)
.select(pick)
.where(storeTable.id.equals(this.storeId))
.executeSelectNoneOrOne(); to const store = await connection
.selectFrom(storeTable)
.select({
lastRawOrderTime: storeTable.lastOrderTime
})
.where(storeTable.id.equals(this.storeId))
.executeSelectNoneOrOne(); Also we should always do this check if (!store) {
throw ...
} And need the same for SelectMany support I think we can put it off for now, I'll think about it again return store as any; |
…ferent kind of query result. Document `dynamicPickPaths` function, `DynamicPickPaths` type and , `expandTypeFromDynamicPickPaths` function #86
Released ts-sql-query 1.52.0 making public and documenting this feature. The function |
no, unfortunately this only works when ts knows exactly which fields are requested, but then the type is already inferred without any extra effort. It doesn't work for dynamic fields: async getStore<TField extends AvailableStoreField>(
fields: TField[]
): Promise<SomeStoreClassGetStoreResult<TField>> {
const connection = this.getConnection();
const pickedFields = dynamicPickPaths(EXTRACTED_COLUMNS_FROM_STORE_TABLE, fields);
const selectQuery = connection
.selectFrom(storeTable)
.select(pickedFields)
.where(storeTable.id.equals(this.storeId));
const store = await selectQuery.executeSelectNoneOrOne();
if (!store) {
throw new Error(`store #${this.storeId} not found`);
}
const res = expandTypeFromDynamicPickPaths(EXTRACTED_COLUMNS_FROM_STORE_TABLE, fields, store);
/**
* Type 'Expand<{ id?: number | undefined; name?: string | undefined; address?: string | undefined; foo?: number | undefined; bar?: boolean | undefined; someField?: string | undefined; someBool?: boolean | undefined; } & ResultObjectValues<...> & Pick<...>>' is not assignable to type 'SomeStoreClassGetStoreResult<TField>'.
Type '{}' is not assignable to type 'SomeStoreClassGetStoreResult<TField>'.ts(2322)
*/
return res;
} At the moment i am doing this: async getStore<TField extends AvailableStoreField>(
fields: TField[]
): Promise<SomeStoreClassGetStoreResult<TField>> {
const connection = this.getConnection();
const selectQuery = connection
.selectFrom(storeTable)
.select(dynamicPick(EXTRACTED_COLUMNS_FROM_STORE_TABLE, {}, fields))
.where(storeTable.id.equals(this.storeId));
const store = (await selectQuery.executeSelectNoneOrOne()) as SomeStoreClassGetStoreResult<TField> | null;
if (!store) {
throw new Error(`store #${this.storeId} not found`);
}
return store;
} I would like without "as", but so far so)) |
mmmm, I already have some usage of that, even in the documentation I placed an example. Can you include here your tsconfig y typescript version? |
Can you include as well your AvailableStoreField definition, as you have in your code? I see you are not placing any mandatory field; I will verify that case as well |
Include as well SomeStoreClassGetStoreResult type definition |
all difinitions from first comment, need only replace method async getStore |
tsconfig.json {
"compilerOptions": {
"target": "es2019",
"module": "commonjs",
"jsx": "preserve",
"lib": [
"dom",
"es2018",
"esnext"
],
"baseUrl": ".",
"moduleResolution": "Node",
"strict": true,
"allowJs": true,
"esModuleInterop": true,
"skipLibCheck": true,
"sourceMap": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": false,
"removeComments": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"incremental": true,
"downlevelIteration": true,
"allowSyntheticDefaultImports": true,
"plugins": [{"name": "typescript-plugin-css-modules"}],
"declaration": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitAny": true,
"outDir": "./lib"
},
"include": [
"next-env.d.ts",
"src/**/*.ts",
"src/**/*.tsx",
"src/**/*.js",
"src/**/*.jsx"
],
"exclude": [
"node_modules",
".yarn"
]
} |
…required columns under some circumstances #86
Hi,
is it possible to make support for such a sample
Or how can I do the right thing?
The text was updated successfully, but these errors were encountered: