diff --git a/packages/mongo/.gitignore b/packages/mongo/.gitignore
new file mode 100644
index 00000000..d95a2f2f
--- /dev/null
+++ b/packages/mongo/.gitignore
@@ -0,0 +1,88 @@
+# See https://www.dartlang.org/tools/private-files.html
+
+# Files and directories created by pub
+.buildlog
+.packages
+.project
+.pub/
+build/
+**/packages/
+.dart_tool
+
+# Files created by dart2js
+# (Most Dart developers will use pub build to compile Dart, use/modify these
+# rules if you intend to use dart2js directly
+# Convention is to use extension '.dart.js' for Dart compiled to Javascript to
+# differentiate from explicit Javascript files)
+*.dart.js
+*.part.js
+*.js.deps
+*.js.map
+*.info.json
+
+# Directory created by dartdoc
+doc/api/
+
+# Don't commit pubspec lock file
+# (Library packages only! Remove pattern if developing an application package)
+pubspec.lock
+### Dart template
+# See https://www.dartlang.org/tools/private-files.html
+
+# Files and directories created by pub
+
+# Files created by dart2js
+# (Most Dart developers will use pub build to compile Dart, use/modify these
+# rules if you intend to use dart2js directly
+# Convention is to use extension '.dart.js' for Dart compiled to Javascript to
+# differentiate from explicit Javascript files)
+
+# Directory created by dartdoc
+
+# Don't commit pubspec lock file
+# (Library packages only! Remove pattern if developing an application package)
+### JetBrains template
+# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
+# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
+
+# User-specific stuff:
+.idea/workspace.xml
+.idea/tasks.xml
+.idea/dictionaries
+.idea/vcs.xml
+.idea/jsLibraryMappings.xml
+
+# Sensitive or high-churn files:
+.idea/dataSources.ids
+.idea/dataSources.xml
+.idea/dataSources.local.xml
+.idea/sqlDataSources.xml
+.idea/dynamic.xml
+.idea/uiDesigner.xml
+
+# Gradle:
+.idea/gradle.xml
+.idea/libraries
+
+# Mongo Explorer plugin:
+.idea/mongoSettings.xml
+
+## File-based project format:
+*.iws
+
+## Plugin-specific files:
+
+# IntelliJ
+/out/
+
+# mpeltonen/sbt-idea plugin
+.idea_modules/
+
+# JIRA plugin
+atlassian-ide-plugin.xml
+
+# Crashlytics plugin (for Android Studio and IntelliJ)
+com_crashlytics_export_strings.xml
+crashlytics.properties
+crashlytics-build.properties
+fabric.properties
diff --git a/packages/mongo/.idea/angel_mongo.iml b/packages/mongo/.idea/angel_mongo.iml
new file mode 100644
index 00000000..02bd9dfb
--- /dev/null
+++ b/packages/mongo/.idea/angel_mongo.iml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/packages/mongo/.idea/modules.xml b/packages/mongo/.idea/modules.xml
new file mode 100644
index 00000000..571c4566
--- /dev/null
+++ b/packages/mongo/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/packages/mongo/.idea/runConfigurations/All_Tests.xml b/packages/mongo/.idea/runConfigurations/All_Tests.xml
new file mode 100644
index 00000000..ac11209e
--- /dev/null
+++ b/packages/mongo/.idea/runConfigurations/All_Tests.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/packages/mongo/.idea/runConfigurations/Generic_Tests.xml b/packages/mongo/.idea/runConfigurations/Generic_Tests.xml
new file mode 100644
index 00000000..034d37e1
--- /dev/null
+++ b/packages/mongo/.idea/runConfigurations/Generic_Tests.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/packages/mongo/.idea/runConfigurations/Typed_Tests.xml b/packages/mongo/.idea/runConfigurations/Typed_Tests.xml
new file mode 100644
index 00000000..0f5d6a48
--- /dev/null
+++ b/packages/mongo/.idea/runConfigurations/Typed_Tests.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/packages/mongo/.idea/vcs.xml b/packages/mongo/.idea/vcs.xml
new file mode 100644
index 00000000..94a25f7f
--- /dev/null
+++ b/packages/mongo/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/packages/mongo/.travis.yml b/packages/mongo/.travis.yml
new file mode 100644
index 00000000..cf32cda7
--- /dev/null
+++ b/packages/mongo/.travis.yml
@@ -0,0 +1,3 @@
+language: dart
+services:
+ - mongodb
\ No newline at end of file
diff --git a/packages/mongo/.vscode/tasks.json b/packages/mongo/.vscode/tasks.json
new file mode 100644
index 00000000..6969f9b5
--- /dev/null
+++ b/packages/mongo/.vscode/tasks.json
@@ -0,0 +1,10 @@
+{
+ // See https://go.microsoft.com/fwlink/?LinkId=733558
+ // for the documentation about the tasks.json format
+ "version": "0.1.0",
+ "command": "test",
+ "isShellCommand": true,
+ "args": ["pub", "run", "test"],
+ "showOutput": "always",
+ "suppressTaskName": true
+}
\ No newline at end of file
diff --git a/packages/mongo/CHANGELOG.md b/packages/mongo/CHANGELOG.md
new file mode 100644
index 00000000..2a97dbec
--- /dev/null
+++ b/packages/mongo/CHANGELOG.md
@@ -0,0 +1,12 @@
+# 2.0.3
+* Add null-coalescing check when processing queries: https://github.com/angel-dart/mongo/pull/12
+
+# 2.0.2
+* Fix flaw where clients could remove all records, even if `allowRemoveAll` were `false`.
+
+# 2.0.1
+* Override `readMany` and `findOne`.
+
+# 2.0.0-
+* Delete `mongo_service_typed`.
+* Update for Angel 2.
diff --git a/packages/mongo/LICENSE b/packages/mongo/LICENSE
new file mode 100644
index 00000000..eb4ce33e
--- /dev/null
+++ b/packages/mongo/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2016 angel-dart
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/packages/mongo/README.md b/packages/mongo/README.md
new file mode 100644
index 00000000..de3dd1c5
--- /dev/null
+++ b/packages/mongo/README.md
@@ -0,0 +1,57 @@
+# angel_mongo
+
+[![Pub](https://img.shields.io/pub/v/angel_mongo.svg)](https://pub.dartlang.org/packages/angel_mongo)
+[![build status](https://travis-ci.org/angel-dart/mongo.svg)](https://travis-ci.org/angel-dart/mongo)
+
+MongoDB-enabled services for the Angel framework.
+
+# Installation
+Add the following to your `pubspec.yaml`:
+
+```yaml
+dependencies:
+ angel_mongo: ^2.0.0
+```
+
+# Usage
+This library exposes one main class: `MongoService`.
+
+## Model
+`Model` is class with no real functionality; however, it represents a basic document, and your services should host inherited classes.
+Other Angel service providers host `Model` as well, so you will easily be able to modify your application if you ever switch databases.
+
+```dart
+class User extends Model {
+ String username;
+ String password;
+}
+
+main() async {
+ var db = new Db('mongodb://localhost:27017/local');
+ await db.open();
+
+ var service = app.use('/api/users', new MongoService(db.collection("users")));
+
+ service.afterCreated.listen((event) {
+ print("New user: ${event.result}");
+ });
+}
+```
+
+## MongoService
+This class interacts with a `DbCollection` (from mongo_dart) and serializing data to and from Maps.
+
+## Querying
+You can query these services as follows:
+
+ /path/to/service?foo=bar
+
+The above will query the database to find records where 'foo' equals 'bar'.
+
+The former will sort result in ascending order of creation, and so will the latter.
+
+ List queried = await MyService.index({r"$query": where.id(new ObjectId.fromHexString("some hex string"})));
+
+And, of course, you can use mongo_dart queries. Just pass it as `query` within `params`.
+
+See the tests for more usage examples.
diff --git a/packages/mongo/analysis_options.yaml b/packages/mongo/analysis_options.yaml
new file mode 100644
index 00000000..c230cee7
--- /dev/null
+++ b/packages/mongo/analysis_options.yaml
@@ -0,0 +1,4 @@
+include: package:pedantic/analysis_options.yaml
+analyzer:
+ strong-mode:
+ implicit-casts: false
\ No newline at end of file
diff --git a/packages/mongo/example/example.dart b/packages/mongo/example/example.dart
new file mode 100644
index 00000000..161c9173
--- /dev/null
+++ b/packages/mongo/example/example.dart
@@ -0,0 +1,15 @@
+import 'package:angel_framework/angel_framework.dart';
+import 'package:angel_mongo/angel_mongo.dart';
+import 'package:mongo_dart/mongo_dart.dart';
+
+main() async {
+ var app = new Angel();
+ Db db = new Db('mongodb://localhost:27017/local');
+ await db.open();
+
+ var service = app.use('/api/users', new MongoService(db.collection("users")));
+
+ service.afterCreated.listen((event) {
+ print("New user: ${event.result}");
+ });
+}
diff --git a/packages/mongo/lib/angel_mongo.dart b/packages/mongo/lib/angel_mongo.dart
new file mode 100644
index 00000000..56747813
--- /dev/null
+++ b/packages/mongo/lib/angel_mongo.dart
@@ -0,0 +1,3 @@
+library angel_mongo;
+
+export 'services.dart';
diff --git a/packages/mongo/lib/model.dart b/packages/mongo/lib/model.dart
new file mode 100644
index 00000000..1c8c8ed4
--- /dev/null
+++ b/packages/mongo/lib/model.dart
@@ -0,0 +1,14 @@
+library angel_mongo.model;
+
+/// Use the `Model` class defined in `package:angel_framework/common.dart` instead.
+@deprecated
+class Model {
+ /// This instance's ID.
+ String id;
+
+ /// The time at which this instance was created.
+ DateTime createdAt;
+
+ /// The time at which this instance was last updated.
+ DateTime updatedAt;
+}
diff --git a/packages/mongo/lib/mongo_service.dart b/packages/mongo/lib/mongo_service.dart
new file mode 100644
index 00000000..5a2dfee6
--- /dev/null
+++ b/packages/mongo/lib/mongo_service.dart
@@ -0,0 +1,237 @@
+part of angel_mongo.services;
+
+/// Manipulates data from MongoDB as Maps.
+class MongoService extends Service> {
+ DbCollection collection;
+
+ /// If set to `true`, clients can remove all items by passing a `null` `id` to `remove`.
+ ///
+ /// `false` by default.
+ final bool allowRemoveAll;
+
+ /// If set to `true`, parameters in `req.query` are applied to the database query.
+ final bool allowQuery;
+
+ /// No longer used. Will be removed by `2.1.0`.
+ @deprecated
+ final bool debug;
+
+ MongoService(DbCollection this.collection,
+ {this.allowRemoveAll = false, this.allowQuery = true, this.debug = true})
+ : super();
+
+ SelectorBuilder _makeQuery([Map params_]) {
+ Map params = new Map.from(params_ ?? {});
+ params = params..remove('provider');
+ SelectorBuilder result = where.exists('_id');
+
+ // You can pass a SelectorBuilder as 'query';
+ if (params['query'] is SelectorBuilder) {
+ return params['query'] as SelectorBuilder;
+ }
+
+ for (var key in params.keys) {
+ if (key == r'$sort' ||
+ key == r'$query' &&
+ (allowQuery == true || !params.containsKey('provider'))) {
+ if (params[key] is Map) {
+ // If they send a map, then we'll sort by every key in the map
+ for (String fieldName in params[key].keys.where((x) => x is String)) {
+ var sorter = params[key][fieldName];
+ if (sorter is num) {
+ result = result.sortBy(fieldName, descending: sorter == -1);
+ } else if (sorter is String) {
+ result = result.sortBy(fieldName, descending: sorter == "-1");
+ } else if (sorter is SelectorBuilder) {
+ result = result.and(sorter);
+ }
+ }
+ } else if (params[key] is String && key == r'$sort') {
+ // If they send just a string, then we'll sort
+ // by that, ascending
+ result = result.sortBy(params[key] as String);
+ }
+ } else if (key == 'query' &&
+ (allowQuery == true || !params.containsKey('provider'))) {
+ var query = params[key] as Map;
+ query?.forEach((key, v) {
+ var value = v is Map ? _filterNoQuery(v) : v;
+
+ if (!_NO_QUERY.contains(key) &&
+ value is! RequestContext &&
+ value is! ResponseContext) {
+ result = result.and(where.eq(key as String, value));
+ }
+ });
+ }
+ }
+
+ return result;
+ }
+
+ Map _jsonify(Map doc,
+ [Map params]) {
+ var result = {};
+
+ for (var key in doc.keys) {
+ var value = doc[key];
+ if (value is ObjectId) {
+ result[key] = value.toHexString();
+ } else if (value is! RequestContext && value is! ResponseContext) {
+ result[key] = value;
+ }
+ }
+
+ return _transformId(result);
+ }
+
+ @override
+ Future>> index(
+ [Map params]) async {
+ return await (await collection.find(_makeQuery(params)))
+ .map((x) => _jsonify(x, params))
+ .toList();
+ }
+
+ static const String _NONCE_KEY = '__angel__mongo__nonce__key__';
+
+ @override
+ Future