@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.
- package/README.md +133 -4
- package/dist/commonjs/client/index.d.ts +79 -6
- package/dist/commonjs/client/index.d.ts.map +1 -1
- package/dist/commonjs/client/index.js +87 -2
- package/dist/commonjs/client/index.js.map +1 -1
- package/dist/commonjs/component/_generated/api.d.ts.map +1 -1
- package/dist/commonjs/component/_generated/api.js +0 -2
- package/dist/commonjs/component/_generated/api.js.map +1 -1
- package/dist/commonjs/component/_generated/server.d.ts.map +1 -1
- package/dist/commonjs/component/_generated/server.js +0 -2
- package/dist/commonjs/component/_generated/server.js.map +1 -1
- package/dist/commonjs/component/public.d.ts +9 -0
- package/dist/commonjs/component/public.d.ts.map +1 -1
- package/dist/commonjs/component/public.js +60 -0
- package/dist/commonjs/component/public.js.map +1 -1
- package/dist/esm/client/index.d.ts +79 -6
- package/dist/esm/client/index.d.ts.map +1 -1
- package/dist/esm/client/index.js +87 -2
- package/dist/esm/client/index.js.map +1 -1
- package/dist/esm/component/_generated/api.d.ts.map +1 -1
- package/dist/esm/component/_generated/api.js +0 -2
- package/dist/esm/component/_generated/api.js.map +1 -1
- package/dist/esm/component/_generated/server.d.ts.map +1 -1
- package/dist/esm/component/_generated/server.js +0 -2
- package/dist/esm/component/_generated/server.js.map +1 -1
- package/dist/esm/component/public.d.ts +9 -0
- package/dist/esm/component/public.d.ts.map +1 -1
- package/dist/esm/component/public.js +60 -0
- package/dist/esm/component/public.js.map +1 -1
- package/package.json +1 -1
- package/src/client/index.ts +135 -7
- package/src/component/_generated/api.d.ts +12 -4
- package/src/component/_generated/api.js +0 -4
- package/src/component/_generated/dataModel.d.ts +0 -4
- package/src/component/_generated/server.d.ts +0 -4
- package/src/component/_generated/server.js +0 -4
- package/src/component/counter.test.ts +11 -1
- package/src/component/public.ts +62 -0
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
/* prettier-ignore-start */
|
|
2
|
-
|
|
3
1
|
/* eslint-disable */
|
|
4
2
|
/**
|
|
5
3
|
* Generated `api` utility.
|
|
@@ -37,6 +35,18 @@ export type Mounts = {
|
|
|
37
35
|
null
|
|
38
36
|
>;
|
|
39
37
|
count: FunctionReference<"query", "public", { name: string }, number>;
|
|
38
|
+
estimateCount: FunctionReference<
|
|
39
|
+
"query",
|
|
40
|
+
"public",
|
|
41
|
+
{ name: string; readFromShards?: number; shards?: number },
|
|
42
|
+
any
|
|
43
|
+
>;
|
|
44
|
+
rebalance: FunctionReference<
|
|
45
|
+
"mutation",
|
|
46
|
+
"public",
|
|
47
|
+
{ name: string; shards?: number },
|
|
48
|
+
any
|
|
49
|
+
>;
|
|
40
50
|
};
|
|
41
51
|
};
|
|
42
52
|
// For now fullApiWithMounts is only fullApi which provides
|
|
@@ -54,5 +64,3 @@ export declare const internal: FilterApi<
|
|
|
54
64
|
>;
|
|
55
65
|
|
|
56
66
|
export declare const components: {};
|
|
57
|
-
|
|
58
|
-
/* prettier-ignore-end */
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
/* prettier-ignore-start */
|
|
2
|
-
|
|
3
1
|
/* eslint-disable */
|
|
4
2
|
/**
|
|
5
3
|
* Generated `api` utility.
|
|
@@ -23,5 +21,3 @@ import { anyApi, componentsGeneric } from "convex/server";
|
|
|
23
21
|
export const api = anyApi;
|
|
24
22
|
export const internal = anyApi;
|
|
25
23
|
export const components = componentsGeneric();
|
|
26
|
-
|
|
27
|
-
/* prettier-ignore-end */
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
/* prettier-ignore-start */
|
|
2
|
-
|
|
3
1
|
/* eslint-disable */
|
|
4
2
|
/**
|
|
5
3
|
* Generated data model types.
|
|
@@ -60,5 +58,3 @@ export type Id<TableName extends TableNames | SystemTableNames> =
|
|
|
60
58
|
* `mutationGeneric` to make them type-safe.
|
|
61
59
|
*/
|
|
62
60
|
export type DataModel = DataModelFromSchemaDefinition<typeof schema>;
|
|
63
|
-
|
|
64
|
-
/* prettier-ignore-end */
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
/* prettier-ignore-start */
|
|
2
|
-
|
|
3
1
|
/* eslint-disable */
|
|
4
2
|
/**
|
|
5
3
|
* Generated utilities for implementing server-side Convex query and mutation functions.
|
|
@@ -149,5 +147,3 @@ export type DatabaseReader = GenericDatabaseReader<DataModel>;
|
|
|
149
147
|
* for the guarantees Convex provides your functions.
|
|
150
148
|
*/
|
|
151
149
|
export type DatabaseWriter = GenericDatabaseWriter<DataModel>;
|
|
152
|
-
|
|
153
|
-
/* prettier-ignore-end */
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
/* prettier-ignore-start */
|
|
2
|
-
|
|
3
1
|
/* eslint-disable */
|
|
4
2
|
/**
|
|
5
3
|
* Generated utilities for implementing server-side Convex query and mutation functions.
|
|
@@ -90,5 +88,3 @@ export const internalAction = internalActionGeneric;
|
|
|
90
88
|
* @returns The wrapped endpoint function. Route a URL path to this function in `convex/http.js`.
|
|
91
89
|
*/
|
|
92
90
|
export const httpAction = httpActionGeneric;
|
|
93
|
-
|
|
94
|
-
/* prettier-ignore-end */
|
|
@@ -26,7 +26,7 @@ describe("counter", () => {
|
|
|
26
26
|
|
|
27
27
|
fcTest.prop({
|
|
28
28
|
updates: fc.array(fc.record({
|
|
29
|
-
v: fc.integer({ min:
|
|
29
|
+
v: fc.integer({ min: -10000, max: 10000 }).map((i) => i / 100),
|
|
30
30
|
key: fc.string(),
|
|
31
31
|
shards: fc.option(fc.integer({ min: 1, max: 100 })),
|
|
32
32
|
})),
|
|
@@ -41,5 +41,15 @@ fcTest.prop({
|
|
|
41
41
|
const count = await t.query(api.public.count, { name: key });
|
|
42
42
|
expect(count).toBeCloseTo(counter.get(key)!);
|
|
43
43
|
}
|
|
44
|
+
for (const [key, value] of counter.entries()) {
|
|
45
|
+
// Rebalancing keeps count the same and makes estimateCount accurate.
|
|
46
|
+
await t.mutation(api.public.rebalance, { name: key });
|
|
47
|
+
const count = await t.query(api.public.count, { name: key });
|
|
48
|
+
expect(count).toBeCloseTo(value);
|
|
49
|
+
for (let i = 1; i <= 16; i++) {
|
|
50
|
+
const estimate = await t.query(api.public.estimateCount, { name: key, readFromShards: i });
|
|
51
|
+
expect(estimate).toBeCloseTo(value);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
44
54
|
},
|
|
45
55
|
);
|
package/src/component/public.ts
CHANGED
|
@@ -41,3 +41,65 @@ export const count = query({
|
|
|
41
41
|
return counters.reduce((sum, counter) => sum + counter.value, 0);
|
|
42
42
|
},
|
|
43
43
|
});
|
|
44
|
+
|
|
45
|
+
export const rebalance = mutation({
|
|
46
|
+
args: { name: v.string(), shards: v.optional(v.number()) },
|
|
47
|
+
handler: async (ctx, args) => {
|
|
48
|
+
const counters = await ctx.db
|
|
49
|
+
.query("counters")
|
|
50
|
+
.withIndex("name", (q) => q.eq("name", args.name))
|
|
51
|
+
.collect();
|
|
52
|
+
const count = counters.reduce((sum, counter) => sum + counter.value, 0);
|
|
53
|
+
const shardCount = args.shards ?? DEFAULT_SHARD_COUNT;
|
|
54
|
+
const value = count / shardCount;
|
|
55
|
+
for (let i = 0; i < shardCount; i++) {
|
|
56
|
+
const shard = counters.find((c) => c.shard === i);
|
|
57
|
+
if (shard) {
|
|
58
|
+
await ctx.db.patch(shard._id, { value });
|
|
59
|
+
} else {
|
|
60
|
+
await ctx.db.insert("counters", {
|
|
61
|
+
name: args.name,
|
|
62
|
+
value,
|
|
63
|
+
shard: i,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
const toDelete = counters.filter((c) => c.shard >= shardCount);
|
|
68
|
+
for (const counter of toDelete) {
|
|
69
|
+
await ctx.db.delete(counter._id);
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
export const estimateCount = query({
|
|
75
|
+
args: {
|
|
76
|
+
name: v.string(),
|
|
77
|
+
readFromShards: v.optional(v.number()),
|
|
78
|
+
shards: v.optional(v.number()),
|
|
79
|
+
},
|
|
80
|
+
handler: async (ctx, args) => {
|
|
81
|
+
const shardCount = args.shards ?? DEFAULT_SHARD_COUNT;
|
|
82
|
+
const readFromShards = Math.min(Math.max(1, args.readFromShards ?? 1), shardCount);
|
|
83
|
+
const shards = shuffle(Array.from({ length: shardCount }, (_, i) => i)).slice(0, readFromShards);
|
|
84
|
+
let readCount = 0;
|
|
85
|
+
for (const shard of shards) {
|
|
86
|
+
const counter = await ctx.db
|
|
87
|
+
.query("counters")
|
|
88
|
+
.withIndex("name", (q) => q.eq("name", args.name).eq("shard", shard))
|
|
89
|
+
.unique();
|
|
90
|
+
if (counter) {
|
|
91
|
+
readCount += counter.value;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return (readCount * shardCount) / readFromShards;
|
|
95
|
+
},
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
// Fisher-Yates shuffle
|
|
99
|
+
function shuffle<T>(array: T[]): T[] {
|
|
100
|
+
for (let i = array.length - 1; i > 0; i--) {
|
|
101
|
+
const j = Math.floor(Math.random() * (i + 1));
|
|
102
|
+
[array[i], array[j]] = [array[j], array[i]];
|
|
103
|
+
}
|
|
104
|
+
return array;
|
|
105
|
+
}
|