From 996fcbdfdd18cddedc95f4ab65db70d7689712bb Mon Sep 17 00:00:00 2001 From: Dillon Nys Date: Sat, 10 Feb 2024 16:55:16 -0800 Subject: [PATCH 01/13] Initial Auth construct --- packages/celest/example/celest/auth/auth.dart | 7 +++++++ packages/celest/lib/celest.dart | 4 ++++ packages/celest/lib/src/auth/auth.dart | 18 ++++++++++++++++++ .../celest/lib/src/auth/auth_provider.dart | 16 ++++++++++++++++ packages/celest/lib/src/core/project.dart | 6 ++++++ 5 files changed, 51 insertions(+) create mode 100644 packages/celest/example/celest/auth/auth.dart create mode 100644 packages/celest/lib/src/auth/auth.dart create mode 100644 packages/celest/lib/src/auth/auth_provider.dart diff --git a/packages/celest/example/celest/auth/auth.dart b/packages/celest/example/celest/auth/auth.dart new file mode 100644 index 00000000..17c15f32 --- /dev/null +++ b/packages/celest/example/celest/auth/auth.dart @@ -0,0 +1,7 @@ +import 'package:celest/celest.dart'; + +const auth = Auth( + providers: [ + AuthProvider.google(), + ], +); diff --git a/packages/celest/lib/celest.dart b/packages/celest/lib/celest.dart index d242310f..8723faba 100644 --- a/packages/celest/lib/celest.dart +++ b/packages/celest/lib/celest.dart @@ -3,6 +3,10 @@ library celest; export 'package:celest_core/celest_core.dart'; +/// Auth +export 'src/auth/auth.dart'; +export 'src/auth/auth_provider.dart'; + /// Config export 'src/config/env.dart'; diff --git a/packages/celest/lib/src/auth/auth.dart b/packages/celest/lib/src/auth/auth.dart new file mode 100644 index 00000000..a099a9f4 --- /dev/null +++ b/packages/celest/lib/src/auth/auth.dart @@ -0,0 +1,18 @@ +import 'package:celest/src/auth/auth_provider.dart'; + +/// {@template celest.auth.auth} +/// The authentication component of your Celest project. +/// +/// The Auth widget defines the authentication providers which can be used to +/// sign in to your Celest project. +/// {@endtemplate} +final class Auth { + /// {@macro celest.auth.auth} + const Auth({ + required this.providers, + }); + + /// The authentication providers which can be used by your users when signing + /// in. + final List providers; +} diff --git a/packages/celest/lib/src/auth/auth_provider.dart b/packages/celest/lib/src/auth/auth_provider.dart new file mode 100644 index 00000000..54c5b624 --- /dev/null +++ b/packages/celest/lib/src/auth/auth_provider.dart @@ -0,0 +1,16 @@ +/// {@template celest.auth.auth_provider} +/// An authentication provider which can be used to sign in to Celest. +/// +/// Currently, Celest supports the following authentication methods: +/// - [AuthProvider.google] Sign in with Google +/// {@endtemplate} +sealed class AuthProvider { + const AuthProvider(); + + /// A provider which enables Sign in with Google. + const factory AuthProvider.google() = _GoogleAuthProvider; +} + +final class _GoogleAuthProvider extends AuthProvider { + const _GoogleAuthProvider(); +} diff --git a/packages/celest/lib/src/core/project.dart b/packages/celest/lib/src/core/project.dart index a66a9c4a..0f140eee 100644 --- a/packages/celest/lib/src/core/project.dart +++ b/packages/celest/lib/src/core/project.dart @@ -11,8 +11,14 @@ class Project implements CloudWidget { /// {@macro celest.core.project} const Project({ required this.name, + this.logoUrl, }); /// The name of the project as its identified in your Celest backend. final String name; + + /// The hosted URL of your project's logo. + /// + /// This is used by widgets like [Auth] to craft a custom login page. + final String? logoUrl; } From 268957684b70889588c82e8a1665115aa81fcfa2 Mon Sep 17 00:00:00 2001 From: Dillon Nys Date: Mon, 5 Feb 2024 08:28:22 -0800 Subject: [PATCH 02/13] Initial extension types work --- .../lib/src/exception/cloud_exception.dart | 2 +- .../lib/src/serialization/serializer.dart | 65 ++++++++++++++----- 2 files changed, 48 insertions(+), 19 deletions(-) diff --git a/packages/celest_core/lib/src/exception/cloud_exception.dart b/packages/celest_core/lib/src/exception/cloud_exception.dart index 5d70b198..84e4082f 100644 --- a/packages/celest_core/lib/src/exception/cloud_exception.dart +++ b/packages/celest_core/lib/src/exception/cloud_exception.dart @@ -21,7 +21,7 @@ class BadRequestException implements CloudException { /// An exception thrown by a Cloud Function when an unrecoverable internal error /// occurs. /// {@endtemplate} -final class InternalServerException implements CloudException { +class InternalServerException implements CloudException { /// Creates a [InternalServerException] with the given [message]. /// /// {@macro celest_core_exception_internal_server_exception} diff --git a/packages/celest_core/lib/src/serialization/serializer.dart b/packages/celest_core/lib/src/serialization/serializer.dart index 03206109..edd6e239 100644 --- a/packages/celest_core/lib/src/serialization/serializer.dart +++ b/packages/celest_core/lib/src/serialization/serializer.dart @@ -61,31 +61,34 @@ abstract mixin class Serializers { static final Serializers _instance = Serializers(); /// Serializes [value] to the wire type. - Object? serialize(T value) { - final serializer = expect(); + Object? serialize(T value, [TypeToken? typeToken]) { + final serializer = expect(typeToken); return _isNullable() ? value?.let(serializer.serialize) : serializer.serialize(value); } /// Deserializes [value] to [T]. - T deserialize(Object? value) { - final serializer = expect(); + T deserialize(Object? value, [TypeToken? typeToken]) { + final serializer = expect(typeToken); return _isNullable() ? value?.let(serializer.deserialize) as T : serializer.deserialize(value); } /// Gets the [Serializer] for type [Dart]. - Serializer? get(); + Serializer? get([TypeToken? typeToken]); /// Gets the [Serializer] for type [Dart] and asserts its existence. - Serializer expect(); + Serializer expect([TypeToken? typeToken]); /// Puts a [Serializer] for type [Dart]. /// /// Returns the previously registered [Serializer] if it existed. - Serializer? put(Serializer serializer); + Serializer? put( + Serializer serializer, [ + TypeToken? typeToken, + ]); } final class _Serializers extends Serializers { @@ -102,29 +105,55 @@ final class _Serializers extends Serializers { put(const InternalServerExceptionSerializer()); } - final _serializersByType = {}; + final _serializersByType = , Serializer>{}; @override - Serializer expect() { - final serializer = get(); + Serializer expect([TypeToken? typeToken]) { + typeToken ??= TypeToken(); + final serializer = get(typeToken); if (serializer == null) { throw SerializationException( - 'No serializer found for $Dart. Did you forget to call `celest.init()` ' - "at the start of your Flutter app's `main` function?", + 'No serializer found for $typeToken. Did you forget to call ' + "`celest.init()` at the start of your Flutter app's `main` function?", ); } return serializer; } @override - Serializer? get() => - _serializersByType[Dart] as Serializer?; + Serializer? get([TypeToken? typeToken]) => + _serializersByType[typeToken ?? TypeToken()] as Serializer?; @override - Serializer? put(Serializer serializer) { - final existing = get(); - _serializersByType[Dart] = serializer; - _serializersByType[_Nullable] = serializer; + Serializer? put( + Serializer serializer, [ + TypeToken? typeToken, + ]) { + typeToken ??= TypeToken(); + final existing = get(typeToken); + _serializersByType[typeToken] = serializer; + _serializersByType[typeToken.nullable] = serializer; return existing; } } + +final class TypeToken { + const TypeToken([this._typeName]); + + final String? _typeName; + + String get typeName => _typeName ?? '$T'; + Type get type => T; + + TypeToken get nullable => TypeToken<_Nullable>(_typeName); + + @override + bool operator ==(Object other) => + other is TypeToken && other._typeName == _typeName && other.type == type; + + @override + int get hashCode => Object.hash(_typeName, type); + + @override + String toString() => typeName; +} From 45ce41484b7078b16cab18e9d796d8a7e05bd0ca Mon Sep 17 00:00:00 2001 From: Dillon Nys Date: Mon, 19 Feb 2024 15:03:25 -0800 Subject: [PATCH 03/13] chore(repo): Bump Dart SDK to 3.3 --- examples/openai/celest/pubspec.yaml | 2 +- examples/todo/celest/pubspec.yaml | 2 +- packages/celest/example/celest/pubspec.yaml | 2 +- packages/celest/pubspec.yaml | 2 +- packages/celest_core/pubspec.yaml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/openai/celest/pubspec.yaml b/examples/openai/celest/pubspec.yaml index 6ebb8a1d..42a27e40 100644 --- a/examples/openai/celest/pubspec.yaml +++ b/examples/openai/celest/pubspec.yaml @@ -3,7 +3,7 @@ description: The Celest backend for celest_project. publish_to: none environment: - sdk: ^3.2.0 + sdk: ^3.3.0 dependencies: celest: ^0.1.0 diff --git a/examples/todo/celest/pubspec.yaml b/examples/todo/celest/pubspec.yaml index 08324eeb..33fe89cd 100644 --- a/examples/todo/celest/pubspec.yaml +++ b/examples/todo/celest/pubspec.yaml @@ -3,7 +3,7 @@ description: The Celest backend for todo. publish_to: none environment: - sdk: ^3.2.0 + sdk: ^3.3.0 dependencies: celest: ^0.1.0 diff --git a/packages/celest/example/celest/pubspec.yaml b/packages/celest/example/celest/pubspec.yaml index ac16384c..a26ba48e 100644 --- a/packages/celest/example/celest/pubspec.yaml +++ b/packages/celest/example/celest/pubspec.yaml @@ -3,7 +3,7 @@ description: The Celest backend for example_app. publish_to: none environment: - sdk: ^3.2.0 + sdk: ^3.3.0 dependencies: celest: ^0.1.0 diff --git a/packages/celest/pubspec.yaml b/packages/celest/pubspec.yaml index 883a2943..8a00d820 100644 --- a/packages/celest/pubspec.yaml +++ b/packages/celest/pubspec.yaml @@ -5,7 +5,7 @@ homepage: https://celest.dev repository: https://github.com/celest-dev/celest/tree/main/packages/celest environment: - sdk: ^3.2.0 + sdk: ^3.3.0 dependencies: async: ^2.11.0 diff --git a/packages/celest_core/pubspec.yaml b/packages/celest_core/pubspec.yaml index ff7fae73..1f4c67e5 100644 --- a/packages/celest_core/pubspec.yaml +++ b/packages/celest_core/pubspec.yaml @@ -5,7 +5,7 @@ homepage: https://celest.dev repository: https://github.com/celest-dev/celest/tree/main/packages/celest_core environment: - sdk: ^3.2.0 + sdk: ^3.3.0 dependencies: collection: ^1.18.0 From a842aed9d57abb399d8a00fc46f2d7c990022f8e Mon Sep 17 00:00:00 2001 From: Dillon Nys Date: Mon, 19 Feb 2024 15:04:47 -0800 Subject: [PATCH 04/13] chore(core): Migrate JsonValue to extension types --- .../lib/src/serialization/json_value.dart | 263 ++++-------------- .../celest_core/test/json_value_test.dart | 103 +++---- 2 files changed, 85 insertions(+), 281 deletions(-) diff --git a/packages/celest_core/lib/src/serialization/json_value.dart b/packages/celest_core/lib/src/serialization/json_value.dart index 0a03c831..2445a685 100644 --- a/packages/celest_core/lib/src/serialization/json_value.dart +++ b/packages/celest_core/lib/src/serialization/json_value.dart @@ -1,7 +1,3 @@ -import 'dart:collection'; -import 'dart:convert'; - -import 'package:collection/collection.dart'; import 'package:meta/meta.dart'; // ignore_for_file: avoid_positional_boolean_parameters @@ -21,8 +17,15 @@ import 'package:meta/meta.dart'; /// this class does. This is because [int] and [double] are not interchangeable /// in Dart and we want to preserve the type information. @immutable -sealed class JsonValue { - const JsonValue._(); +extension type const JsonValue._(Object value) { + factory JsonValue(Object value) { + return switch (value) { + String() || num() || bool() || List() || Map() => value as JsonValue, + _ => throw FormatException( + 'Unsupported JSON value: $value (${value.runtimeType})', + ), + }; + } /// Creates a [JsonString] from [value]. const factory JsonValue.string(String value) = JsonString; @@ -42,185 +45,64 @@ sealed class JsonValue { /// Creates a [JsonMap] from [value]. const factory JsonValue.map(Map value) = JsonMap; - /// Creates a [JsonValue] from [value]. - static JsonValue? from(Object? value) { - return switch (value) { - null => null, - JsonValue() => value, - String() => JsonString(value), - int() => JsonInt(value), - double() => JsonDouble(value), - bool() => JsonBool(value), - List() => JsonList(value), - Map() => JsonMap(value), - Map() => JsonMap(value.cast()), - _ => throw FormatException( - 'Unsupported JSON value: $value (${value.runtimeType})', - ), - }; - } - - /// The wrapped JSON value. - Object? get wrapped; - List get _path; - T _expect(String key, Object? value) { if (value is T) { return value; } - throw FormatException( - 'Expected $T for key "${[..._path, key].join('.')}" but got: $value', - ); + throw FormatException('Expected $T for key "$key" but got: $value'); } - - @override - bool operator ==(Object other) => - identical(this, other) || other is JsonValue && wrapped == other.wrapped; - - @override - int get hashCode => wrapped.hashCode; - - @override - String toString() => wrapped.toString(); } -/// A [JsonValue] which is guaranteed to be a [String]. -final class JsonString extends JsonValue { - /// Creates a [JsonString] from [wrapped]. - const JsonString(this.wrapped) - : _path = const [], - super._(); +/// A [JsonValue] which represents a [String]. +extension type const JsonString(String value) implements JsonValue, String {} - const JsonString._(this.wrapped, [this._path = const []]) : super._(); +/// A [JsonValue] which represents a [num]. +extension type const JsonNum(num value) implements JsonValue, num { + /// Converts this to a [JsonInt]. + JsonInt toInt() => JsonInt(value.toInt()); - @override - final String wrapped; - - @override - final List _path; + /// Converts this to a [JsonDouble]. + JsonDouble toDouble() => JsonDouble(value.toDouble()); } -/// A [JsonValue] which is guaranteed to be a [num]. -mixin JsonNum on JsonValue { - @override - num get wrapped; - - /// Converts this [JsonNum] to an [JsonInt]. - JsonInt toInt() => JsonInt._(wrapped.toInt(), _path); +/// A [JsonValue] which represents an [int]. +extension type const JsonInt(int value) implements JsonNum, int { + @redeclare + JsonInt toInt() => this; - /// Converts this [JsonNum] to an [JsonDouble]. - JsonDouble toDouble() => JsonDouble._(wrapped.toDouble(), _path); + @redeclare + JsonDouble toDouble() => JsonDouble(value.toDouble()); } -/// A [JsonValue] which is guaranteed to be an [int]. -final class JsonInt extends JsonValue with JsonNum { - /// Creates a [JsonInt] from [wrapped]. - const JsonInt(this.wrapped) - : _path = const [], - super._(); - - const JsonInt._(this.wrapped, [this._path = const []]) : super._(); +/// A [JsonValue] which represents a [double]. +extension type const JsonDouble(double value) implements JsonNum, double { + @redeclare + JsonInt toInt() => JsonInt(value.toInt()); - @override - final int wrapped; - - @override - final List _path; + @redeclare + JsonDouble toDouble() => this; } -/// A [JsonValue] which is guaranteed to be a [double]. -final class JsonDouble extends JsonValue with JsonNum { - /// Creates a [JsonDouble] from [wrapped]. - const JsonDouble(this.wrapped) - : _path = const [], - super._(); - - const JsonDouble._(this.wrapped, [this._path = const []]) : super._(); +/// A [JsonValue] which represents a [bool]. +extension type const JsonBool(bool value) implements JsonValue, bool {} - @override - final double wrapped; - - @override - final List _path; +/// A [JsonValue] which represents a [List]. +extension type const JsonList._(List value) + implements JsonValue, List { + const JsonList(List value) : this._(value as List); } -/// A [JsonValue] which is guaranteed to be a [bool]. -final class JsonBool extends JsonValue { - /// Creates a [JsonBool] from [wrapped]. - const JsonBool(this.wrapped) - : _path = const [], - super._(); - - const JsonBool._(this.wrapped, [this._path = const []]) : super._(); - - @override - final bool wrapped; - - @override - final List _path; -} - -/// A [JsonValue] which is guaranteed to be a [List]. -final class JsonList extends JsonValue with ListMixin { - /// Creates a [JsonList] from [wrapped]. - const JsonList(this.wrapped) - : _path = const [], - super._(); - - const JsonList._(this.wrapped, [this._path = const []]) : super._(); - - @override - final List wrapped; - - @override - final List _path; - - @override - int get length => wrapped.length; - - @override - set length(int length) => wrapped.length = length; - - @override - JsonValue? operator [](int index) => JsonValue.from(wrapped[index]); - - @override - void operator []=(int index, JsonValue? value) { - wrapped[index] = value?.wrapped; - } - - @override - bool operator ==(Object other) => - identical(this, other) || - other is JsonList && _deepEquals(wrapped, other.wrapped); - - @override - int get hashCode => _deepHash(wrapped); - - @override - String toString() => _jsonEncoder.convert(wrapped); -} - -/// A [JsonValue] which is guaranteed to be a [Map]. -final class JsonMap extends JsonValue with MapMixin { - /// Creates a [JsonMap] from [wrapped]. - const JsonMap(this.wrapped) - : _path = const [], - super._(); - - const JsonMap._(this.wrapped, [this._path = const []]) : super._(); - - @override - final Map wrapped; - - @override - final List _path; +/// A [JsonValue] which represents a [Map]. +extension type const JsonMap._(Map value) + implements JsonValue, Map { + const JsonMap(Map value) + : this._(value as Map); /// Returns the string associated with [key] or `null` if [key] is not in the /// map. JsonString? optionalString(String key) { - if (wrapped[key] case final value?) { - return JsonString._(_expect(key, value), [..._path, key]); + if (value[key] case final value?) { + return JsonString(_expect(key, value)); } return null; } @@ -234,8 +116,8 @@ final class JsonMap extends JsonValue with MapMixin { /// Returns the int associated with [key] or `null` if [key] is not in the /// map. JsonInt? optionalInt(String key) { - if (wrapped[key] case final value?) { - return JsonInt._(_expect(key, value).toInt(), [..._path, key]); + if (value[key] case final value?) { + return JsonInt(_expect(key, value).toInt()); } return null; } @@ -249,8 +131,8 @@ final class JsonMap extends JsonValue with MapMixin { /// Returns the double associated with [key] or `null` if [key] is not in the /// map. JsonDouble? optionalDouble(String key) { - if (wrapped[key] case final value?) { - return JsonDouble._(_expect(key, value).toDouble(), [..._path, key]); + if (value[key] case final value?) { + return JsonDouble(_expect(key, value).toDouble()); } return null; } @@ -264,8 +146,8 @@ final class JsonMap extends JsonValue with MapMixin { /// Returns the bool associated with [key] or `null` if [key] is not in the /// map. JsonBool? optionalBool(String key) { - if (wrapped[key] case final value?) { - return JsonBool._(_expect(key, value), [..._path, key]); + if (value[key] case final value?) { + return JsonBool(_expect(key, value)); } return null; } @@ -279,8 +161,8 @@ final class JsonMap extends JsonValue with MapMixin { /// Returns the list associated with [key] or `null` if [key] is not in the /// map. JsonList? optionalList(String key) { - if (wrapped[key] case final value?) { - return JsonList._(_expect(key, value), [..._path, key]); + if (value[key] case final value?) { + return JsonList(_expect(key, value)); } return null; } @@ -294,14 +176,8 @@ final class JsonMap extends JsonValue with MapMixin { /// Returns the map associated with [key] or `null` if [key] is not in the /// map. JsonMap? optionalMap(String key) { - if (wrapped[key] case final value?) { - return JsonMap._( - switch (value) { - Map() => value, - _ => _expect>(key, value).cast(), - }, - [..._path, key], - ); + if (value[key] case final value?) { + return JsonMap(_expect(key, value)); } return null; } @@ -311,37 +187,4 @@ final class JsonMap extends JsonValue with MapMixin { JsonMap requiredMap(String key) { return _expect(key, optionalMap(key)); } - - @override - JsonValue? operator [](Object? key) => JsonValue.from(wrapped[key]); - - @override - void operator []=(String key, JsonValue? value) { - wrapped[key] = value?.wrapped; - } - - @override - void clear() => wrapped.clear(); - - @override - Iterable get keys => wrapped.keys; - - @override - JsonValue? remove(Object? key) => JsonValue.from(wrapped.remove(key)); - - @override - bool operator ==(Object other) => - identical(this, other) || - other is JsonMap && _deepEquals(wrapped, other.wrapped); - - @override - int get hashCode => _deepHash(wrapped); - - @override - String toString() => _jsonEncoder.convert(wrapped); } - -const _jsonEncoder = JsonEncoder.withIndent(' '); -bool _deepEquals(Object? a, Object? b) => - const DeepCollectionEquality().equals(a, b); -int _deepHash(Object? a) => const DeepCollectionEquality().hash(a); diff --git a/packages/celest_core/test/json_value_test.dart b/packages/celest_core/test/json_value_test.dart index 612ae7c2..5894a3c4 100644 --- a/packages/celest_core/test/json_value_test.dart +++ b/packages/celest_core/test/json_value_test.dart @@ -8,50 +8,46 @@ void main() { group('JsonValue', () { test('primitives', () { expect( - JsonValue.from('abc'), - isA().having((j) => j.wrapped, 'value', 'abc'), + JsonValue('abc'), + isA().having((j) => j.value, 'value', 'abc'), ); expect( - JsonValue.from(123), - isA().having((j) => j.wrapped, 'value', 123), + JsonValue(123), + isA().having((j) => j.value, 'value', 123), ); expect( - JsonValue.from(123.456), - isA().having((j) => j.wrapped, 'value', 123.456), + JsonValue(123.456), + isA().having((j) => j.value, 'value', 123.456), ); expect( - JsonValue.from(true), - isA().having((j) => j.wrapped, 'value', true), + JsonValue(true), + isA().having((j) => j.value, 'value', true), ); expect( - JsonValue.from(null), - isNull, - ); - expect( - JsonValue.from([1, 2, 3]), + JsonValue([1, 2, 3]), isA().having( - (j) => j.wrapped, + (j) => j.value, 'value', [1, 2, 3], ), ); expect( - JsonValue.from({'a': 1, 'b': 2, 'c': 3}), + JsonValue({'a': 1, 'b': 2, 'c': 3}), isA().having( - (j) => j.wrapped, + (j) => j.value, 'value', {'a': 1, 'b': 2, 'c': 3}, ), ); expect( - () => JsonValue.from(RegExp('unsupported')), + () => JsonValue(RegExp('unsupported')), throwsA(isA()), ); }); test('JsonList', () { expect( - JsonValue.from([1, 2, 3]), + JsonValue([1, 2, 3]), orderedEquals([ JsonInt(1), JsonInt(2), @@ -59,14 +55,12 @@ void main() { ]), ); expect( - JsonValue.from([ + JsonValue([ 'a', 1, 1.23, true, null, - [1, 2, 3], - {'a': 1}, ]), orderedEquals([ JsonString('a'), @@ -74,24 +68,20 @@ void main() { JsonDouble(1.23), JsonBool(true), null, - JsonList([1, 2, 3]), - JsonMap({ - 'a': 1, - }), ]), ); }); test('JsonMap', () { expect( - JsonValue.from({'a': 1, 'b': 2, 'c': 3}), + JsonValue({'a': 1, 'b': 2, 'c': 3}), equals({ 'a': JsonInt(1), 'b': JsonInt(2), 'c': JsonInt(3), }), ); - final allTypes = { + final allTypes = { 'a': 'a', 'b': 1, 'c': 1.23, @@ -100,7 +90,7 @@ void main() { 'f': [1, 2, 3], 'g': {'a': 1}, }; - final allTypesJson = JsonValue.from(allTypes) as JsonMap; + final allTypesJson = JsonValue(allTypes) as JsonMap; expect( allTypesJson, equals({ @@ -115,41 +105,27 @@ void main() { }), }), ); - expect( - allTypesJson.values, - orderedEquals([ - JsonString('a'), - JsonInt(1), - JsonDouble(1.23), - JsonBool(true), - null, - JsonList([1, 2, 3]), - JsonMap({ - 'a': 1, - }), - ]), - ); expect( allTypesJson.optionalString('a'), - isA().having((j) => j.wrapped, 'value', 'a'), + isA().having((j) => j.value, 'value', 'a'), ); expect( allTypesJson.optionalInt('b'), - isA().having((j) => j.wrapped, 'value', 1), + isA().having((j) => j.value, 'value', 1), ); expect( allTypesJson.optionalDouble('c'), - isA().having((j) => j.wrapped, 'value', 1.23), + isA().having((j) => j.value, 'value', 1.23), ); expect( allTypesJson.optionalBool('d'), - isA().having((j) => j.wrapped, 'value', true), + isA().having((j) => j.value, 'value', true), ); expect( allTypesJson.optionalList('f'), isA().having( - (j) => j.wrapped, + (j) => j.value, 'value', [1, 2, 3], ), @@ -157,7 +133,7 @@ void main() { expect( allTypesJson.optionalMap('g'), isA().having( - (j) => j.wrapped, + (j) => j.value, 'value', {'a': 1}, ), @@ -165,24 +141,24 @@ void main() { expect( allTypesJson.requiredString('a'), - isA().having((j) => j.wrapped, 'value', 'a'), + isA().having((j) => j.value, 'value', 'a'), ); expect( allTypesJson.requiredInt('b'), - isA().having((j) => j.wrapped, 'value', 1), + isA().having((j) => j.value, 'value', 1), ); expect( allTypesJson.requiredDouble('c'), - isA().having((j) => j.wrapped, 'value', 1.23), + isA().having((j) => j.value, 'value', 1.23), ); expect( allTypesJson.requiredBool('d'), - isA().having((j) => j.wrapped, 'value', true), + isA().having((j) => j.value, 'value', true), ); expect( allTypesJson.requiredList('f'), isA().having( - (j) => j.wrapped, + (j) => j.value, 'value', [1, 2, 3], ), @@ -190,7 +166,7 @@ void main() { expect( allTypesJson.requiredMap('g'), isA().having( - (j) => j.wrapped, + (j) => j.value, 'value', {'a': 1}, ), @@ -235,29 +211,14 @@ void main() { .requiredMap('aMap') .requiredMap('aNestedMap') .requiredString('aNestedKey'), - isA().having((j) => j.wrapped, 'value', 'abc'), + isA().having((j) => j.value, 'value', 'abc'), ); expect( nestedJson .requiredMap('aMap') .optionalMap('aNestedMap') ?.optionalString('aNestedKey'), - isA().having((j) => j.wrapped, 'value', 'abc'), - ); - expect( - () => nestedJson - .requiredMap('aMap') - .requiredMap('aNestedMap') - .requiredString('aNonExistentKey'), - throwsA( - isA().having( - (e) => e.message, - 'message', - contains('aMap.aNestedMap.aNonExistentKey'), - ), - ), - reason: 'Accessing a non-existent key on a nested map should throw ' - 'an exception with the path to the requested key.', + isA().having((j) => j.value, 'value', 'abc'), ); }); }); From 6a149097cea607675dbec9d4cd856c47cee1f25b Mon Sep 17 00:00:00 2001 From: Dillon Nys Date: Mon, 19 Feb 2024 15:05:13 -0800 Subject: [PATCH 05/13] chore(core): Export JsonValue --- packages/celest_core/lib/celest_core.dart | 1 + packages/celest_core/lib/src/serialization/serializer.dart | 1 + 2 files changed, 2 insertions(+) diff --git a/packages/celest_core/lib/celest_core.dart b/packages/celest_core/lib/celest_core.dart index a560902e..cb69a98f 100644 --- a/packages/celest_core/lib/celest_core.dart +++ b/packages/celest_core/lib/celest_core.dart @@ -7,4 +7,5 @@ export 'src/exception/cloud_exception.dart'; export 'src/exception/serialization_exception.dart'; /// Serialization +export 'src/serialization/json_value.dart'; export 'src/serialization/serializer.dart'; diff --git a/packages/celest_core/lib/src/serialization/serializer.dart b/packages/celest_core/lib/src/serialization/serializer.dart index edd6e239..558151da 100644 --- a/packages/celest_core/lib/src/serialization/serializer.dart +++ b/packages/celest_core/lib/src/serialization/serializer.dart @@ -137,6 +137,7 @@ final class _Serializers extends Serializers { } } +@immutable final class TypeToken { const TypeToken([this._typeName]); From d6b06376626b697df19366a0644d21c9856f9656 Mon Sep 17 00:00:00 2001 From: Dillon Nys Date: Mon, 19 Feb 2024 15:49:18 -0800 Subject: [PATCH 06/13] chore(core): JsonList/Map should implement raw type --- .../lib/src/serialization/json_value.dart | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/packages/celest_core/lib/src/serialization/json_value.dart b/packages/celest_core/lib/src/serialization/json_value.dart index 2445a685..ba0a84cb 100644 --- a/packages/celest_core/lib/src/serialization/json_value.dart +++ b/packages/celest_core/lib/src/serialization/json_value.dart @@ -87,17 +87,12 @@ extension type const JsonDouble(double value) implements JsonNum, double { extension type const JsonBool(bool value) implements JsonValue, bool {} /// A [JsonValue] which represents a [List]. -extension type const JsonList._(List value) - implements JsonValue, List { - const JsonList(List value) : this._(value as List); -} +extension type const JsonList(List value) + implements JsonValue, List {} /// A [JsonValue] which represents a [Map]. -extension type const JsonMap._(Map value) - implements JsonValue, Map { - const JsonMap(Map value) - : this._(value as Map); - +extension type const JsonMap(Map value) + implements JsonValue, Map { /// Returns the string associated with [key] or `null` if [key] is not in the /// map. JsonString? optionalString(String key) { From 23210be64fe1c215538d6b1319513773c4722a58 Mon Sep 17 00:00:00 2001 From: Dillon Nys Date: Mon, 19 Feb 2024 17:47:58 -0800 Subject: [PATCH 07/13] chore: Unexport Auth types --- packages/celest/example/celest/auth/auth.dart | 7 ------- packages/celest/lib/celest.dart | 4 ---- 2 files changed, 11 deletions(-) delete mode 100644 packages/celest/example/celest/auth/auth.dart diff --git a/packages/celest/example/celest/auth/auth.dart b/packages/celest/example/celest/auth/auth.dart deleted file mode 100644 index 17c15f32..00000000 --- a/packages/celest/example/celest/auth/auth.dart +++ /dev/null @@ -1,7 +0,0 @@ -import 'package:celest/celest.dart'; - -const auth = Auth( - providers: [ - AuthProvider.google(), - ], -); diff --git a/packages/celest/lib/celest.dart b/packages/celest/lib/celest.dart index 8723faba..d242310f 100644 --- a/packages/celest/lib/celest.dart +++ b/packages/celest/lib/celest.dart @@ -3,10 +3,6 @@ library celest; export 'package:celest_core/celest_core.dart'; -/// Auth -export 'src/auth/auth.dart'; -export 'src/auth/auth_provider.dart'; - /// Config export 'src/config/env.dart'; From 4eb672dedd06b845a1a89ee9deb9b12bc21c0128 Mon Sep 17 00:00:00 2001 From: Dillon Nys Date: Tue, 20 Feb 2024 14:40:51 -0800 Subject: [PATCH 08/13] Do not treat Celest types specially --- packages/celest/lib/src/runtime/serve.dart | 12 ----- .../exception/serialization_exception.dart | 5 +++ .../lib/src/serialization/serializer.dart | 3 -- .../cloud_exception_serializer.dart | 45 ------------------- 4 files changed, 5 insertions(+), 60 deletions(-) delete mode 100644 packages/celest_core/lib/src/serialization/serializers/cloud_exception_serializer.dart diff --git a/packages/celest/lib/src/runtime/serve.dart b/packages/celest/lib/src/runtime/serve.dart index 92dd1712..737bd89d 100644 --- a/packages/celest/lib/src/runtime/serve.dart +++ b/packages/celest/lib/src/runtime/serve.dart @@ -121,18 +121,6 @@ Handler _cloudExceptionMiddleware(Handler inner) { return (request) async { try { return await inner(request); - } on BadRequestException catch (e) { - print('Bad request: ${e.message}'); - return _badRequest( - code: 'BadRequestException', - details: Serializers.instance.serialize(e), - ); - } on InternalServerException catch (e) { - print('Internal server error: ${e.message}'); - return _internalServerError( - code: 'InternalServerException', - details: Serializers.instance.serialize(e), - ); } on Exception catch (e, st) { print('An unexpected error occurred: $e'); print(st); diff --git a/packages/celest_core/lib/src/exception/serialization_exception.dart b/packages/celest_core/lib/src/exception/serialization_exception.dart index ffb40203..9b69f5e0 100644 --- a/packages/celest_core/lib/src/exception/serialization_exception.dart +++ b/packages/celest_core/lib/src/exception/serialization_exception.dart @@ -13,6 +13,11 @@ final class SerializationException extends FormatException /// {@macro celest_core_exceptions_serialization_exception} const SerializationException(super.message); + @override + // TODO(dnys1): Find a better resolution to this. + // ignore: overridden_fields + final Null source = null; + @override String toString() => 'SerializationException: $message'; } diff --git a/packages/celest_core/lib/src/serialization/serializer.dart b/packages/celest_core/lib/src/serialization/serializer.dart index 558151da..d863fda6 100644 --- a/packages/celest_core/lib/src/serialization/serializer.dart +++ b/packages/celest_core/lib/src/serialization/serializer.dart @@ -3,7 +3,6 @@ import 'dart:typed_data'; import 'package:celest_core/celest_core.dart'; import 'package:celest_core/src/serialization/serializers/big_int_serializer.dart'; -import 'package:celest_core/src/serialization/serializers/cloud_exception_serializer.dart'; import 'package:celest_core/src/serialization/serializers/date_time_serializer.dart'; import 'package:celest_core/src/serialization/serializers/duration_serializer.dart'; import 'package:celest_core/src/serialization/serializers/regexp_serializer.dart'; @@ -101,8 +100,6 @@ final class _Serializers extends Serializers { put(const UriSerializer()); put(const UriDataSerializer()); put(const Uint8ListSerializer()); - put(const BadRequestExceptionSerializer()); - put(const InternalServerExceptionSerializer()); } final _serializersByType = , Serializer>{}; diff --git a/packages/celest_core/lib/src/serialization/serializers/cloud_exception_serializer.dart b/packages/celest_core/lib/src/serialization/serializers/cloud_exception_serializer.dart deleted file mode 100644 index bf410c89..00000000 --- a/packages/celest_core/lib/src/serialization/serializers/cloud_exception_serializer.dart +++ /dev/null @@ -1,45 +0,0 @@ -import 'package:celest_core/celest_core.dart'; - -/// A [Serializer] for [BadRequestException] objects. -final class BadRequestExceptionSerializer - extends Serializer { - /// Creates a [Serializer] for [BadRequestException] objects. - const BadRequestExceptionSerializer(); - - @override - BadRequestException deserialize(Object? value) { - final body = assertWireType>(value); - return BadRequestException( - (body[r'message'] as String), - ); - } - - @override - Map serialize(BadRequestException value) { - return { - 'message': value.message, - }; - } -} - -/// A [Serializer] for [InternalServerException] objects. -final class InternalServerExceptionSerializer - extends Serializer { - /// Creates a [Serializer] for [InternalServerException] objects. - const InternalServerExceptionSerializer(); - - @override - InternalServerException deserialize(Object? value) { - final body = assertWireType>(value); - return InternalServerException( - (body[r'message'] as String), - ); - } - - @override - Map serialize(InternalServerException value) { - return { - 'message': value.message, - }; - } -} From a9a00624590ecbb8ab493538875b6b73a1a09299 Mon Sep 17 00:00:00 2001 From: Dillon Nys Date: Wed, 21 Feb 2024 08:07:25 -0800 Subject: [PATCH 09/13] chore: Add inline serializer factory --- .../lib/src/serialization/serializer.dart | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/packages/celest_core/lib/src/serialization/serializer.dart b/packages/celest_core/lib/src/serialization/serializer.dart index d863fda6..b2f00735 100644 --- a/packages/celest_core/lib/src/serialization/serializer.dart +++ b/packages/celest_core/lib/src/serialization/serializer.dart @@ -20,6 +20,17 @@ abstract base class Serializer { /// {@macro celest_core.serialization.serializer} const Serializer(); + /// Defines a [Serializer] for a [Dart] type with the given [serialize] and + /// [deserialize] methods. + static Serializer define({ + required Object? Function(Dart value) serialize, + required Dart Function(Wire value) deserialize, + }) => + _Serializer( + serialize: serialize, + deserialize: deserialize, + ); + /// Serializes [value] to the wire type. Object? serialize(Dart value); @@ -43,6 +54,26 @@ abstract base class Serializer { } } +final class _Serializer + extends Serializer { + const _Serializer({ + required Object? Function(Dart value) serialize, + required Dart Function(Wire value) deserialize, + }) : _serialize = serialize, + _deserialize = deserialize; + + final Object? Function(Dart value) _serialize; + final Dart Function(Wire value) _deserialize; + + @override + Object? serialize(Dart value) => _serialize(value); + + @override + Dart deserialize(Object? value) => _deserialize( + assertWireType(value), + ); +} + typedef _Nullable = T?; bool _isNullable() => null is T; From ed24d3963bc2b46b2f7abe4262a6d3b3e3c75175 Mon Sep 17 00:00:00 2001 From: Dillon Nys Date: Wed, 21 Feb 2024 17:34:31 -0800 Subject: [PATCH 10/13] chore: Release dev version --- packages/celest/CHANGELOG.md | 2 ++ packages/celest/pubspec.yaml | 2 +- packages/celest_core/CHANGELOG.md | 7 +++++++ packages/celest_core/pubspec.yaml | 2 +- 4 files changed, 11 insertions(+), 2 deletions(-) diff --git a/packages/celest/CHANGELOG.md b/packages/celest/CHANGELOG.md index 95c71dbd..91aeb165 100644 --- a/packages/celest/CHANGELOG.md +++ b/packages/celest/CHANGELOG.md @@ -1,3 +1,5 @@ +## 0.2.0-dev.3 + ## 0.2.0-dev.2 - Fix route path in heartbeat monitor diff --git a/packages/celest/pubspec.yaml b/packages/celest/pubspec.yaml index 8a00d820..bd36c857 100644 --- a/packages/celest/pubspec.yaml +++ b/packages/celest/pubspec.yaml @@ -1,6 +1,6 @@ name: celest description: The Flutter cloud platform. Celest enables you to build your entire backend in Dart. -version: 0.2.0-dev.2 +version: 0.2.0-dev.3 homepage: https://celest.dev repository: https://github.com/celest-dev/celest/tree/main/packages/celest diff --git a/packages/celest_core/CHANGELOG.md b/packages/celest_core/CHANGELOG.md index a6e852d8..df09163d 100644 --- a/packages/celest_core/CHANGELOG.md +++ b/packages/celest_core/CHANGELOG.md @@ -1,3 +1,10 @@ +## 0.2.0-dev.1 + +- Bumps minimum Dart SDK to 3.3 +- Adds `JsonValue` hierarchy for representing JSON primitives safely +- Adds `Serializer.define` for creating serializers from functions +- Adds `TypeToken` to enable serialization of extension types + ## 0.2.0-dev - Make `SerializationException` implement `BadRequestException` diff --git a/packages/celest_core/pubspec.yaml b/packages/celest_core/pubspec.yaml index 1f4c67e5..cbd29eba 100644 --- a/packages/celest_core/pubspec.yaml +++ b/packages/celest_core/pubspec.yaml @@ -1,6 +1,6 @@ name: celest_core description: Celest types and utilities shared between the client and the cloud. -version: 0.2.0-dev +version: 0.2.0-dev.1 homepage: https://celest.dev repository: https://github.com/celest-dev/celest/tree/main/packages/celest_core From 751823ecf3d932241afcb907e0351a935d455eae Mon Sep 17 00:00:00 2001 From: Dillon Nys Date: Wed, 28 Feb 2024 19:53:36 -0800 Subject: [PATCH 11/13] chore: Bump version to 0.2.0 --- packages/celest/CHANGELOG.md | 23 ++++++++++++++--------- packages/celest/pubspec.yaml | 4 ++-- packages/celest_core/CHANGELOG.md | 5 +---- packages/celest_core/pubspec.yaml | 2 +- 4 files changed, 18 insertions(+), 16 deletions(-) diff --git a/packages/celest/CHANGELOG.md b/packages/celest/CHANGELOG.md index 91aeb165..e8d5297a 100644 --- a/packages/celest/CHANGELOG.md +++ b/packages/celest/CHANGELOG.md @@ -1,12 +1,17 @@ -## 0.2.0-dev.3 - -## 0.2.0-dev.2 - -- Fix route path in heartbeat monitor - -## 0.2.0-dev.1 - -## 0.2.0-dev +## 0.2.0 + +- Bumps minimum Dart SDK to 3.3 +- Adds `JsonValue` hierarchy for representing JSON primitives safely +- Model/exception types from third-party packages no longer need to be exported from `models.dart`/`exceptions.dart`. Only types you've defined in your backend. +- Adds support for `lib/models/` and `lib/exceptions/` folders for better organization of custom types + +### Fixes +- fix: Celest crashing when editing files [#25](https://github.com/celest-dev/celest/issues/25) +- fix: Allow Object and Object?/dynamic is models, functions and exceptions [#35](https://github.com/celest-dev/celest/issues/35) +- fix: Incompatibility of custom toJson/fromJson with other non-Celest code [#38](https://github.com/celest-dev/celest/issues/38) +- fix: Allow the code to specify/check if Celest is running locally or in the cloud (and where in the cloud). [#43](https://github.com/celest-dev/celest/issues/43) +- fix: Bug: Custom exception not thrown [#48](https://github.com/celest-dev/celest/issues/48) +- fix: DRY up exception handling in generated client [#49](https://github.com/celest-dev/celest/issues/49) ## 0.1.1 diff --git a/packages/celest/pubspec.yaml b/packages/celest/pubspec.yaml index bd36c857..b0079c37 100644 --- a/packages/celest/pubspec.yaml +++ b/packages/celest/pubspec.yaml @@ -1,6 +1,6 @@ name: celest description: The Flutter cloud platform. Celest enables you to build your entire backend in Dart. -version: 0.2.0-dev.3 +version: 0.2.0 homepage: https://celest.dev repository: https://github.com/celest-dev/celest/tree/main/packages/celest @@ -9,7 +9,7 @@ environment: dependencies: async: ^2.11.0 - celest_core: ^0.2.0-0 + celest_core: ^0.2.0 chunked_stream: ^1.4.2 meta: ^1.9.0 shelf: ^1.4.1 diff --git a/packages/celest_core/CHANGELOG.md b/packages/celest_core/CHANGELOG.md index df09163d..b737a7f4 100644 --- a/packages/celest_core/CHANGELOG.md +++ b/packages/celest_core/CHANGELOG.md @@ -1,12 +1,9 @@ -## 0.2.0-dev.1 +## 0.2.0 - Bumps minimum Dart SDK to 3.3 - Adds `JsonValue` hierarchy for representing JSON primitives safely - Adds `Serializer.define` for creating serializers from functions - Adds `TypeToken` to enable serialization of extension types - -## 0.2.0-dev - - Make `SerializationException` implement `BadRequestException` ## 0.1.1 diff --git a/packages/celest_core/pubspec.yaml b/packages/celest_core/pubspec.yaml index cbd29eba..dd0b7f11 100644 --- a/packages/celest_core/pubspec.yaml +++ b/packages/celest_core/pubspec.yaml @@ -1,6 +1,6 @@ name: celest_core description: Celest types and utilities shared between the client and the cloud. -version: 0.2.0-dev.1 +version: 0.2.0 homepage: https://celest.dev repository: https://github.com/celest-dev/celest/tree/main/packages/celest_core From 3aa12c24cf85709acb1791bdf35272f6e39cb414 Mon Sep 17 00:00:00 2001 From: Dillon Nys Date: Wed, 28 Feb 2024 20:38:37 -0800 Subject: [PATCH 12/13] chore(repo): Update examples for 0.2.0 --- examples/gemini/celest/functions/gemini.dart | 55 ++++- examples/gemini/celest/lib/client.dart | 53 ++++- examples/gemini/celest/lib/exceptions.dart | 2 - examples/gemini/celest/lib/models.dart | 13 -- .../celest/lib/src/client/functions.dart | 95 +++++---- .../celest/lib/src/client/serializers.dart | 90 +++++++-- examples/gemini/celest/pubspec.lock | 26 ++- examples/gemini/celest/pubspec.yaml | 6 +- examples/gemini/celest/resources.dart | 6 +- examples/gemini/lib/main.dart | 71 +------ examples/gemini/pubspec.lock | 36 ++-- examples/gemini/pubspec.yaml | 2 +- examples/openai/celest/functions/open_ai.dart | 4 +- examples/openai/celest/lib/client.dart | 53 ++++- .../celest/lib/src/client/functions.dart | 79 ++++---- .../celest/lib/src/client/serializers.dart | 49 +++-- examples/openai/celest/pubspec.lock | 38 ++-- examples/openai/celest/pubspec.yaml | 4 +- examples/openai/celest/resources.dart | 6 +- .../ios/Runner.xcodeproj/project.pbxproj | 2 +- .../xcshareddata/xcschemes/Runner.xcscheme | 2 +- examples/openai/lib/main.dart | 3 +- .../macos/Runner.xcodeproj/project.pbxproj | 2 +- .../xcshareddata/xcschemes/Runner.xcscheme | 2 +- examples/openai/pubspec.lock | 70 +++++-- examples/todo/celest/lib/client.dart | 55 +++-- .../todo/celest/lib/src/client/functions.dart | 191 +++++++----------- .../celest/lib/src/client/serializers.dart | 90 ++++----- examples/todo/celest/pubspec.lock | 34 ++-- examples/todo/celest/pubspec.yaml | 4 +- examples/todo/celest/resources.dart | 4 +- .../todo/ios/Flutter/AppFrameworkInfo.plist | 2 +- .../todo/ios/Runner.xcodeproj/project.pbxproj | 8 +- .../xcshareddata/xcschemes/Runner.xcscheme | 2 +- examples/todo/pubspec.lock | 18 +- .../example/celest/functions/greeting.dart | 17 +- .../celest/example/celest/lib/client.dart | 53 ++++- .../celest/example/celest/lib/exceptions.dart | 2 - .../lib/exceptions/bad_name_exception.dart | 11 + .../celest/example/celest/lib/models.dart | 2 - .../example/celest/lib/models/person.dart | 8 + .../celest/lib/src/client/functions.dart | 50 +++-- .../celest/lib/src/client/serializers.dart | 25 +++ packages/celest/example/celest/project.dart | 2 +- packages/celest/example/celest/pubspec.yaml | 6 +- packages/celest/example/celest/resources.dart | 4 +- .../celest/test/functions/greeting_test.dart | 10 +- .../ios/Runner.xcodeproj/project.pbxproj | 2 +- .../xcshareddata/xcschemes/Runner.xcscheme | 2 +- packages/celest/example/lib/main.dart | 5 +- .../macos/Runner.xcodeproj/project.pbxproj | 2 +- .../xcshareddata/xcschemes/Runner.xcscheme | 2 +- 52 files changed, 789 insertions(+), 591 deletions(-) delete mode 100644 examples/gemini/celest/lib/exceptions.dart delete mode 100644 examples/gemini/celest/lib/models.dart delete mode 100644 packages/celest/example/celest/lib/exceptions.dart create mode 100644 packages/celest/example/celest/lib/exceptions/bad_name_exception.dart delete mode 100644 packages/celest/example/celest/lib/models.dart create mode 100644 packages/celest/example/celest/lib/models/person.dart create mode 100644 packages/celest/example/celest/lib/src/client/serializers.dart diff --git a/examples/gemini/celest/functions/gemini.dart b/examples/gemini/celest/functions/gemini.dart index 615e2971..f2892a3c 100644 --- a/examples/gemini/celest/functions/gemini.dart +++ b/examples/gemini/celest/functions/gemini.dart @@ -1,8 +1,9 @@ // Cloud functions are top-level Dart functions defined in the `functions/` // folder of your Celest project. +import 'dart:convert'; + import 'package:celest/celest.dart'; -import 'package:celest_backend/models.dart'; import 'package:google_generative_ai/google_generative_ai.dart'; import '../resources.dart'; @@ -24,8 +25,7 @@ const _availableModels = [ Future generateContent({ required String modelName, required String prompt, - ModelParameters parameters = const ModelParameters(), - @env.geminiApiKey required String apiKey, + @Env.geminiApiKey required String apiKey, }) async { if (!_availableModels.contains(modelName)) { throw BadRequestException('Invalid model: $modelName'); @@ -35,19 +35,52 @@ Future generateContent({ final request = [ Content.text(prompt), ]; - final response = await model.generateContent( - request, - generationConfig: GenerationConfig( - maxOutputTokens: parameters.maxTokens, - temperature: parameters.temperature, - ), - ); + print('Sending prompt: $prompt'); + + final response = await model.generateContent(request); + print('Got response: ${_prettyJson(response.toJson())}'); switch (response.text) { case final text?: - print('Gemini response: $text'); + print('Selected answer: $text'); return text; case _: throw InternalServerException('Failed to generate content'); } } + +extension on GenerateContentResponse { + Map toJson() => { + 'promptFeedback': { + 'blockReason': promptFeedback?.blockReason?.name, + 'blockReasonMessage': promptFeedback?.blockReasonMessage, + 'safetyRatings': [ + for (final rating + in promptFeedback?.safetyRatings ?? []) + { + 'category': rating.category.name, + 'probability': rating.probability.name, + }, + ], + }, + 'candidates': [ + for (final candidate in candidates) + { + 'content': candidate.content.toJson(), + 'safetyRatings': [ + for (final rating + in candidate.safetyRatings ?? []) + { + 'category': rating.category.name, + 'probability': rating.probability.name, + }, + ], + 'finishReason': candidate.finishReason?.name, + 'finishMessage': candidate.finishMessage, + }, + ], + }; +} + +String _prettyJson(Object? json) => + const JsonEncoder.withIndent(' ').convert(json); diff --git a/examples/gemini/celest/lib/client.dart b/examples/gemini/celest/lib/client.dart index f1d42e40..546780c3 100644 --- a/examples/gemini/celest/lib/client.dart +++ b/examples/gemini/celest/lib/client.dart @@ -2,29 +2,60 @@ // it can be checked into version control. // ignore_for_file: type=lint, unused_local_variable, unnecessary_cast, unnecessary_import -library; +library; // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'dart:io'; +import 'dart:io' as _$io; -import 'package:celest/celest.dart'; import 'package:celest_core/src/util/globals.dart'; -import 'package:http/http.dart' as http; +import 'package:http/http.dart' as _$http; import 'src/client/functions.dart'; import 'src/client/serializers.dart'; final Celest celest = Celest(); +enum CelestEnvironment { + local; + + Uri get baseUri => switch (this) { + local => kIsWeb || !_$io.Platform.isAndroid + ? Uri.parse('http://localhost:7781') + : Uri.parse('http://10.0.2.2:7781'), + }; +} + class Celest { - late http.Client httpClient = http.Client(); + var _initialized = false; + + late CelestEnvironment _currentEnvironment; + + late _$http.Client httpClient = _$http.Client(); + + late Uri _baseUri; + + final _functions = CelestFunctions(); + + T _checkInitialized(T Function() value) { + if (!_initialized) { + throw StateError( + 'Celest has not been initialized. Make sure to call `celest.init()` at the start of your `main` method.'); + } + return value(); + } + + CelestEnvironment get currentEnvironment => + _checkInitialized(() => _currentEnvironment); - late final Uri baseUri = kIsWeb || !Platform.isAndroid - ? Uri.parse('http://localhost:7777') - : Uri.parse('http://10.0.2.2:7777'); + Uri get baseUri => _checkInitialized(() => _baseUri); - final functions = CelestFunctions(); + CelestFunctions get functions => _checkInitialized(() => _functions); - void init() { - Serializers.instance.put(const ModelParametersSerializer()); + void init({CelestEnvironment environment = CelestEnvironment.local}) { + _currentEnvironment = environment; + _baseUri = environment.baseUri; + if (!_initialized) { + initSerializers(); + } + _initialized = true; } } diff --git a/examples/gemini/celest/lib/exceptions.dart b/examples/gemini/celest/lib/exceptions.dart deleted file mode 100644 index 1c7d9dde..00000000 --- a/examples/gemini/celest/lib/exceptions.dart +++ /dev/null @@ -1,2 +0,0 @@ -// By convention, any custom exception types thrown by an API must be defined -// in this file or exported from this file. diff --git a/examples/gemini/celest/lib/models.dart b/examples/gemini/celest/lib/models.dart deleted file mode 100644 index 8bd76650..00000000 --- a/examples/gemini/celest/lib/models.dart +++ /dev/null @@ -1,13 +0,0 @@ -// By convention, any custom types used within an API request/response must be -// defined in this file or exported from this file. - -final class ModelParameters { - const ModelParameters({ - double? temperature, - int? maxTokens, - }) : maxTokens = maxTokens ?? 100, - temperature = temperature ?? 1.0; - - final double temperature; - final int maxTokens; -} diff --git a/examples/gemini/celest/lib/src/client/functions.dart b/examples/gemini/celest/lib/src/client/functions.dart index 8eaa3a5e..2a117d0d 100644 --- a/examples/gemini/celest/lib/src/client/functions.dart +++ b/examples/gemini/celest/lib/src/client/functions.dart @@ -2,13 +2,15 @@ // it can be checked into version control. // ignore_for_file: type=lint, unused_local_variable, unnecessary_cast, unnecessary_import -library; +library; // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'dart:convert'; +import 'dart:convert' as _$convert; import 'package:celest/celest.dart'; -import 'package:celest_backend/models.dart'; import 'package:celest_core/src/exception/cloud_exception.dart'; +import 'package:celest_core/src/exception/serialization_exception.dart'; +import 'package:google_generative_ai/src/error.dart' as _$error; +import 'package:http/src/exception.dart' as _$exception; import '../../client.dart'; @@ -17,18 +19,10 @@ class CelestFunctions { } class CelestFunctionsGemini { - /// Returns a list of available models. - Future> availableModels() async { - final $response = await celest.httpClient.post( - celest.baseUri.resolve('/gemini/available-models'), - headers: const {'Content-Type': 'application/json; charset=utf-8'}, - ); - final $body = (jsonDecode($response.body) as Map); - if ($response.statusCode == 200) { - return ($body['response'] as Iterable) - .map((el) => (el as String)) - .toList(); - } + Never _throwError({ + required int $statusCode, + required Map $body, + }) { final $error = ($body['error'] as Map); final $code = ($error['code'] as String); final $details = ($error['details'] as Map?); @@ -38,8 +32,25 @@ class CelestFunctionsGemini { case r'InternalServerException': throw Serializers.instance .deserialize($details); + case r'SerializationException': + throw Serializers.instance + .deserialize($details); + case r'GenerativeAIException': + throw Serializers.instance + .deserialize<_$error.GenerativeAIException>($details); + case r'InvalidApiKey': + throw Serializers.instance.deserialize<_$error.InvalidApiKey>($details); + case r'UnsupportedUserLocation': + throw Serializers.instance + .deserialize<_$error.UnsupportedUserLocation>($details); + case r'ServerException': + throw Serializers.instance + .deserialize<_$error.ServerException>($details); + case r'ClientException': + throw Serializers.instance + .deserialize<_$exception.ClientException>($details); case _: - switch ($response.statusCode) { + switch ($statusCode) { case 400: throw BadRequestException($code); case _: @@ -48,44 +59,48 @@ class CelestFunctionsGemini { } } + /// Returns a list of available models. + Future> availableModels() async { + final $response = await celest.httpClient.post( + celest.baseUri.resolve('/gemini/available-models'), + headers: const {'Content-Type': 'application/json; charset=utf-8'}, + ); + final $body = + (_$convert.jsonDecode($response.body) as Map); + if ($response.statusCode != 200) { + _throwError( + $statusCode: $response.statusCode, + $body: $body, + ); + } + return ($body['response'] as Iterable) + .map((el) => (el as String)) + .toList(); + } + /// Prompts the Gemini [modelName] with the given [prompt] and [parameters]. /// /// Returns the generated text. Future generateContent({ required String modelName, required String prompt, - ModelParameters parameters = const ModelParameters(), }) async { final $response = await celest.httpClient.post( celest.baseUri.resolve('/gemini/generate-content'), headers: const {'Content-Type': 'application/json; charset=utf-8'}, - body: jsonEncode({ + body: _$convert.jsonEncode({ r'modelName': modelName, r'prompt': prompt, - r'parameters': - Serializers.instance.serialize(parameters), }), ); - final $body = (jsonDecode($response.body) as Map); - if ($response.statusCode == 200) { - return ($body['response'] as String); - } - final $error = ($body['error'] as Map); - final $code = ($error['code'] as String); - final $details = ($error['details'] as Map?); - switch ($code) { - case r'BadRequestException': - throw Serializers.instance.deserialize($details); - case r'InternalServerException': - throw Serializers.instance - .deserialize($details); - case _: - switch ($response.statusCode) { - case 400: - throw BadRequestException($code); - case _: - throw InternalServerException($code); - } + final $body = + (_$convert.jsonDecode($response.body) as Map); + if ($response.statusCode != 200) { + _throwError( + $statusCode: $response.statusCode, + $body: $body, + ); } + return ($body['response'] as String); } } diff --git a/examples/gemini/celest/lib/src/client/serializers.dart b/examples/gemini/celest/lib/src/client/serializers.dart index 958fd6f0..bc4b892d 100644 --- a/examples/gemini/celest/lib/src/client/serializers.dart +++ b/examples/gemini/celest/lib/src/client/serializers.dart @@ -1,23 +1,77 @@ // ignore_for_file: type=lint, unused_local_variable, unnecessary_cast, unnecessary_import +// ignore_for_file: no_leading_underscores_for_library_prefixes import 'package:celest/celest.dart'; -import 'package:celest_backend/models.dart'; +import 'package:celest_core/src/exception/cloud_exception.dart'; +import 'package:celest_core/src/exception/serialization_exception.dart'; +import 'package:google_generative_ai/src/error.dart' as _$error; +import 'package:http/src/exception.dart' as _$exception; -final class ModelParametersSerializer extends Serializer { - const ModelParametersSerializer(); - - @override - ModelParameters deserialize(Object? value) { - final serialized = assertWireType?>(value); - return ModelParameters( - temperature: ((serialized?[r'temperature'] as num?)?.toDouble()) ?? null, - maxTokens: ((serialized?[r'maxTokens'] as num?)?.toInt()) ?? null, - ); - } - - @override - Map serialize(ModelParameters value) => { - r'temperature': value.temperature, - r'maxTokens': value.maxTokens, - }; +void initSerializers() { + Serializers.instance + .put(Serializer.define>( + serialize: ($value) => {r'message': $value.message}, + deserialize: ($serialized) { + return BadRequestException(($serialized[r'message'] as String)); + }, + )); + Serializers.instance + .put(Serializer.define>( + serialize: ($value) => {r'message': $value.message}, + deserialize: ($serialized) { + return InternalServerException(($serialized[r'message'] as String)); + }, + )); + Serializers.instance + .put(Serializer.define>( + serialize: ($value) => { + r'message': $value.message, + r'offset': $value.offset, + r'source': $value.source, + }, + deserialize: ($serialized) { + return SerializationException(($serialized[r'message'] as String)); + }, + )); + Serializers.instance.put( + Serializer.define<_$error.GenerativeAIException, Map>( + serialize: ($value) => {r'message': $value.message}, + deserialize: ($serialized) { + return _$error.GenerativeAIException(($serialized[r'message'] as String)); + }, + )); + Serializers.instance + .put(Serializer.define<_$error.InvalidApiKey, Map>( + serialize: ($value) => {r'message': $value.message}, + deserialize: ($serialized) { + return _$error.InvalidApiKey(($serialized[r'message'] as String)); + }, + )); + Serializers.instance + .put(Serializer.define<_$error.ServerException, Map>( + serialize: ($value) => {r'message': $value.message}, + deserialize: ($serialized) { + return _$error.ServerException(($serialized[r'message'] as String)); + }, + )); + Serializers.instance.put( + Serializer.define<_$error.UnsupportedUserLocation, Map?>( + serialize: ($value) => {}, + deserialize: ($serialized) { + return _$error.UnsupportedUserLocation(); + }, + )); + Serializers.instance + .put(Serializer.define<_$exception.ClientException, Map>( + serialize: ($value) => { + r'message': $value.message, + r'uri': Serializers.instance.serialize($value.uri), + }, + deserialize: ($serialized) { + return _$exception.ClientException( + ($serialized[r'message'] as String), + Serializers.instance.deserialize($serialized[r'uri']), + ); + }, + )); } diff --git a/examples/gemini/celest/pubspec.lock b/examples/gemini/celest/pubspec.lock index e7427849..fb2f5741 100644 --- a/examples/gemini/celest/pubspec.lock +++ b/examples/gemini/celest/pubspec.lock @@ -44,19 +44,17 @@ packages: celest: dependency: "direct main" description: - name: celest - sha256: fa5f2cf62073d2e71a2ce4c559d703886c2a48062b08752040d3afcc4531b846 - url: "https://pub.dev" - source: hosted - version: "0.1.1" + path: "../../../packages/celest" + relative: true + source: path + version: "0.2.0" celest_core: dependency: "direct main" description: - name: celest_core - sha256: c5d6408d047816f2b81c63b75c8f270377768612ee47272a5782ed48af887e55 - url: "https://pub.dev" - source: hosted - version: "0.1.1" + path: "../../../packages/celest_core" + relative: true + source: path + version: "0.2.0" chunked_stream: dependency: transitive description: @@ -125,10 +123,10 @@ packages: dependency: "direct main" description: name: google_generative_ai - sha256: "2c8cb557c196487c5624da02a0914935e48874112a8201cb7b9379c79cd3bde7" + sha256: b2d3f7277a85e3e6be4c4392c59e73ea211b5b6c8bb21c24c71fd411a2d1822e url: "https://pub.dev" source: hosted - version: "0.2.0" + version: "0.2.2" http: dependency: "direct main" description: @@ -389,10 +387,10 @@ packages: dependency: transitive description: name: vm_service - sha256: a2662fb1f114f4296cf3f5a50786a2d888268d7776cf681aa17d660ffa23b246 + sha256: e7d5ecd604e499358c5fe35ee828c0298a320d54455e791e9dcf73486bc8d9f0 url: "https://pub.dev" source: hosted - version: "14.0.0" + version: "14.1.0" watcher: dependency: transitive description: diff --git a/examples/gemini/celest/pubspec.yaml b/examples/gemini/celest/pubspec.yaml index b50e4886..c01c72d4 100644 --- a/examples/gemini/celest/pubspec.yaml +++ b/examples/gemini/celest/pubspec.yaml @@ -3,11 +3,11 @@ description: The Celest backend for celest_project. publish_to: none environment: - sdk: ^3.2.0 + sdk: ^3.3.0 dependencies: - celest: ^0.1.0 - celest_core: ^0.1.0 + celest: ^0.2.0 + celest_core: ^0.2.0 google_generative_ai: ^0.2.0 http: ">=0.13.0 <2.0.0" diff --git a/examples/gemini/celest/resources.dart b/examples/gemini/celest/resources.dart index 7a2357d1..90db0e06 100644 --- a/examples/gemini/celest/resources.dart +++ b/examples/gemini/celest/resources.dart @@ -6,11 +6,11 @@ library; import 'package:celest/celest.dart'; -abstract final class apis { +abstract final class Apis { static const gemini = CloudApi(name: r'gemini'); } -abstract final class functions { +abstract final class Functions { static const geminiAvailableModels = CloudFunction( api: r'gemini', functionName: r'availableModels', @@ -22,6 +22,6 @@ abstract final class functions { ); } -abstract final class env { +abstract final class Env { static const geminiApiKey = EnvironmentVariable(name: r'GEMINI_API_KEY'); } diff --git a/examples/gemini/lib/main.dart b/examples/gemini/lib/main.dart index ee1a273e..6985f87c 100644 --- a/examples/gemini/lib/main.dart +++ b/examples/gemini/lib/main.dart @@ -2,9 +2,7 @@ import 'dart:developer'; // Import the generated Celest client import 'package:celest_backend/client.dart'; -import 'package:celest_backend/models.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; void main() { // Initializes Celest in your Flutter app @@ -23,7 +21,6 @@ class _GeminiAppState extends State { // Controllers for the text fields. final _questionController = TextEditingController(); final _answerController = TextEditingController(); - final _maxTokensController = TextEditingController(); /// The selected model to use. /// @@ -31,9 +28,6 @@ class _GeminiAppState extends State { /// are loaded from the backend. late String _selectedModel; - /// The value of the temperature slider. - var _temperatureSliderValue = 1.0; - /// Whether [_availableModelsFuture] has completed and the available models /// have been loaded fromthe backend. var _loadedAvailableModels = false; @@ -46,12 +40,6 @@ class _GeminiAppState extends State { final response = await celest.functions.gemini.generateContent( prompt: _questionController.text, modelName: _selectedModel, - parameters: ModelParameters( - maxTokens: _maxTokensController.text.isNotEmpty - ? int.parse(_maxTokensController.text) - : null, - temperature: _temperatureSliderValue, - ), ); log(response); setState(() { @@ -60,7 +48,7 @@ class _GeminiAppState extends State { } on Exception catch (e) { log('Failed to generate content', error: e); setState(() { - _answerController.text = '${e.runtimeType}: $e'; + _answerController.text = '$e'; }); } } @@ -127,67 +115,12 @@ class _GeminiAppState extends State { _availableModelsDropdown(data), // Handle the error case - AsyncSnapshot(:final error?) => - Text('${error.runtimeType}: $error'), + AsyncSnapshot(:final error?) => Text('$error'), // If waiting, show a progress indicator _ => const CircularProgressIndicator(), }, ), - SizedBox( - width: double.infinity, - child: Wrap( - alignment: WrapAlignment.spaceEvenly, - children: [ - Tooltip( - message: - 'The maximum number of words for the AI to generate', - child: SizedBox( - width: 150, - child: TextField( - keyboardType: TextInputType.number, - // Allow only digits - inputFormatters: [ - FilteringTextInputFormatter.digitsOnly, - ], - decoration: const InputDecoration( - hintText: 'Max tokens', - ), - controller: _maxTokensController, - ), - ), - ), - Tooltip( - message: - 'The higher the temperature, the more random the text', - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Text( - 'Temperature', - style: Theme.of(context).textTheme.bodyMedium, - ), - SizedBox( - width: 150, - child: Slider( - value: _temperatureSliderValue, - min: 0, - max: 2, - divisions: 10, - label: _temperatureSliderValue.toString(), - onChanged: (double value) { - setState(() { - _temperatureSliderValue = value; - }); - }, - ), - ), - ], - ), - ), - ], - ), - ), const SizedBox(height: 20), TextField( maxLines: 10, diff --git a/examples/gemini/pubspec.lock b/examples/gemini/pubspec.lock index e152749f..7285476d 100644 --- a/examples/gemini/pubspec.lock +++ b/examples/gemini/pubspec.lock @@ -18,13 +18,12 @@ packages: source: hosted version: "2.1.1" celest: - dependency: transitive + dependency: "direct overridden" description: - name: celest - sha256: fa5f2cf62073d2e71a2ce4c559d703886c2a48062b08752040d3afcc4531b846 - url: "https://pub.dev" - source: hosted - version: "0.1.1" + path: "../../packages/celest" + relative: true + source: path + version: "0.2.0" celest_backend: dependency: "direct main" description: @@ -33,13 +32,12 @@ packages: source: path version: "0.0.0" celest_core: - dependency: transitive + dependency: "direct overridden" description: - name: celest_core - sha256: c5d6408d047816f2b81c63b75c8f270377768612ee47272a5782ed48af887e55 - url: "https://pub.dev" - source: hosted - version: "0.1.1" + path: "../../packages/celest_core" + relative: true + source: path + version: "0.2.0" characters: dependency: transitive description: @@ -110,18 +108,18 @@ packages: dependency: transitive description: name: google_generative_ai - sha256: "2c8cb557c196487c5624da02a0914935e48874112a8201cb7b9379c79cd3bde7" + sha256: b2d3f7277a85e3e6be4c4392c59e73ea211b5b6c8bb21c24c71fd411a2d1822e url: "https://pub.dev" source: hosted - version: "0.2.0" + version: "0.2.2" http: dependency: transitive description: name: http - sha256: a2bbf9d017fcced29139daa8ed2bba4ece450ab222871df93ca9eec6f80c34ba + sha256: "761a297c042deedc1ffbb156d6e2af13886bb305c2a343a4d972504cd67dd938" url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.2.1" http_methods: dependency: transitive description: @@ -299,9 +297,9 @@ packages: dependency: transitive description: name: web - sha256: "4188706108906f002b3a293509234588823c8c979dc83304e229ff400c996b05" + sha256: "1d9158c616048c38f712a6646e317a3426da10e884447626167240d45209cbad" url: "https://pub.dev" source: hosted - version: "0.4.2" + version: "0.5.0" sdks: - dart: ">=3.3.0-279.3.beta <4.0.0" + dart: ">=3.3.0 <4.0.0" diff --git a/examples/gemini/pubspec.yaml b/examples/gemini/pubspec.yaml index 0a9cb2b1..5aec0aa4 100644 --- a/examples/gemini/pubspec.yaml +++ b/examples/gemini/pubspec.yaml @@ -19,7 +19,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev version: 1.0.0+1 environment: - sdk: '>=3.3.0-279.3.beta <4.0.0' + sdk: ^3.3.0 # Dependencies specify other packages that your package needs in order to work. # To automatically upgrade your package dependencies to the latest versions diff --git a/examples/openai/celest/functions/open_ai.dart b/examples/openai/celest/functions/open_ai.dart index bc725313..c69d6bee 100644 --- a/examples/openai/celest/functions/open_ai.dart +++ b/examples/openai/celest/functions/open_ai.dart @@ -26,7 +26,6 @@ Future> availableModels() async => _availableModels; /// client or Flutter app. const _availableModels = [ 'gpt-4', - 'gpt-4-turbo-preview', 'gpt-3.5-turbo', ]; @@ -37,7 +36,7 @@ Future openAIRequest({ required String model, required String prompt, ModelParameters parameters = const ModelParameters(), - @env.openAiToken required String openAiToken, + @Env.openAiToken required String openAiToken, }) async { final openAI = _createOpenAI(openAiToken); @@ -49,6 +48,7 @@ Future openAIRequest({ model: ChatModelFromValue(model: model), maxToken: parameters.maxTokens, temperature: parameters.temperature, + topP: null, ); final requestJson = _prettyJson(request.toJson()); diff --git a/examples/openai/celest/lib/client.dart b/examples/openai/celest/lib/client.dart index f1d42e40..546780c3 100644 --- a/examples/openai/celest/lib/client.dart +++ b/examples/openai/celest/lib/client.dart @@ -2,29 +2,60 @@ // it can be checked into version control. // ignore_for_file: type=lint, unused_local_variable, unnecessary_cast, unnecessary_import -library; +library; // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'dart:io'; +import 'dart:io' as _$io; -import 'package:celest/celest.dart'; import 'package:celest_core/src/util/globals.dart'; -import 'package:http/http.dart' as http; +import 'package:http/http.dart' as _$http; import 'src/client/functions.dart'; import 'src/client/serializers.dart'; final Celest celest = Celest(); +enum CelestEnvironment { + local; + + Uri get baseUri => switch (this) { + local => kIsWeb || !_$io.Platform.isAndroid + ? Uri.parse('http://localhost:7781') + : Uri.parse('http://10.0.2.2:7781'), + }; +} + class Celest { - late http.Client httpClient = http.Client(); + var _initialized = false; + + late CelestEnvironment _currentEnvironment; + + late _$http.Client httpClient = _$http.Client(); + + late Uri _baseUri; + + final _functions = CelestFunctions(); + + T _checkInitialized(T Function() value) { + if (!_initialized) { + throw StateError( + 'Celest has not been initialized. Make sure to call `celest.init()` at the start of your `main` method.'); + } + return value(); + } + + CelestEnvironment get currentEnvironment => + _checkInitialized(() => _currentEnvironment); - late final Uri baseUri = kIsWeb || !Platform.isAndroid - ? Uri.parse('http://localhost:7777') - : Uri.parse('http://10.0.2.2:7777'); + Uri get baseUri => _checkInitialized(() => _baseUri); - final functions = CelestFunctions(); + CelestFunctions get functions => _checkInitialized(() => _functions); - void init() { - Serializers.instance.put(const ModelParametersSerializer()); + void init({CelestEnvironment environment = CelestEnvironment.local}) { + _currentEnvironment = environment; + _baseUri = environment.baseUri; + if (!_initialized) { + initSerializers(); + } + _initialized = true; } } diff --git a/examples/openai/celest/lib/src/client/functions.dart b/examples/openai/celest/lib/src/client/functions.dart index a8d5b9a9..bf2f5649 100644 --- a/examples/openai/celest/lib/src/client/functions.dart +++ b/examples/openai/celest/lib/src/client/functions.dart @@ -2,12 +2,12 @@ // it can be checked into version control. // ignore_for_file: type=lint, unused_local_variable, unnecessary_cast, unnecessary_import -library; +library; // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'dart:convert'; +import 'dart:convert' as _$convert; import 'package:celest/celest.dart'; -import 'package:celest_backend/models.dart'; +import 'package:celest_backend/models.dart' as _$models; import 'package:celest_core/src/exception/cloud_exception.dart'; import '../../client.dart'; @@ -17,18 +17,10 @@ class CelestFunctions { } class CelestFunctionsOpenAi { - /// Returns a list of available models. - Future> availableModels() async { - final $response = await celest.httpClient.post( - celest.baseUri.resolve('/open-ai/available-models'), - headers: const {'Content-Type': 'application/json; charset=utf-8'}, - ); - final $body = (jsonDecode($response.body) as Map); - if ($response.statusCode == 200) { - return ($body['response'] as Iterable) - .map((el) => (el as String)) - .toList(); - } + Never _throwError({ + required int $statusCode, + required Map $body, + }) { final $error = ($body['error'] as Map); final $code = ($error['code'] as String); final $details = ($error['details'] as Map?); @@ -39,7 +31,7 @@ class CelestFunctionsOpenAi { throw Serializers.instance .deserialize($details); case _: - switch ($response.statusCode) { + switch ($statusCode) { case 400: throw BadRequestException($code); case _: @@ -48,44 +40,51 @@ class CelestFunctionsOpenAi { } } + /// Returns a list of available models. + Future> availableModels() async { + final $response = await celest.httpClient.post( + celest.baseUri.resolve('/open-ai/available-models'), + headers: const {'Content-Type': 'application/json; charset=utf-8'}, + ); + final $body = + (_$convert.jsonDecode($response.body) as Map); + if ($response.statusCode != 200) { + _throwError( + $statusCode: $response.statusCode, + $body: $body, + ); + } + return ($body['response'] as Iterable) + .map((el) => (el as String)) + .toList(); + } + /// Prompts the GPT [model] with the given [prompt] and [parameters]. /// /// Returns the generated text. Future openAiRequest({ required String model, required String prompt, - ModelParameters parameters = const ModelParameters(), + _$models.ModelParameters parameters = const _$models.ModelParameters(), }) async { final $response = await celest.httpClient.post( celest.baseUri.resolve('/open-ai/open-ai-request'), headers: const {'Content-Type': 'application/json; charset=utf-8'}, - body: jsonEncode({ + body: _$convert.jsonEncode({ r'model': model, r'prompt': prompt, - r'parameters': - Serializers.instance.serialize(parameters), + r'parameters': Serializers.instance + .serialize<_$models.ModelParameters>(parameters), }), ); - final $body = (jsonDecode($response.body) as Map); - if ($response.statusCode == 200) { - return ($body['response'] as String); - } - final $error = ($body['error'] as Map); - final $code = ($error['code'] as String); - final $details = ($error['details'] as Map?); - switch ($code) { - case r'BadRequestException': - throw Serializers.instance.deserialize($details); - case r'InternalServerException': - throw Serializers.instance - .deserialize($details); - case _: - switch ($response.statusCode) { - case 400: - throw BadRequestException($code); - case _: - throw InternalServerException($code); - } + final $body = + (_$convert.jsonDecode($response.body) as Map); + if ($response.statusCode != 200) { + _throwError( + $statusCode: $response.statusCode, + $body: $body, + ); } + return ($body['response'] as String); } } diff --git a/examples/openai/celest/lib/src/client/serializers.dart b/examples/openai/celest/lib/src/client/serializers.dart index 958fd6f0..9f4a2275 100644 --- a/examples/openai/celest/lib/src/client/serializers.dart +++ b/examples/openai/celest/lib/src/client/serializers.dart @@ -1,23 +1,36 @@ // ignore_for_file: type=lint, unused_local_variable, unnecessary_cast, unnecessary_import +// ignore_for_file: no_leading_underscores_for_library_prefixes import 'package:celest/celest.dart'; -import 'package:celest_backend/models.dart'; +import 'package:celest_backend/models.dart' as _$models; +import 'package:celest_core/src/exception/cloud_exception.dart'; -final class ModelParametersSerializer extends Serializer { - const ModelParametersSerializer(); - - @override - ModelParameters deserialize(Object? value) { - final serialized = assertWireType?>(value); - return ModelParameters( - temperature: ((serialized?[r'temperature'] as num?)?.toDouble()) ?? null, - maxTokens: ((serialized?[r'maxTokens'] as num?)?.toInt()) ?? null, - ); - } - - @override - Map serialize(ModelParameters value) => { - r'temperature': value.temperature, - r'maxTokens': value.maxTokens, - }; +void initSerializers() { + Serializers.instance + .put(Serializer.define<_$models.ModelParameters, Map?>( + serialize: ($value) => { + r'temperature': $value.temperature, + r'maxTokens': $value.maxTokens, + }, + deserialize: ($serialized) { + return _$models.ModelParameters( + temperature: ($serialized?[r'temperature'] as num?)?.toDouble(), + maxTokens: ($serialized?[r'maxTokens'] as num?)?.toInt(), + ); + }, + )); + Serializers.instance + .put(Serializer.define>( + serialize: ($value) => {r'message': $value.message}, + deserialize: ($serialized) { + return BadRequestException(($serialized[r'message'] as String)); + }, + )); + Serializers.instance + .put(Serializer.define>( + serialize: ($value) => {r'message': $value.message}, + deserialize: ($serialized) { + return InternalServerException(($serialized[r'message'] as String)); + }, + )); } diff --git a/examples/openai/celest/pubspec.lock b/examples/openai/celest/pubspec.lock index 98d39703..9156f2af 100644 --- a/examples/openai/celest/pubspec.lock +++ b/examples/openai/celest/pubspec.lock @@ -45,18 +45,18 @@ packages: dependency: "direct main" description: name: celest - sha256: fa5f2cf62073d2e71a2ce4c559d703886c2a48062b08752040d3afcc4531b846 + sha256: aa0d6bd43ed45f52d2f1a88b5dc00bf88439ab21f1d727c68938f54ccf3ba445 url: "https://pub.dev" source: hosted - version: "0.1.1" + version: "0.2.0" celest_core: dependency: "direct main" description: name: celest_core - sha256: c5d6408d047816f2b81c63b75c8f270377768612ee47272a5782ed48af887e55 + sha256: b9da8eb41f3b0ea5f76733dd74fede23f5430a57d8c8138963b28e99319bc0d0 url: "https://pub.dev" source: hosted - version: "0.1.1" + version: "0.2.0" chat_gpt_sdk: dependency: "direct main" description: @@ -109,10 +109,10 @@ packages: dependency: transitive description: name: dio - sha256: "797e1e341c3dd2f69f2dad42564a6feff3bfb87187d05abb93b9609e6f1645c3" + sha256: "49af28382aefc53562459104f64d16b9dfd1e8ef68c862d5af436cc8356ce5a8" url: "https://pub.dev" source: hosted - version: "5.4.0" + version: "5.4.1" file: dependency: transitive description: @@ -141,10 +141,10 @@ packages: dependency: "direct main" description: name: http - sha256: a2bbf9d017fcced29139daa8ed2bba4ece450ab222871df93ca9eec6f80c34ba + sha256: "761a297c042deedc1ffbb156d6e2af13886bb305c2a343a4d972504cd67dd938" url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.2.1" http_methods: dependency: transitive description: @@ -181,10 +181,10 @@ packages: dependency: transitive description: name: js - sha256: "4186c61b32f99e60f011f7160e32c89a758ae9b1d0c6d28e2c02ef0382300e2b" + sha256: c1b2e9b5ea78c45e1a0788d29606ba27dc5f71f019f32ca5140f61ef071838cf url: "https://pub.dev" source: hosted - version: "0.7.0" + version: "0.7.1" lints: dependency: "direct dev" description: @@ -213,10 +213,10 @@ packages: dependency: transitive description: name: meta - sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04 + sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" url: "https://pub.dev" source: hosted - version: "1.11.0" + version: "1.12.0" mime: dependency: transitive description: @@ -397,10 +397,10 @@ packages: dependency: transitive description: name: vm_service - sha256: a2662fb1f114f4296cf3f5a50786a2d888268d7776cf681aa17d660ffa23b246 + sha256: e7d5ecd604e499358c5fe35ee828c0298a320d54455e791e9dcf73486bc8d9f0 url: "https://pub.dev" source: hosted - version: "14.0.0" + version: "14.1.0" watcher: dependency: transitive description: @@ -413,18 +413,18 @@ packages: dependency: transitive description: name: web - sha256: "4188706108906f002b3a293509234588823c8c979dc83304e229ff400c996b05" + sha256: "1d9158c616048c38f712a6646e317a3426da10e884447626167240d45209cbad" url: "https://pub.dev" source: hosted - version: "0.4.2" + version: "0.5.0" web_socket_channel: dependency: transitive description: name: web_socket_channel - sha256: "939ab60734a4f8fa95feacb55804fa278de28bdeef38e616dc08e44a84adea23" + sha256: "1d8e795e2a8b3730c41b8a98a2dff2e0fb57ae6f0764a1c46ec5915387d257b2" url: "https://pub.dev" source: hosted - version: "2.4.3" + version: "2.4.4" webkit_inspection_protocol: dependency: transitive description: @@ -442,4 +442,4 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.2.3 <4.0.0" + dart: ">=3.3.0 <4.0.0" diff --git a/examples/openai/celest/pubspec.yaml b/examples/openai/celest/pubspec.yaml index 42a27e40..740aa8bf 100644 --- a/examples/openai/celest/pubspec.yaml +++ b/examples/openai/celest/pubspec.yaml @@ -6,8 +6,8 @@ environment: sdk: ^3.3.0 dependencies: - celest: ^0.1.0 - celest_core: ^0.1.0 + celest: ^0.2.0 + celest_core: ^0.2.0 chat_gpt_sdk: ^2.2.8 http: ">=0.13.0 <2.0.0" diff --git a/examples/openai/celest/resources.dart b/examples/openai/celest/resources.dart index ab92674c..8a77c198 100644 --- a/examples/openai/celest/resources.dart +++ b/examples/openai/celest/resources.dart @@ -6,11 +6,11 @@ library; import 'package:celest/celest.dart'; -abstract final class apis { +abstract final class Apis { static const openAi = CloudApi(name: r'open_ai'); } -abstract final class functions { +abstract final class Functions { static const openAiAvailableModels = CloudFunction( api: r'open_ai', functionName: r'availableModels', @@ -22,6 +22,6 @@ abstract final class functions { ); } -abstract final class env { +abstract final class Env { static const openAiToken = EnvironmentVariable(name: r'OPEN_AI_TOKEN'); } diff --git a/examples/openai/ios/Runner.xcodeproj/project.pbxproj b/examples/openai/ios/Runner.xcodeproj/project.pbxproj index adaabd3f..9aee34ad 100644 --- a/examples/openai/ios/Runner.xcodeproj/project.pbxproj +++ b/examples/openai/ios/Runner.xcodeproj/project.pbxproj @@ -169,7 +169,7 @@ isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = YES; - LastUpgradeCheck = 1430; + LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { 331C8080294A63A400263BE5 = { diff --git a/examples/openai/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/examples/openai/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 87131a09..8e3ca5df 100644 --- a/examples/openai/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/examples/openai/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ { /// Whether [_availableModelsFuture] has completed and the available models /// have been loaded fromthe backend. var _loadedAvailableModels = false; - final _availableModelsFuture = celest.functions.openAi.availableModels(); + Future> get _availableModelsFuture => + celest.functions.openAi.availableModels(); /// Sends the prompt request to the backend and updates the UI with the /// response. diff --git a/examples/openai/macos/Runner.xcodeproj/project.pbxproj b/examples/openai/macos/Runner.xcodeproj/project.pbxproj index b5035f35..e2b51a76 100644 --- a/examples/openai/macos/Runner.xcodeproj/project.pbxproj +++ b/examples/openai/macos/Runner.xcodeproj/project.pbxproj @@ -227,7 +227,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0920; - LastUpgradeCheck = 1430; + LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { 331C80D4294CF70F00263BE5 = { diff --git a/examples/openai/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/examples/openai/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 7b4e89d7..654b8e8c 100644 --- a/examples/openai/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/examples/openai/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ =3.2.5 <4.0.0" + dart: ">=3.3.0 <4.0.0" diff --git a/examples/todo/celest/lib/client.dart b/examples/todo/celest/lib/client.dart index 7f77957d..546780c3 100644 --- a/examples/todo/celest/lib/client.dart +++ b/examples/todo/celest/lib/client.dart @@ -2,31 +2,60 @@ // it can be checked into version control. // ignore_for_file: type=lint, unused_local_variable, unnecessary_cast, unnecessary_import -library; +library; // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'dart:io'; +import 'dart:io' as _$io; -import 'package:celest/celest.dart'; import 'package:celest_core/src/util/globals.dart'; -import 'package:http/http.dart' as http; +import 'package:http/http.dart' as _$http; import 'src/client/functions.dart'; import 'src/client/serializers.dart'; final Celest celest = Celest(); +enum CelestEnvironment { + local; + + Uri get baseUri => switch (this) { + local => kIsWeb || !_$io.Platform.isAndroid + ? Uri.parse('http://localhost:7781') + : Uri.parse('http://10.0.2.2:7781'), + }; +} + class Celest { - late http.Client httpClient = http.Client(); + var _initialized = false; + + late CelestEnvironment _currentEnvironment; + + late _$http.Client httpClient = _$http.Client(); + + late Uri _baseUri; + + final _functions = CelestFunctions(); + + T _checkInitialized(T Function() value) { + if (!_initialized) { + throw StateError( + 'Celest has not been initialized. Make sure to call `celest.init()` at the start of your `main` method.'); + } + return value(); + } + + CelestEnvironment get currentEnvironment => + _checkInitialized(() => _currentEnvironment); - late final Uri baseUri = kIsWeb || !Platform.isAndroid - ? Uri.parse('http://localhost:7777') - : Uri.parse('http://10.0.2.2:7777'); + Uri get baseUri => _checkInitialized(() => _baseUri); - final functions = CelestFunctions(); + CelestFunctions get functions => _checkInitialized(() => _functions); - void init() { - Serializers.instance.put(const ImportanceSerializer()); - Serializers.instance.put(const TaskSerializer()); - Serializers.instance.put(const ServerExceptionSerializer()); + void init({CelestEnvironment environment = CelestEnvironment.local}) { + _currentEnvironment = environment; + _baseUri = environment.baseUri; + if (!_initialized) { + initSerializers(); + } + _initialized = true; } } diff --git a/examples/todo/celest/lib/src/client/functions.dart b/examples/todo/celest/lib/src/client/functions.dart index fcd6f4e8..d2d2abfb 100644 --- a/examples/todo/celest/lib/src/client/functions.dart +++ b/examples/todo/celest/lib/src/client/functions.dart @@ -2,14 +2,13 @@ // it can be checked into version control. // ignore_for_file: type=lint, unused_local_variable, unnecessary_cast, unnecessary_import -library; +library; // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'dart:convert'; +import 'dart:convert' as _$convert; import 'package:celest/celest.dart'; -import 'package:celest_backend/exceptions.dart'; -import 'package:celest_backend/models.dart'; -import 'package:celest_core/src/exception/cloud_exception.dart'; +import 'package:celest_backend/exceptions.dart' as _$exceptions; +import 'package:celest_backend/models.dart' as _$models; import '../../client.dart'; @@ -18,33 +17,19 @@ class CelestFunctions { } class CelestFunctionsTasks { - Future> listAllTasks() async { - final $response = await celest.httpClient.post( - celest.baseUri.resolve('/tasks/list-all-tasks'), - headers: const {'Content-Type': 'application/json; charset=utf-8'}, - ); - final $body = (jsonDecode($response.body) as Map); - if ($response.statusCode == 200) { - return ($body['response'] as Map).map(( - key, - value, - ) => - MapEntry( - key, - Serializers.instance.deserialize(value), - )); - } + Never _throwError({ + required int $statusCode, + required Map $body, + }) { final $error = ($body['error'] as Map); final $code = ($error['code'] as String); final $details = ($error['details'] as Map?); switch ($code) { - case r'BadRequestException': - throw Serializers.instance.deserialize($details); - case r'InternalServerException': + case r'ServerException': throw Serializers.instance - .deserialize($details); + .deserialize<_$exceptions.ServerException>($details); case _: - switch ($response.statusCode) { + switch ($statusCode) { case 400: throw BadRequestException($code); case _: @@ -53,131 +38,101 @@ class CelestFunctionsTasks { } } + Future> listAllTasks() async { + final $response = await celest.httpClient.post( + celest.baseUri.resolve('/tasks/list-all-tasks'), + headers: const {'Content-Type': 'application/json; charset=utf-8'}, + ); + final $body = + (_$convert.jsonDecode($response.body) as Map); + if ($response.statusCode != 200) { + _throwError( + $statusCode: $response.statusCode, + $body: $body, + ); + } + return ($body['response'] as Map).map(( + key, + value, + ) => + MapEntry( + key, + Serializers.instance.deserialize<_$models.Task>(value), + )); + } + Future addTask({ required String title, - required Importance importance, + required _$models.Importance importance, }) async { final $response = await celest.httpClient.post( celest.baseUri.resolve('/tasks/add-task'), headers: const {'Content-Type': 'application/json; charset=utf-8'}, - body: jsonEncode({ + body: _$convert.jsonEncode({ r'title': title, - r'importance': Serializers.instance.serialize(importance), + r'importance': + Serializers.instance.serialize<_$models.Importance>(importance), }), ); - final $body = (jsonDecode($response.body) as Map); - if ($response.statusCode == 200) { - return; - } - final $error = ($body['error'] as Map); - final $code = ($error['code'] as String); - final $details = ($error['details'] as Map?); - switch ($code) { - case r'ServerException': - throw Serializers.instance.deserialize($details); - case r'BadRequestException': - throw Serializers.instance.deserialize($details); - case r'InternalServerException': - throw Serializers.instance - .deserialize($details); - case _: - switch ($response.statusCode) { - case 400: - throw BadRequestException($code); - case _: - throw InternalServerException($code); - } + final $body = + (_$convert.jsonDecode($response.body) as Map); + if ($response.statusCode != 200) { + _throwError( + $statusCode: $response.statusCode, + $body: $body, + ); } + return; } Future deleteTask({required String id}) async { final $response = await celest.httpClient.post( celest.baseUri.resolve('/tasks/delete-task'), headers: const {'Content-Type': 'application/json; charset=utf-8'}, - body: jsonEncode({r'id': id}), + body: _$convert.jsonEncode({r'id': id}), ); - final $body = (jsonDecode($response.body) as Map); - if ($response.statusCode == 200) { - return; - } - final $error = ($body['error'] as Map); - final $code = ($error['code'] as String); - final $details = ($error['details'] as Map?); - switch ($code) { - case r'BadRequestException': - throw Serializers.instance.deserialize($details); - case r'InternalServerException': - throw Serializers.instance - .deserialize($details); - case _: - switch ($response.statusCode) { - case 400: - throw BadRequestException($code); - case _: - throw InternalServerException($code); - } + final $body = + (_$convert.jsonDecode($response.body) as Map); + if ($response.statusCode != 200) { + _throwError( + $statusCode: $response.statusCode, + $body: $body, + ); } + return; } Future markAsCompleted({required String id}) async { final $response = await celest.httpClient.post( celest.baseUri.resolve('/tasks/mark-as-completed'), headers: const {'Content-Type': 'application/json; charset=utf-8'}, - body: jsonEncode({r'id': id}), + body: _$convert.jsonEncode({r'id': id}), ); - final $body = (jsonDecode($response.body) as Map); - if ($response.statusCode == 200) { - return; - } - final $error = ($body['error'] as Map); - final $code = ($error['code'] as String); - final $details = ($error['details'] as Map?); - switch ($code) { - case r'ServerException': - throw Serializers.instance.deserialize($details); - case r'BadRequestException': - throw Serializers.instance.deserialize($details); - case r'InternalServerException': - throw Serializers.instance - .deserialize($details); - case _: - switch ($response.statusCode) { - case 400: - throw BadRequestException($code); - case _: - throw InternalServerException($code); - } + final $body = + (_$convert.jsonDecode($response.body) as Map); + if ($response.statusCode != 200) { + _throwError( + $statusCode: $response.statusCode, + $body: $body, + ); } + return; } Future markAsIncomplete({required String id}) async { final $response = await celest.httpClient.post( celest.baseUri.resolve('/tasks/mark-as-incomplete'), headers: const {'Content-Type': 'application/json; charset=utf-8'}, - body: jsonEncode({r'id': id}), + body: _$convert.jsonEncode({r'id': id}), ); - final $body = (jsonDecode($response.body) as Map); - if ($response.statusCode == 200) { - return; - } - final $error = ($body['error'] as Map); - final $code = ($error['code'] as String); - final $details = ($error['details'] as Map?); - switch ($code) { - case r'ServerException': - throw Serializers.instance.deserialize($details); - case r'BadRequestException': - throw Serializers.instance.deserialize($details); - case r'InternalServerException': - throw Serializers.instance - .deserialize($details); - case _: - switch ($response.statusCode) { - case 400: - throw BadRequestException($code); - case _: - throw InternalServerException($code); - } + final $body = + (_$convert.jsonDecode($response.body) as Map); + if ($response.statusCode != 200) { + _throwError( + $statusCode: $response.statusCode, + $body: $body, + ); } + return; } } diff --git a/examples/todo/celest/lib/src/client/serializers.dart b/examples/todo/celest/lib/src/client/serializers.dart index 128b87b8..645be481 100644 --- a/examples/todo/celest/lib/src/client/serializers.dart +++ b/examples/todo/celest/lib/src/client/serializers.dart @@ -1,57 +1,41 @@ // ignore_for_file: type=lint, unused_local_variable, unnecessary_cast, unnecessary_import +// ignore_for_file: no_leading_underscores_for_library_prefixes import 'package:celest/celest.dart'; -import 'package:celest_backend/exceptions.dart'; -import 'package:celest_backend/models.dart'; - -final class ImportanceSerializer extends Serializer { - const ImportanceSerializer(); - - @override - Importance deserialize(Object? value) { - final serialized = assertWireType(value); - return Importance.values.byName(serialized); - } - - @override - String serialize(Importance value) => value.name; -} - -final class TaskSerializer extends Serializer { - const TaskSerializer(); - - @override - Task deserialize(Object? value) { - final serialized = assertWireType>(value); - return Task( - id: (serialized[r'id'] as String), - title: (serialized[r'title'] as String), - importance: Serializers.instance - .deserialize(serialized[r'importance']), - isCompleted: ((serialized[r'isCompleted'] as bool?)) ?? false, - ); - } - - @override - Map serialize(Task value) => { - r'id': value.id, - r'title': value.title, - r'importance': - Serializers.instance.serialize(value.importance), - r'isCompleted': value.isCompleted, - }; -} - -final class ServerExceptionSerializer extends Serializer { - const ServerExceptionSerializer(); - - @override - ServerException deserialize(Object? value) { - final serialized = assertWireType>(value); - return ServerException((serialized[r'message'] as String)); - } - - @override - Map serialize(ServerException value) => - {r'message': value.message}; +import 'package:celest_backend/exceptions.dart' as _$exceptions; +import 'package:celest_backend/models.dart' as _$models; + +void initSerializers() { + Serializers.instance.put( + Serializer.define<_$exceptions.ServerException, Map>( + serialize: ($value) => {r'message': $value.message}, + deserialize: ($serialized) { + return _$exceptions.ServerException(($serialized[r'message'] as String)); + }, + )); + Serializers.instance.put(Serializer.define<_$models.Importance, String>( + serialize: ($value) => $value.name, + deserialize: ($serialized) { + return _$models.Importance.values.byName($serialized); + }, + )); + Serializers.instance + .put(Serializer.define<_$models.Task, Map>( + serialize: ($value) => { + r'id': $value.id, + r'title': $value.title, + r'importance': Serializers.instance + .serialize<_$models.Importance>($value.importance), + r'isCompleted': $value.isCompleted, + }, + deserialize: ($serialized) { + return _$models.Task( + id: ($serialized[r'id'] as String), + title: ($serialized[r'title'] as String), + importance: Serializers.instance + .deserialize<_$models.Importance>($serialized[r'importance']), + isCompleted: (($serialized[r'isCompleted'] as bool?)) ?? false, + ); + }, + )); } diff --git a/examples/todo/celest/pubspec.lock b/examples/todo/celest/pubspec.lock index 170ce673..d63a84c0 100644 --- a/examples/todo/celest/pubspec.lock +++ b/examples/todo/celest/pubspec.lock @@ -45,18 +45,18 @@ packages: dependency: "direct main" description: name: celest - sha256: fa5f2cf62073d2e71a2ce4c559d703886c2a48062b08752040d3afcc4531b846 + sha256: aa0d6bd43ed45f52d2f1a88b5dc00bf88439ab21f1d727c68938f54ccf3ba445 url: "https://pub.dev" source: hosted - version: "0.1.1" + version: "0.2.0" celest_core: dependency: "direct main" description: name: celest_core - sha256: c5d6408d047816f2b81c63b75c8f270377768612ee47272a5782ed48af887e55 + sha256: b9da8eb41f3b0ea5f76733dd74fede23f5430a57d8c8138963b28e99319bc0d0 url: "https://pub.dev" source: hosted - version: "0.1.1" + version: "0.2.0" chunked_stream: dependency: transitive description: @@ -133,10 +133,10 @@ packages: dependency: "direct main" description: name: http - sha256: a2bbf9d017fcced29139daa8ed2bba4ece450ab222871df93ca9eec6f80c34ba + sha256: "761a297c042deedc1ffbb156d6e2af13886bb305c2a343a4d972504cd67dd938" url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.2.1" http_methods: dependency: transitive description: @@ -173,10 +173,10 @@ packages: dependency: transitive description: name: js - sha256: "4186c61b32f99e60f011f7160e32c89a758ae9b1d0c6d28e2c02ef0382300e2b" + sha256: c1b2e9b5ea78c45e1a0788d29606ba27dc5f71f019f32ca5140f61ef071838cf url: "https://pub.dev" source: hosted - version: "0.7.0" + version: "0.7.1" lints: dependency: "direct dev" description: @@ -205,10 +205,10 @@ packages: dependency: transitive description: name: meta - sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04 + sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" url: "https://pub.dev" source: hosted - version: "1.11.0" + version: "1.12.0" mime: dependency: transitive description: @@ -405,10 +405,10 @@ packages: dependency: transitive description: name: vm_service - sha256: a2662fb1f114f4296cf3f5a50786a2d888268d7776cf681aa17d660ffa23b246 + sha256: e7d5ecd604e499358c5fe35ee828c0298a320d54455e791e9dcf73486bc8d9f0 url: "https://pub.dev" source: hosted - version: "14.0.0" + version: "14.1.0" watcher: dependency: transitive description: @@ -421,18 +421,18 @@ packages: dependency: transitive description: name: web - sha256: "4188706108906f002b3a293509234588823c8c979dc83304e229ff400c996b05" + sha256: "1d9158c616048c38f712a6646e317a3426da10e884447626167240d45209cbad" url: "https://pub.dev" source: hosted - version: "0.4.2" + version: "0.5.0" web_socket_channel: dependency: transitive description: name: web_socket_channel - sha256: "939ab60734a4f8fa95feacb55804fa278de28bdeef38e616dc08e44a84adea23" + sha256: "1d8e795e2a8b3730c41b8a98a2dff2e0fb57ae6f0764a1c46ec5915387d257b2" url: "https://pub.dev" source: hosted - version: "2.4.3" + version: "2.4.4" webkit_inspection_protocol: dependency: transitive description: @@ -450,4 +450,4 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.2.0 <4.0.0" + dart: ">=3.3.0 <4.0.0" diff --git a/examples/todo/celest/pubspec.yaml b/examples/todo/celest/pubspec.yaml index 33fe89cd..a2f16401 100644 --- a/examples/todo/celest/pubspec.yaml +++ b/examples/todo/celest/pubspec.yaml @@ -6,8 +6,8 @@ environment: sdk: ^3.3.0 dependencies: - celest: ^0.1.0 - celest_core: ^0.1.0 + celest: ^0.2.0 + celest_core: ^0.2.0 http: ">=0.13.0 <2.0.0" uuid: ^4.3.3 diff --git a/examples/todo/celest/resources.dart b/examples/todo/celest/resources.dart index 4e9a6a94..842003ec 100644 --- a/examples/todo/celest/resources.dart +++ b/examples/todo/celest/resources.dart @@ -6,11 +6,11 @@ library; import 'package:celest/celest.dart'; -abstract final class apis { +abstract final class Apis { static const tasks = CloudApi(name: r'tasks'); } -abstract final class functions { +abstract final class Functions { static const tasksAddTask = CloudFunction( api: r'tasks', functionName: r'addTask', diff --git a/examples/todo/ios/Flutter/AppFrameworkInfo.plist b/examples/todo/ios/Flutter/AppFrameworkInfo.plist index 9625e105..7c569640 100644 --- a/examples/todo/ios/Flutter/AppFrameworkInfo.plist +++ b/examples/todo/ios/Flutter/AppFrameworkInfo.plist @@ -21,6 +21,6 @@ CFBundleVersion 1.0 MinimumOSVersion - 11.0 + 12.0 diff --git a/examples/todo/ios/Runner.xcodeproj/project.pbxproj b/examples/todo/ios/Runner.xcodeproj/project.pbxproj index 7fb33734..ab68198e 100644 --- a/examples/todo/ios/Runner.xcodeproj/project.pbxproj +++ b/examples/todo/ios/Runner.xcodeproj/project.pbxproj @@ -169,7 +169,7 @@ isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = YES; - LastUpgradeCheck = 1430; + LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { 331C8080294A63A400263BE5 = { @@ -345,7 +345,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -472,7 +472,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -521,7 +521,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; diff --git a/examples/todo/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/examples/todo/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 87131a09..8e3ca5df 100644 --- a/examples/todo/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/examples/todo/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ =3.2.3 <4.0.0" + dart: ">=3.3.0 <4.0.0" diff --git a/packages/celest/example/celest/functions/greeting.dart b/packages/celest/example/celest/functions/greeting.dart index 9da487b8..b5692dfb 100644 --- a/packages/celest/example/celest/functions/greeting.dart +++ b/packages/celest/example/celest/functions/greeting.dart @@ -1,10 +1,19 @@ // Cloud functions are top-level Dart functions defined in the `functions/` // folder of your Celest project. -/// Says hello to a person called [name]. -Future sayHello(String name) async { +import 'package:celest_backend/exceptions/bad_name_exception.dart'; +import 'package:celest_backend/models/person.dart'; + +/// Says hello to a [person]. +Future sayHello({required Person person}) async { + if (person.name.isEmpty) { + // Throw a custom exception defined in the `lib/exceptions/` and catch + // it on the frontend. + throw BadNameException('Name cannot be empty'); + } + // Logging is handled automatically when you print to the console. - print('Saying hello to $name'); + print('Saying hello to ${person.name}'); - return 'Hello, $name!'; + return 'Hello, ${person.name}!'; } diff --git a/packages/celest/example/celest/lib/client.dart b/packages/celest/example/celest/lib/client.dart index aced9430..546780c3 100644 --- a/packages/celest/example/celest/lib/client.dart +++ b/packages/celest/example/celest/lib/client.dart @@ -2,25 +2,60 @@ // it can be checked into version control. // ignore_for_file: type=lint, unused_local_variable, unnecessary_cast, unnecessary_import -library; +library; // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'dart:io'; +import 'dart:io' as _$io; import 'package:celest_core/src/util/globals.dart'; -import 'package:http/http.dart' as http; +import 'package:http/http.dart' as _$http; import 'src/client/functions.dart'; +import 'src/client/serializers.dart'; final Celest celest = Celest(); +enum CelestEnvironment { + local; + + Uri get baseUri => switch (this) { + local => kIsWeb || !_$io.Platform.isAndroid + ? Uri.parse('http://localhost:7781') + : Uri.parse('http://10.0.2.2:7781'), + }; +} + class Celest { - late http.Client httpClient = http.Client(); + var _initialized = false; + + late CelestEnvironment _currentEnvironment; + + late _$http.Client httpClient = _$http.Client(); + + late Uri _baseUri; + + final _functions = CelestFunctions(); + + T _checkInitialized(T Function() value) { + if (!_initialized) { + throw StateError( + 'Celest has not been initialized. Make sure to call `celest.init()` at the start of your `main` method.'); + } + return value(); + } + + CelestEnvironment get currentEnvironment => + _checkInitialized(() => _currentEnvironment); - late final Uri baseUri = kIsWeb || !Platform.isAndroid - ? Uri.parse('http://localhost:7777') - : Uri.parse('http://10.0.2.2:7777'); + Uri get baseUri => _checkInitialized(() => _baseUri); - final functions = CelestFunctions(); + CelestFunctions get functions => _checkInitialized(() => _functions); - void init() {} + void init({CelestEnvironment environment = CelestEnvironment.local}) { + _currentEnvironment = environment; + _baseUri = environment.baseUri; + if (!_initialized) { + initSerializers(); + } + _initialized = true; + } } diff --git a/packages/celest/example/celest/lib/exceptions.dart b/packages/celest/example/celest/lib/exceptions.dart deleted file mode 100644 index 1c7d9dde..00000000 --- a/packages/celest/example/celest/lib/exceptions.dart +++ /dev/null @@ -1,2 +0,0 @@ -// By convention, any custom exception types thrown by an API must be defined -// in this file or exported from this file. diff --git a/packages/celest/example/celest/lib/exceptions/bad_name_exception.dart b/packages/celest/example/celest/lib/exceptions/bad_name_exception.dart new file mode 100644 index 00000000..28c67ecb --- /dev/null +++ b/packages/celest/example/celest/lib/exceptions/bad_name_exception.dart @@ -0,0 +1,11 @@ +// By convention, any custom exception types thrown by an API must be defined +// in this file or exported from this file. + +class BadNameException implements Exception { + const BadNameException(this.message); + + final String message; + + @override + String toString() => 'BadNameException: $message'; +} diff --git a/packages/celest/example/celest/lib/models.dart b/packages/celest/example/celest/lib/models.dart deleted file mode 100644 index 9d381311..00000000 --- a/packages/celest/example/celest/lib/models.dart +++ /dev/null @@ -1,2 +0,0 @@ -// By convention, any custom types used within an API request/response must be -// defined in this file or exported from this file. diff --git a/packages/celest/example/celest/lib/models/person.dart b/packages/celest/example/celest/lib/models/person.dart new file mode 100644 index 00000000..b2b60309 --- /dev/null +++ b/packages/celest/example/celest/lib/models/person.dart @@ -0,0 +1,8 @@ +// By convention, any custom types used within an API request/response must be +// defined in the `models/` folder. + +class Person { + const Person({required this.name}); + + final String name; +} diff --git a/packages/celest/example/celest/lib/src/client/functions.dart b/packages/celest/example/celest/lib/src/client/functions.dart index 02705f66..97ac8f14 100644 --- a/packages/celest/example/celest/lib/src/client/functions.dart +++ b/packages/celest/example/celest/lib/src/client/functions.dart @@ -2,12 +2,14 @@ // it can be checked into version control. // ignore_for_file: type=lint, unused_local_variable, unnecessary_cast, unnecessary_import -library; +library; // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'dart:convert'; +import 'dart:convert' as _$convert; import 'package:celest/celest.dart'; -import 'package:celest_core/src/exception/cloud_exception.dart'; +import 'package:celest_backend/exceptions/bad_name_exception.dart' + as _$bad_name_exception; +import 'package:celest_backend/models/person.dart' as _$person; import '../../client.dart'; @@ -16,28 +18,19 @@ class CelestFunctions { } class CelestFunctionsGreeting { - /// Says hello to a person called [name]. - Future sayHello(String name) async { - final $response = await celest.httpClient.post( - celest.baseUri.resolve('/greeting/say-hello'), - headers: const {'Content-Type': 'application/json; charset=utf-8'}, - body: jsonEncode({r'name': name}), - ); - final $body = (jsonDecode($response.body) as Map); - if ($response.statusCode == 200) { - return ($body['response'] as String); - } + Never _throwError({ + required int $statusCode, + required Map $body, + }) { final $error = ($body['error'] as Map); final $code = ($error['code'] as String); final $details = ($error['details'] as Map?); switch ($code) { - case r'BadRequestException': - throw Serializers.instance.deserialize($details); - case r'InternalServerException': + case r'BadNameException': throw Serializers.instance - .deserialize($details); + .deserialize<_$bad_name_exception.BadNameException>($details); case _: - switch ($response.statusCode) { + switch ($statusCode) { case 400: throw BadRequestException($code); case _: @@ -45,4 +38,23 @@ class CelestFunctionsGreeting { } } } + + /// Says hello to a [person]. + Future sayHello({required _$person.Person person}) async { + final $response = await celest.httpClient.post( + celest.baseUri.resolve('/greeting/say-hello'), + headers: const {'Content-Type': 'application/json; charset=utf-8'}, + body: _$convert.jsonEncode( + {r'person': Serializers.instance.serialize<_$person.Person>(person)}), + ); + final $body = + (_$convert.jsonDecode($response.body) as Map); + if ($response.statusCode != 200) { + _throwError( + $statusCode: $response.statusCode, + $body: $body, + ); + } + return ($body['response'] as String); + } } diff --git a/packages/celest/example/celest/lib/src/client/serializers.dart b/packages/celest/example/celest/lib/src/client/serializers.dart new file mode 100644 index 00000000..381836e8 --- /dev/null +++ b/packages/celest/example/celest/lib/src/client/serializers.dart @@ -0,0 +1,25 @@ +// ignore_for_file: type=lint, unused_local_variable, unnecessary_cast, unnecessary_import + +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'package:celest/celest.dart'; +import 'package:celest_backend/exceptions/bad_name_exception.dart' + as _$bad_name_exception; +import 'package:celest_backend/models/person.dart' as _$person; + +void initSerializers() { + Serializers.instance.put(Serializer.define< + _$bad_name_exception.BadNameException, Map>( + serialize: ($value) => {r'message': $value.message}, + deserialize: ($serialized) { + return _$bad_name_exception.BadNameException( + ($serialized[r'message'] as String)); + }, + )); + Serializers.instance + .put(Serializer.define<_$person.Person, Map>( + serialize: ($value) => {r'name': $value.name}, + deserialize: ($serialized) { + return _$person.Person(name: ($serialized[r'name'] as String)); + }, + )); +} diff --git a/packages/celest/example/celest/project.dart b/packages/celest/example/celest/project.dart index c85f1904..5f6c5a24 100644 --- a/packages/celest/example/celest/project.dart +++ b/packages/celest/example/celest/project.dart @@ -1,5 +1,5 @@ import 'package:celest/celest.dart'; const project = Project( - name: 'example_app', + name: 'example', ); diff --git a/packages/celest/example/celest/pubspec.yaml b/packages/celest/example/celest/pubspec.yaml index a26ba48e..d6693e1d 100644 --- a/packages/celest/example/celest/pubspec.yaml +++ b/packages/celest/example/celest/pubspec.yaml @@ -1,13 +1,13 @@ name: celest_backend -description: The Celest backend for example_app. +description: The Celest backend for example. publish_to: none environment: sdk: ^3.3.0 dependencies: - celest: ^0.1.0 - celest_core: ^0.1.0 + celest: ^0.2.0 + celest_core: ^0.2.0 http: ">=0.13.0 <2.0.0" dev_dependencies: diff --git a/packages/celest/example/celest/resources.dart b/packages/celest/example/celest/resources.dart index 4cb26244..2925f5ca 100644 --- a/packages/celest/example/celest/resources.dart +++ b/packages/celest/example/celest/resources.dart @@ -6,11 +6,11 @@ library; import 'package:celest/celest.dart'; -abstract final class apis { +abstract final class Apis { static const greeting = CloudApi(name: r'greeting'); } -abstract final class functions { +abstract final class Functions { static const greetingSayHello = CloudFunction( api: r'greeting', functionName: r'sayHello', diff --git a/packages/celest/example/celest/test/functions/greeting_test.dart b/packages/celest/example/celest/test/functions/greeting_test.dart index 1068709e..dc9ec6df 100644 --- a/packages/celest/example/celest/test/functions/greeting_test.dart +++ b/packages/celest/example/celest/test/functions/greeting_test.dart @@ -1,3 +1,5 @@ +import 'package:celest_backend/exceptions/bad_name_exception.dart'; +import 'package:celest_backend/models/person.dart'; import 'package:test/test.dart'; import '../../functions/greeting.dart'; @@ -5,7 +7,13 @@ import '../../functions/greeting.dart'; void main() { group('greeting', () { test('sayHello', () async { - expect(await sayHello('Celest'), 'Hello, Celest!'); + expect(await sayHello(person: Person(name: 'Celest')), 'Hello, Celest!'); + }); + test('sayHello (empty name)', () async { + expect( + sayHello(person: Person(name: '')), + throwsA(isA()), + ); }); }); } diff --git a/packages/celest/example/ios/Runner.xcodeproj/project.pbxproj b/packages/celest/example/ios/Runner.xcodeproj/project.pbxproj index 9c89ada2..4363bd5a 100644 --- a/packages/celest/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/celest/example/ios/Runner.xcodeproj/project.pbxproj @@ -169,7 +169,7 @@ isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = YES; - LastUpgradeCheck = 1430; + LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { 331C8080294A63A400263BE5 = { diff --git a/packages/celest/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/celest/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 87131a09..8e3ca5df 100644 --- a/packages/celest/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/packages/celest/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ switch (snapshot) { AsyncSnapshot(:final data?) => Text(data), AsyncSnapshot(:final error?) => diff --git a/packages/celest/example/macos/Runner.xcodeproj/project.pbxproj b/packages/celest/example/macos/Runner.xcodeproj/project.pbxproj index b07ddf06..8b005e22 100644 --- a/packages/celest/example/macos/Runner.xcodeproj/project.pbxproj +++ b/packages/celest/example/macos/Runner.xcodeproj/project.pbxproj @@ -227,7 +227,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0920; - LastUpgradeCheck = 1430; + LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { 331C80D4294CF70F00263BE5 = { diff --git a/packages/celest/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/celest/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index e93dc924..1242ce6d 100644 --- a/packages/celest/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/packages/celest/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ Date: Wed, 28 Feb 2024 20:38:50 -0800 Subject: [PATCH 13/13] chore: Overrides `toString` for `CloudException` types. --- packages/celest_core/CHANGELOG.md | 4 ++++ packages/celest_core/lib/src/exception/cloud_exception.dart | 6 ++++++ packages/celest_core/pubspec.yaml | 2 +- 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/packages/celest_core/CHANGELOG.md b/packages/celest_core/CHANGELOG.md index b737a7f4..077668af 100644 --- a/packages/celest_core/CHANGELOG.md +++ b/packages/celest_core/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.2.1 + +- Overrides `toString` for `CloudException` types. + ## 0.2.0 - Bumps minimum Dart SDK to 3.3 diff --git a/packages/celest_core/lib/src/exception/cloud_exception.dart b/packages/celest_core/lib/src/exception/cloud_exception.dart index 84e4082f..6f3d7ebd 100644 --- a/packages/celest_core/lib/src/exception/cloud_exception.dart +++ b/packages/celest_core/lib/src/exception/cloud_exception.dart @@ -15,6 +15,9 @@ class BadRequestException implements CloudException { @override final String message; + + @override + String toString() => 'BadRequestException: $message'; } /// {@template celest_core.exception.internal_server_exception} @@ -29,4 +32,7 @@ class InternalServerException implements CloudException { @override final String message; + + @override + String toString() => 'InternalServerException: $message'; } diff --git a/packages/celest_core/pubspec.yaml b/packages/celest_core/pubspec.yaml index dd0b7f11..a6bb16a4 100644 --- a/packages/celest_core/pubspec.yaml +++ b/packages/celest_core/pubspec.yaml @@ -1,6 +1,6 @@ name: celest_core description: Celest types and utilities shared between the client and the cloud. -version: 0.2.0 +version: 0.2.1 homepage: https://celest.dev repository: https://github.com/celest-dev/celest/tree/main/packages/celest_core