Skip to content

Extract shared query scaffolding from compare/beginsWith/between #50

@mckalexee

Description

@mckalexee

Location

`lib/query.ts:102-188 (compare)`, `:300-385 (beginsWith)`, `:395-499 (between)`

Problem

Three methods with ~90% identical code:

  1. Resolve sort key(s) — string passthrough vs `facet.sk()`/`index.sk()`
  2. Build `QueryInput` with `TableName`, `IndexName`, `ExpressionAttributeNames = { '#PK', '#SK' }`, `ExpressionAttributeValues` with the partition value plus sort values
  3. Apply `Limit`, `ScanIndexForward`, `ExclusiveStartKey` (decoded cursor)
  4. Apply filter via `expression-builder` and merge into names/values
  5. Call `dynamoDb.query`, collect items through `facet.out()`, attach cursor from `LastEvaluatedKey`

Only two things differ across the three: the `KeyConditionExpression` template, and how many sort placeholders (`:sort` vs `:start`/`:end`). About 200 lines of dead weight.

Proposed shape

```ts
private async runQuery(
keyCondition: {
expression: string; // uses '#PK', '#SK', and named sort placeholders
sortValues: Record<string, AttributeValue>; // e.g. { ':sort': {...} }
},
options: QueryOptions<T, PK, SK>,
): Promise<QueryResult> {
// scaffolding lives here
}

// callers shrink to:
equals(sort, options = {}) {
return this.runQuery(
{ expression: '#PK = :partition AND #SK = :sort', sortValues: this.buildSort(sort, options.shard) },
options,
);
}
```

Benefits

  • One place to add query features (e.g., `ProjectionExpression`, `ConsistentRead`, `Select`).
  • Fewer lines; easier review.
  • Removes three duplicate call sites to `facet.out()` / `encodeCursor(LastEvaluatedKey)`.

Risks

Behavior must stay identical. The sort-key construction (string passthrough vs built key) needs to be factored out carefully — `between` builds two, the others build one. A small `buildSortValues(...)` helper handles both.

Priority

Medium — not urgent, but this file is the hottest surface for future changes and shrinking it pays back fast.

Metadata

Metadata

Assignees

No one assigned

    Labels

    refactorCode organization / internal refactor

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions