@convex-dev/sharded-counter 0.1.3 → 0.1.4

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.
Files changed (38) hide show
  1. package/README.md +133 -4
  2. package/dist/commonjs/client/index.d.ts +79 -6
  3. package/dist/commonjs/client/index.d.ts.map +1 -1
  4. package/dist/commonjs/client/index.js +87 -2
  5. package/dist/commonjs/client/index.js.map +1 -1
  6. package/dist/commonjs/component/_generated/api.d.ts.map +1 -1
  7. package/dist/commonjs/component/_generated/api.js +0 -2
  8. package/dist/commonjs/component/_generated/api.js.map +1 -1
  9. package/dist/commonjs/component/_generated/server.d.ts.map +1 -1
  10. package/dist/commonjs/component/_generated/server.js +0 -2
  11. package/dist/commonjs/component/_generated/server.js.map +1 -1
  12. package/dist/commonjs/component/public.d.ts +9 -0
  13. package/dist/commonjs/component/public.d.ts.map +1 -1
  14. package/dist/commonjs/component/public.js +60 -0
  15. package/dist/commonjs/component/public.js.map +1 -1
  16. package/dist/esm/client/index.d.ts +79 -6
  17. package/dist/esm/client/index.d.ts.map +1 -1
  18. package/dist/esm/client/index.js +87 -2
  19. package/dist/esm/client/index.js.map +1 -1
  20. package/dist/esm/component/_generated/api.d.ts.map +1 -1
  21. package/dist/esm/component/_generated/api.js +0 -2
  22. package/dist/esm/component/_generated/api.js.map +1 -1
  23. package/dist/esm/component/_generated/server.d.ts.map +1 -1
  24. package/dist/esm/component/_generated/server.js +0 -2
  25. package/dist/esm/component/_generated/server.js.map +1 -1
  26. package/dist/esm/component/public.d.ts +9 -0
  27. package/dist/esm/component/public.d.ts.map +1 -1
  28. package/dist/esm/component/public.js +60 -0
  29. package/dist/esm/component/public.js.map +1 -1
  30. package/package.json +1 -1
  31. package/src/client/index.ts +135 -7
  32. package/src/component/_generated/api.d.ts +12 -4
  33. package/src/component/_generated/api.js +0 -4
  34. package/src/component/_generated/dataModel.d.ts +0 -4
  35. package/src/component/_generated/server.d.ts +0 -4
  36. package/src/component/_generated/server.js +0 -4
  37. package/src/component/counter.test.ts +11 -1
  38. package/src/component/public.ts +62 -0
@@ -1,7 +1,11 @@
1
- import { Expand, FunctionReference, GenericDataModel, GenericMutationCtx, GenericQueryCtx } from "convex/server";
1
+ import { DocumentByName, Expand, FunctionReference, GenericDataModel, GenericMutationCtx, GenericQueryCtx, TableNamesInDataModel } from "convex/server";
2
2
  import { GenericId } from "convex/values";
3
3
  import { api } from "../component/_generated/api";
4
- export declare class ShardedCounter<Shards extends Record<string, number>> {
4
+ /**
5
+ * A sharded counter is a map from string -> counter, where each counter can
6
+ * be incremented or decremented atomically.
7
+ */
8
+ export declare class ShardedCounter<ShardsKey extends string> {
5
9
  private component;
6
10
  private options?;
7
11
  /**
@@ -20,7 +24,7 @@ export declare class ShardedCounter<Shards extends Record<string, number>> {
20
24
  * keys not in `options.shards`.
21
25
  */
22
26
  constructor(component: UseApi<typeof api>, options?: {
23
- shards?: Shards | undefined;
27
+ shards?: Record<ShardsKey, number> | undefined;
24
28
  defaultShards?: number | undefined;
25
29
  } | undefined);
26
30
  /**
@@ -30,14 +34,51 @@ export declare class ShardedCounter<Shards extends Record<string, number>> {
30
34
  * @param name The key to update the counter for.
31
35
  * @param count The amount to increment the counter by. Defaults to 1.
32
36
  */
33
- add<Name extends string = keyof Shards & string>(ctx: RunMutationCtx, name: Name, count?: number): Promise<null>;
37
+ add<Name extends string = ShardsKey>(ctx: RunMutationCtx, name: Name, count?: number): Promise<null>;
38
+ /**
39
+ * Decrease the counter for key `name` by `count`.
40
+ */
41
+ subtract<Name extends string = ShardsKey>(ctx: RunMutationCtx, name: Name, count?: number): Promise<null>;
42
+ /**
43
+ * Increment the counter for key `name` by 1.
44
+ */
45
+ inc<Name extends string = ShardsKey>(ctx: RunMutationCtx, name: Name): Promise<null>;
46
+ /**
47
+ * Decrement the counter for key `name` by 1.
48
+ */
49
+ dec<Name extends string = ShardsKey>(ctx: RunMutationCtx, name: Name): Promise<null>;
34
50
  /**
35
51
  * Gets the counter for key `name`.
36
52
  *
37
53
  * NOTE: this reads from all shards. If used in a mutation, it will contend
38
54
  * with all mutations that update the counter for this key.
39
55
  */
40
- count<Name extends string = keyof Shards & string>(ctx: RunQueryCtx, name: Name): Promise<number>;
56
+ count<Name extends string = ShardsKey>(ctx: RunQueryCtx, name: Name): Promise<number>;
57
+ /**
58
+ * Redistribute counts evenly across the counter's shards.
59
+ *
60
+ * If there were more shards for this counter at some point, those shards
61
+ * will be removed.
62
+ *
63
+ * If there were fewer shards for this counter, or if the random distribution
64
+ * of counts is uneven, the counts will be redistributed evenly.
65
+ *
66
+ * This operation reads and writes all shards, so it can cause contention if
67
+ * called too often.
68
+ */
69
+ rebalance<Name extends string = ShardsKey>(ctx: RunMutationCtx, name: Name): Promise<void>;
70
+ /**
71
+ * Estimate the count of a counter by only reading from a subset of shards,
72
+ * and extrapolating the total count.
73
+ *
74
+ * After a `rebalance`, or if there were a lot of data points to yield a
75
+ * random distribution across shards, this should be a good approximation of
76
+ * the total count. If there are few data points, which are not evenly
77
+ * distributed across shards, this will be a poor approximation.
78
+ *
79
+ * Use this to reduce contention when reading the counter.
80
+ */
81
+ estimateCount<Name extends string = ShardsKey>(ctx: RunQueryCtx, name: Name, readFromShards?: number): Promise<number>;
41
82
  /**
42
83
  * Returns an object with methods to update and query the counter for key
43
84
  * `name`. For fixed keys, you can call `counter.for("<key>")` to get methods
@@ -53,7 +94,7 @@ export declare class ShardedCounter<Shards extends Record<string, number>> {
53
94
  * });
54
95
  * ```
55
96
  */
56
- for<Name extends string = keyof Shards & string>(name: Name): {
97
+ for<Name extends string = ShardsKey>(name: Name): {
57
98
  /**
58
99
  * Add `count` to the counter.
59
100
  */
@@ -77,8 +118,40 @@ export declare class ShardedCounter<Shards extends Record<string, number>> {
77
118
  * contend with all mutations that update the counter for this key.
78
119
  */
79
120
  count: (ctx: RunQueryCtx) => Promise<number>;
121
+ /**
122
+ * Redistribute counts evenly across the counter's shards.
123
+ *
124
+ * This operation reads and writes all shards, so it can cause contention
125
+ * if called too often.
126
+ */
127
+ rebalance: (ctx: RunMutationCtx) => Promise<void>;
128
+ /**
129
+ * Estimate the counter by only reading from a subset of shards,
130
+ * and extrapolating the total count.
131
+ *
132
+ * Use this to reduce contention when reading the counter.
133
+ */
134
+ estimateCount: (ctx: RunQueryCtx, readFromShards?: number) => Promise<number>;
80
135
  };
136
+ trigger<Ctx extends RunMutationCtx, Name extends string = ShardsKey>(name: Name): Trigger<Ctx, GenericDataModel, TableNamesInDataModel<GenericDataModel>>;
137
+ private shardsForKey;
81
138
  }
139
+ export type Trigger<Ctx, DataModel extends GenericDataModel, TableName extends TableNamesInDataModel<DataModel>> = (ctx: Ctx, change: Change<DataModel, TableName>) => Promise<void>;
140
+ export type Change<DataModel extends GenericDataModel, TableName extends TableNamesInDataModel<DataModel>> = {
141
+ id: GenericId<TableName>;
142
+ } & ({
143
+ operation: "insert";
144
+ oldDoc: null;
145
+ newDoc: DocumentByName<DataModel, TableName>;
146
+ } | {
147
+ operation: "update";
148
+ oldDoc: DocumentByName<DataModel, TableName>;
149
+ newDoc: DocumentByName<DataModel, TableName>;
150
+ } | {
151
+ operation: "delete";
152
+ oldDoc: DocumentByName<DataModel, TableName>;
153
+ newDoc: null;
154
+ });
82
155
  type RunQueryCtx = {
83
156
  runQuery: GenericQueryCtx<GenericDataModel>["runQuery"];
84
157
  };
@@ -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;IAiB7D,OAAO,CAAC,SAAS;IACjB,OAAO,CAAC,OAAO,CAAC;IAjBlB;;;;;;;;;;;;;;OAcG;gBAEO,SAAS,EAAE,MAAM,CAAC,OAAO,GAAG,CAAC,EAC7B,OAAO,CAAC;;;iBAA6C;IAE/D;;;;;;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
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/client/index.ts"],"names":[],"mappings":"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"}
@@ -1,3 +1,7 @@
1
+ /**
2
+ * A sharded counter is a map from string -> counter, where each counter can
3
+ * be incremented or decremented atomically.
4
+ */
1
5
  export class ShardedCounter {
2
6
  component;
3
7
  options;
@@ -28,13 +32,30 @@ export class ShardedCounter {
28
32
  * @param count The amount to increment the counter by. Defaults to 1.
29
33
  */
30
34
  async add(ctx, name, count = 1) {
31
- const shards = this.options?.shards?.[name] ?? this.options?.defaultShards;
32
35
  return ctx.runMutation(this.component.public.add, {
33
36
  name,
34
37
  count,
35
- shards,
38
+ shards: this.shardsForKey(name),
36
39
  });
37
40
  }
41
+ /**
42
+ * Decrease the counter for key `name` by `count`.
43
+ */
44
+ async subtract(ctx, name, count = 1) {
45
+ return this.add(ctx, name, -count);
46
+ }
47
+ /**
48
+ * Increment the counter for key `name` by 1.
49
+ */
50
+ async inc(ctx, name) {
51
+ return this.add(ctx, name, 1);
52
+ }
53
+ /**
54
+ * Decrement the counter for key `name` by 1.
55
+ */
56
+ async dec(ctx, name) {
57
+ return this.add(ctx, name, -1);
58
+ }
38
59
  /**
39
60
  * Gets the counter for key `name`.
40
61
  *
@@ -44,6 +65,42 @@ export class ShardedCounter {
44
65
  async count(ctx, name) {
45
66
  return ctx.runQuery(this.component.public.count, { name });
46
67
  }
68
+ /**
69
+ * Redistribute counts evenly across the counter's shards.
70
+ *
71
+ * If there were more shards for this counter at some point, those shards
72
+ * will be removed.
73
+ *
74
+ * If there were fewer shards for this counter, or if the random distribution
75
+ * of counts is uneven, the counts will be redistributed evenly.
76
+ *
77
+ * This operation reads and writes all shards, so it can cause contention if
78
+ * called too often.
79
+ */
80
+ async rebalance(ctx, name) {
81
+ await ctx.runMutation(this.component.public.rebalance, {
82
+ name,
83
+ shards: this.shardsForKey(name),
84
+ });
85
+ }
86
+ /**
87
+ * Estimate the count of a counter by only reading from a subset of shards,
88
+ * and extrapolating the total count.
89
+ *
90
+ * After a `rebalance`, or if there were a lot of data points to yield a
91
+ * random distribution across shards, this should be a good approximation of
92
+ * the total count. If there are few data points, which are not evenly
93
+ * distributed across shards, this will be a poor approximation.
94
+ *
95
+ * Use this to reduce contention when reading the counter.
96
+ */
97
+ async estimateCount(ctx, name, readFromShards = 1) {
98
+ return await ctx.runQuery(this.component.public.estimateCount, {
99
+ name,
100
+ shards: this.shardsForKey(name),
101
+ readFromShards,
102
+ });
103
+ }
47
104
  /**
48
105
  * Returns an object with methods to update and query the counter for key
49
106
  * `name`. For fixed keys, you can call `counter.for("<key>")` to get methods
@@ -84,7 +141,35 @@ export class ShardedCounter {
84
141
  * contend with all mutations that update the counter for this key.
85
142
  */
86
143
  count: async (ctx) => this.count(ctx, name),
144
+ /**
145
+ * Redistribute counts evenly across the counter's shards.
146
+ *
147
+ * This operation reads and writes all shards, so it can cause contention
148
+ * if called too often.
149
+ */
150
+ rebalance: async (ctx) => this.rebalance(ctx, name),
151
+ /**
152
+ * Estimate the counter by only reading from a subset of shards,
153
+ * and extrapolating the total count.
154
+ *
155
+ * Use this to reduce contention when reading the counter.
156
+ */
157
+ estimateCount: async (ctx, readFromShards = 1) => this.estimateCount(ctx, name, readFromShards),
158
+ };
159
+ }
160
+ trigger(name) {
161
+ return async (ctx, change) => {
162
+ if (change.operation === "insert") {
163
+ await this.inc(ctx, name);
164
+ }
165
+ else if (change.operation === "delete") {
166
+ await this.dec(ctx, name);
167
+ }
87
168
  };
88
169
  }
170
+ shardsForKey(name) {
171
+ const explicitShards = this.options?.shards?.[name];
172
+ return explicitShards ?? this.options?.defaultShards;
173
+ }
89
174
  }
90
175
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/client/index.ts"],"names":[],"mappings":"AAUA,MAAM,OAAO,cAAc;IAiBf;IACA;IAjBV;;;;;;;;;;;;;;OAcG;IACH,YACU,SAA6B,EAC7B,OAAqD;QADrD,cAAS,GAAT,SAAS,CAAoB;QAC7B,YAAO,GAAP,OAAO,CAA8C;IAC5D,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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/client/index.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../../../src/component/_generated/api.js"],"names":[],"mappings":"AAcA;;;;;;;GAOG;AACH,iDAA0B;AAC1B,sDAA+B;AAC/B;;EAA8C"}
1
+ {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../../../src/component/_generated/api.js"],"names":[],"mappings":"AAYA;;;;;;;GAOG;AACH,iDAA0B;AAC1B,sDAA+B;AAC/B;;EAA8C"}
@@ -1,4 +1,3 @@
1
- /* prettier-ignore-start */
2
1
  /* eslint-disable */
3
2
  /**
4
3
  * Generated `api` utility.
@@ -20,5 +19,4 @@ import { anyApi, componentsGeneric } from "convex/server";
20
19
  export const api = anyApi;
21
20
  export const internal = anyApi;
22
21
  export const components = componentsGeneric();
23
- /* prettier-ignore-end */
24
22
  //# sourceMappingURL=api.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"api.js","sourceRoot":"","sources":["../../../../src/component/_generated/api.js"],"names":[],"mappings":"AAAA,2BAA2B;AAE3B,oBAAoB;AACpB;;;;;;;GAOG;AAEH,OAAO,EAAE,MAAM,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAE1D;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,GAAG,GAAG,MAAM,CAAC;AAC1B,MAAM,CAAC,MAAM,QAAQ,GAAG,MAAM,CAAC;AAC/B,MAAM,CAAC,MAAM,UAAU,GAAG,iBAAiB,EAAE,CAAC;AAE9C,yBAAyB"}
1
+ {"version":3,"file":"api.js","sourceRoot":"","sources":["../../../../src/component/_generated/api.js"],"names":[],"mappings":"AAAA,oBAAoB;AACpB;;;;;;;GAOG;AAEH,OAAO,EAAE,MAAM,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAE1D;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,GAAG,GAAG,MAAM,CAAC;AAC1B,MAAM,CAAC,MAAM,QAAQ,GAAG,MAAM,CAAC;AAC/B,MAAM,CAAC,MAAM,UAAU,GAAG,iBAAiB,EAAE,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../../../src/component/_generated/server.js"],"names":[],"mappings":"AAuBA;;;;;;;GAOG;AACH,wEAAkC;AAElC;;;;;;;GAOG;AACH,kFAAkD;AAElD;;;;;;;GAOG;AACH,8EAAwC;AAExC;;;;;;;GAOG;AACH,wFAAwD;AAExD;;;;;;;;;;GAUG;AACH,0EAAoC;AAEpC;;;;;GAKG;AACH,oFAAoD;AAEpD;;;;;;GAMG;AACH,8MAA4C"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../../../src/component/_generated/server.js"],"names":[],"mappings":"AAqBA;;;;;;;GAOG;AACH,wEAAkC;AAElC;;;;;;;GAOG;AACH,kFAAkD;AAElD;;;;;;;GAOG;AACH,8EAAwC;AAExC;;;;;;;GAOG;AACH,wFAAwD;AAExD;;;;;;;;;;GAUG;AACH,0EAAoC;AAEpC;;;;;GAKG;AACH,oFAAoD;AAEpD;;;;;;GAMG;AACH,8MAA4C"}
@@ -1,4 +1,3 @@
1
- /* prettier-ignore-start */
2
1
  /* eslint-disable */
3
2
  /**
4
3
  * Generated utilities for implementing server-side Convex query and mutation functions.
@@ -72,5 +71,4 @@ export const internalAction = internalActionGeneric;
72
71
  * @returns The wrapped endpoint function. Route a URL path to this function in `convex/http.js`.
73
72
  */
74
73
  export const httpAction = httpActionGeneric;
75
- /* prettier-ignore-end */
76
74
  //# sourceMappingURL=server.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"server.js","sourceRoot":"","sources":["../../../../src/component/_generated/server.js"],"names":[],"mappings":"AAAA,2BAA2B;AAE3B,oBAAoB;AACpB;;;;;;;GAOG;AAEH,OAAO,EACL,aAAa,EACb,iBAAiB,EACjB,YAAY,EACZ,eAAe,EACf,qBAAqB,EACrB,uBAAuB,EACvB,oBAAoB,EACpB,iBAAiB,GAClB,MAAM,eAAe,CAAC;AAEvB;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,KAAK,GAAG,YAAY,CAAC;AAElC;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,oBAAoB,CAAC;AAElD;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG,eAAe,CAAC;AAExC;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,uBAAuB,CAAC;AAExD;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,MAAM,GAAG,aAAa,CAAC;AAEpC;;;;;GAKG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,qBAAqB,CAAC;AAEpD;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG,iBAAiB,CAAC;AAE5C,yBAAyB"}
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../../../../src/component/_generated/server.js"],"names":[],"mappings":"AAAA,oBAAoB;AACpB;;;;;;;GAOG;AAEH,OAAO,EACL,aAAa,EACb,iBAAiB,EACjB,YAAY,EACZ,eAAe,EACf,qBAAqB,EACrB,uBAAuB,EACvB,oBAAoB,EACpB,iBAAiB,GAClB,MAAM,eAAe,CAAC;AAEvB;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,KAAK,GAAG,YAAY,CAAC;AAElC;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,oBAAoB,CAAC;AAElD;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG,eAAe,CAAC;AAExC;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,uBAAuB,CAAC;AAExD;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,MAAM,GAAG,aAAa,CAAC;AAEpC;;;;;GAKG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,qBAAqB,CAAC;AAEpD;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG,iBAAiB,CAAC"}
@@ -7,4 +7,13 @@ export declare const add: import("convex/server").RegisteredMutation<"public", {
7
7
  export declare const count: import("convex/server").RegisteredQuery<"public", {
8
8
  name: string;
9
9
  }, Promise<number>>;
10
+ export declare const rebalance: import("convex/server").RegisteredMutation<"public", {
11
+ shards?: number | undefined;
12
+ name: string;
13
+ }, Promise<void>>;
14
+ export declare const estimateCount: import("convex/server").RegisteredQuery<"public", {
15
+ shards?: number | undefined;
16
+ readFromShards?: number | undefined;
17
+ name: string;
18
+ }, Promise<number>>;
10
19
  //# sourceMappingURL=public.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"public.d.ts","sourceRoot":"","sources":["../../../src/component/public.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,mBAAmB,KAAK,CAAC;AAEtC,eAAO,MAAM,GAAG;;;;iBAyBd,CAAC;AAEH,eAAO,MAAM,KAAK;;mBAUhB,CAAC"}
1
+ {"version":3,"file":"public.d.ts","sourceRoot":"","sources":["../../../src/component/public.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,mBAAmB,KAAK,CAAC;AAEtC,eAAO,MAAM,GAAG;;;;iBAyBd,CAAC;AAEH,eAAO,MAAM,KAAK;;mBAUhB,CAAC;AAEH,eAAO,MAAM,SAAS;;;iBA2BpB,CAAC;AAEH,eAAO,MAAM,aAAa;;;;mBAsBxB,CAAC"}
@@ -39,4 +39,64 @@ export const count = query({
39
39
  return counters.reduce((sum, counter) => sum + counter.value, 0);
40
40
  },
41
41
  });
42
+ export const rebalance = mutation({
43
+ args: { name: v.string(), shards: v.optional(v.number()) },
44
+ handler: async (ctx, args) => {
45
+ const counters = await ctx.db
46
+ .query("counters")
47
+ .withIndex("name", (q) => q.eq("name", args.name))
48
+ .collect();
49
+ const count = counters.reduce((sum, counter) => sum + counter.value, 0);
50
+ const shardCount = args.shards ?? DEFAULT_SHARD_COUNT;
51
+ const value = count / shardCount;
52
+ for (let i = 0; i < shardCount; i++) {
53
+ const shard = counters.find((c) => c.shard === i);
54
+ if (shard) {
55
+ await ctx.db.patch(shard._id, { value });
56
+ }
57
+ else {
58
+ await ctx.db.insert("counters", {
59
+ name: args.name,
60
+ value,
61
+ shard: i,
62
+ });
63
+ }
64
+ }
65
+ const toDelete = counters.filter((c) => c.shard >= shardCount);
66
+ for (const counter of toDelete) {
67
+ await ctx.db.delete(counter._id);
68
+ }
69
+ },
70
+ });
71
+ export const estimateCount = query({
72
+ args: {
73
+ name: v.string(),
74
+ readFromShards: v.optional(v.number()),
75
+ shards: v.optional(v.number()),
76
+ },
77
+ handler: async (ctx, args) => {
78
+ const shardCount = args.shards ?? DEFAULT_SHARD_COUNT;
79
+ const readFromShards = Math.min(Math.max(1, args.readFromShards ?? 1), shardCount);
80
+ const shards = shuffle(Array.from({ length: shardCount }, (_, i) => i)).slice(0, readFromShards);
81
+ let readCount = 0;
82
+ for (const shard of shards) {
83
+ const counter = await ctx.db
84
+ .query("counters")
85
+ .withIndex("name", (q) => q.eq("name", args.name).eq("shard", shard))
86
+ .unique();
87
+ if (counter) {
88
+ readCount += counter.value;
89
+ }
90
+ }
91
+ return (readCount * shardCount) / readFromShards;
92
+ },
93
+ });
94
+ // Fisher-Yates shuffle
95
+ function shuffle(array) {
96
+ for (let i = array.length - 1; i > 0; i--) {
97
+ const j = Math.floor(Math.random() * (i + 1));
98
+ [array[i], array[j]] = [array[j], array[i]];
99
+ }
100
+ return array;
101
+ }
42
102
  //# sourceMappingURL=public.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"public.js","sourceRoot":"","sources":["../../../src/component/public.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,eAAe,CAAC;AAClC,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AAEtD,MAAM,CAAC,MAAM,mBAAmB,GAAG,EAAE,CAAC;AAEtC,MAAM,CAAC,MAAM,GAAG,GAAG,QAAQ,CAAC;IAC1B,IAAI,EAAE;QACJ,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;QAChB,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE;QACjB,MAAM,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;KAC/B;IACD,OAAO,EAAE,CAAC,CAAC,IAAI,EAAE;IACjB,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE;QAC3B,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,CAAC,IAAI,CAAC,MAAM,IAAI,mBAAmB,CAAC,CAAC,CAAC;QAC/E,MAAM,OAAO,GAAG,MAAM,GAAG,CAAC,EAAE;aACzB,KAAK,CAAC,UAAU,CAAC;aACjB,SAAS,CAAC,MAAM,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;aACpE,MAAM,EAAE,CAAC;QACZ,IAAI,OAAO,EAAE;YACX,MAAM,GAAG,CAAC,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,EAAE;gBAC9B,KAAK,EAAE,OAAO,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK;aAClC,CAAC,CAAC;SACJ;aAAM;YACL,MAAM,GAAG,CAAC,EAAE,CAAC,MAAM,CAAC,UAAU,EAAE;gBAC9B,IAAI,EAAE,IAAI,CAAC,IAAI;gBACf,KAAK,EAAE,IAAI,CAAC,KAAK;gBACjB,KAAK;aACN,CAAC,CAAC;SACJ;IACH,CAAC;CACF,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,KAAK,GAAG,KAAK,CAAC;IACzB,IAAI,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE;IAC1B,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE;IACnB,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE;QAC3B,MAAM,QAAQ,GAAG,MAAM,GAAG,CAAC,EAAE;aAC1B,KAAK,CAAC,UAAU,CAAC;aACjB,SAAS,CAAC,MAAM,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;aACjD,OAAO,EAAE,CAAC;QACb,OAAO,QAAQ,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,OAAO,EAAE,EAAE,CAAC,GAAG,GAAG,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;IACnE,CAAC;CACF,CAAC,CAAC"}
1
+ {"version":3,"file":"public.js","sourceRoot":"","sources":["../../../src/component/public.ts"],"names":[],"mappings":"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"}
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.3",
10
+ "version": "0.1.4",
11
11
  "license": "Apache-2.0",
12
12
  "keywords": [
13
13
  "convex",
@@ -1,14 +1,20 @@
1
1
  import {
2
+ DocumentByName,
2
3
  Expand,
3
4
  FunctionReference,
4
5
  GenericDataModel,
5
6
  GenericMutationCtx,
6
7
  GenericQueryCtx,
8
+ TableNamesInDataModel,
7
9
  } from "convex/server";
8
10
  import { GenericId } from "convex/values";
9
11
  import { api } from "../component/_generated/api";
10
12
 
11
- export class ShardedCounter<Shards extends Record<string, number>> {
13
+ /**
14
+ * A sharded counter is a map from string -> counter, where each counter can
15
+ * be incremented or decremented atomically.
16
+ */
17
+ export class ShardedCounter<ShardsKey extends string> {
12
18
  /**
13
19
  * A sharded counter is a map from string -> counter, where each counter can
14
20
  * be incremented or decremented.
@@ -26,7 +32,7 @@ export class ShardedCounter<Shards extends Record<string, number>> {
26
32
  */
27
33
  constructor(
28
34
  private component: UseApi<typeof api>,
29
- private options?: { shards?: Shards; defaultShards?: number }
35
+ private options?: { shards?: Record<ShardsKey, number>; defaultShards?: number }
30
36
  ) {}
31
37
  /**
32
38
  * Increase the counter for key `name` by `count`.
@@ -35,30 +41,94 @@ export class ShardedCounter<Shards extends Record<string, number>> {
35
41
  * @param name The key to update the counter for.
36
42
  * @param count The amount to increment the counter by. Defaults to 1.
37
43
  */
38
- async add<Name extends string = keyof Shards & string>(
44
+ async add<Name extends string = ShardsKey>(
39
45
  ctx: RunMutationCtx,
40
46
  name: Name,
41
47
  count: number = 1
42
48
  ) {
43
- const shards = this.options?.shards?.[name] ?? this.options?.defaultShards;
44
49
  return ctx.runMutation(this.component.public.add, {
45
50
  name,
46
51
  count,
47
- shards,
52
+ shards: this.shardsForKey(name),
48
53
  });
49
54
  }
55
+ /**
56
+ * Decrease the counter for key `name` by `count`.
57
+ */
58
+ async subtract<Name extends string = ShardsKey>(
59
+ ctx: RunMutationCtx,
60
+ name: Name,
61
+ count: number = 1
62
+ ) {
63
+ return this.add(ctx, name, -count);
64
+ }
65
+ /**
66
+ * Increment the counter for key `name` by 1.
67
+ */
68
+ async inc<Name extends string = ShardsKey>(ctx: RunMutationCtx, name: Name) {
69
+ return this.add(ctx, name, 1);
70
+ }
71
+ /**
72
+ * Decrement the counter for key `name` by 1.
73
+ */
74
+ async dec<Name extends string = ShardsKey>(ctx: RunMutationCtx, name: Name) {
75
+ return this.add(ctx, name, -1);
76
+ }
50
77
  /**
51
78
  * Gets the counter for key `name`.
52
79
  *
53
80
  * NOTE: this reads from all shards. If used in a mutation, it will contend
54
81
  * with all mutations that update the counter for this key.
55
82
  */
56
- async count<Name extends string = keyof Shards & string>(
83
+ async count<Name extends string = ShardsKey>(
57
84
  ctx: RunQueryCtx,
58
85
  name: Name
59
86
  ) {
60
87
  return ctx.runQuery(this.component.public.count, { name });
61
88
  }
89
+ /**
90
+ * Redistribute counts evenly across the counter's shards.
91
+ *
92
+ * If there were more shards for this counter at some point, those shards
93
+ * will be removed.
94
+ *
95
+ * If there were fewer shards for this counter, or if the random distribution
96
+ * of counts is uneven, the counts will be redistributed evenly.
97
+ *
98
+ * This operation reads and writes all shards, so it can cause contention if
99
+ * called too often.
100
+ */
101
+ async rebalance<Name extends string = ShardsKey>(
102
+ ctx: RunMutationCtx,
103
+ name: Name,
104
+ ) {
105
+ await ctx.runMutation(this.component.public.rebalance, {
106
+ name,
107
+ shards: this.shardsForKey(name),
108
+ });
109
+ }
110
+ /**
111
+ * Estimate the count of a counter by only reading from a subset of shards,
112
+ * and extrapolating the total count.
113
+ *
114
+ * After a `rebalance`, or if there were a lot of data points to yield a
115
+ * random distribution across shards, this should be a good approximation of
116
+ * the total count. If there are few data points, which are not evenly
117
+ * distributed across shards, this will be a poor approximation.
118
+ *
119
+ * Use this to reduce contention when reading the counter.
120
+ */
121
+ async estimateCount<Name extends string = ShardsKey>(
122
+ ctx: RunQueryCtx,
123
+ name: Name,
124
+ readFromShards: number = 1,
125
+ ) {
126
+ return await ctx.runQuery(this.component.public.estimateCount, {
127
+ name,
128
+ shards: this.shardsForKey(name),
129
+ readFromShards,
130
+ });
131
+ }
62
132
  /**
63
133
  * Returns an object with methods to update and query the counter for key
64
134
  * `name`. For fixed keys, you can call `counter.for("<key>")` to get methods
@@ -74,7 +144,7 @@ export class ShardedCounter<Shards extends Record<string, number>> {
74
144
  * });
75
145
  * ```
76
146
  */
77
- for<Name extends string = keyof Shards & string>(name: Name) {
147
+ for<Name extends string = ShardsKey>(name: Name) {
78
148
  return {
79
149
  /**
80
150
  * Add `count` to the counter.
@@ -101,12 +171,70 @@ export class ShardedCounter<Shards extends Record<string, number>> {
101
171
  * contend with all mutations that update the counter for this key.
102
172
  */
103
173
  count: async (ctx: RunQueryCtx) => this.count(ctx, name),
174
+ /**
175
+ * Redistribute counts evenly across the counter's shards.
176
+ *
177
+ * This operation reads and writes all shards, so it can cause contention
178
+ * if called too often.
179
+ */
180
+ rebalance: async (ctx: RunMutationCtx) => this.rebalance(ctx, name),
181
+ /**
182
+ * Estimate the counter by only reading from a subset of shards,
183
+ * and extrapolating the total count.
184
+ *
185
+ * Use this to reduce contention when reading the counter.
186
+ */
187
+ estimateCount: async (ctx: RunQueryCtx, readFromShards: number = 1) =>
188
+ this.estimateCount(ctx, name, readFromShards),
104
189
  };
105
190
  }
191
+ trigger<
192
+ Ctx extends RunMutationCtx,
193
+ Name extends string = ShardsKey,
194
+ >(
195
+ name: Name,
196
+ ): Trigger<Ctx, GenericDataModel, TableNamesInDataModel<GenericDataModel>> {
197
+ return async (ctx, change) => {
198
+ if (change.operation === "insert") {
199
+ await this.inc(ctx, name);
200
+ } else if (change.operation === "delete") {
201
+ await this.dec(ctx, name);
202
+ }
203
+ };
204
+ }
205
+ private shardsForKey<Name extends string = ShardsKey>(name: Name) {
206
+ const explicitShards = this.options?.shards?.[name as string as ShardsKey];
207
+ return explicitShards ?? this.options?.defaultShards;
208
+ }
106
209
  }
107
210
 
108
211
  /* Type utils follow */
109
212
 
213
+ export type Trigger<
214
+ Ctx,
215
+ DataModel extends GenericDataModel,
216
+ TableName extends TableNamesInDataModel<DataModel>,
217
+ > = (ctx: Ctx, change: Change<DataModel, TableName>) => Promise<void>;
218
+
219
+ export type Change<
220
+ DataModel extends GenericDataModel,
221
+ TableName extends TableNamesInDataModel<DataModel>,
222
+ > = {
223
+ id: GenericId<TableName>;
224
+ } & ({
225
+ operation: "insert";
226
+ oldDoc: null
227
+ newDoc: DocumentByName<DataModel, TableName>;
228
+ } | {
229
+ operation: "update";
230
+ oldDoc: DocumentByName<DataModel, TableName>;
231
+ newDoc: DocumentByName<DataModel, TableName>;
232
+ } | {
233
+ operation: "delete";
234
+ oldDoc: DocumentByName<DataModel, TableName>;
235
+ newDoc: null;
236
+ });
237
+
110
238
  type RunQueryCtx = {
111
239
  runQuery: GenericQueryCtx<GenericDataModel>["runQuery"];
112
240
  };