-
Notifications
You must be signed in to change notification settings - Fork 64
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
can't specify attributes
in scan
operation?
#439
Comments
I was able to come up with a hacky workaround that allows specifying the attributes for a scan operation with fully type-safe results. Here is an example of the API const Product = new Entity({
...
attributes: {
userId: { type: 'string', required: true },
productId: { type: 'string', required: true },
title: { type: 'string', required: true },
price: { type: 'number', required: true },
imageUrl: { type: 'string', required: true },
},
...
});
const query = Product.scan.where(({ price }, { gt }) => gt(price, 100));
const options = getScanOptionsWithProjection({
entity: Product,
attributes: ['productId', 'title'], // type safe
getParams: query.params,
options: {
// any other options (e.g `order`, etc.)
},
});
const { data }: ScanResultWithProjection<typeof options> = await query.go(options);
// ^? { productId: string; title: string; }[] This produces the following dynamo command: {
"TableName": "products",
"ExpressionAttributeNames": {
"#price": "price",
"#__edb_e__": "__edb_e__",
"#__edb_v__": "__edb_v__",
"#pk": "pk",
"#sk": "sk",
"#productId": "productId",
"#title": "title"
},
"ExpressionAttributeValues": {
":price0": 100,
":__edb_e__0": "product",
":__edb_v__0": "1",
":pk": "$app#userid_",
":sk": "$product_1#productid_"
},
"FilterExpression": "begins_with(#pk, :pk) AND begins_with(#sk, :sk) AND (#price > :price0) AND #__edb_e__ = :__edb_e__0 AND #__edb_v__ = :__edb_v__0",
"ProjectionExpression": "#__edb_e__, #__edb_v__, #productId, #title"
} And here is the code that make this happen: declare const __brand: unique symbol;
type Brand<T, U> = T & { [__brand]: U };
export type EntityAttributeKey<T> =
T extends Entity<any, any, any, any>
? keyof T['schema']['attributes']
: never;
export type ScanResultWithProjection<
TOptions extends {
[__brand]: {
entity: Entity<any, any, any, any>;
attributes: EntityAttributeKey<TOptions[typeof __brand]['entity']>[];
};
},
> = {
data: Pick<
EntityItem<TOptions[typeof __brand]['entity']>,
TOptions[typeof __brand]['attributes'][number]
>[];
cursor: string | null;
};
export function getScanOptionsWithProjection<
TEntity extends Entity<any, any, any, any>,
TAttributes extends EntityAttributeKey<TEntity>[],
TOptions extends QueryOptions = {},
>({
attributes,
getParams,
options = {} as TOptions,
}: {
entity: TEntity;
attributes: TAttributes;
getParams: (opts: QueryOptions) => object;
options?: TOptions;
}) {
const attributesSet = new Set<string>([
'__edb_e__',
'__edb_v__',
...(attributes as any),
]);
const params = getParams(options);
const { ExpressionAttributeNames } = params as {
ExpressionAttributeNames: Record<string, string>;
};
for (const attr of attributesSet) {
ExpressionAttributeNames[`#${attr}`] = attr;
}
const mergedOptions = {
...options,
params: {
...params,
ExpressionAttributeNames,
ProjectionExpression: Array.from(attributesSet)
.map((attr) => `#${attr}`)
.join(', '),
},
} as const;
return mergedOptions as Brand<
typeof mergedOptions,
{
entity: TEntity;
attributes: TAttributes;
}
>;
} |
Great request! I'm surprised it's not an option currently tbh |
@tywalch just wanted to express my deep appreciation for what you have done with ElectroDB! ElectroDB makes it a breeze to work with Dynamo and covers 99% of cases. And thanks to the escape hatches you have implemented, I can implement functionality that ElectroDB doesn't support relatively easily. One example is my workaround for specifying the attributes in a scan operation. Another example is the ability to scan a GSI which is not natively supported by ElectroDB but can be easily accomplished by performing a scan and passing the index name in const userCheapProducts = await Product.query.byUserAndCheap({ userId: '123' }).go();
const allCheapProducts = await Product.scan.go({
params: {
IndexName: Product.schema.indexes.byUserAndCheap.index,
},
});
const Product = new Entity({
attributes: {
userId: { type: 'string', required: true },
productId: { type: 'string', required: true },
price: { type: 'number', required: true },
},
indexes: {
byUser: {
pk: {
field: 'pk',
composite: ['userId'],
},
sk: {
field: 'sk',
composite: ['productId'],
},
},
byUserAndCheap: {
index: 'gsi1',
condition: (attrs) => {
// checking for undefined prevents the index from
// being deleted during updates that omit the price field
return attrs.price === undefined || attrs.price < 100;
},
pk: {
field: 'gsi1pk',
composite: ['userId'],
},
sk: {
field: 'gsi1sk',
composite: ['price', 'productId'],
},
},
},
}); again, thanks so much! 🙏 |
Describe the bug
When performing a
get
orquery
operation, you can passattributes
to thego
orparams
method.However when performing a
scan
operation, you cannot passattributes
. is this by design?It would be nice to be able to specify
attributes
when performing a scan to not pull unnecessary data over the wire.ElectroDB Version
2.15.0
The text was updated successfully, but these errors were encountered: