-
Notifications
You must be signed in to change notification settings - Fork 0
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
allow non-string keys #5
base: main
Are you sure you want to change the base?
Changes from 3 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -9,7 +9,7 @@ import { customCtx, customMutation } from "convex-helpers/server/customFunctions | |||||||||||||
|
||||||||||||||
/// Example of ShardedCounter initialization. | ||||||||||||||
|
||||||||||||||
const counter = new ShardedCounter(components.shardedCounter, { | ||||||||||||||
const counter = new ShardedCounter<"beans" | "users" | "accomplishments">(components.shardedCounter, { | ||||||||||||||
shards: { beans: 10, users: 3 }, | ||||||||||||||
}); | ||||||||||||||
Comment on lines
+12
to
14
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||
const numUsers = counter.for("users"); | ||||||||||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
/// <reference types="vite/client" /> | ||
|
||
import { convexTest } from "convex-test"; | ||
import { describe, expect, test } from "vitest" | ||
import schema from "./schema"; | ||
import componentSchema from "../../src/component/schema"; | ||
import { api } from "./_generated/api"; | ||
|
||
const modules = import.meta.glob("./**/*.ts"); | ||
const componentModules = import.meta.glob("../../src/component/**/*.ts"); | ||
|
||
describe("nested sharded counter", () => { | ||
async function setupTest() { | ||
const t = convexTest(schema, modules); | ||
t.registerComponent("nestedCounter", componentSchema, componentModules); | ||
return t; | ||
} | ||
|
||
test("follower and follows count", async () => { | ||
const t = await setupTest(); | ||
const users = await t.run((ctx) => { | ||
return Promise.all([ | ||
ctx.db.insert("users", { name: "1" }), | ||
ctx.db.insert("users", { name: "2" }), | ||
]); | ||
}); | ||
await t.mutation(api.nested.addFollower, { follower: users[0], followee: users[1] }); | ||
expect(await t.query(api.nested.countFollows, { user: users[0] })).toStrictEqual(1); | ||
expect(await t.query(api.nested.countFollowers, { user: users[0] })).toStrictEqual(0); | ||
expect(await t.query(api.nested.countFollowers, { user: users[1] })).toStrictEqual(1); | ||
}) | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
/// Example of a hierarchical counter. | ||
/// This is the same as a regular sharded counter (see example.ts), but the keys | ||
/// are tuples instead of strings. | ||
/// You cannot accumulate across a prefix of the tuple; if you want to do that, | ||
/// use an additional counter for each prefix, or use the Aggregate component: | ||
/// https://www.npmjs.com/package/@convex-dev/aggregate | ||
|
||
import { ShardedCounter } from "@convex-dev/sharded-counter"; | ||
import { components } from "./_generated/api"; | ||
import { Id } from "./_generated/dataModel"; | ||
import { v } from "convex/values"; | ||
import { mutation, query } from "./_generated/server"; | ||
|
||
const nestedCounter = new ShardedCounter<[Id<"users">, "follows" | "followers"]>( | ||
components.nestedCounter, | ||
{ defaultShards: 3 }, | ||
); | ||
|
||
export const addFollower = mutation({ | ||
args: { follower: v.id("users"), followee: v.id("users") }, | ||
handler: async (ctx, { follower, followee }) => { | ||
await nestedCounter.inc(ctx, [followee, "followers"]); | ||
await nestedCounter.inc(ctx, [follower, "follows"]); | ||
}, | ||
}); | ||
|
||
export const countFollows = query({ | ||
args: { user: v.id("users") }, | ||
handler: async (ctx, { user }) => { | ||
return await nestedCounter.count(ctx, [user, "follows"]); | ||
}, | ||
}); | ||
|
||
export const countFollowers = query({ | ||
args: { user: v.id("users") }, | ||
handler: async (ctx, { user }) => { | ||
return await nestedCounter.count(ctx, [user, "followers"]); | ||
}, | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,14 +7,14 @@ import { | |
GenericQueryCtx, | ||
TableNamesInDataModel, | ||
} from "convex/server"; | ||
import { GenericId } from "convex/values"; | ||
import { GenericId, Value as ConvexValue } from "convex/values"; | ||
import { api } from "../component/_generated/api"; | ||
|
||
/** | ||
* A sharded counter is a map from string -> counter, where each counter can | ||
* be incremented or decremented atomically. | ||
*/ | ||
export class ShardedCounter<ShardsKey extends string> { | ||
export class ShardedCounter<ShardsKey extends ConvexValue> { | ||
ldanilek marked this conversation as resolved.
Show resolved
Hide resolved
|
||
/** | ||
* A sharded counter is a map from string -> counter, where each counter can | ||
* be incremented or decremented. | ||
|
@@ -32,7 +32,7 @@ export class ShardedCounter<ShardsKey extends string> { | |
*/ | ||
constructor( | ||
private component: UseApi<typeof api>, | ||
private options?: { shards?: Record<ShardsKey, number>; defaultShards?: number } | ||
private options?: { shards?: Partial<Record<ShardsKey & string, number>>; defaultShards?: number } | ||
) {} | ||
/** | ||
* Increase the counter for key `name` by `count`. | ||
|
@@ -41,7 +41,7 @@ export class ShardedCounter<ShardsKey extends string> { | |
* @param name The key to update the counter for. | ||
* @param count The amount to increment the counter by. Defaults to 1. | ||
*/ | ||
async add<Name extends string = ShardsKey>( | ||
async add<Name extends ShardsKey>( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this means (I believe) that you can no longer pass in a custom string if you've defined shards in your config There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. correct, but i think that's actually good. if you explicitly give the generic, you get the same behavior as before const counter = new ShardedCounter<string>(components.shardedCounter, {
shards: { users: 10 },
});
await counter.inc(ctx, "friends"); // this works and if you have a fixed set of keys, it protects against typos const counter = new ShardedCounter(components.shardedCounter, {
shards: { users: 10, friends: 20 },
});
await counter.inc(ctx, "frends"); // errors There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yeah that's a good call. In fact what we probably want is for shards to either be keyed or fixed, so |
||
ctx: RunMutationCtx, | ||
name: Name, | ||
count: number = 1 | ||
|
@@ -55,7 +55,7 @@ export class ShardedCounter<ShardsKey extends string> { | |
/** | ||
* Decrease the counter for key `name` by `count`. | ||
*/ | ||
async subtract<Name extends string = ShardsKey>( | ||
async subtract<Name extends ShardsKey>( | ||
ctx: RunMutationCtx, | ||
name: Name, | ||
count: number = 1 | ||
|
@@ -65,13 +65,13 @@ export class ShardedCounter<ShardsKey extends string> { | |
/** | ||
* Increment the counter for key `name` by 1. | ||
*/ | ||
async inc<Name extends string = ShardsKey>(ctx: RunMutationCtx, name: Name) { | ||
async inc<Name extends ShardsKey>(ctx: RunMutationCtx, name: Name) { | ||
return this.add(ctx, name, 1); | ||
} | ||
/** | ||
* Decrement the counter for key `name` by 1. | ||
*/ | ||
async dec<Name extends string = ShardsKey>(ctx: RunMutationCtx, name: Name) { | ||
async dec<Name extends ShardsKey>(ctx: RunMutationCtx, name: Name) { | ||
return this.add(ctx, name, -1); | ||
} | ||
/** | ||
|
@@ -80,7 +80,7 @@ export class ShardedCounter<ShardsKey extends string> { | |
* NOTE: this reads from all shards. If used in a mutation, it will contend | ||
* with all mutations that update the counter for this key. | ||
*/ | ||
async count<Name extends string = ShardsKey>( | ||
async count<Name extends ShardsKey>( | ||
ctx: RunQueryCtx, | ||
name: Name | ||
) { | ||
|
@@ -98,7 +98,7 @@ export class ShardedCounter<ShardsKey extends string> { | |
* This operation reads and writes all shards, so it can cause contention if | ||
* called too often. | ||
*/ | ||
async rebalance<Name extends string = ShardsKey>( | ||
async rebalance<Name extends ShardsKey>( | ||
ctx: RunMutationCtx, | ||
name: Name, | ||
) { | ||
|
@@ -118,7 +118,7 @@ export class ShardedCounter<ShardsKey extends string> { | |
* | ||
* Use this to reduce contention when reading the counter. | ||
*/ | ||
async estimateCount<Name extends string = ShardsKey>( | ||
async estimateCount<Name extends ShardsKey>( | ||
ctx: RunQueryCtx, | ||
name: Name, | ||
readFromShards: number = 1, | ||
|
@@ -144,7 +144,7 @@ export class ShardedCounter<ShardsKey extends string> { | |
* }); | ||
* ``` | ||
*/ | ||
for<Name extends string = ShardsKey>(name: Name) { | ||
for<Name extends ShardsKey>(name: Name) { | ||
return { | ||
/** | ||
* Add `count` to the counter. | ||
|
@@ -190,7 +190,7 @@ export class ShardedCounter<ShardsKey extends string> { | |
} | ||
trigger< | ||
Ctx extends RunMutationCtx, | ||
Name extends string = ShardsKey, | ||
Name extends ShardsKey, | ||
>( | ||
name: Name, | ||
): Trigger<Ctx, GenericDataModel, TableNamesInDataModel<GenericDataModel>> { | ||
|
@@ -202,8 +202,8 @@ export class ShardedCounter<ShardsKey extends string> { | |
} | ||
}; | ||
} | ||
private shardsForKey<Name extends string = ShardsKey>(name: Name) { | ||
const explicitShards = this.options?.shards?.[name as string as ShardsKey]; | ||
private shardsForKey<Name extends ShardsKey>(name: Name) { | ||
const explicitShards = this.options?.shards?.[name as unknown as string & ShardsKey]; | ||
return explicitShards ?? this.options?.defaultShards; | ||
} | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.