Skip to content

Commit

Permalink
feat: add support for customizable comparison operators in 'createSto…
Browse files Browse the repository at this point in the history
…reListerClient'
  • Loading branch information
BravoNatalie committed Dec 13, 2024
1 parent c3e7a30 commit 8165def
Show file tree
Hide file tree
Showing 5 changed files with 52 additions and 29 deletions.
8 changes: 6 additions & 2 deletions billing/data/allocations.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { DecodeFailure, EncodeFailure, Schema } from './lib.js'
* @typedef {import('../lib/api').AllocationKey} AllocationKey
* @typedef {import('../lib/api').AllocationListKey} AllocationListKey
* @typedef {import('../types').InferStoreRecord<AllocationKey>} AllocationKeyStoreRecord
* @typedef {{ space: string, insertedAt?: string }} AllocationListStoreRecord
* @typedef {{ space: string, insertedAt?: string[] }} AllocationListStoreRecord
* @typedef {import('../types').StoreRecord} StoreRecord
*/

Expand Down Expand Up @@ -69,12 +69,16 @@ export const decode = (input) => {
}

export const lister = {
/** @type Record<string, import('@aws-sdk/client-dynamodb').ComparisonOperator> */
comparisonOperator: { space: 'EQ', insertedAt: 'BETWEEN' },
/** @type {import('../lib/api').Encoder<AllocationListKey, AllocationListStoreRecord>} */
encodeKey: (input) => {
/** @type AllocationListStoreRecord */
const conditions = { space: input.space.toString() }
if (input.insertedAt) {
conditions.insertedAt = input.insertedAt.toISOString()
conditions.insertedAt = input.insertedAt.map((value) =>
value.toISOString()
)
}
return {
ok: {
Expand Down
6 changes: 4 additions & 2 deletions billing/data/store.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { DecodeFailure, EncodeFailure, Schema } from './lib.js'
* @typedef {import('../lib/api.js').StoreTableKey} StoreTableKey
* @typedef {import('../lib/api.js').StoreTableListKey} StoreTableListKey
* @typedef {import('../types.js').InferStoreRecord<StoreTableKey>} StoreTableKeyStoreRecord
* @typedef {{ space: string, insertedAt?: string }} StoreTableListStoreRecord
* @typedef {{ space: string, insertedAt?: string[] }} StoreTableListStoreRecord
* @typedef {import('../types.js').StoreRecord} StoreRecord
*/

Expand Down Expand Up @@ -72,12 +72,14 @@ export const decode = (input) => {
}

export const lister = {
/** @type Record<string, import('@aws-sdk/client-dynamodb').ComparisonOperator> */
comparisonOperator: { space: 'EQ', insertedAt: 'BETWEEN' },
/** @type {import('../lib/api.js').Encoder<StoreTableListKey, StoreTableListStoreRecord>} */
encodeKey: (input) => {
/** @type StoreTableListStoreRecord */
const conditions = { space: input.space.toString() }
if (input.insertedAt) {
conditions.insertedAt = input.insertedAt.toISOString()
conditions.insertedAt = input.insertedAt.map(value => value.toISOString())
}
return {
ok: {
Expand Down
8 changes: 5 additions & 3 deletions billing/lib/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ export interface Allocation {

export type AllocationSpaceInsertedAtIndex = Omit< Allocation, "multihash" | "cause" >
export interface AllocationKey { multihash: string }
export interface AllocationListKey { space: ConsumerDID, insertedAt?: Date}
export interface AllocationListKey { space: ConsumerDID, insertedAt?: [Date] | [Date, Date] }

export type AllocationStore =
& StoreGetter<AllocationKey, Allocation>
Expand Down Expand Up @@ -188,7 +188,7 @@ export interface StoreTable {

export type StoreTableSpaceInsertedAtIndex = Omit< StoreTable, "invocation" | "link" | "issuer" >
export interface StoreTableKey { link: string }
export interface StoreTableListKey { space: ConsumerDID, insertedAt?: Date}
export interface StoreTableListKey { space: ConsumerDID, insertedAt?: [Date] | [Date, Date] }

export type StoreTableStore =
& StoreGetter<StoreTableKey, StoreTable>
Expand Down Expand Up @@ -431,9 +431,11 @@ export interface QueueAdder<T> {
/** Adds a message to the end of the queue. */
add: (message: T) => Promise<Result<Unit, EncodeFailure|QueueOperationFailure|Failure>>
}

export type QueryStoreRecord = Record<string, string|number|string[]>
export interface CreateStoreListerContext<K,V> {
tableName: string
encodeKey: Encoder<K, StoreRecord>
encodeKey: Encoder<K, StoreRecord> | Encoder<K, QueryStoreRecord>
decode: Decoder<StoreRecord, V>
indexName?: string
comparisonOperator?: Record<string, ComparisonOperator>
Expand Down
17 changes: 9 additions & 8 deletions billing/lib/space-billing-queue.js
Original file line number Diff line number Diff line change
Expand Up @@ -147,32 +147,33 @@ export const calculateSpaceAllocation = async (store, instruction, ctx) => {
let cursor
let size = 0n
let usage = 0n
let done = false
// let done = false
while(true){
const {ok: allocations, error} = await ctxStore.list(
{space: instruction.space, insertedAt: instruction.from},
{space: instruction.space, insertedAt: [instruction.from, instruction.to]}, // NOTE: insertedAt between from and to
{cursor, size: 100}
)

if (error) return { error }

for (const allocation of allocations.results){
/**
* NOTE: Currently, the query only retrieves items with 'insertedAt' values greater than 'instruction.from'.
* NOTE: Currently, the query only retrieves items with 'insertedAt' values greater or equal than 'instruction.from'.
* This limitation is due to the 'ComparisonOperator' being hardcoded in the 'createdStoreListerClient'.
* As a result, we need to programmatically filter out allocations with 'insertedAt' dates after 'to'.
* A similar filtering process is also applied in the 'iterateSpaceDiffs' function.
* TODO: discuss the possibility of refactoring the 'createdStoreListerClient'.
*/
if(allocation.insertedAt.getTime() > instruction.to.getTime()){
done = true
break
}
// if(allocation.insertedAt.getTime() > instruction.to.getTime()){
// done = true
// break
// }
size += allocation.size
usage += allocation.size * BigInt(instruction.to.getTime() - allocation.insertedAt.getTime())
}

if (done || !allocations.cursor) break
// if (done || !allocations.cursor) break
if (!allocations.cursor) break
cursor = allocations.cursor
}

Expand Down
42 changes: 28 additions & 14 deletions billing/tables/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -154,11 +154,7 @@ export const createStoreGetterClient = (conf, context) => {
* @template {object} K
* @template V
* @param {{ region: string } | import('@aws-sdk/client-dynamodb').DynamoDBClient} conf
* @param {object} context
* @param {string} context.tableName
* @param {import('../lib/api').Encoder<K, import('../types').StoreRecord>} context.encodeKey
* @param {import('../lib/api').Decoder<import('../types').StoreRecord, V>} context.decode
* @param {string} [context.indexName]
* @param {import('../lib/api').CreateStoreListerContext<K,V>} context
* @returns {import('../lib/api').StoreLister<K, V>}
*/
export const createStoreListerClient = (conf, context) => {
Expand All @@ -168,19 +164,37 @@ export const createStoreListerClient = (conf, context) => {
const encoding = context.encodeKey(key)
if (encoding.error) return encoding

/** @type {Record<string, import('@aws-sdk/client-dynamodb').Condition>|undefined} */
let conditions
for (const [k, v] of Object.entries(encoding.ok)) {
conditions = conditions ?? {}
conditions[k] = {
// Multiple conditions imply a sort key so must be GE in order to
/** @type {Record<string, import('@aws-sdk/client-dynamodb').Condition>} */
const conditions = {}
const conditionsInput = Object.entries(encoding.ok)
for (const [k, v] of conditionsInput) {
const comparisonOperator =
context.comparisonOperator?.[k] ||
// Default: Multiple conditions imply a sort key so must be GE in order to
// list more than one item. Otherwise this would be a StoreGetter.
ComparisonOperator: Object.keys(conditions).length ? 'GE' : 'EQ',
AttributeValueList: [convertToAttr(v)]
(Object.keys(conditions).length ? 'GE' : 'EQ')

const attributeValueList = Array.isArray(v) ? v.map(v=>convertToAttr(v)) : [convertToAttr(v)]

conditions[k] = {
ComparisonOperator: comparisonOperator,
AttributeValueList: attributeValueList,
}
}

const cmd = conditions
// /** @type {Record<string, import('@aws-sdk/client-dynamodb').Condition>|undefined} */
// let conditions
// for (const [k, v] of Object.entries(encoding.ok)) {
// conditions = conditions ?? {}
// conditions[k] = {
// // Multiple conditions imply a sort key so must be GE in order to
// // list more than one item. Otherwise this would be a StoreGetter.
// ComparisonOperator: Object.keys(conditions).length ? 'GE' : 'EQ',
// AttributeValueList: [convertToAttr(v)]
// }
// }

const cmd = conditionsInput.length
? new QueryCommand({
TableName: context.tableName,
IndexName: context.indexName,
Expand Down

0 comments on commit 8165def

Please sign in to comment.