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

🏞️ Implement streams commands #86

Merged
merged 154 commits into from
Jul 2, 2020

Conversation

Terkwood
Copy link
Contributor

@Terkwood Terkwood commented Jun 7, 2020

Add stream commands

This change set resolves #36. 🚒

API design 🎨

We gladly welcome feedback on the design. You can read the method defs in command.ts to see the most up-to-date version of the command inputs & outputs

Strongly Typed Message IDs

We implemented strong types for stream entry IDs:

export interface XId {
  unixMs: number;
  seqNo: number;
}

We made an effort to ensure flexibility in the command interfaces. For instance, in xadd, you may also pass simple numbers, or paired numbers [1000,10], or the special string "*".

Methods which return XIds will always return the exact form, so that you can deconstruct them with minimal pain.

See XId and the related types in streams.ts for details.

Return type for XREAD πŸ“•

We offer an interface for the XREAD reply type which is hopefully less annoying to use than the Redis default array type. Please note that we return field_value pairs as a Map.

export interface XMessage {
  id: XId;
  field_values: Map<string, string>;
}
export type XReadStream = { key: string; messages: XMessage[] };
export type XReadReply = XReadStream[];
  • implement a nice return type for XREAD and XREADGROUP

Allow input of record objects in XADD

In xadd, we chose to allow input of arbitrary Record<string | number,string | number> objects, as well as Map<string | number,string | number>.

⚠ This allows greater flexibility for input, but may end up being a little confusing since the field_value outputs of xread and xreadgroup are always Map<string,string>. ⚠

  • support record objects in XADD
  • Remove support for array of string field_values in XADD, touch up docs to reflect
  • expose a single xadd implementation & interface that allows default options

Commands to implement and test

  • xadd
  • xread
  • xreadgroup
  • xack
  • xclaim
  • xpending
  • xtrim
  • xdel
  • xrange
  • xrevrange
  • xinfo
  • xlen
  • xgroup

docs and sanity checks

We made an effort to verify that argument names match the canonical names exposed by Redis. One exception is references to ID in Redis docs, which we call xid: XId in order to match the parsed, typed flavor of data. Minimal command form should be included in each doc string.

The JsDoc approach isn't very nice since only typescript export statements can trigger deno doc, but at least these docs are available to developers via their editors.

  • xack
  • xadd
  • xread
  • xreadgroup
  • xclaim
  • xpending
  • xtrim
  • xdel
  • xrange
  • xrevrange
  • xinfo
  • xlen
  • xgroup

special reply types to model

Some hints for reply types https://github.com/Terkwood/redis-rs/blob/0d126f54d6970b931594bf677b09a368af70617d/src/streams.rs#L231

Additional requirements

Always Remember

  • pass deno lint (see chore: pass "deno lint"Β #98 for inspiration). The CI will enforce this anyway.
  • pass deno fmt --check. The CI will enforce this also.

@Terkwood Terkwood mentioned this pull request Jun 8, 2020
@Terkwood Terkwood marked this pull request as ready for review June 30, 2020 14:33
@uki00a uki00a added this to the v0.11.0 milestone Jun 30, 2020
@uki00a uki00a self-assigned this Jun 30, 2020
@uki00a
Copy link
Member

uki00a commented Jun 30, 2020

Thanks @Terkwood! I'll review this PR in the coming days.


let created = await client.xgroup_create(key, groupName, "$", true);
assertEquals(created, "OK");
try {
Copy link
Member

Choose a reason for hiding this comment

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

I think it's better to use assertThrowsAsync.

Suggested change
try {
await assertThrowsAsync(async () => {
await client.xgroup_create(
key,
groupName,
0,
true
);
}, Error, "-BUSYERR");

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Altered, but for now the Error type and exact error message are left undefined. The message text contains a bunch of whitespace and a newline and is a little tricky to match. Haven't tracked down the Error class yet. I can come back to this if it's a sticking point...

Copy link
Contributor Author

@Terkwood Terkwood Jul 2, 2020

Choose a reason for hiding this comment

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

AssertionError: Expected error to be instance of "Error", but got "Error". 

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Aha, got the message part matching, at least!

@@ -286,6 +313,352 @@ export type RedisCommands = {
srem(key: string, ...members: string[]): Promise<Integer>;
sunion(...keys: string[]): Promise<BulkString[]>;
sunionstore(destination: string, ...keys: string[]): Promise<Integer>;
// Stream
/**
Copy link
Member

Choose a reason for hiding this comment

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

nice documents!


export interface XMessage {
xid: XId;
field_values: Map<string, string>;
Copy link
Member

Choose a reason for hiding this comment

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

Hmmm, it's hard to decide whether Map or Record is better... πŸ€”

@uki00a
Copy link
Member

uki00a commented Jul 1, 2020

Looks good to me overall, but a few things need to be fixed.
Please check it.

@Terkwood
Copy link
Contributor Author

Terkwood commented Jul 1, 2020

Thanks for the fast review! I will be able to respond on Thursday or Friday! The comments look straightforward.

I don't have a strong opinion on Record vs Map for the return type from the xread and xreadgroup methods, but my ultimate rationale was that iterating thru Maps seems a bit less clunky: https://stackoverflow.com/questions/16174182/typescript-looping-through-a-dictionary

But for accessing individual fields, Record (dictionary a.b) syntax seems a little friendlier to the library user.

I'm happy to use either choice.

Copy link
Member

@uki00a uki00a left a comment

Choose a reason for hiding this comment

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

Thanks for submitting the changes!
There is just one thing to fix.
Please check it.

@uki00a
Copy link
Member

uki00a commented Jul 2, 2020

@Terkwood
IMO it's not a bad idea to adopt Map over Record (because Map keeps the order of the fields in the stream entry).
Having said that, I'd like to hear the opinions of other people, so I'll open the issue about it.

Let's merge this PR for now!

@uki00a uki00a merged commit b6e5f3d into denodrivers:master Jul 2, 2020
@uki00a
Copy link
Member

uki00a commented Jul 2, 2020

Merged, thanks a lot @Terkwood!

@Terkwood
Copy link
Contributor Author

Terkwood commented Jul 2, 2020

Woohoo! Thanks, my pleasure!

@uki00a
Copy link
Member

uki00a commented Jul 8, 2020

Released in v0.11.0!

@Terkwood
Copy link
Contributor Author

Terkwood commented Jul 8, 2020

So cool!! Thanks!

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

Successfully merging this pull request may close these issues.

Stream API
2 participants