Skip to content
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

Namespace aggregates #20

Merged
merged 14 commits into from
Nov 6, 2024
Merged

Namespace aggregates #20

merged 14 commits into from
Nov 6, 2024

Conversation

ldanilek
Copy link
Collaborator

@ldanilek ldanilek commented Nov 4, 2024

Add namespacing to aggregates.

All of the data and even the APIs should be backwards-compatible.


By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

@ldanilek ldanilek requested a review from ianmacartney November 4, 2024 18:03
Copy link
Contributor

@ianmacartney ianmacartney left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very cool. Since namespace is being baked into the base Aggregate classes, I'd rather not have separate classes, and just have a required argument to the base function if namespace's type is not undefined.
And then we can (optionally) have a .for helper if you want to specify the namespace once and have an api that injects it, along with some fancy types so the argument is (1) required if you specified a namespace document fn but didn't pass one to the constructure, (2) not allowed if you didn't specify a namespace fn, or already specified the namespace via for.

Are you game for that?

README.md Outdated Show resolved Hide resolved
README.md Outdated Show resolved Hide resolved
README.md Outdated Show resolved Hide resolved
README.md Outdated
Comment on lines 251 to 254
[string, number],
DataModel,
"leaderboard",
Id<"games">
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should use an object type param here if it can also work. Knowing what each parameter means is hard. What do you think of something like:

NamespacedTableAggregate<{ 
  sortKey: [string, number],
  DataModel: DataModel, // maybe just `DataModel,` works?
  tableName: "leaderboard",
  namespace: Id<"games">
}>

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also tried for a bit but couldn't get working: having this type inferred from the parameters somehow. e.g. the namespace and sortKey types should be inferable somehow, since their return values below dictate the types. But I couldn't get it working when I tried

README.md Outdated
Comment on lines 261 to 265
Now when you need to aggregate within a game, you call `.get` to narrow down the
computation to a single game.

```ts
const countTimesGamePlayed = await aggregateByGame.get(gameId).count();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Now when you need to aggregate within a game, you call `.get` to narrow down the
computation to a single game.
```ts
const countTimesGamePlayed = await aggregateByGame.get(gameId).count();
Now when you need to aggregate within a game, you call `.for` to narrow down the
computation to a single game.
```ts
const countTimesGamePlayed = await aggregateByGame.for(gameId).count();

handler: async (ctx, { offset, numItems }) => {
const { key: firstPhotoCreationTime } = await photos.at(ctx, offset);
handler: async (ctx, { offset, numItems, album }) => {
const { key: firstPhotoCreationTime } = await photos.get(album).at(ctx, offset);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const { key: firstPhotoCreationTime } = await photos.get(album).at(ctx, offset);
const { key: firstPhotoCreationTime } = await photos.for(album).at(ctx, offset);

protected component: UsedAPI,
) {}

get(namespace: Namespace): Aggregate<K, ID, Namespace> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
get(namespace: Namespace): Aggregate<K, ID, Namespace> {
for(namespace: Namespace): Aggregate<K, ID, Namespace> {

src/component/public.ts Outdated Show resolved Hide resolved
@@ -1,12 +1,12 @@
import { v } from "convex/values";
import { Id } from "./_generated/dataModel.js";
import { DatabaseReader, query } from "./_generated/server.js";
import { getTree, p } from "./btree.js";
import { getTree, Namespace, p } from "./btree.js";

export const display = query({
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wow I've missed the existence of this - would be nice to document how to view / debug your state in the readme. One common thing for me is trying to figure out why some documents ended up busted - and finding myself crawling through the dashboard data to try to look for the data ordering. another utility I've wanted is for table aggregate: "refresh this document with the current doc value / sort key" - and a backfill utility to just crawl a table and re-calculate every doc would be handy in dev. maybe just copying from your example is enough there though

src/component/btree.ts Show resolved Hide resolved
@ldanilek ldanilek requested a review from ianmacartney November 6, 2024 02:32
Copy link
Contributor

@ianmacartney ianmacartney left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks good. The type names should be TitleCase even as object keys (I believe). I started adding inline suggestions, but doing a find & replace in an editor will likely be faster

README.md Outdated
Comment on lines 121 to 124
namespace: Id<"games">,
key: number,
dataModel: DataModel,
tableName: "scores",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
namespace: Id<"games">,
key: number,
dataModel: DataModel,
tableName: "scores",
Namespace: Id<"games">,
Key: number,
DataModel: DataModel,
TableName: "scores",

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and I'm not sure if that allows you to do just DataModel,

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It does not allow that. While i'm find-replacing, i was wondering if Key should be called SortKey

README.md Outdated
Comment on lines 214 to 217
namespace: undefined,
key: number,
dataModel: DataModel,
tableName: "mytable",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
namespace: undefined,
key: number,
dataModel: DataModel,
tableName: "mytable",
Namespace: undefined,
Key: number,
DataModel: DataModel,
TableName: "mytable",

README.md Outdated
Comment on lines 238 to 241
namespace: Id<"games">,
key: [string, number],
dataModel: DataModel,
tableName: "leaderboard"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
namespace: Id<"games">,
key: [string, number],
dataModel: DataModel,
tableName: "leaderboard"
Namespace: Id<"games">,
Key: [string, number],
DataModel: DataModel,
TableName: "leaderboard"

README.md Outdated
Comment on lines 313 to 316
namespace: undefined,
key: null,
dataModel: DataModel,
tableName: "mytable",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
namespace: undefined,
key: null,
dataModel: DataModel,
tableName: "mytable",
Namespace: undefined,
Key: null,
DataModel: DataModel,
TableName: "mytable",

README.md Outdated
Comment on lines 357 to 360
namespace: string, // album name
key: number, // creation time
dataModel: DataModel,
tableName: "photos",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
namespace: string, // album name
key: number, // creation time
dataModel: DataModel,
tableName: "photos",
Namespace: string, // album name
Key: number, // creation time
DataModel: DataModel,
TableName: "photos",


/// Aggregate queries.

/**
* Counts items between the given bounds.
*/
async count(ctx: RunQueryCtx, bounds?: Bounds<K, ID>): Promise<number> {
async count(ctx: RunQueryCtx, ...opts: NamespacedOpts<{ bounds: Bounds<K, ID> }, Namespace>): Promise<number> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
async count(ctx: RunQueryCtx, ...opts: NamespacedOpts<{ bounds: Bounds<K, ID> }, Namespace>): Promise<number> {
async count(ctx: RunQueryCtx, ...opts: NamespacedOpts<{ Bounds: Bounds<K, ID> }, Namespace>): Promise<number> {

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this one should be TitleCase. That would mean you call it like aggregate.count(ctx, { Bounds: { prefix: [username] } })

Comment on lines 556 to 559
key: K,
dataModel: DataModel,
tableName: TableName,
namespace: Namespace,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
key: K,
dataModel: DataModel,
tableName: TableName,
namespace: Namespace,
Key: K,
DataModel: DataModel,
TableName: TableName,
Namespace: Namespace,

@@ -657,3 +745,31 @@ export function btreeItemToAggregateItem<K extends Key, ID extends string>({
sumValue: s,
};
}

export type NamespacedArgs<Args, Namespace> =
Args & { namespace: Namespace } | (Namespace extends undefined ? Args : never);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so you can provide namespace: undefined if you didn't specify one? I wonder if this works if that's not intended:

Args & (Namespace extends undefined ? object : { namespace: Namespace });

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is intended. It's useful for methods like min that call other methods and pass in the namespace.

export type NamespacedOpts<Opts, Namespace> =
[{ namespace: Namespace } & Opts] | (
Namespace extends undefined
? [] | [Opts]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This might be possible to do with [Opts?]

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TIL that syntax

README.md Outdated Show resolved Hide resolved
@ldanilek ldanilek merged commit d4f882c into main Nov 6, 2024
1 check passed
@ianmacartney
Copy link
Contributor

ianmacartney commented Nov 6, 2024 via email

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants