Skip to content

Commit

Permalink
implement some command & setup the base api
Browse files Browse the repository at this point in the history
  • Loading branch information
rebaz94 committed Jul 4, 2022
0 parents commit 24e5a29
Show file tree
Hide file tree
Showing 29 changed files with 1,490 additions and 0 deletions.
11 changes: 11 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Files and directories created by pub.
.dart_tool/
.packages

# Conventional directory for build outputs.
build/

# Omit committing pubspec.lock for library packages; see
# https://dart.dev/guides/libraries/private-files#pubspeclock.
pubspec.lock
/.idea/
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
## 1.0.0

- Initial version.
39 changes: 39 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<!--
This README describes the package. If you publish this package to pub.dev,
this README's contents appear on the landing page for your package.
For information about how to write a good package README, see the guide for
[writing package pages](https://dart.dev/guides/libraries/writing-package-pages).
For general information about developing packages, see the Dart guide for
[creating packages](https://dart.dev/guides/libraries/create-library-packages)
and the Flutter guide for
[developing packages and plugins](https://flutter.dev/developing-packages).
-->

TODO: Put a short description of the package here that helps potential users
know whether this package might be useful for them.

## Features

TODO: List what your package can do. Maybe include images, gifs, or videos.

## Getting started

TODO: List prerequisites and provide or point to information on how to
start using the package.

## Usage

TODO: Include short and useful examples for package users. Add longer examples
to `/example` folder.

```dart
const like = 'sample';
```

## Additional information

TODO: Tell users more about the package: where to find more information, how to
contribute to the package, how to file issues, what response they can expect
from the package authors, and more.
30 changes: 30 additions & 0 deletions analysis_options.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# This file configures the static analysis results for your project (errors,
# warnings, and lints).
#
# This enables the 'recommended' set of lints from `package:lints`.
# This set helps identify many issues that may lead to problems when running
# or consuming Dart code, and enforces writing Dart using a single, idiomatic
# style and format.
#
# If you want a smaller set of lints you can change this to specify
# 'package:lints/core.yaml'. These are just the most critical lints
# (the recommended set includes the core lints).
# The core lints are also what is used by pub.dev for scoring packages.

include: package:lints/recommended.yaml

# Uncomment the following section to specify additional rules.

# linter:
# rules:
# - camel_case_types

# analyzer:
# exclude:
# - path/to/excluded/files/**

# For more information about the core and recommended set of lints, see
# https://dart.dev/go/core-lints

# For additional information about configuring this file, see
# https://dart.dev/guides/language/analysis-options
30 changes: 30 additions & 0 deletions example/upstash_redis_example.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import 'package:upstash_redis/src/commands/zadd.dart';
import 'package:upstash_redis/upstash_redis.dart';

Future<void> main() async {
final redis = Redis.fromEnv();

print(await redis.set('name', 'rebaz', ex: 60));
print(await redis.set(
'obj',
{
'v': {
'a': [1, 2],
'b': [3, 4]
}
},
ex: 60));
print(await redis.get<String>('name'));
print(await redis.get<Map<String, Map<String, List<int>>>>('obj'));
print(await redis.set('name', 'raouf', ex: 60, nx: true));
print(await redis.zadd('z', score: 1, member: 'rebaz'));
print(await redis.zadd(
'z2',
scores: [
ScoreMember(score: 2, member: 'Mike'),
ScoreMember(score: 3, member: 'Ali'),
ScoreMember(score: 4, member: 'Jack'),
],
));
print(await redis.zrem('z2', ['Jack', 'Ali']));
}
72 changes: 72 additions & 0 deletions lib/src/commands/command.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import 'dart:convert';

import 'package:upstash_redis/src/http.dart';
import 'package:upstash_redis/src/upstash_error.dart';
import 'package:upstash_redis/src/utils.dart';

typedef Serialize = String Function(dynamic data);
typedef Deserialize<TResult, TData> = TData Function(TResult result);

String defaultSerializer(dynamic data) {
if (data is String) return data;
return json.encode(data);
}

TData _castedDeserializer<TResult, TData>(TResult result) {
return (result as dynamic) as TData;
}

class CommandOption<TResult, TData> {
CommandOption({this.deserialize, this.automaticDeserialization = true});

/// Custom deserialize
final Deserialize<TResult, TData>? deserialize;

/// Automatically try to deserialize the returned data from upstash using `json.decode`
///
/// default is true
final bool automaticDeserialization;
}

/// Command offers default (de)serialization and the exec method to all commands.
///
/// TData represents what the user will enter or receive,
/// TResult is the raw data returned from upstash, which may need to be transformed or parsed.
abstract class Command<TResult, TData> {
Command(
/*String|unknown*/
List<dynamic> command, [
CommandOption<TResult, TData>? opts,
this.serialize = defaultSerializer,
]) : deserialize = (opts == null || opts.automaticDeserialization == true)
? opts?.deserialize ?? parseResponse
: _castedDeserializer,
command = command.map(serialize).toList();

final List<dynamic> command;
final Serialize serialize;
final Deserialize<TResult, TData> deserialize;

Future<TData> exec(Requester client) async {
final response = await client.request<TResult>(body: command);
final result = checkUpstashResponse<TResult>(response);
return deserialize(result as TResult);
}
}

@pragma('vm:prefer-inline')
@pragma('dart2js:tryInline')
TResult? checkUpstashResponse<TResult>(UpstashResponse<TResult> response) {
final error = response.error;
final result = response.result;

if (error != null) {
throw UpstashError(error);
}

if (result == undefined) {
throw Exception('Request did not return a result');
}

return result;
}
12 changes: 12 additions & 0 deletions lib/src/commands/del.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import 'package:upstash_redis/src/commands/command.dart';

class DelCommand extends Command<int, int> {
DelCommand._(super.command, super.opts);

factory DelCommand(
List<dynamic> command, [
CommandOption<int, int>? opts,
]) {
return DelCommand._(['del', ...command], opts);
}
}
12 changes: 12 additions & 0 deletions lib/src/commands/get.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import 'package:upstash_redis/src/commands/command.dart';

class GetCommand<TData> extends Command<dynamic, TData?> {
GetCommand._(super.command, super.opts);

factory GetCommand(
List<dynamic> command, [
CommandOption<dynamic, TData?>? opts,
]) {
return GetCommand._(['get', ...command], opts);
}
}
7 changes: 7 additions & 0 deletions lib/src/commands/mod.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export 'command.dart';
export 'get.dart';
export 'set.dart';
export 'zrem.dart';
export 'del.dart';
export 'zadd.dart';
export 'zscore.dart';
40 changes: 40 additions & 0 deletions lib/src/commands/set.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import 'package:upstash_redis/src/commands/command.dart';
import 'package:upstash_redis/src/commands/mod.dart';

class SetCommand<TData, TResult extends String> extends Command<TResult?, String?> {
SetCommand._(super.command, super.opts);

factory SetCommand(
String key,
TData value, {
int? ex,
int? px,
bool? nx,
bool? xx,
CommandOption<TResult, String>? cmdOpts,
}) {
final command = ["set", key, value];

if (ex != null && px != null) {
throw StateError('should only provide "ex" or "px"');
}

if (nx != null && xx != null) {
throw StateError('should only provide "nx" or "xx"');
}

if (ex is int) {
command.addAll(['ex', ex]);
} else if (px is int) {
command.addAll(['px', px]);
}

if (nx == true) {
command.add('nx');
} else if (xx == true) {
command.add('xx');
}

return SetCommand._(command, cmdOpts);
}
}
89 changes: 89 additions & 0 deletions lib/src/commands/zadd.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import 'package:upstash_redis/src/commands/command.dart';
import 'package:collection/collection.dart';
import 'package:upstash_redis/src/http.dart';

class ScoreMember<TData> {
const ScoreMember({
required this.score,
required this.member,
});

final num score;
final TData member;
}

class ZAddCommand<TData> extends Command<num?, num?> {
ZAddCommand._(super.command, super.opts);

factory ZAddCommand.single(
String key, {
num? score,
TData? member,
bool? ch,
bool? incr,
bool? nx,
bool? xx,
CommandOption<num?, num?>? cmdOpts,
}) {
ScoreMember<TData>? scoreMember;
if (score != null && member != null) {
scoreMember = ScoreMember(score: score, member: member);
}
return ZAddCommand(
key,
[if (scoreMember != null) scoreMember],
ch: ch,
incr: incr,
nx: nx,
xx: xx,
cmdOpts: cmdOpts,
);
}

factory ZAddCommand(
String key,
List<ScoreMember<TData>> scoreMembers, {
bool? ch,
bool? incr,
bool? nx,
bool? xx,
CommandOption<num?, num?>? cmdOpts,
}) {
if (nx != null && xx != null) {
throw StateError('should only provide "nx" or "xx"');
}

final command = <dynamic>['zadd', key];

if (nx == true) {
command.add('nx');
} else if (xx == true) {
command.add('xx');
}

if (ch == true) {
command.add('ch');
}
if (incr == true) {
command.add('incr');
}

final flatScoreMap = scoreMembers.map((e) => [e.score, e.member]).flattened;
command.addAll(flatScoreMap);

return ZAddCommand._(command, cmdOpts);
}

@override
Future<num?> exec(Requester client) async {
final response = await client.request<dynamic>(body: command);
final result = checkUpstashResponse<dynamic>(response);

if (result is String) {
return num?.tryParse(result);
} else if (result is num) {
return result;
}
return deserialize(result);
}
}
13 changes: 13 additions & 0 deletions lib/src/commands/zrem.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import 'package:upstash_redis/src/commands/command.dart';

class ZRemCommand<TData> extends Command<int, int> {
ZRemCommand._(super.command, super.opts);

factory ZRemCommand(
String key,
List<TData> members, [
CommandOption<int, int>? opts,
]) {
return ZRemCommand._(['zrem', key, ...members], opts);
}
}
13 changes: 13 additions & 0 deletions lib/src/commands/zscore.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import 'package:upstash_redis/src/commands/command.dart';

class ZScoreCommand<TData> extends Command<String?, num?> {
ZScoreCommand._(super.command, super.opts);

factory ZScoreCommand(
String key,
TData member, [
CommandOption<String?, num?>? opts,
]) {
return ZScoreCommand._(['zscore', key, member], opts);
}
}
Loading

0 comments on commit 24e5a29

Please sign in to comment.