@convex-dev/sharded-counter 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +39 -89
- package/dist/commonjs/client/index.d.ts +61 -0
- package/dist/commonjs/client/index.d.ts.map +1 -1
- package/dist/commonjs/client/index.js +61 -1
- package/dist/commonjs/client/index.js.map +1 -1
- package/dist/esm/client/index.d.ts +61 -0
- package/dist/esm/client/index.d.ts.map +1 -1
- package/dist/esm/client/index.js +61 -1
- package/dist/esm/client/index.js.map +1 -1
- package/package.json +3 -6
- package/src/client/index.ts +61 -1
package/README.md
CHANGED
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://badge.fury.io/js/@convex-dev%2Fsharded-counter)
|
|
4
4
|
|
|
5
|
+
**Note: Convex Components are currently in beta.**
|
|
6
|
+
|
|
7
|
+
<!-- START: Include on https://convex.dev/components -->
|
|
8
|
+
|
|
5
9
|
This component adds counters to Convex. It acts as a key-value store from
|
|
6
10
|
string to number, with sharding to increase throughput when updating values.
|
|
7
11
|
|
|
@@ -11,7 +15,7 @@ and cached.
|
|
|
11
15
|
For example, if you want to display
|
|
12
16
|
[one million checkboxes](https://en.wikipedia.org/wiki/One_Million_Checkboxes)
|
|
13
17
|
[on your Convex site](https://www.youtube.com/watch?v=LRUWplYoejQ), you want to
|
|
14
|
-
count the checkboxes in real-time while allowing
|
|
18
|
+
count the checkboxes in real-time while allowing a lot of the boxes to change in
|
|
15
19
|
parallel.
|
|
16
20
|
|
|
17
21
|
More generally, whenever you have a counter that is changing frequently, you
|
|
@@ -19,10 +23,12 @@ can use this component to keep track of it efficiently.
|
|
|
19
23
|
|
|
20
24
|
```ts
|
|
21
25
|
export const checkBox = mutation({
|
|
22
|
-
args: {i: v.number()},
|
|
26
|
+
args: { i: v.number() },
|
|
23
27
|
handler: async (ctx, args) => {
|
|
24
|
-
const checkbox = await ctx.db
|
|
25
|
-
.
|
|
28
|
+
const checkbox = await ctx.db
|
|
29
|
+
.query("checkboxes")
|
|
30
|
+
.withIndex("i", (q) => q.eq("i", args.i))
|
|
31
|
+
.unique();
|
|
26
32
|
if (!checkbox.isChecked) {
|
|
27
33
|
await ctx.db.patch(checkbox._id, { isChecked: true });
|
|
28
34
|
|
|
@@ -39,6 +45,18 @@ export const getCount = query({
|
|
|
39
45
|
});
|
|
40
46
|
```
|
|
41
47
|
|
|
48
|
+
This relies on the assumption that you need to frequently modify the counter,
|
|
49
|
+
but only need to read its value from a query, or infrequently in a mutation.
|
|
50
|
+
If you read the count every time you modify it, you lose the sharding benefit.
|
|
51
|
+
|
|
52
|
+
## Pre-requisite: Convex
|
|
53
|
+
|
|
54
|
+
You'll need an existing Convex project to use the component.
|
|
55
|
+
Convex is a hosted backend platform, including a database, serverless functions,
|
|
56
|
+
and a ton more you can learn about [here](https://docs.convex.dev/get-started).
|
|
57
|
+
|
|
58
|
+
Run `npm create convex` or follow any of the [quickstarts](https://docs.convex.dev/home) to set one up.
|
|
59
|
+
|
|
42
60
|
## Installation
|
|
43
61
|
|
|
44
62
|
First, install the component package:
|
|
@@ -66,11 +84,9 @@ the installed component.
|
|
|
66
84
|
|
|
67
85
|
```ts
|
|
68
86
|
import { components } from "./_generated/api";
|
|
69
|
-
import { ShardedCounter } from "@convex-dev/counter";
|
|
87
|
+
import { ShardedCounter } from "@convex-dev/sharded-counter";
|
|
70
88
|
|
|
71
|
-
const counter = new ShardedCounter(components.
|
|
72
|
-
...options
|
|
73
|
-
});
|
|
89
|
+
const counter = new ShardedCounter(components.shardedCounter);
|
|
74
90
|
```
|
|
75
91
|
|
|
76
92
|
## Updating and reading counters
|
|
@@ -140,6 +156,9 @@ const friendCounts = new ShardedCounter<Record<Id<"users">, number>>(
|
|
|
140
156
|
components.shardedCounter,
|
|
141
157
|
{ defaultShards: 1 },
|
|
142
158
|
);
|
|
159
|
+
|
|
160
|
+
// Decrement a user's friend count by 1
|
|
161
|
+
await friendsCount.add(ctx, userId, -1);
|
|
143
162
|
```
|
|
144
163
|
|
|
145
164
|
## Backfilling an existing count
|
|
@@ -148,8 +167,12 @@ If you want to count items like documents in a table, you may already have
|
|
|
148
167
|
documents before installing the ShardedCounter component, and these should be
|
|
149
168
|
accounted for.
|
|
150
169
|
|
|
151
|
-
The
|
|
152
|
-
|
|
170
|
+
The easy version of this is to calculate the value once and add that value, if
|
|
171
|
+
there aren't active requests happening. You can also periodically re-calculate
|
|
172
|
+
the value and update the counter, if there aren't in-flight requests.
|
|
173
|
+
|
|
174
|
+
The tricky part is handling requests while doing the calculation: making sure to
|
|
175
|
+
merge active updates to counts with old values that you want to backfill.
|
|
153
176
|
|
|
154
177
|
See example code at the bottom of
|
|
155
178
|
[example/convex/example.ts](example/convex/example.ts).
|
|
@@ -158,89 +181,16 @@ Walkthrough of steps:
|
|
|
158
181
|
|
|
159
182
|
1. Create `backfillCursor` table in schema.ts
|
|
160
183
|
2. Create a new document in this table, with fields
|
|
161
|
-
`{ creationTime: 0, id: "", isDone: false }`
|
|
184
|
+
`{ creationTime: 0, id: "", isDone: false }`
|
|
162
185
|
3. Wherever you want to update a counter based on a document changing, wrap the
|
|
163
|
-
update in a conditional, so it only gets updated if the backfill has processed
|
|
164
|
-
that document. In the example, you would be changing `insertUserBeforeBackfill`
|
|
165
|
-
to be implemented as `insertUserDuringBackfill`.
|
|
186
|
+
update in a conditional, so it only gets updated if the backfill has processed
|
|
187
|
+
that document. In the example, you would be changing `insertUserBeforeBackfill`
|
|
188
|
+
to be implemented as `insertUserDuringBackfill`.
|
|
166
189
|
4. Define backfill functions similar to `backfillUsers` and `backfillUsersBatch`
|
|
167
190
|
5. Call `backfillUsersBatch` from the dashboard.
|
|
168
191
|
6. Remove the conditional when updating counters. In the example, you would be
|
|
169
|
-
changing `insertUserDuringBackfill` to be implemented as
|
|
170
|
-
`insertUserAfterBackfill`.
|
|
192
|
+
changing `insertUserDuringBackfill` to be implemented as
|
|
193
|
+
`insertUserAfterBackfill`.
|
|
171
194
|
7. Delete the `backfillCursor` table.
|
|
172
195
|
|
|
173
196
|
<!-- END: Include on https://convex.dev/components -->
|
|
174
|
-
|
|
175
|
-
# 🧑🏫 What is Convex?
|
|
176
|
-
|
|
177
|
-
[Convex](https://convex.dev) is a hosted backend platform with a
|
|
178
|
-
built-in database that lets you write your
|
|
179
|
-
[database schema](https://docs.convex.dev/database/schemas) and
|
|
180
|
-
[server functions](https://docs.convex.dev/functions) in
|
|
181
|
-
[TypeScript](https://docs.convex.dev/typescript). Server-side database
|
|
182
|
-
[queries](https://docs.convex.dev/functions/query-functions) automatically
|
|
183
|
-
[cache](https://docs.convex.dev/functions/query-functions#caching--reactivity) and
|
|
184
|
-
[subscribe](https://docs.convex.dev/client/react#reactivity) to data, powering a
|
|
185
|
-
[realtime `useQuery` hook](https://docs.convex.dev/client/react#fetching-data) in our
|
|
186
|
-
[React client](https://docs.convex.dev/client/react). There are also clients for
|
|
187
|
-
[Python](https://docs.convex.dev/client/python),
|
|
188
|
-
[Rust](https://docs.convex.dev/client/rust),
|
|
189
|
-
[ReactNative](https://docs.convex.dev/client/react-native), and
|
|
190
|
-
[Node](https://docs.convex.dev/client/javascript), as well as a straightforward
|
|
191
|
-
[HTTP API](https://docs.convex.dev/http-api/).
|
|
192
|
-
|
|
193
|
-
The database supports
|
|
194
|
-
[NoSQL-style documents](https://docs.convex.dev/database/document-storage) with
|
|
195
|
-
[opt-in schema validation](https://docs.convex.dev/database/schemas),
|
|
196
|
-
[relationships](https://docs.convex.dev/database/document-ids) and
|
|
197
|
-
[custom indexes](https://docs.convex.dev/database/indexes/)
|
|
198
|
-
(including on fields in nested objects).
|
|
199
|
-
|
|
200
|
-
The
|
|
201
|
-
[`query`](https://docs.convex.dev/functions/query-functions) and
|
|
202
|
-
[`mutation`](https://docs.convex.dev/functions/mutation-functions) server functions have transactional,
|
|
203
|
-
low latency access to the database and leverage our
|
|
204
|
-
[`v8` runtime](https://docs.convex.dev/functions/runtimes) with
|
|
205
|
-
[determinism guardrails](https://docs.convex.dev/functions/runtimes#using-randomness-and-time-in-queries-and-mutations)
|
|
206
|
-
to provide the strongest ACID guarantees on the market:
|
|
207
|
-
immediate consistency,
|
|
208
|
-
serializable isolation, and
|
|
209
|
-
automatic conflict resolution via
|
|
210
|
-
[optimistic multi-version concurrency control](https://docs.convex.dev/database/advanced/occ) (OCC / MVCC).
|
|
211
|
-
|
|
212
|
-
The [`action` server functions](https://docs.convex.dev/functions/actions) have
|
|
213
|
-
access to external APIs and enable other side-effects and non-determinism in
|
|
214
|
-
either our
|
|
215
|
-
[optimized `v8` runtime](https://docs.convex.dev/functions/runtimes) or a more
|
|
216
|
-
[flexible `node` runtime](https://docs.convex.dev/functions/runtimes#nodejs-runtime).
|
|
217
|
-
|
|
218
|
-
Functions can run in the background via
|
|
219
|
-
[scheduling](https://docs.convex.dev/scheduling/scheduled-functions) and
|
|
220
|
-
[cron jobs](https://docs.convex.dev/scheduling/cron-jobs).
|
|
221
|
-
|
|
222
|
-
Development is cloud-first, with
|
|
223
|
-
[hot reloads for server function](https://docs.convex.dev/cli#run-the-convex-dev-server) editing via the
|
|
224
|
-
[CLI](https://docs.convex.dev/cli),
|
|
225
|
-
[preview deployments](https://docs.convex.dev/production/hosting/preview-deployments),
|
|
226
|
-
[logging and exception reporting integrations](https://docs.convex.dev/production/integrations/),
|
|
227
|
-
There is a
|
|
228
|
-
[dashboard UI](https://docs.convex.dev/dashboard) to
|
|
229
|
-
[browse and edit data](https://docs.convex.dev/dashboard/deployments/data),
|
|
230
|
-
[edit environment variables](https://docs.convex.dev/production/environment-variables),
|
|
231
|
-
[view logs](https://docs.convex.dev/dashboard/deployments/logs),
|
|
232
|
-
[run server functions](https://docs.convex.dev/dashboard/deployments/functions), and more.
|
|
233
|
-
|
|
234
|
-
There are built-in features for
|
|
235
|
-
[reactive pagination](https://docs.convex.dev/database/pagination),
|
|
236
|
-
[file storage](https://docs.convex.dev/file-storage),
|
|
237
|
-
[reactive text search](https://docs.convex.dev/text-search),
|
|
238
|
-
[vector search](https://docs.convex.dev/vector-search),
|
|
239
|
-
[https endpoints](https://docs.convex.dev/functions/http-actions) (for webhooks),
|
|
240
|
-
[snapshot import/export](https://docs.convex.dev/database/import-export/),
|
|
241
|
-
[streaming import/export](https://docs.convex.dev/production/integrations/streaming-import-export), and
|
|
242
|
-
[runtime validation](https://docs.convex.dev/database/schemas#validators) for
|
|
243
|
-
[function arguments](https://docs.convex.dev/functions/args-validation) and
|
|
244
|
-
[database data](https://docs.convex.dev/database/schemas#schema-validation).
|
|
245
|
-
|
|
246
|
-
Everything scales automatically, and it’s [free to start](https://www.convex.dev/plans).
|
|
@@ -7,17 +7,78 @@ export declare class ShardedCounter<Shards extends Record<string, number>> {
|
|
|
7
7
|
shards?: Shards | undefined;
|
|
8
8
|
defaultShards?: number | undefined;
|
|
9
9
|
} | undefined;
|
|
10
|
+
/**
|
|
11
|
+
* A sharded counter is a map from string -> counter, where each counter can
|
|
12
|
+
* be incremented or decremented.
|
|
13
|
+
*
|
|
14
|
+
* The counter is sharded into multiple documents to allow for higher
|
|
15
|
+
* throughput of updates. The default number of shards is 16.
|
|
16
|
+
*
|
|
17
|
+
* - More shards => higher throughput of updates.
|
|
18
|
+
* - Fewer shards => lower latency when querying the counter.
|
|
19
|
+
*
|
|
20
|
+
* @param options.shards The number of shards for each counter, for fixed
|
|
21
|
+
* keys.
|
|
22
|
+
* @param options.defaultShards The number of shards for each counter, for
|
|
23
|
+
* keys not in `options.shards`.
|
|
24
|
+
*/
|
|
10
25
|
constructor(component: UseApi<typeof api>, options?: {
|
|
11
26
|
shards?: Shards | undefined;
|
|
12
27
|
defaultShards?: number | undefined;
|
|
13
28
|
} | undefined);
|
|
29
|
+
/**
|
|
30
|
+
* Increase the counter for key `name` by `count`.
|
|
31
|
+
* If `count` is negative, the counter will decrease.
|
|
32
|
+
*
|
|
33
|
+
* @param name The key to update the counter for.
|
|
34
|
+
* @param count The amount to increment the counter by. Defaults to 1.
|
|
35
|
+
*/
|
|
14
36
|
add<Name extends string = keyof Shards & string>(ctx: RunMutationCtx, name: Name, count?: number): Promise<null>;
|
|
37
|
+
/**
|
|
38
|
+
* Gets the counter for key `name`.
|
|
39
|
+
*
|
|
40
|
+
* NOTE: this reads from all shards. If used in a mutation, it will contend
|
|
41
|
+
* with all mutations that update the counter for this key.
|
|
42
|
+
*/
|
|
15
43
|
count<Name extends string = keyof Shards & string>(ctx: RunQueryCtx, name: Name): Promise<number>;
|
|
44
|
+
/**
|
|
45
|
+
* Returns an object with methods to update and query the counter for key
|
|
46
|
+
* `name`. For fixed keys, you can call `counter.for("<key>")` to get methods
|
|
47
|
+
* for updating or querying the counter for that key. Example:
|
|
48
|
+
*
|
|
49
|
+
* ```ts
|
|
50
|
+
* const counter = new ShardedCounter(components.shardedCounter);
|
|
51
|
+
* const beanCounter = counter.for("beans");
|
|
52
|
+
* export const pushPapers = mutation({
|
|
53
|
+
* handler: async (ctx) => {
|
|
54
|
+
* await beanCounter.inc(ctx);
|
|
55
|
+
* },
|
|
56
|
+
* });
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
16
59
|
for<Name extends string = keyof Shards & string>(name: Name): {
|
|
60
|
+
/**
|
|
61
|
+
* Add `count` to the counter.
|
|
62
|
+
*/
|
|
17
63
|
add: (ctx: RunMutationCtx, count?: number) => Promise<null>;
|
|
64
|
+
/**
|
|
65
|
+
* Subtract `count` from the counter.
|
|
66
|
+
*/
|
|
18
67
|
subtract: (ctx: RunMutationCtx, count?: number) => Promise<null>;
|
|
68
|
+
/**
|
|
69
|
+
* Increment the counter by 1.
|
|
70
|
+
*/
|
|
19
71
|
inc: (ctx: RunMutationCtx) => Promise<null>;
|
|
72
|
+
/**
|
|
73
|
+
* Decrement the counter by 1.
|
|
74
|
+
*/
|
|
20
75
|
dec: (ctx: RunMutationCtx) => Promise<null>;
|
|
76
|
+
/**
|
|
77
|
+
* Get the current value of the counter.
|
|
78
|
+
*
|
|
79
|
+
* NOTE: this reads from all shards. If used in a mutation, it will
|
|
80
|
+
* contend with all mutations that update the counter for this key.
|
|
81
|
+
*/
|
|
21
82
|
count: (ctx: RunQueryCtx) => Promise<number>;
|
|
22
83
|
};
|
|
23
84
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/client/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,MAAM,EACN,iBAAiB,EACjB,gBAAgB,EAChB,kBAAkB,EAClB,eAAe,EAChB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC1C,OAAO,EAAE,GAAG,EAAE,MAAM,6BAA6B,CAAC;AAElD,qBAAa,cAAc,CAAC,MAAM,SAAS,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/client/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,MAAM,EACN,iBAAiB,EACjB,gBAAgB,EAChB,kBAAkB,EAClB,eAAe,EAChB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC1C,OAAO,EAAE,GAAG,EAAE,MAAM,6BAA6B,CAAC;AAElD,qBAAa,cAAc,CAAC,MAAM,SAAS,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;IAiBtD,SAAS,EAAE,MAAM,CAAC,OAAO,GAAG,CAAC;IAC7B,OAAO,CAAC;;;;IAjBjB;;;;;;;;;;;;;;OAcG;gBAEM,SAAS,EAAE,MAAM,CAAC,OAAO,GAAG,CAAC,EAC7B,OAAO,CAAC;;;iBAA6C;IAE9D;;;;;;OAMG;IACG,GAAG,CAAC,IAAI,SAAS,MAAM,GAAG,MAAM,MAAM,GAAG,MAAM,EACnD,GAAG,EAAE,cAAc,EACnB,IAAI,EAAE,IAAI,EACV,KAAK,GAAE,MAAU;IASnB;;;;;OAKG;IACG,KAAK,CAAC,IAAI,SAAS,MAAM,GAAG,MAAM,MAAM,GAAG,MAAM,EACrD,GAAG,EAAE,WAAW,EAChB,IAAI,EAAE,IAAI;IAIZ;;;;;;;;;;;;;;OAcG;IACH,GAAG,CAAC,IAAI,SAAS,MAAM,GAAG,MAAM,MAAM,GAAG,MAAM,EAAE,IAAI,EAAE,IAAI;QAEvD;;WAEG;mBACc,cAAc,UAAS,MAAM;QAE9C;;WAEG;wBACmB,cAAc,UAAS,MAAM;QAEnD;;WAEG;mBACc,cAAc;QAC/B;;WAEG;mBACc,cAAc;QAC/B;;;;;WAKG;qBACgB,WAAW;;CAGnC;AAID,KAAK,WAAW,GAAG;IACjB,QAAQ,EAAE,eAAe,CAAC,gBAAgB,CAAC,CAAC,UAAU,CAAC,CAAC;CACzD,CAAC;AACF,KAAK,cAAc,GAAG;IACpB,WAAW,EAAE,kBAAkB,CAAC,gBAAgB,CAAC,CAAC,aAAa,CAAC,CAAC;CAClE,CAAC;AAEF,MAAM,MAAM,SAAS,CAAC,CAAC,IACrB,CAAC,SAAS,SAAS,CAAC,MAAM,EAAE,CAAC,GACzB,MAAM,GACN,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,EAAE,GACnB,SAAS,CAAC,CAAC,CAAC,EAAE,GACd,CAAC,SAAS,MAAM,GACd;KAAG,CAAC,IAAI,MAAM,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;CAAE,GACnC,CAAC,CAAC;AAEZ,MAAM,MAAM,MAAM,CAAC,GAAG,IAAI,MAAM,CAAC;KAC9B,GAAG,IAAI,MAAM,GAAG,GAAG,GAAG,CAAC,GAAG,CAAC,SAAS,iBAAiB,CACpD,MAAM,KAAK,EACX,QAAQ,EACR,MAAM,KAAK,EACX,MAAM,WAAW,EACjB,MAAM,cAAc,CACrB,GACG,iBAAiB,CACf,KAAK,EACL,UAAU,EACV,SAAS,CAAC,KAAK,CAAC,EAChB,SAAS,CAAC,WAAW,CAAC,EACtB,cAAc,CACf,GACD,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;CACrB,CAAC,CAAC"}
|
|
@@ -1,10 +1,32 @@
|
|
|
1
1
|
export class ShardedCounter {
|
|
2
2
|
component;
|
|
3
3
|
options;
|
|
4
|
+
/**
|
|
5
|
+
* A sharded counter is a map from string -> counter, where each counter can
|
|
6
|
+
* be incremented or decremented.
|
|
7
|
+
*
|
|
8
|
+
* The counter is sharded into multiple documents to allow for higher
|
|
9
|
+
* throughput of updates. The default number of shards is 16.
|
|
10
|
+
*
|
|
11
|
+
* - More shards => higher throughput of updates.
|
|
12
|
+
* - Fewer shards => lower latency when querying the counter.
|
|
13
|
+
*
|
|
14
|
+
* @param options.shards The number of shards for each counter, for fixed
|
|
15
|
+
* keys.
|
|
16
|
+
* @param options.defaultShards The number of shards for each counter, for
|
|
17
|
+
* keys not in `options.shards`.
|
|
18
|
+
*/
|
|
4
19
|
constructor(component, options) {
|
|
5
20
|
this.component = component;
|
|
6
21
|
this.options = options;
|
|
7
22
|
}
|
|
23
|
+
/**
|
|
24
|
+
* Increase the counter for key `name` by `count`.
|
|
25
|
+
* If `count` is negative, the counter will decrease.
|
|
26
|
+
*
|
|
27
|
+
* @param name The key to update the counter for.
|
|
28
|
+
* @param count The amount to increment the counter by. Defaults to 1.
|
|
29
|
+
*/
|
|
8
30
|
async add(ctx, name, count = 1) {
|
|
9
31
|
const shards = this.options?.shards?.[name] ?? this.options?.defaultShards;
|
|
10
32
|
return ctx.runMutation(this.component.public.add, {
|
|
@@ -13,16 +35,54 @@ export class ShardedCounter {
|
|
|
13
35
|
shards,
|
|
14
36
|
});
|
|
15
37
|
}
|
|
38
|
+
/**
|
|
39
|
+
* Gets the counter for key `name`.
|
|
40
|
+
*
|
|
41
|
+
* NOTE: this reads from all shards. If used in a mutation, it will contend
|
|
42
|
+
* with all mutations that update the counter for this key.
|
|
43
|
+
*/
|
|
16
44
|
async count(ctx, name) {
|
|
17
45
|
return ctx.runQuery(this.component.public.count, { name });
|
|
18
46
|
}
|
|
19
|
-
|
|
47
|
+
/**
|
|
48
|
+
* Returns an object with methods to update and query the counter for key
|
|
49
|
+
* `name`. For fixed keys, you can call `counter.for("<key>")` to get methods
|
|
50
|
+
* for updating or querying the counter for that key. Example:
|
|
51
|
+
*
|
|
52
|
+
* ```ts
|
|
53
|
+
* const counter = new ShardedCounter(components.shardedCounter);
|
|
54
|
+
* const beanCounter = counter.for("beans");
|
|
55
|
+
* export const pushPapers = mutation({
|
|
56
|
+
* handler: async (ctx) => {
|
|
57
|
+
* await beanCounter.inc(ctx);
|
|
58
|
+
* },
|
|
59
|
+
* });
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
20
62
|
for(name) {
|
|
21
63
|
return {
|
|
64
|
+
/**
|
|
65
|
+
* Add `count` to the counter.
|
|
66
|
+
*/
|
|
22
67
|
add: async (ctx, count = 1) => this.add(ctx, name, count),
|
|
68
|
+
/**
|
|
69
|
+
* Subtract `count` from the counter.
|
|
70
|
+
*/
|
|
23
71
|
subtract: async (ctx, count = 1) => this.add(ctx, name, -count),
|
|
72
|
+
/**
|
|
73
|
+
* Increment the counter by 1.
|
|
74
|
+
*/
|
|
24
75
|
inc: async (ctx) => this.add(ctx, name, 1),
|
|
76
|
+
/**
|
|
77
|
+
* Decrement the counter by 1.
|
|
78
|
+
*/
|
|
25
79
|
dec: async (ctx) => this.add(ctx, name, -1),
|
|
80
|
+
/**
|
|
81
|
+
* Get the current value of the counter.
|
|
82
|
+
*
|
|
83
|
+
* NOTE: this reads from all shards. If used in a mutation, it will
|
|
84
|
+
* contend with all mutations that update the counter for this key.
|
|
85
|
+
*/
|
|
26
86
|
count: async (ctx) => this.count(ctx, name),
|
|
27
87
|
};
|
|
28
88
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/client/index.ts"],"names":[],"mappings":"AAUA,MAAM,OAAO,cAAc;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/client/index.ts"],"names":[],"mappings":"AAUA,MAAM,OAAO,cAAc;IAiBhB;IACA;IAjBT;;;;;;;;;;;;;;OAcG;IACH,YACS,SAA6B,EAC7B,OAAqD;QADrD,cAAS,GAAT,SAAS,CAAoB;QAC7B,YAAO,GAAP,OAAO,CAA8C;IAC3D,CAAC;IACJ;;;;;;OAMG;IACH,KAAK,CAAC,GAAG,CACP,GAAmB,EACnB,IAAU,EACV,QAAgB,CAAC;QAEjB,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,OAAO,EAAE,aAAa,CAAC;QAC3E,OAAO,GAAG,CAAC,WAAW,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,GAAG,EAAE;YAChD,IAAI;YACJ,KAAK;YACL,MAAM;SACP,CAAC,CAAC;IACL,CAAC;IACD;;;;;OAKG;IACH,KAAK,CAAC,KAAK,CACT,GAAgB,EAChB,IAAU;QAEV,OAAO,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,KAAK,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC;IAC7D,CAAC;IACD;;;;;;;;;;;;;;OAcG;IACH,GAAG,CAA8C,IAAU;QACzD,OAAO;YACL;;eAEG;YACH,GAAG,EAAE,KAAK,EAAE,GAAmB,EAAE,QAAgB,CAAC,EAAE,EAAE,CACpD,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,EAAE,KAAK,CAAC;YAC5B;;eAEG;YACH,QAAQ,EAAE,KAAK,EAAE,GAAmB,EAAE,QAAgB,CAAC,EAAE,EAAE,CACzD,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,KAAK,CAAC;YAC7B;;eAEG;YACH,GAAG,EAAE,KAAK,EAAE,GAAmB,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC;YAC1D;;eAEG;YACH,GAAG,EAAE,KAAK,EAAE,GAAmB,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;YAC3D;;;;;eAKG;YACH,KAAK,EAAE,KAAK,EAAE,GAAgB,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,IAAI,CAAC;SACzD,CAAC;IACJ,CAAC;CACF"}
|
|
@@ -7,17 +7,78 @@ export declare class ShardedCounter<Shards extends Record<string, number>> {
|
|
|
7
7
|
shards?: Shards | undefined;
|
|
8
8
|
defaultShards?: number | undefined;
|
|
9
9
|
} | undefined;
|
|
10
|
+
/**
|
|
11
|
+
* A sharded counter is a map from string -> counter, where each counter can
|
|
12
|
+
* be incremented or decremented.
|
|
13
|
+
*
|
|
14
|
+
* The counter is sharded into multiple documents to allow for higher
|
|
15
|
+
* throughput of updates. The default number of shards is 16.
|
|
16
|
+
*
|
|
17
|
+
* - More shards => higher throughput of updates.
|
|
18
|
+
* - Fewer shards => lower latency when querying the counter.
|
|
19
|
+
*
|
|
20
|
+
* @param options.shards The number of shards for each counter, for fixed
|
|
21
|
+
* keys.
|
|
22
|
+
* @param options.defaultShards The number of shards for each counter, for
|
|
23
|
+
* keys not in `options.shards`.
|
|
24
|
+
*/
|
|
10
25
|
constructor(component: UseApi<typeof api>, options?: {
|
|
11
26
|
shards?: Shards | undefined;
|
|
12
27
|
defaultShards?: number | undefined;
|
|
13
28
|
} | undefined);
|
|
29
|
+
/**
|
|
30
|
+
* Increase the counter for key `name` by `count`.
|
|
31
|
+
* If `count` is negative, the counter will decrease.
|
|
32
|
+
*
|
|
33
|
+
* @param name The key to update the counter for.
|
|
34
|
+
* @param count The amount to increment the counter by. Defaults to 1.
|
|
35
|
+
*/
|
|
14
36
|
add<Name extends string = keyof Shards & string>(ctx: RunMutationCtx, name: Name, count?: number): Promise<null>;
|
|
37
|
+
/**
|
|
38
|
+
* Gets the counter for key `name`.
|
|
39
|
+
*
|
|
40
|
+
* NOTE: this reads from all shards. If used in a mutation, it will contend
|
|
41
|
+
* with all mutations that update the counter for this key.
|
|
42
|
+
*/
|
|
15
43
|
count<Name extends string = keyof Shards & string>(ctx: RunQueryCtx, name: Name): Promise<number>;
|
|
44
|
+
/**
|
|
45
|
+
* Returns an object with methods to update and query the counter for key
|
|
46
|
+
* `name`. For fixed keys, you can call `counter.for("<key>")` to get methods
|
|
47
|
+
* for updating or querying the counter for that key. Example:
|
|
48
|
+
*
|
|
49
|
+
* ```ts
|
|
50
|
+
* const counter = new ShardedCounter(components.shardedCounter);
|
|
51
|
+
* const beanCounter = counter.for("beans");
|
|
52
|
+
* export const pushPapers = mutation({
|
|
53
|
+
* handler: async (ctx) => {
|
|
54
|
+
* await beanCounter.inc(ctx);
|
|
55
|
+
* },
|
|
56
|
+
* });
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
16
59
|
for<Name extends string = keyof Shards & string>(name: Name): {
|
|
60
|
+
/**
|
|
61
|
+
* Add `count` to the counter.
|
|
62
|
+
*/
|
|
17
63
|
add: (ctx: RunMutationCtx, count?: number) => Promise<null>;
|
|
64
|
+
/**
|
|
65
|
+
* Subtract `count` from the counter.
|
|
66
|
+
*/
|
|
18
67
|
subtract: (ctx: RunMutationCtx, count?: number) => Promise<null>;
|
|
68
|
+
/**
|
|
69
|
+
* Increment the counter by 1.
|
|
70
|
+
*/
|
|
19
71
|
inc: (ctx: RunMutationCtx) => Promise<null>;
|
|
72
|
+
/**
|
|
73
|
+
* Decrement the counter by 1.
|
|
74
|
+
*/
|
|
20
75
|
dec: (ctx: RunMutationCtx) => Promise<null>;
|
|
76
|
+
/**
|
|
77
|
+
* Get the current value of the counter.
|
|
78
|
+
*
|
|
79
|
+
* NOTE: this reads from all shards. If used in a mutation, it will
|
|
80
|
+
* contend with all mutations that update the counter for this key.
|
|
81
|
+
*/
|
|
21
82
|
count: (ctx: RunQueryCtx) => Promise<number>;
|
|
22
83
|
};
|
|
23
84
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/client/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,MAAM,EACN,iBAAiB,EACjB,gBAAgB,EAChB,kBAAkB,EAClB,eAAe,EAChB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC1C,OAAO,EAAE,GAAG,EAAE,MAAM,6BAA6B,CAAC;AAElD,qBAAa,cAAc,CAAC,MAAM,SAAS,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/client/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,MAAM,EACN,iBAAiB,EACjB,gBAAgB,EAChB,kBAAkB,EAClB,eAAe,EAChB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC1C,OAAO,EAAE,GAAG,EAAE,MAAM,6BAA6B,CAAC;AAElD,qBAAa,cAAc,CAAC,MAAM,SAAS,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;IAiBtD,SAAS,EAAE,MAAM,CAAC,OAAO,GAAG,CAAC;IAC7B,OAAO,CAAC;;;;IAjBjB;;;;;;;;;;;;;;OAcG;gBAEM,SAAS,EAAE,MAAM,CAAC,OAAO,GAAG,CAAC,EAC7B,OAAO,CAAC;;;iBAA6C;IAE9D;;;;;;OAMG;IACG,GAAG,CAAC,IAAI,SAAS,MAAM,GAAG,MAAM,MAAM,GAAG,MAAM,EACnD,GAAG,EAAE,cAAc,EACnB,IAAI,EAAE,IAAI,EACV,KAAK,GAAE,MAAU;IASnB;;;;;OAKG;IACG,KAAK,CAAC,IAAI,SAAS,MAAM,GAAG,MAAM,MAAM,GAAG,MAAM,EACrD,GAAG,EAAE,WAAW,EAChB,IAAI,EAAE,IAAI;IAIZ;;;;;;;;;;;;;;OAcG;IACH,GAAG,CAAC,IAAI,SAAS,MAAM,GAAG,MAAM,MAAM,GAAG,MAAM,EAAE,IAAI,EAAE,IAAI;QAEvD;;WAEG;mBACc,cAAc,UAAS,MAAM;QAE9C;;WAEG;wBACmB,cAAc,UAAS,MAAM;QAEnD;;WAEG;mBACc,cAAc;QAC/B;;WAEG;mBACc,cAAc;QAC/B;;;;;WAKG;qBACgB,WAAW;;CAGnC;AAID,KAAK,WAAW,GAAG;IACjB,QAAQ,EAAE,eAAe,CAAC,gBAAgB,CAAC,CAAC,UAAU,CAAC,CAAC;CACzD,CAAC;AACF,KAAK,cAAc,GAAG;IACpB,WAAW,EAAE,kBAAkB,CAAC,gBAAgB,CAAC,CAAC,aAAa,CAAC,CAAC;CAClE,CAAC;AAEF,MAAM,MAAM,SAAS,CAAC,CAAC,IACrB,CAAC,SAAS,SAAS,CAAC,MAAM,EAAE,CAAC,GACzB,MAAM,GACN,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,EAAE,GACnB,SAAS,CAAC,CAAC,CAAC,EAAE,GACd,CAAC,SAAS,MAAM,GACd;KAAG,CAAC,IAAI,MAAM,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;CAAE,GACnC,CAAC,CAAC;AAEZ,MAAM,MAAM,MAAM,CAAC,GAAG,IAAI,MAAM,CAAC;KAC9B,GAAG,IAAI,MAAM,GAAG,GAAG,GAAG,CAAC,GAAG,CAAC,SAAS,iBAAiB,CACpD,MAAM,KAAK,EACX,QAAQ,EACR,MAAM,KAAK,EACX,MAAM,WAAW,EACjB,MAAM,cAAc,CACrB,GACG,iBAAiB,CACf,KAAK,EACL,UAAU,EACV,SAAS,CAAC,KAAK,CAAC,EAChB,SAAS,CAAC,WAAW,CAAC,EACtB,cAAc,CACf,GACD,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;CACrB,CAAC,CAAC"}
|
package/dist/esm/client/index.js
CHANGED
|
@@ -1,10 +1,32 @@
|
|
|
1
1
|
export class ShardedCounter {
|
|
2
2
|
component;
|
|
3
3
|
options;
|
|
4
|
+
/**
|
|
5
|
+
* A sharded counter is a map from string -> counter, where each counter can
|
|
6
|
+
* be incremented or decremented.
|
|
7
|
+
*
|
|
8
|
+
* The counter is sharded into multiple documents to allow for higher
|
|
9
|
+
* throughput of updates. The default number of shards is 16.
|
|
10
|
+
*
|
|
11
|
+
* - More shards => higher throughput of updates.
|
|
12
|
+
* - Fewer shards => lower latency when querying the counter.
|
|
13
|
+
*
|
|
14
|
+
* @param options.shards The number of shards for each counter, for fixed
|
|
15
|
+
* keys.
|
|
16
|
+
* @param options.defaultShards The number of shards for each counter, for
|
|
17
|
+
* keys not in `options.shards`.
|
|
18
|
+
*/
|
|
4
19
|
constructor(component, options) {
|
|
5
20
|
this.component = component;
|
|
6
21
|
this.options = options;
|
|
7
22
|
}
|
|
23
|
+
/**
|
|
24
|
+
* Increase the counter for key `name` by `count`.
|
|
25
|
+
* If `count` is negative, the counter will decrease.
|
|
26
|
+
*
|
|
27
|
+
* @param name The key to update the counter for.
|
|
28
|
+
* @param count The amount to increment the counter by. Defaults to 1.
|
|
29
|
+
*/
|
|
8
30
|
async add(ctx, name, count = 1) {
|
|
9
31
|
const shards = this.options?.shards?.[name] ?? this.options?.defaultShards;
|
|
10
32
|
return ctx.runMutation(this.component.public.add, {
|
|
@@ -13,16 +35,54 @@ export class ShardedCounter {
|
|
|
13
35
|
shards,
|
|
14
36
|
});
|
|
15
37
|
}
|
|
38
|
+
/**
|
|
39
|
+
* Gets the counter for key `name`.
|
|
40
|
+
*
|
|
41
|
+
* NOTE: this reads from all shards. If used in a mutation, it will contend
|
|
42
|
+
* with all mutations that update the counter for this key.
|
|
43
|
+
*/
|
|
16
44
|
async count(ctx, name) {
|
|
17
45
|
return ctx.runQuery(this.component.public.count, { name });
|
|
18
46
|
}
|
|
19
|
-
|
|
47
|
+
/**
|
|
48
|
+
* Returns an object with methods to update and query the counter for key
|
|
49
|
+
* `name`. For fixed keys, you can call `counter.for("<key>")` to get methods
|
|
50
|
+
* for updating or querying the counter for that key. Example:
|
|
51
|
+
*
|
|
52
|
+
* ```ts
|
|
53
|
+
* const counter = new ShardedCounter(components.shardedCounter);
|
|
54
|
+
* const beanCounter = counter.for("beans");
|
|
55
|
+
* export const pushPapers = mutation({
|
|
56
|
+
* handler: async (ctx) => {
|
|
57
|
+
* await beanCounter.inc(ctx);
|
|
58
|
+
* },
|
|
59
|
+
* });
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
20
62
|
for(name) {
|
|
21
63
|
return {
|
|
64
|
+
/**
|
|
65
|
+
* Add `count` to the counter.
|
|
66
|
+
*/
|
|
22
67
|
add: async (ctx, count = 1) => this.add(ctx, name, count),
|
|
68
|
+
/**
|
|
69
|
+
* Subtract `count` from the counter.
|
|
70
|
+
*/
|
|
23
71
|
subtract: async (ctx, count = 1) => this.add(ctx, name, -count),
|
|
72
|
+
/**
|
|
73
|
+
* Increment the counter by 1.
|
|
74
|
+
*/
|
|
24
75
|
inc: async (ctx) => this.add(ctx, name, 1),
|
|
76
|
+
/**
|
|
77
|
+
* Decrement the counter by 1.
|
|
78
|
+
*/
|
|
25
79
|
dec: async (ctx) => this.add(ctx, name, -1),
|
|
80
|
+
/**
|
|
81
|
+
* Get the current value of the counter.
|
|
82
|
+
*
|
|
83
|
+
* NOTE: this reads from all shards. If used in a mutation, it will
|
|
84
|
+
* contend with all mutations that update the counter for this key.
|
|
85
|
+
*/
|
|
26
86
|
count: async (ctx) => this.count(ctx, name),
|
|
27
87
|
};
|
|
28
88
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/client/index.ts"],"names":[],"mappings":"AAUA,MAAM,OAAO,cAAc;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/client/index.ts"],"names":[],"mappings":"AAUA,MAAM,OAAO,cAAc;IAiBhB;IACA;IAjBT;;;;;;;;;;;;;;OAcG;IACH,YACS,SAA6B,EAC7B,OAAqD;QADrD,cAAS,GAAT,SAAS,CAAoB;QAC7B,YAAO,GAAP,OAAO,CAA8C;IAC3D,CAAC;IACJ;;;;;;OAMG;IACH,KAAK,CAAC,GAAG,CACP,GAAmB,EACnB,IAAU,EACV,QAAgB,CAAC;QAEjB,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,OAAO,EAAE,aAAa,CAAC;QAC3E,OAAO,GAAG,CAAC,WAAW,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,GAAG,EAAE;YAChD,IAAI;YACJ,KAAK;YACL,MAAM;SACP,CAAC,CAAC;IACL,CAAC;IACD;;;;;OAKG;IACH,KAAK,CAAC,KAAK,CACT,GAAgB,EAChB,IAAU;QAEV,OAAO,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,KAAK,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC;IAC7D,CAAC;IACD;;;;;;;;;;;;;;OAcG;IACH,GAAG,CAA8C,IAAU;QACzD,OAAO;YACL;;eAEG;YACH,GAAG,EAAE,KAAK,EAAE,GAAmB,EAAE,QAAgB,CAAC,EAAE,EAAE,CACpD,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,EAAE,KAAK,CAAC;YAC5B;;eAEG;YACH,QAAQ,EAAE,KAAK,EAAE,GAAmB,EAAE,QAAgB,CAAC,EAAE,EAAE,CACzD,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,KAAK,CAAC;YAC7B;;eAEG;YACH,GAAG,EAAE,KAAK,EAAE,GAAmB,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC;YAC1D;;eAEG;YACH,GAAG,EAAE,KAAK,EAAE,GAAmB,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;YAC3D;;;;;eAKG;YACH,KAAK,EAAE,KAAK,EAAE,GAAgB,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,IAAI,CAAC;SACzD,CAAC;IACJ,CAAC;CACF"}
|
package/package.json
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
"email": "support@convex.dev",
|
|
8
8
|
"url": "https://github.com/get-convex/sharded-counter/issues"
|
|
9
9
|
},
|
|
10
|
-
"version": "0.1.
|
|
10
|
+
"version": "0.1.2",
|
|
11
11
|
"license": "Apache-2.0",
|
|
12
12
|
"keywords": [
|
|
13
13
|
"convex",
|
|
@@ -22,16 +22,13 @@
|
|
|
22
22
|
"dev": "cd example; npm run dev",
|
|
23
23
|
"typecheck": "tsc --noEmit",
|
|
24
24
|
"prepare": "npm run build",
|
|
25
|
-
"prepack": "node node10stubs.mjs",
|
|
26
|
-
"postpack": "node node10stubs.mjs --cleanup",
|
|
27
25
|
"test": "vitest run",
|
|
28
26
|
"test:debug": "vitest --inspect-brk --no-file-parallelism",
|
|
29
27
|
"test:coverage": "vitest run --coverage --coverage.reporter=text"
|
|
30
28
|
},
|
|
31
29
|
"files": [
|
|
32
30
|
"dist",
|
|
33
|
-
"src"
|
|
34
|
-
"react"
|
|
31
|
+
"src"
|
|
35
32
|
],
|
|
36
33
|
"exports": {
|
|
37
34
|
"./package.json": "./package.json",
|
|
@@ -56,7 +53,7 @@
|
|
|
56
53
|
}
|
|
57
54
|
},
|
|
58
55
|
"peerDependencies": {
|
|
59
|
-
"convex": "
|
|
56
|
+
"convex": "~1.16.5 || ~1.17.0"
|
|
60
57
|
},
|
|
61
58
|
"devDependencies": {
|
|
62
59
|
"@eslint/js": "^9.9.1",
|
package/src/client/index.ts
CHANGED
|
@@ -9,10 +9,32 @@ import { GenericId } from "convex/values";
|
|
|
9
9
|
import { api } from "../component/_generated/api";
|
|
10
10
|
|
|
11
11
|
export class ShardedCounter<Shards extends Record<string, number>> {
|
|
12
|
+
/**
|
|
13
|
+
* A sharded counter is a map from string -> counter, where each counter can
|
|
14
|
+
* be incremented or decremented.
|
|
15
|
+
*
|
|
16
|
+
* The counter is sharded into multiple documents to allow for higher
|
|
17
|
+
* throughput of updates. The default number of shards is 16.
|
|
18
|
+
*
|
|
19
|
+
* - More shards => higher throughput of updates.
|
|
20
|
+
* - Fewer shards => lower latency when querying the counter.
|
|
21
|
+
*
|
|
22
|
+
* @param options.shards The number of shards for each counter, for fixed
|
|
23
|
+
* keys.
|
|
24
|
+
* @param options.defaultShards The number of shards for each counter, for
|
|
25
|
+
* keys not in `options.shards`.
|
|
26
|
+
*/
|
|
12
27
|
constructor(
|
|
13
28
|
public component: UseApi<typeof api>,
|
|
14
29
|
public options?: { shards?: Shards; defaultShards?: number }
|
|
15
30
|
) {}
|
|
31
|
+
/**
|
|
32
|
+
* Increase the counter for key `name` by `count`.
|
|
33
|
+
* If `count` is negative, the counter will decrease.
|
|
34
|
+
*
|
|
35
|
+
* @param name The key to update the counter for.
|
|
36
|
+
* @param count The amount to increment the counter by. Defaults to 1.
|
|
37
|
+
*/
|
|
16
38
|
async add<Name extends string = keyof Shards & string>(
|
|
17
39
|
ctx: RunMutationCtx,
|
|
18
40
|
name: Name,
|
|
@@ -25,21 +47,59 @@ export class ShardedCounter<Shards extends Record<string, number>> {
|
|
|
25
47
|
shards,
|
|
26
48
|
});
|
|
27
49
|
}
|
|
50
|
+
/**
|
|
51
|
+
* Gets the counter for key `name`.
|
|
52
|
+
*
|
|
53
|
+
* NOTE: this reads from all shards. If used in a mutation, it will contend
|
|
54
|
+
* with all mutations that update the counter for this key.
|
|
55
|
+
*/
|
|
28
56
|
async count<Name extends string = keyof Shards & string>(
|
|
29
57
|
ctx: RunQueryCtx,
|
|
30
58
|
name: Name
|
|
31
59
|
) {
|
|
32
60
|
return ctx.runQuery(this.component.public.count, { name });
|
|
33
61
|
}
|
|
34
|
-
|
|
62
|
+
/**
|
|
63
|
+
* Returns an object with methods to update and query the counter for key
|
|
64
|
+
* `name`. For fixed keys, you can call `counter.for("<key>")` to get methods
|
|
65
|
+
* for updating or querying the counter for that key. Example:
|
|
66
|
+
*
|
|
67
|
+
* ```ts
|
|
68
|
+
* const counter = new ShardedCounter(components.shardedCounter);
|
|
69
|
+
* const beanCounter = counter.for("beans");
|
|
70
|
+
* export const pushPapers = mutation({
|
|
71
|
+
* handler: async (ctx) => {
|
|
72
|
+
* await beanCounter.inc(ctx);
|
|
73
|
+
* },
|
|
74
|
+
* });
|
|
75
|
+
* ```
|
|
76
|
+
*/
|
|
35
77
|
for<Name extends string = keyof Shards & string>(name: Name) {
|
|
36
78
|
return {
|
|
79
|
+
/**
|
|
80
|
+
* Add `count` to the counter.
|
|
81
|
+
*/
|
|
37
82
|
add: async (ctx: RunMutationCtx, count: number = 1) =>
|
|
38
83
|
this.add(ctx, name, count),
|
|
84
|
+
/**
|
|
85
|
+
* Subtract `count` from the counter.
|
|
86
|
+
*/
|
|
39
87
|
subtract: async (ctx: RunMutationCtx, count: number = 1) =>
|
|
40
88
|
this.add(ctx, name, -count),
|
|
89
|
+
/**
|
|
90
|
+
* Increment the counter by 1.
|
|
91
|
+
*/
|
|
41
92
|
inc: async (ctx: RunMutationCtx) => this.add(ctx, name, 1),
|
|
93
|
+
/**
|
|
94
|
+
* Decrement the counter by 1.
|
|
95
|
+
*/
|
|
42
96
|
dec: async (ctx: RunMutationCtx) => this.add(ctx, name, -1),
|
|
97
|
+
/**
|
|
98
|
+
* Get the current value of the counter.
|
|
99
|
+
*
|
|
100
|
+
* NOTE: this reads from all shards. If used in a mutation, it will
|
|
101
|
+
* contend with all mutations that update the counter for this key.
|
|
102
|
+
*/
|
|
43
103
|
count: async (ctx: RunQueryCtx) => this.count(ctx, name),
|
|
44
104
|
};
|
|
45
105
|
}
|