Merge pull request #87 from dukefirehawk/feature/fix-deprecated
Feature/fix deprecated
This commit is contained in:
commit
a7c750e70f
155 changed files with 164 additions and 5786 deletions
4
.github/workflows/dart.yml
vendored
4
.github/workflows/dart.yml
vendored
|
@ -21,7 +21,7 @@ jobs:
|
|||
|
||||
- uses: dart-lang/setup-dart@v1
|
||||
with:
|
||||
sdk: "2.17.0"
|
||||
sdk: "2.18.0"
|
||||
|
||||
- id: angel3_container_upgrade
|
||||
name: angel3_container; Upgrade depedencies
|
||||
|
@ -87,7 +87,7 @@ jobs:
|
|||
|
||||
- uses: dart-lang/setup-dart@v1
|
||||
with:
|
||||
sdk: "2.17.0"
|
||||
sdk: "2.18.0"
|
||||
|
||||
# Angel3 ORM
|
||||
- id: angel3_orm_upgrade
|
||||
|
|
BIN
archived_packages/eventsource.zip
Normal file
BIN
archived_packages/eventsource.zip
Normal file
Binary file not shown.
65
archived_packages/eventsource/.gitignore
vendored
65
archived_packages/eventsource/.gitignore
vendored
|
@ -1,65 +0,0 @@
|
|||
# Created by .ignore support plugin (hsz.mobi)
|
||||
### 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
|
||||
|
||||
# Sensitive or high-churn files:
|
||||
.idea/**/dataSources/
|
||||
.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
|
||||
|
||||
# CMake
|
||||
cmake-build-debug/
|
||||
|
||||
# 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
|
||||
|
||||
# Cursive Clojure plugin
|
||||
.idea/replstate.xml
|
||||
|
||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||
com_crashlytics_export_strings.xml
|
||||
crashlytics.properties
|
||||
crashlytics-build.properties
|
||||
fabric.properties
|
||||
### Dart template
|
||||
# See https://www.dartlang.org/tools/private-files.html
|
||||
|
||||
# Files and directories created by pub
|
||||
.packages
|
||||
.pub/
|
||||
build/
|
||||
# If you're building an application, you may want to check-in your pubspec.lock
|
||||
pubspec.lock
|
||||
|
||||
# Directory created by dartdoc
|
||||
# If you don't generate documentation locally you can remove this line.
|
||||
doc/api/
|
||||
.dart_tool
|
|
@ -1,21 +0,0 @@
|
|||
MIT License (MIT)
|
||||
|
||||
Copyright (c) 2021 dukefirehawk.com
|
||||
|
||||
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.
|
|
@ -1,33 +0,0 @@
|
|||
# eventsource
|
||||
Server-sent Events (SSE) plugin for Angel.
|
||||
|
||||
## Installation
|
||||
In your `pubspec.yaml`:
|
||||
|
||||
```yaml
|
||||
dependencies:
|
||||
angel_eventsource: ^1.0.0
|
||||
```
|
||||
|
||||
## Usage
|
||||
SSE and WebSockets are somewhat similar in that they allow pushing of events from server
|
||||
to client. SSE is not bi-directional, but the same abstractions used for WebSockets can be
|
||||
applied to SSE easily.
|
||||
|
||||
For this reason, the `AngelEventSourcePublisher` class is a simple adapter that
|
||||
hands control of SSE requests to an existing `AngelWebSocket` driver.
|
||||
|
||||
So, using this is pretty straightforward. You can dispatch events
|
||||
via WebSocket as per usual, and have them propagated to SSE clients
|
||||
as well.
|
||||
|
||||
```dart
|
||||
var app = new Angel();
|
||||
var ws = new AngelWebSocket(app);
|
||||
var events = new AngelEventSourcePublisher(ws);
|
||||
|
||||
await app.configure(ws.configureServer);
|
||||
|
||||
app.all('/ws', ws.handleRequest);
|
||||
app.get('/events', events.handleRequest);
|
||||
```
|
|
@ -1,3 +0,0 @@
|
|||
analyzer:
|
||||
strong-mode:
|
||||
implicit-casts: false
|
|
@ -1,14 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="WEB_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<excludeFolder url="file://$MODULE_DIR$/.pub" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" name="Dart SDK" level="project" />
|
||||
<orderEntry type="library" name="Dart Packages" level="project" />
|
||||
</component>
|
||||
</module>
|
|
@ -1,50 +0,0 @@
|
|||
import 'package:angel_eventsource/server.dart';
|
||||
import 'package:angel_framework/angel_framework.dart';
|
||||
import 'package:angel_framework/http.dart';
|
||||
import 'package:angel_websocket/server.dart';
|
||||
import 'package:eventsource/eventsource.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'pretty_logging.dart';
|
||||
|
||||
main() async {
|
||||
var app = new Angel();
|
||||
var ws = new AngelWebSocket(app);
|
||||
var events = new AngelEventSourcePublisher(ws);
|
||||
|
||||
await app.configure(ws.configureServer);
|
||||
|
||||
app.use('/api/todos', new MapService());
|
||||
app.all('/ws', ws.handleRequest);
|
||||
app.get('/events', events.handleRequest);
|
||||
|
||||
app.logger = new Logger('angel_eventsource')..onRecord.listen(prettyLog);
|
||||
|
||||
var http = new AngelHttp(app);
|
||||
var server = await http.startServer('127.0.0.1', 3000);
|
||||
var url = Uri.parse('http://${server.address.address}:${server.port}');
|
||||
print('Listening at $url');
|
||||
|
||||
/*
|
||||
var sock = await Socket.connect(server.address, server.port);
|
||||
sock
|
||||
..writeln('GET /sse HTTP/1.1')
|
||||
..writeln('Accept: text/event-stream')
|
||||
..writeln('Host: 127.0.0.1')
|
||||
..writeln()
|
||||
..flush();
|
||||
sock.transform(UTF8.decoder).transform(const LineSplitter()).listen(print);
|
||||
*/
|
||||
|
||||
/*
|
||||
var client = new HttpClient();
|
||||
var rq = await client.openUrl('GET', url);
|
||||
var rs = await rq.close();
|
||||
rs.transform(UTF8.decoder).transform(const LineSplitter()).listen(print);
|
||||
*/
|
||||
|
||||
var eventSource = await EventSource.connect(url);
|
||||
|
||||
await for (var event in eventSource) {
|
||||
print(event.data);
|
||||
}
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
import 'package:console/console.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
|
||||
/// Prints the contents of a [LogRecord] with pretty colors.
|
||||
prettyLog(LogRecord record) async {
|
||||
var pen = new TextPen();
|
||||
chooseLogColor(pen.reset(), record.level);
|
||||
pen(record.toString());
|
||||
|
||||
if (record.error != null) pen(record.error.toString());
|
||||
if (record.stackTrace != null) pen(record.stackTrace.toString());
|
||||
|
||||
pen();
|
||||
}
|
||||
|
||||
/// Chooses a color based on the logger [level].
|
||||
void chooseLogColor(TextPen pen, Level level) {
|
||||
if (level == Level.SHOUT)
|
||||
pen.darkRed();
|
||||
else if (level == Level.SEVERE)
|
||||
pen.red();
|
||||
else if (level == Level.WARNING)
|
||||
pen.yellow();
|
||||
else if (level == Level.INFO)
|
||||
pen.magenta();
|
||||
else if (level == Level.FINER)
|
||||
pen.blue();
|
||||
else if (level == Level.FINEST) pen.darkBlue();
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
export 'package:angel_websocket/angel_websocket.dart';
|
|
@ -1,51 +0,0 @@
|
|||
import 'dart:async';
|
||||
import 'package:angel_framework/angel_framework.dart';
|
||||
import 'package:angel_websocket/server.dart';
|
||||
import 'package:eventsource/eventsource.dart';
|
||||
import 'package:eventsource/src/encoder.dart';
|
||||
import 'package:eventsource/publisher.dart';
|
||||
import 'package:stream_channel/stream_channel.dart';
|
||||
|
||||
class AngelEventSourcePublisher {
|
||||
final AngelWebSocket webSocketDriver;
|
||||
|
||||
final String channel;
|
||||
|
||||
int _count = 0;
|
||||
|
||||
AngelEventSourcePublisher(this.webSocketDriver, {this.channel: ''});
|
||||
|
||||
Future handleRequest(RequestContext req, ResponseContext res) async {
|
||||
if (!req.accepts('text/event-stream', strict: false))
|
||||
throw new AngelHttpException.badRequest();
|
||||
|
||||
res.headers.addAll({
|
||||
'cache-control': 'no-cache, no-store, must-revalidate',
|
||||
'content-type': 'text/event-stream',
|
||||
'connection': 'keep-alive',
|
||||
});
|
||||
|
||||
var acceptsGzip =
|
||||
(req.headers['accept-encoding']?.contains('gzip') == true);
|
||||
|
||||
if (acceptsGzip) res.headers['content-encoding'] = 'gzip';
|
||||
|
||||
var eventSink = new EventSourceEncoder(compressed: acceptsGzip)
|
||||
.startChunkedConversion(res);
|
||||
|
||||
// Listen for events.
|
||||
var ctrl = new StreamChannelController();
|
||||
|
||||
// Incoming events are strings, and should be sent via the eventSink.
|
||||
ctrl.local.stream.cast<String>().listen((data) {
|
||||
eventSink.add(new Event(
|
||||
id: (_count++).toString(),
|
||||
data: data,
|
||||
));
|
||||
});
|
||||
|
||||
// Create a new WebSocketContext, and hand it off to the driver.
|
||||
var socket = new WebSocketContext(ctrl.foreign, req, res);
|
||||
return await webSocketDriver.handleClient(socket);
|
||||
}
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
name: angel_eventsource
|
||||
version: 2.0.0
|
||||
description: Server-sent Events (SSE) plugin for Angel.
|
||||
homepage: https://github.com/angel-dart/eventsource
|
||||
publish_to: none
|
||||
environment:
|
||||
sdk: ">=2.10.0 <3.0.0"
|
||||
dependencies:
|
||||
angel_framework:
|
||||
git:
|
||||
url: https://github.com/dukefirehawk/angel.git
|
||||
ref: sdk-2.12.x
|
||||
path: packages/framework
|
||||
angel_websocket:
|
||||
git:
|
||||
url: https://github.com/dukefirehawk/angel.git
|
||||
ref: sdk-2.12.x
|
||||
path: packages/websocket
|
||||
eventsource:
|
||||
git:
|
||||
url: https://github.com/dukefirehawk/dart-eventsource.git
|
||||
stream_channel: ^2.0.0
|
||||
dev_dependencies:
|
||||
console: ^4.0.0
|
||||
logging: ^1.0.0
|
||||
test: ^1.16.5
|
BIN
archived_packages/orm.zip
Normal file
BIN
archived_packages/orm.zip
Normal file
Binary file not shown.
|
@ -1,21 +0,0 @@
|
|||
MIT License (MIT)
|
||||
|
||||
Copyright (c) 2021 dukefirehawk.com
|
||||
|
||||
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.
|
|
@ -1,5 +0,0 @@
|
|||
name: angel_orm_sqlite
|
||||
environment:
|
||||
sdk: ">=2.16.0 <3.0.0"
|
||||
dependencies:
|
||||
angel_orm: ^2.0.0-dev
|
BIN
archived_packages/poll.zip
Normal file
BIN
archived_packages/poll.zip
Normal file
Binary file not shown.
64
archived_packages/poll/.gitignore
vendored
64
archived_packages/poll/.gitignore
vendored
|
@ -1,64 +0,0 @@
|
|||
# Created by .ignore support plugin (hsz.mobi)
|
||||
### 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
|
||||
|
||||
# Sensitive or high-churn files:
|
||||
.idea/**/dataSources/
|
||||
.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
|
||||
|
||||
# CMake
|
||||
cmake-build-debug/
|
||||
|
||||
# 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
|
||||
|
||||
# Cursive Clojure plugin
|
||||
.idea/replstate.xml
|
||||
|
||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||
com_crashlytics_export_strings.xml
|
||||
crashlytics.properties
|
||||
crashlytics-build.properties
|
||||
fabric.properties
|
||||
### Dart template
|
||||
# See https://www.dartlang.org/tools/private-files.html
|
||||
|
||||
# Files and directories created by pub
|
||||
.packages
|
||||
.pub/
|
||||
build/
|
||||
# If you're building an application, you may want to check-in your pubspec.lock
|
||||
pubspec.lock
|
||||
|
||||
# Directory created by dartdoc
|
||||
# If you don't generate documentation locally you can remove this line.
|
||||
doc/api/
|
|
@ -1 +0,0 @@
|
|||
language: dart
|
|
@ -1,12 +0,0 @@
|
|||
Primary Authors
|
||||
===============
|
||||
|
||||
* __[Thomas Hii](dukefirehawk.apps@gmail.com)__
|
||||
|
||||
Thomas is the current maintainer of the code base. He has refactored and migrated the
|
||||
code base to support NNBD.
|
||||
|
||||
* __[Tobe O](thosakwe@gmail.com)__
|
||||
|
||||
Tobe has written much of the original code prior to NNBD migration. He has moved on and
|
||||
is no longer involved with the project.
|
|
@ -1,9 +0,0 @@
|
|||
# Change Log
|
||||
|
||||
## 2.0.0
|
||||
|
||||
* Migrated to support Dart >= 2.12 NNBD
|
||||
|
||||
## 1.0.0
|
||||
|
||||
* Created package + tests
|
|
@ -1,29 +0,0 @@
|
|||
BSD 3-Clause License
|
||||
|
||||
Copyright (c) 2021, dukefirehawk.com
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@ -1,42 +0,0 @@
|
|||
# poll
|
||||
[![Pub](https://img.shields.io/pub/v/angel_poll.svg)](https://pub.dartlang.org/packages/angel_poll)
|
||||
[![build status](https://travis-ci.org/angel-dart/poll.svg?branch=master)](https://travis-ci.org/angel-dart/poll)
|
||||
|
||||
`package:angel_client` support for "realtime" interactions with Angel via long polling.
|
||||
|
||||
Angel supports [WebSockets](https://github.com/angel-dart/websocket) on the server and client, which
|
||||
makes it very straightforward to implement realtime collections. However, not every user's browser
|
||||
supports WebSockets. In such a case, applications might *gracefully degrade* to long-polling
|
||||
the server for changes.
|
||||
|
||||
A `PollingService` wraps a client-side `Service` (typically a REST-based one), and calls its
|
||||
`index` method at a regular interval. After indexing, the `PollingService` performs a diff
|
||||
and identifies whether items have been created, modified, or removed. The updates are sent out
|
||||
through `onCreated`, `onModified`, etc., effectively managing a real-time collection of data.
|
||||
|
||||
A common use-case would be passing this service to `ServiceList`, a class that manages the state
|
||||
of a collection managed in real-time.
|
||||
|
||||
```dart
|
||||
import 'package:angel_client/io.dart';
|
||||
import 'package:angel_poll/angel_poll.dart';
|
||||
|
||||
main() {
|
||||
var app = new Rest('http://localhost:3000');
|
||||
|
||||
var todos = new ServiceList(
|
||||
new PollingService(
|
||||
// Typically, you'll pass a REST-based service instance here.
|
||||
app.service('api/todos'),
|
||||
|
||||
// `index` called every 5 seconds
|
||||
const Duration(seconds: 5),
|
||||
),
|
||||
);
|
||||
|
||||
todos.onChange.listen((_) {
|
||||
// Something happened here.
|
||||
// Maybe an item was created, modified, etc.
|
||||
});
|
||||
}
|
||||
```
|
|
@ -1 +0,0 @@
|
|||
include: package:lints/recommended.yaml
|
|
@ -1,21 +0,0 @@
|
|||
import 'package:angel_client/io.dart';
|
||||
import 'package:angel_poll/angel_poll.dart';
|
||||
|
||||
void main() {
|
||||
var app = Rest('http://localhost:3000');
|
||||
|
||||
var todos = ServiceList(
|
||||
PollingService(
|
||||
// Typically, you'll pass a REST-based service instance here.
|
||||
app.service('api/todos'),
|
||||
|
||||
// `index` called every 5 seconds
|
||||
const Duration(seconds: 5),
|
||||
),
|
||||
);
|
||||
|
||||
todos.onChange.listen((_) {
|
||||
// Something happened here.
|
||||
// Maybe an item was created, modified, etc.
|
||||
});
|
||||
}
|
|
@ -1,265 +0,0 @@
|
|||
import 'dart:async';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:angel_client/angel_client.dart';
|
||||
|
||||
/// A [Service] that facilitates real-time updates via the long polling of an [inner] service.
|
||||
///
|
||||
/// Works well with [ServiceList].
|
||||
class PollingService extends Service {
|
||||
/// The underlying [Service] that does the actual communication with the server.
|
||||
final Service inner;
|
||||
|
||||
/// Perform computations after polling to discern whether new items were created.
|
||||
final bool checkForCreated;
|
||||
|
||||
/// Perform computations after polling to discern whether items were modified.
|
||||
final bool checkForModified;
|
||||
|
||||
/// Perform computations after polling to discern whether items were removed.
|
||||
final bool checkForRemoved;
|
||||
|
||||
/// An [EqualityBy] used to compare the ID's of two items.
|
||||
///
|
||||
/// Defaults to comparing the [idField] of two `Map` instances.
|
||||
final EqualityBy compareId;
|
||||
|
||||
/// An [Equality] used to discern whether two items, with the same [idField], are the same item.
|
||||
///
|
||||
/// Defaults to [MapEquality], which deep-compares `Map` instances.
|
||||
final Equality compareItems;
|
||||
|
||||
/// A [String] used as an index through which to compare `Map` instances.
|
||||
///
|
||||
/// Defaults to `id`.
|
||||
final String idField;
|
||||
|
||||
/// If `true` (default: `false`), then `index` events will be handled as a [Map] containing a `data` field.
|
||||
///
|
||||
/// See https://github.com/angel-dart/paginate.
|
||||
final bool asPaginated;
|
||||
|
||||
final List _items = [];
|
||||
final List<StreamSubscription> _subs = [];
|
||||
|
||||
final StreamController<List<dynamic>> _onIndexed = StreamController(),
|
||||
_onRead = StreamController(),
|
||||
_onCreated = StreamController(),
|
||||
_onModified = StreamController(),
|
||||
_onUpdated = StreamController(),
|
||||
_onRemoved = StreamController();
|
||||
|
||||
late Timer _timer;
|
||||
|
||||
@override
|
||||
Angel get app => inner.app;
|
||||
|
||||
@override
|
||||
Stream<List<dynamic>> get onIndexed => _onIndexed.stream;
|
||||
|
||||
@override
|
||||
Stream get onRead => _onRead.stream;
|
||||
|
||||
@override
|
||||
Stream get onCreated => _onCreated.stream;
|
||||
|
||||
@override
|
||||
Stream get onModified => _onModified.stream;
|
||||
|
||||
@override
|
||||
Stream get onUpdated => _onUpdated.stream;
|
||||
|
||||
@override
|
||||
Stream get onRemoved => _onRemoved.stream;
|
||||
|
||||
PollingService(this.inner, Duration interval,
|
||||
{this.checkForCreated = true,
|
||||
this.checkForModified = true,
|
||||
this.checkForRemoved = true,
|
||||
this.idField = 'id',
|
||||
this.asPaginated = false,
|
||||
EqualityBy? compareId,
|
||||
this.compareItems = const MapEquality()})
|
||||
: compareId = compareId ?? EqualityBy((map) => map[idField]) {
|
||||
_timer = Timer.periodic(interval, (_) {
|
||||
index().catchError((error) {
|
||||
_onIndexed.addError(error as Object);
|
||||
});
|
||||
});
|
||||
|
||||
var streams = <Stream, StreamController>{
|
||||
inner.onRead: _onRead,
|
||||
inner.onCreated: _onCreated,
|
||||
inner.onModified: _onModified,
|
||||
inner.onUpdated: _onUpdated,
|
||||
inner.onRemoved: _onRemoved,
|
||||
};
|
||||
|
||||
streams.forEach((stream, ctrl) {
|
||||
_subs.add(stream.listen(ctrl.add, onError: ctrl.addError));
|
||||
});
|
||||
|
||||
_subs.add(
|
||||
inner.onIndexed.listen(
|
||||
_handleIndexed,
|
||||
onError: _onIndexed.addError,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future close() async {
|
||||
_timer.cancel();
|
||||
for (var s in _subs) {
|
||||
s.cancel();
|
||||
}
|
||||
await _onIndexed.close();
|
||||
await _onRead.close();
|
||||
await _onCreated.close();
|
||||
await _onModified.close();
|
||||
await _onUpdated.close();
|
||||
await _onRemoved.close();
|
||||
}
|
||||
|
||||
// TODO: To revisit this logic
|
||||
@override
|
||||
Future<List<dynamic>> index([Map? params]) {
|
||||
return inner.index().then((data) {
|
||||
//return asPaginated == true ? data['data'] : data;
|
||||
//return asPaginated == true ? data[0] : data;
|
||||
return data;
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
@override
|
||||
Future index([Map params]) {
|
||||
}
|
||||
*/
|
||||
@override
|
||||
Future remove(id, [Map<String, dynamic>? params]) {
|
||||
return inner.remove(id, params).then((result) {
|
||||
_items.remove(result);
|
||||
return result;
|
||||
}).catchError(_onRemoved.addError);
|
||||
}
|
||||
|
||||
dynamic _handleUpdate(result) {
|
||||
var index = -1;
|
||||
|
||||
for (var i = 0; i < _items.length; i++) {
|
||||
if (compareId.equals(_items[i], result)) {
|
||||
index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (index > -1) {
|
||||
_items[index] = result;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@override
|
||||
Future update(id, data, [Map<String, dynamic>? params]) {
|
||||
return inner
|
||||
.update(id, data, params)
|
||||
.then(_handleUpdate)
|
||||
.catchError(_onUpdated.addError);
|
||||
}
|
||||
|
||||
@override
|
||||
Future modify(id, data, [Map<String, dynamic>? params]) {
|
||||
return inner
|
||||
.modify(id, data, params)
|
||||
.then(_handleUpdate)
|
||||
.catchError(_onModified.addError);
|
||||
}
|
||||
|
||||
@override
|
||||
Future create(data, [Map<String, dynamic>? params]) {
|
||||
return inner.create(data, params).then((result) {
|
||||
_items.add(result);
|
||||
return result;
|
||||
}).catchError(_onCreated.addError);
|
||||
}
|
||||
|
||||
@override
|
||||
Future read(id, [Map<String, dynamic>? params]) {
|
||||
return inner.read(id, params);
|
||||
}
|
||||
|
||||
void _handleIndexed(List<dynamic> data) {
|
||||
//var items = asPaginated == true ? data['data'] : data;
|
||||
var items = data;
|
||||
var changesComputed = false;
|
||||
|
||||
if (checkForCreated != false) {
|
||||
var newItems = <int, dynamic>{};
|
||||
|
||||
for (var i = 0; i < items.length; i++) {
|
||||
var item = items[i];
|
||||
|
||||
if (!_items.any((i) => compareId.equals(i, item))) {
|
||||
newItems[i] = item;
|
||||
}
|
||||
}
|
||||
|
||||
newItems.forEach((index, item) {
|
||||
_items.insert(index, item);
|
||||
_onCreated.add([item]);
|
||||
});
|
||||
|
||||
changesComputed = newItems.isNotEmpty;
|
||||
}
|
||||
|
||||
if (checkForRemoved != false) {
|
||||
var removedItems = <int, dynamic>{};
|
||||
|
||||
for (var i = 0; i < _items.length; i++) {
|
||||
var item = _items[i];
|
||||
|
||||
if (!items.any((i) => compareId.equals(i, item))) {
|
||||
removedItems[i] = item;
|
||||
}
|
||||
}
|
||||
|
||||
removedItems.forEach((index, item) {
|
||||
_items.removeAt(index);
|
||||
_onRemoved.add([item]);
|
||||
});
|
||||
|
||||
changesComputed = changesComputed || removedItems.isNotEmpty;
|
||||
}
|
||||
|
||||
if (checkForModified != false) {
|
||||
var modifiedItems = <int, dynamic>{};
|
||||
|
||||
for (var item in items) {
|
||||
for (var i = 0; i < _items.length; i++) {
|
||||
var localItem = _items[i];
|
||||
|
||||
if (compareId.equals(item, localItem)) {
|
||||
if (!compareItems.equals(item, localItem)) {
|
||||
modifiedItems[i] = item;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
modifiedItems.forEach((index, item) {
|
||||
_onModified.add([_items[index] = item]);
|
||||
});
|
||||
|
||||
changesComputed = changesComputed || modifiedItems.isNotEmpty;
|
||||
}
|
||||
|
||||
if (!changesComputed) {
|
||||
_items
|
||||
..clear()
|
||||
..add(items);
|
||||
_onIndexed.add([items]);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="WEB_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<excludeFolder url="file://$MODULE_DIR$/.pub" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" name="Dart SDK" level="project" />
|
||||
<orderEntry type="library" name="Dart Packages" level="project" />
|
||||
</component>
|
||||
</module>
|
|
@ -1,23 +0,0 @@
|
|||
name: angel_poll
|
||||
version: 2.0.0
|
||||
description: package:angel_client support for "realtime" interactions with Angel via long polling.
|
||||
publish_to: none
|
||||
environment:
|
||||
sdk: '>=2.12.0 <3.0.0'
|
||||
homepage: https://github.com/angel-dart/poll
|
||||
dependencies:
|
||||
angel_client: # ^1.0.0
|
||||
git:
|
||||
url: https://github.com/dukefirehawk/angel.git
|
||||
ref: sdk-2.12.x_nnbd
|
||||
path: packages/client
|
||||
async: ^2.7.0
|
||||
collection: ^1.15.0
|
||||
dev_dependencies:
|
||||
angel_test:
|
||||
git:
|
||||
url: https://github.com/dukefirehawk/angel.git
|
||||
ref: sdk-2.12.x_nnbd
|
||||
path: packages/test
|
||||
test: ^1.17.8
|
||||
lints: ^1.0.0
|
|
@ -1,105 +0,0 @@
|
|||
import 'package:angel_framework/angel_framework.dart' as srv;
|
||||
import 'package:angel_container/mirrors.dart';
|
||||
import 'package:angel_poll/angel_poll.dart';
|
||||
import 'package:angel_test/angel_test.dart';
|
||||
import 'package:async/async.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
void main() {
|
||||
late srv.Service store;
|
||||
late TestClient client;
|
||||
late PollingService pollingService;
|
||||
|
||||
var created;
|
||||
late StreamQueue onCreated;
|
||||
late StreamQueue onModified;
|
||||
late StreamQueue onRemoved;
|
||||
|
||||
setUp(() async {
|
||||
var app = srv.Angel(reflector: MirrorsReflector());
|
||||
app.logger = Logger.detached('angel_poll')
|
||||
..onRecord.listen((rec) {
|
||||
print(rec);
|
||||
if (rec.error != null) {
|
||||
print(rec.error);
|
||||
print(rec.stackTrace);
|
||||
}
|
||||
});
|
||||
|
||||
store = app.use(
|
||||
'/api/todos',
|
||||
srv.MapService(
|
||||
autoIdAndDateFields: false,
|
||||
),
|
||||
);
|
||||
|
||||
client = await connectTo(app);
|
||||
|
||||
pollingService = PollingService(
|
||||
client.service('api/todos'),
|
||||
const Duration(milliseconds: 100),
|
||||
);
|
||||
|
||||
onCreated = StreamQueue(pollingService.onCreated);
|
||||
onModified = StreamQueue(pollingService.onModified);
|
||||
onRemoved = StreamQueue(pollingService.onRemoved);
|
||||
|
||||
created = await store.create({
|
||||
'id': '0',
|
||||
'text': 'Clean your room',
|
||||
'completed': false,
|
||||
});
|
||||
});
|
||||
|
||||
tearDown(() {
|
||||
onCreated.cancel();
|
||||
onModified.cancel();
|
||||
onRemoved.cancel();
|
||||
client.close();
|
||||
});
|
||||
|
||||
group('events', () {
|
||||
test('fires indexed', () async {
|
||||
var indexed = await pollingService.index();
|
||||
print(indexed);
|
||||
expect(await pollingService.onIndexed.first, indexed);
|
||||
});
|
||||
|
||||
test('fires created', () async {
|
||||
var result = await onCreated.next;
|
||||
print(result);
|
||||
expect(created, result);
|
||||
});
|
||||
|
||||
test('fires modified', () async {
|
||||
await pollingService.index();
|
||||
await store.modify('0', {
|
||||
'text': 'go to school',
|
||||
});
|
||||
|
||||
var result = await onModified.next;
|
||||
print(result);
|
||||
expect(result, Map.from({'': created})..['text'] = 'go to school');
|
||||
});
|
||||
|
||||
test('manual modify', () async {
|
||||
await pollingService.index();
|
||||
await pollingService.modify('0', {
|
||||
'text': 'eat',
|
||||
});
|
||||
|
||||
var result = await onModified.next;
|
||||
print(result);
|
||||
expect(result, Map.from({'': created})..['text'] = 'eat');
|
||||
});
|
||||
|
||||
test('fires removed', () async {
|
||||
await pollingService.index();
|
||||
var removed = await store.remove('0');
|
||||
var result = await onRemoved.next;
|
||||
print(result);
|
||||
expect(result, removed);
|
||||
});
|
||||
});
|
||||
}
|
BIN
archived_packages/relations.zip
Normal file
BIN
archived_packages/relations.zip
Normal file
Binary file not shown.
93
archived_packages/relations/.gitignore
vendored
93
archived_packages/relations/.gitignore
vendored
|
@ -1,93 +0,0 @@
|
|||
# See https://www.dartlang.org/tools/private-files.html
|
||||
|
||||
# Files and directories created by pub
|
||||
.buildlog
|
||||
.packages
|
||||
.project
|
||||
.pub/
|
||||
.scripts-bin/
|
||||
build/
|
||||
**/packages/
|
||||
|
||||
# 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
|
||||
|
||||
# SDK 1.20 and later (no longer creates packages directories)
|
||||
|
||||
# Older SDK versions
|
||||
# (Include if the minimum SDK version specified in pubsepc.yaml is earlier than 1.20)
|
||||
|
||||
|
||||
# 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
|
||||
|
||||
# Sensitive or high-churn files:
|
||||
.idea/**/dataSources/
|
||||
.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
|
|
@ -1 +0,0 @@
|
|||
language: dart
|
|
@ -1,12 +0,0 @@
|
|||
Primary Authors
|
||||
===============
|
||||
|
||||
* __[Thomas Hii](dukefirehawk.apps@gmail.com)__
|
||||
|
||||
Thomas is the current maintainer of the code base. He has refactored and migrated the
|
||||
code base to support NNBD.
|
||||
|
||||
* __[Tobe O](thosakwe@gmail.com)__
|
||||
|
||||
Tobe has written much of the original code prior to NNBD migration. He has moved on and
|
||||
is no longer involved with the project.
|
|
@ -1,9 +0,0 @@
|
|||
# Change Log
|
||||
|
||||
## 2.0.0
|
||||
|
||||
* Migrated to support Dart >= 2.12 NNBD
|
||||
|
||||
## 1.0.0
|
||||
|
||||
* Initial checkin
|
|
@ -1,29 +0,0 @@
|
|||
BSD 3-Clause License
|
||||
|
||||
Copyright (c) 2021, dukefirehawk.com
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@ -1,25 +0,0 @@
|
|||
# relations
|
||||
[![version 1.0.1](https://img.shields.io/badge/pub-v1.0.1-brightgreen.svg)](https://pub.dartlang.org/packages/angel_relations)
|
||||
[![build status](https://travis-ci.org/angel-dart/relations.svg)](https://travis-ci.org/angel-dart/relations)
|
||||
|
||||
Database-agnostic relations between Angel services.
|
||||
|
||||
```dart
|
||||
// Authors owning one book
|
||||
app.service('authors').afterAll(
|
||||
relations.hasOne('books', as: 'book', foreignKey: 'authorId'));
|
||||
|
||||
// Or multiple
|
||||
app.service('authors').afterAll(
|
||||
relations.hasMany('books', foreignKey: 'authorId'));
|
||||
|
||||
// Or, books belonging to authors
|
||||
app.service('books').afterAll(relations.belongsTo('authors'));
|
||||
```
|
||||
|
||||
Supports:
|
||||
* `hasOne`
|
||||
* `hasMany`
|
||||
* `hasManyThrough`
|
||||
* `belongsTo`
|
||||
* `belongsToMany`
|
|
@ -1 +0,0 @@
|
|||
include: package:lints/recommended.yaml
|
|
@ -1,9 +0,0 @@
|
|||
/// Hooks to populate data returned from services, in a fashion
|
||||
/// reminiscent of a relational database.
|
||||
library angel_relations;
|
||||
|
||||
export 'src/belongs_to_many.dart';
|
||||
export 'src/belongs_to.dart';
|
||||
export 'src/has_many.dart';
|
||||
export 'src/has_many_through.dart';
|
||||
export 'src/has_one.dart';
|
|
@ -1,75 +0,0 @@
|
|||
import 'dart:async';
|
||||
import 'dart:mirrors';
|
||||
import 'package:angel_framework/angel_framework.dart';
|
||||
import 'plural.dart' as pluralize;
|
||||
import 'no_service.dart';
|
||||
|
||||
/// Represents a relationship in which the current [service] "belongs to"
|
||||
/// a single member of the service at [servicePath]. Use [as] to set the name
|
||||
/// on the target object.
|
||||
///
|
||||
/// Defaults:
|
||||
/// * [localKey]: `userId`
|
||||
/// * [foreignKey]: `id`
|
||||
HookedServiceEventListener belongsTo(Pattern servicePath,
|
||||
{String? as,
|
||||
String? foreignKey,
|
||||
String? localKey,
|
||||
Function(dynamic obj)? getForeignKey,
|
||||
Function(dynamic foreign, dynamic obj)? assignForeignObject}) {
|
||||
var localId = localKey;
|
||||
var foreignName =
|
||||
as?.isNotEmpty == true ? as! : pluralize.singular(servicePath.toString());
|
||||
|
||||
localId ??= foreignName + 'Id';
|
||||
|
||||
return (HookedServiceEvent e) async {
|
||||
var ref = e.getService(servicePath);
|
||||
if (ref == null) throw noService(servicePath);
|
||||
|
||||
dynamic _getForeignKey(obj) {
|
||||
if (getForeignKey != null) {
|
||||
return getForeignKey(obj);
|
||||
} else if (obj is Map) {
|
||||
return obj[localId];
|
||||
} else if (localId == null || localId == 'userId') {
|
||||
return obj.userId;
|
||||
} else {
|
||||
return reflect(obj).getField(Symbol(localId)).reflectee;
|
||||
}
|
||||
}
|
||||
|
||||
dynamic _assignForeignObject(foreign, obj) {
|
||||
if (assignForeignObject != null) {
|
||||
return assignForeignObject(foreign, obj);
|
||||
} else if (obj is Map) {
|
||||
obj[foreignName] = foreign;
|
||||
} else {
|
||||
reflect(obj).setField(Symbol(foreignName), foreign);
|
||||
}
|
||||
}
|
||||
|
||||
Future _normalize(obj) async {
|
||||
if (obj != null) {
|
||||
var id = await _getForeignKey(obj);
|
||||
var indexed = await ref.index({
|
||||
'query': {foreignKey ?? 'id': id}
|
||||
});
|
||||
|
||||
if (indexed is! List || indexed.isNotEmpty != true) {
|
||||
await _assignForeignObject(null, obj);
|
||||
} else {
|
||||
var child = indexed.first;
|
||||
await _assignForeignObject(child, obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (e.result is Iterable) {
|
||||
//await Future.wait(e.result.map(_normalize));
|
||||
await e.result.map(_normalize);
|
||||
} else {
|
||||
await _normalize(e.result);
|
||||
}
|
||||
};
|
||||
}
|
|
@ -1,75 +0,0 @@
|
|||
import 'dart:async';
|
||||
import 'dart:mirrors';
|
||||
import 'package:angel_framework/angel_framework.dart';
|
||||
import 'plural.dart' as pluralize;
|
||||
import 'no_service.dart';
|
||||
|
||||
/// Represents a relationship in which the current [service] "belongs to"
|
||||
/// multiple members of the service at [servicePath]. Use [as] to set the name
|
||||
/// on the target object.
|
||||
///
|
||||
/// Defaults:
|
||||
/// * [foreignKey]: `userId`
|
||||
/// * [localKey]: `id`
|
||||
HookedServiceEventListener belongsToMany(Pattern servicePath,
|
||||
{String? as,
|
||||
String? foreignKey,
|
||||
String? localKey,
|
||||
Function(dynamic obj)? getForeignKey,
|
||||
Function(dynamic foreign, dynamic obj)? assignForeignObject}) {
|
||||
var localId = localKey;
|
||||
var foreignName =
|
||||
as?.isNotEmpty == true ? as! : pluralize.plural(servicePath.toString());
|
||||
|
||||
localId ??= foreignName + 'Id';
|
||||
|
||||
return (HookedServiceEvent e) async {
|
||||
var ref = e.getService(servicePath);
|
||||
if (ref == null) throw noService(servicePath);
|
||||
|
||||
dynamic _getForeignKey(obj) {
|
||||
if (getForeignKey != null) {
|
||||
return getForeignKey(obj);
|
||||
} else if (obj is Map) {
|
||||
return obj[localId];
|
||||
} else if (localId == null || localId == 'userId') {
|
||||
return obj.userId;
|
||||
} else {
|
||||
return reflect(obj).getField(Symbol(localId)).reflectee;
|
||||
}
|
||||
}
|
||||
|
||||
dynamic _assignForeignObject(foreign, obj) {
|
||||
if (assignForeignObject != null) {
|
||||
return assignForeignObject(foreign as List?, obj);
|
||||
} else if (obj is Map) {
|
||||
obj[foreignName] = foreign;
|
||||
} else {
|
||||
reflect(obj).setField(Symbol(foreignName), foreign);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _normalize(obj) async {
|
||||
if (obj != null) {
|
||||
var id = await _getForeignKey(obj);
|
||||
var indexed = await ref.index({
|
||||
'query': {foreignKey ?? 'id': id}
|
||||
});
|
||||
|
||||
if (indexed is! List || indexed.isNotEmpty != true) {
|
||||
await _assignForeignObject(null, obj);
|
||||
} else {
|
||||
var child = indexed is Iterable ? indexed.toList() : [indexed];
|
||||
await _assignForeignObject(child, obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (e.result is Iterable) {
|
||||
//await Future.wait(e.result.map(_normalize));
|
||||
await e.result.map(_normalize);
|
||||
} else {
|
||||
await _normalize(e.result);
|
||||
}
|
||||
};
|
||||
}
|
|
@ -1,70 +0,0 @@
|
|||
import 'dart:async';
|
||||
import 'dart:mirrors';
|
||||
import 'package:angel_framework/angel_framework.dart';
|
||||
import 'plural.dart' as pluralize;
|
||||
import 'no_service.dart';
|
||||
|
||||
/// Represents a relationship in which the current [service] "owns"
|
||||
/// members of the service at [servicePath]. Use [as] to set the name
|
||||
/// on the target object.
|
||||
///
|
||||
/// Defaults:
|
||||
/// * [foreignKey]: `userId`
|
||||
/// * [localKey]: `id`
|
||||
HookedServiceEventListener hasMany(Pattern servicePath,
|
||||
{String? as,
|
||||
String? foreignKey,
|
||||
String? localKey,
|
||||
Function(dynamic obj)? getLocalKey,
|
||||
Function(dynamic foreign, dynamic obj)? assignForeignObjects}) {
|
||||
return (HookedServiceEvent e) async {
|
||||
var ref = e.getService(servicePath);
|
||||
var foreignName =
|
||||
as?.isNotEmpty == true ? as : pluralize.plural(servicePath.toString());
|
||||
if (ref == null) throw noService(servicePath);
|
||||
|
||||
dynamic _getLocalKey(obj) {
|
||||
if (getLocalKey != null) {
|
||||
return getLocalKey(obj);
|
||||
} else if (obj is Map) {
|
||||
return obj[localKey ?? 'id'];
|
||||
} else if (localKey == null || localKey == 'id') {
|
||||
return obj.id;
|
||||
} else {
|
||||
return reflect(obj).getField(Symbol(localKey)).reflectee;
|
||||
}
|
||||
}
|
||||
|
||||
dynamic _assignForeignObjects(foreign, obj) {
|
||||
if (assignForeignObjects != null) {
|
||||
return assignForeignObjects(foreign, obj);
|
||||
} else if (obj is Map) {
|
||||
obj[foreignName] = foreign;
|
||||
} else {
|
||||
reflect(obj).setField(Symbol(foreignName!), foreign);
|
||||
}
|
||||
}
|
||||
|
||||
Future _normalize(obj) async {
|
||||
if (obj != null) {
|
||||
var id = await _getLocalKey(obj);
|
||||
var indexed = await ref.index({
|
||||
'query': {foreignKey ?? 'userId': id}
|
||||
});
|
||||
|
||||
if (indexed is! List || indexed.isNotEmpty != true) {
|
||||
await _assignForeignObjects([], obj);
|
||||
} else {
|
||||
await _assignForeignObjects(indexed, obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (e.result is Iterable) {
|
||||
//await Future.wait(e.result.map(_normalize));
|
||||
await e.result.map(_normalize);
|
||||
} else {
|
||||
await _normalize(e.result);
|
||||
}
|
||||
};
|
||||
}
|
|
@ -1,97 +0,0 @@
|
|||
import 'dart:async';
|
||||
import 'dart:mirrors';
|
||||
import 'package:angel_framework/angel_framework.dart';
|
||||
import 'plural.dart' as pluralize;
|
||||
import 'no_service.dart';
|
||||
|
||||
HookedServiceEventListener hasManyThrough(String servicePath, String pivotPath,
|
||||
{String? as,
|
||||
String? localKey,
|
||||
String? pivotKey,
|
||||
String? foreignKey,
|
||||
Function(dynamic obj)? getLocalKey,
|
||||
Function(dynamic obj)? getPivotKey,
|
||||
Function(dynamic obj)? getForeignKey,
|
||||
Function(dynamic foreign, dynamic obj)? assignForeignObjects}) {
|
||||
var foreignName =
|
||||
as?.isNotEmpty == true ? as : pluralize.plural(servicePath.toString());
|
||||
|
||||
return (HookedServiceEvent e) async {
|
||||
var pivotService = e.getService(pivotPath);
|
||||
var foreignService = e.getService(servicePath);
|
||||
|
||||
if (pivotService == null) {
|
||||
throw noService(pivotPath);
|
||||
} else if (foreignService == null) throw noService(servicePath);
|
||||
|
||||
dynamic _assignForeignObjects(foreign, obj) {
|
||||
if (assignForeignObjects != null) {
|
||||
return assignForeignObjects(foreign, obj);
|
||||
} else if (obj is Map) {
|
||||
obj[foreignName] = foreign;
|
||||
} else {
|
||||
reflect(obj).setField(Symbol(foreignName!), foreign);
|
||||
}
|
||||
}
|
||||
|
||||
dynamic _getLocalKey(obj) {
|
||||
if (getLocalKey != null) {
|
||||
return getLocalKey(obj);
|
||||
} else if (obj is Map) {
|
||||
return obj[localKey ?? 'id'];
|
||||
} else if (localKey == null || localKey == 'id') {
|
||||
return obj.id;
|
||||
} else {
|
||||
return reflect(obj).getField(Symbol(localKey)).reflectee;
|
||||
}
|
||||
}
|
||||
|
||||
dynamic _getPivotKey(obj) {
|
||||
if (getPivotKey != null) {
|
||||
return getPivotKey(obj);
|
||||
} else if (obj is Map) {
|
||||
return obj[pivotKey ?? 'id'];
|
||||
} else if (pivotKey == null || pivotKey == 'id') {
|
||||
return obj.id;
|
||||
} else {
|
||||
return reflect(obj).getField(Symbol(pivotKey)).reflectee;
|
||||
}
|
||||
}
|
||||
|
||||
Future _normalize(obj) async {
|
||||
// First, resolve pivot
|
||||
var id = await _getLocalKey(obj);
|
||||
var indexed = await pivotService.index({
|
||||
'query': {pivotKey ?? 'userId': id}
|
||||
});
|
||||
|
||||
if (indexed is! List || indexed.isNotEmpty != true) {
|
||||
await _assignForeignObjects([], obj);
|
||||
} else {
|
||||
// Now, resolve from foreign service
|
||||
var mapped = await Future.wait(indexed.map((pivot) async {
|
||||
var id = await _getPivotKey(obj);
|
||||
var indexed = await foreignService.index({
|
||||
'query': {foreignKey ?? 'postId': id}
|
||||
});
|
||||
|
||||
if (indexed is! List || indexed.isNotEmpty != true) {
|
||||
await _assignForeignObjects([], pivot);
|
||||
} else {
|
||||
await _assignForeignObjects(indexed, pivot);
|
||||
}
|
||||
|
||||
return pivot;
|
||||
}));
|
||||
await _assignForeignObjects(mapped, obj);
|
||||
}
|
||||
}
|
||||
|
||||
if (e.result is Iterable) {
|
||||
//await Future.wait(e.result.map(_normalize));
|
||||
await e.result.map(_normalize);
|
||||
} else {
|
||||
await _normalize(e.result);
|
||||
}
|
||||
};
|
||||
}
|
|
@ -1,73 +0,0 @@
|
|||
import 'dart:async';
|
||||
import 'dart:mirrors';
|
||||
import 'package:angel_framework/angel_framework.dart';
|
||||
import 'plural.dart' as pluralize;
|
||||
import 'no_service.dart';
|
||||
|
||||
/// Represents a relationship in which the current [service] "owns"
|
||||
/// a single member of the service at [servicePath]. Use [as] to set the name
|
||||
/// on the target object.
|
||||
///
|
||||
/// Defaults:
|
||||
/// * [foreignKey]: `userId`
|
||||
/// * [localKey]: `id`
|
||||
HookedServiceEventListener hasOne(Pattern servicePath,
|
||||
{String? as,
|
||||
String? foreignKey,
|
||||
String? localKey,
|
||||
Function(dynamic obj)? getLocalKey,
|
||||
Function(dynamic foreign, dynamic obj)? assignForeignObject}) {
|
||||
return (HookedServiceEvent e) async {
|
||||
var ref = e.getService(servicePath);
|
||||
var foreignName = as?.isNotEmpty == true
|
||||
? as
|
||||
: pluralize.singular(servicePath.toString());
|
||||
if (ref == null) throw noService(servicePath);
|
||||
|
||||
dynamic _getLocalKey(obj) {
|
||||
if (getLocalKey != null) {
|
||||
return getLocalKey(obj);
|
||||
} else if (obj is Map) {
|
||||
return obj[localKey ?? 'id'];
|
||||
} else if (localKey == null || localKey == 'id') {
|
||||
return obj.id;
|
||||
} else {
|
||||
return reflect(obj).getField(Symbol(localKey)).reflectee;
|
||||
}
|
||||
}
|
||||
|
||||
dynamic _assignForeignObject(foreign, obj) {
|
||||
if (assignForeignObject != null) {
|
||||
return assignForeignObject(foreign, obj);
|
||||
} else if (obj is Map) {
|
||||
obj[foreignName] = foreign;
|
||||
} else {
|
||||
reflect(obj).setField(Symbol(foreignName!), foreign);
|
||||
}
|
||||
}
|
||||
|
||||
Future _normalize(obj) async {
|
||||
if (obj != null) {
|
||||
var id = await _getLocalKey(obj);
|
||||
|
||||
var indexed = await ref.index({
|
||||
'query': {foreignKey ?? 'userId': id}
|
||||
});
|
||||
|
||||
if (indexed is! List || indexed.isNotEmpty != true) {
|
||||
await _assignForeignObject(null, obj);
|
||||
} else {
|
||||
var child = indexed.first;
|
||||
await _assignForeignObject(child, obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (e.result is Iterable) {
|
||||
//await Future.wait(e.result.map(_normalize));
|
||||
await e.result.map(_normalize);
|
||||
} else {
|
||||
await _normalize(e.result);
|
||||
}
|
||||
};
|
||||
}
|
|
@ -1,2 +0,0 @@
|
|||
ArgumentError noService(Pattern path) =>
|
||||
ArgumentError("No service exists at path '$path'.");
|
|
@ -1,23 +0,0 @@
|
|||
String singular(String path) {
|
||||
var str = path.trim().split('/').where((str) => str.isNotEmpty).last;
|
||||
|
||||
if (str.endsWith('ies')) {
|
||||
return str.substring(0, str.length - 3) + 'y';
|
||||
} else if (str.endsWith('s')) {
|
||||
return str.substring(0, str.length - 1);
|
||||
} else {
|
||||
return str;
|
||||
}
|
||||
}
|
||||
|
||||
String plural(String path) {
|
||||
var str = path.trim().split('/').where((str) => str.isNotEmpty).last;
|
||||
|
||||
if (str.endsWith('y')) {
|
||||
return str.substring(0, str.length - 1) + 'ies';
|
||||
} else if (str.endsWith('s')) {
|
||||
return str;
|
||||
} else {
|
||||
return str + 's';
|
||||
}
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
name: angel_relations
|
||||
version: 2.0.0
|
||||
description: Database-agnostic relations between Angel services.
|
||||
homepage: "https://github.com/angel-dart/relations.git"
|
||||
publish_to: none
|
||||
environment:
|
||||
sdk: '>=2.12.0 <3.0.0'
|
||||
dependencies:
|
||||
angel_framework:
|
||||
git:
|
||||
url: https://github.com/dukefirehawk/angel.git
|
||||
ref: sdk-2.12.x_nnbd
|
||||
path: packages/framework
|
||||
dev_dependencies:
|
||||
angel_seeder:
|
||||
git:
|
||||
url: https://github.com/dukefirehawk/angel.git
|
||||
ref: sdk-2.12.x_nnbd
|
||||
path: packages/seeder
|
||||
test: ^1.17.8
|
||||
lints: ^1.0.0
|
|
@ -1,54 +0,0 @@
|
|||
import 'package:angel_framework/angel_framework.dart';
|
||||
import 'package:angel_seeder/angel_seeder.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'common.dart';
|
||||
|
||||
void main() {
|
||||
late Angel app;
|
||||
|
||||
setUp(() async {
|
||||
app = Angel()..use('/authors', MapService())..use('/books', MapService());
|
||||
|
||||
await app.configure(seed(
|
||||
'authors',
|
||||
SeederConfiguration<Map>(
|
||||
count: 10,
|
||||
template: {'name': (Faker faker) => faker.person.name()},
|
||||
callback: (Map author, seed) {
|
||||
return seed(
|
||||
'books',
|
||||
SeederConfiguration(delete: false, count: 10, template: {
|
||||
'authorId': author['id'],
|
||||
'title': (Faker faker) =>
|
||||
'I love to eat ${faker.food.dish()}'
|
||||
}));
|
||||
})));
|
||||
|
||||
// TODO: Missing method afterAll
|
||||
//app.findService ('books').afterAll(relations.belongsTo('authors'));
|
||||
});
|
||||
|
||||
test('index', () async {
|
||||
var books = await app.findService('books')!.index();
|
||||
print(books);
|
||||
|
||||
expect(books, allOf(isList, isNotEmpty));
|
||||
|
||||
for (var book in books.whereType<Map>()) {
|
||||
expect(book.keys, contains('author'));
|
||||
|
||||
var author = book['author'] as Map;
|
||||
expect(author['id'], equals(book['authorId']));
|
||||
}
|
||||
});
|
||||
|
||||
test('create', () async {
|
||||
var warAndPeace = await app
|
||||
.findService('books')!
|
||||
.create(Book(title: 'War and Peace').toJson());
|
||||
|
||||
print(warAndPeace);
|
||||
expect(warAndPeace.keys, contains('author'));
|
||||
expect(warAndPeace['author'], isNull);
|
||||
});
|
||||
}
|
|
@ -1,69 +0,0 @@
|
|||
import 'dart:convert';
|
||||
import 'package:angel_framework/angel_framework.dart';
|
||||
//import 'package:json_god/json_god.dart' as god;
|
||||
|
||||
@deprecated
|
||||
class CustomMapService extends Service {
|
||||
final List<Map> _items = [];
|
||||
|
||||
Iterable<Map> tailor(Iterable<Map> items, Map? params) {
|
||||
if (params == null) return items;
|
||||
|
||||
var r = items;
|
||||
|
||||
if (params['query'] is Map) {
|
||||
var query = params['query'] as Map;
|
||||
|
||||
for (var key in query.keys) {
|
||||
r = r.where((m) => m[key] == query[key]);
|
||||
}
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<Map>> index([params]) async => tailor(_items, params).toList();
|
||||
|
||||
@override
|
||||
Future<Map> read(id, [Map? params]) async {
|
||||
return tailor(_items, params).firstWhere((m) => m['id'] == id,
|
||||
orElse: (() => throw AngelHttpException.notFound()));
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Map> create(data, [params]) async {
|
||||
var d = data is Map ? data : (jsonDecode(data as String) as Map?)!;
|
||||
d['id'] = _items.length.toString();
|
||||
_items.add(d);
|
||||
return d;
|
||||
}
|
||||
|
||||
@override
|
||||
Future remove(id, [params]) async {
|
||||
if (id == null) _items.clear();
|
||||
}
|
||||
}
|
||||
|
||||
class Author {
|
||||
String? id, name;
|
||||
|
||||
Author({this.id, this.name});
|
||||
|
||||
Map toJson() => {'id': id, 'name': name};
|
||||
}
|
||||
|
||||
class Book {
|
||||
String? authorId, title;
|
||||
|
||||
Book({this.authorId, this.title});
|
||||
|
||||
Map toJson() => {'authorId': authorId, 'title': title};
|
||||
}
|
||||
|
||||
class Chapter {
|
||||
String? bookId, title;
|
||||
int? pageCount;
|
||||
|
||||
Chapter({this.bookId, this.title, this.pageCount});
|
||||
}
|
|
@ -1,60 +0,0 @@
|
|||
import 'package:angel_framework/angel_framework.dart';
|
||||
//import 'package:angel_relations/angel_relations.dart' as relations;
|
||||
import 'package:angel_seeder/angel_seeder.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'common.dart';
|
||||
|
||||
void main() {
|
||||
late Angel app;
|
||||
|
||||
setUp(() async {
|
||||
app = Angel()..use('/authors', MapService())..use('/books', MapService());
|
||||
|
||||
await app.configure(seed(
|
||||
'authors',
|
||||
SeederConfiguration<Map>(
|
||||
count: 10,
|
||||
template: {'name': (Faker faker) => faker.person.name()},
|
||||
callback: (Map author, seed) {
|
||||
return seed(
|
||||
'books',
|
||||
SeederConfiguration(delete: false, count: 10, template: {
|
||||
'authorId': author['id'],
|
||||
'title': (Faker faker) =>
|
||||
'I love to eat ${faker.food.dish()}'
|
||||
}));
|
||||
})));
|
||||
|
||||
// TODO: Missing afterAll method
|
||||
// app
|
||||
// .findService('authors')
|
||||
// .afterAll(relations.hasMany('books', foreignKey: 'authorId'));
|
||||
});
|
||||
|
||||
test('index', () async {
|
||||
var authors = await app.findService('authors')!.index();
|
||||
print(authors);
|
||||
|
||||
expect(authors, allOf(isList, isNotEmpty));
|
||||
|
||||
for (var author in authors.whereType<Map>()) {
|
||||
expect(author.keys, contains('books'));
|
||||
|
||||
var books = author['books'] as List<Map>;
|
||||
|
||||
for (var book in books) {
|
||||
expect(book['authorId'], equals(author['id']));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test('create', () async {
|
||||
var tolstoy = await app
|
||||
.findService('authors')!
|
||||
.create(Author(name: 'Leo Tolstoy').toJson());
|
||||
|
||||
print(tolstoy);
|
||||
expect(tolstoy.keys, contains('books'));
|
||||
expect(tolstoy['books'], allOf(isList, isEmpty));
|
||||
});
|
||||
}
|
|
@ -1,57 +0,0 @@
|
|||
import 'package:angel_framework/angel_framework.dart';
|
||||
import 'package:angel_seeder/angel_seeder.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'common.dart';
|
||||
|
||||
void main() {
|
||||
late Angel app;
|
||||
|
||||
setUp(() async {
|
||||
app = Angel()..use('/authors', MapService())..use('/books', MapService());
|
||||
|
||||
await app.configure(seed(
|
||||
'authors',
|
||||
SeederConfiguration<Map>(
|
||||
count: 10,
|
||||
template: {'name': (Faker faker) => faker.person.name()},
|
||||
callback: (Map author, seed) {
|
||||
return seed(
|
||||
'books',
|
||||
SeederConfiguration(delete: false, count: 10, template: {
|
||||
'authorId': author['id'],
|
||||
'title': (Faker faker) =>
|
||||
'I love to eat ${faker.food.dish()}'
|
||||
}));
|
||||
})));
|
||||
|
||||
// TODO: Missing afterAll method
|
||||
// app.findService('authors').afterAll(
|
||||
// relations.hasOne('books', as: 'book', foreignKey: 'authorId'));
|
||||
});
|
||||
|
||||
test('index', () async {
|
||||
var authors = await app.findService('authors')!.index();
|
||||
print(authors);
|
||||
|
||||
expect(authors, allOf(isList, isNotEmpty));
|
||||
|
||||
for (var author in authors.whereType<Map>()) {
|
||||
expect(author.keys, contains('book'));
|
||||
|
||||
var book = author['book'] as Map;
|
||||
print('Author: $author');
|
||||
print('Book: $book');
|
||||
expect(book['authorId'], equals(author['id']));
|
||||
}
|
||||
});
|
||||
|
||||
test('create', () async {
|
||||
var tolstoy = await app
|
||||
.findService('authors')!
|
||||
.create(Author(name: 'Leo Tolstoy').toJson());
|
||||
|
||||
print(tolstoy);
|
||||
expect(tolstoy.keys, contains('book'));
|
||||
expect(tolstoy['book'], isNull);
|
||||
});
|
||||
}
|
BIN
archived_packages/rethink.zip
Normal file
BIN
archived_packages/rethink.zip
Normal file
Binary file not shown.
31
archived_packages/rethink/.gitignore
vendored
31
archived_packages/rethink/.gitignore
vendored
|
@ -1,31 +0,0 @@
|
|||
# See https://www.dartlang.org/tools/private-files.html
|
||||
|
||||
# Files and directories created by pub
|
||||
.buildlog
|
||||
.packages
|
||||
.project
|
||||
.pub/
|
||||
build/
|
||||
**/packages/
|
||||
|
||||
# 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
|
||||
|
||||
rethinkdb_data/
|
||||
.idea
|
||||
.dart_tool
|
|
@ -1,4 +0,0 @@
|
|||
language: dart
|
||||
addons:
|
||||
rethinkdb: '2.3'
|
||||
before_script: 'dart test/bootstrap.dart'
|
|
@ -1,12 +0,0 @@
|
|||
Primary Authors
|
||||
===============
|
||||
|
||||
* __[Thomas Hii](dukefirehawk.apps@gmail.com)__
|
||||
|
||||
Thomas is the current maintainer of the code base. He has refactored and migrated the
|
||||
code base to support NNBD.
|
||||
|
||||
* __[Tobe O](thosakwe@gmail.com)__
|
||||
|
||||
Tobe has written much of the original code prior to NNBD migration. He has moved on and
|
||||
is no longer involved with the project.
|
|
@ -1,10 +0,0 @@
|
|||
# Change Log
|
||||
|
||||
## 2.0.0
|
||||
|
||||
* Migrated to support Dart >= 2.12 NNBD
|
||||
|
||||
## 1.1.0
|
||||
|
||||
* Moved to `package:rethinkdb_driver`
|
||||
* Fixed references to old hooked event names
|
|
@ -1,29 +0,0 @@
|
|||
BSD 3-Clause License
|
||||
|
||||
Copyright (c) 2021, dukefirehawk.com
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@ -1,87 +0,0 @@
|
|||
# rethink
|
||||
[![version 1.0.7](https://img.shields.io/badge/pub-1.0.7-brightgreen.svg)](https://pub.dartlang.org/packages/angel_rethink)
|
||||
[![build status](https://travis-ci.org/angel-dart/rethink.svg?branch=master)](https://travis-ci.org/angel-dart/rethink)
|
||||
|
||||
RethinkDB-enabled services for the Angel framework.
|
||||
|
||||
# Installation
|
||||
Add the following to your `pubspec.yaml`:
|
||||
|
||||
```yaml
|
||||
dependencies:
|
||||
angel_rethink: ^1.0.0
|
||||
```
|
||||
|
||||
`package:rethinkdb_driver2` will be installed as well.
|
||||
|
||||
# Usage
|
||||
This library exposes one class: `RethinkService`. By default, these services will even
|
||||
listen to [changefeeds](https://www.rethinkdb.com/docs/changefeeds/ruby/) from the database,
|
||||
which makes them very suitable for WebSocket use.
|
||||
|
||||
However, only `CREATED`, `UPDATED` and `REMOVED` events will be fired. This is technically not
|
||||
a problem, as it lowers the numbers of events you have to handle on the client side. ;)
|
||||
|
||||
## 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 r = new RethinkDb();
|
||||
var conn = await r.connect();
|
||||
|
||||
app.use('/api/users', new RethinkService(conn, r.table('users')));
|
||||
|
||||
// Add type de/serialization if you want
|
||||
app.use('/api/users', new TypedService<User>(new RethinkService(conn, r.table('users'))));
|
||||
|
||||
// You don't have to even use a table...
|
||||
app.use('/api/pro_users', new RethinkService(conn, r.table('users').filter({'membership': 'pro'})));
|
||||
|
||||
app.service('api/users').afterCreated.listen((event) {
|
||||
print("New user: ${event.result}");
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
## RethinkService
|
||||
This class interacts with a `Query` (usually a table) and serializes data to and from Maps.
|
||||
|
||||
## RethinkTypedService<T>
|
||||
Does the same as above, but serializes to and from a target class using `package:json_god` and its support for reflection.
|
||||
|
||||
## 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.
|
||||
|
||||
You can use advanced queries:
|
||||
|
||||
```dart
|
||||
// Pass an actual query...
|
||||
service.index({'query': r.table('foo').filter(...)});
|
||||
|
||||
// Or, a function that creates a query from a table...
|
||||
service.index({'query': (table) => table.getAll('foo')});
|
||||
|
||||
// Or, a Map, which will be transformed into a `filter` query:
|
||||
service.index({'query': {'foo': 'bar', 'baz': 'quux'}});
|
||||
```
|
||||
|
||||
You can also apply sorting by adding a `reql` parameter on the server-side.
|
||||
|
||||
```dart
|
||||
service.index({'reql': (query) => query.sort(...)});
|
||||
```
|
||||
|
||||
See the tests for more usage examples.
|
|
@ -1 +0,0 @@
|
|||
include: package:lints/recommended.yaml
|
|
@ -1 +0,0 @@
|
|||
export 'src/rethink_service.dart';
|
|
@ -1,251 +0,0 @@
|
|||
import 'dart:async';
|
||||
//import 'dart:io';
|
||||
import 'package:angel_framework/angel_framework.dart';
|
||||
import 'package:json_god/json_god.dart' as god;
|
||||
import 'package:rethinkdb_dart/rethinkdb_dart.dart';
|
||||
|
||||
// Extends a RethinkDB query.
|
||||
typedef QueryCallback = RqlQuery Function(RqlQuery query);
|
||||
|
||||
/// Queries a single RethinkDB table or query.
|
||||
class RethinkService extends Service {
|
||||
/// 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;
|
||||
|
||||
final bool debug;
|
||||
|
||||
/// If set to `true`, then a HookedService mounted over this instance
|
||||
/// will fire events when RethinkDB pushes events.
|
||||
///
|
||||
/// Good for scaling. ;)
|
||||
final bool listenForChanges;
|
||||
|
||||
final Connection connection;
|
||||
|
||||
/// Doesn't actually have to be a table, just a RethinkDB query.
|
||||
///
|
||||
/// However, a table is the most common usecase.
|
||||
final RqlQuery table;
|
||||
|
||||
RethinkService(this.connection, this.table,
|
||||
{this.allowRemoveAll = false,
|
||||
this.allowQuery = true,
|
||||
this.debug = false,
|
||||
this.listenForChanges = true})
|
||||
: super();
|
||||
|
||||
RqlQuery buildQuery(RqlQuery initialQuery, Map params) {
|
||||
if (params != null) {
|
||||
params['broadcast'] = params.containsKey('broadcast')
|
||||
? params['broadcast']
|
||||
: (listenForChanges != true);
|
||||
}
|
||||
|
||||
var q = _getQueryInner(initialQuery, params);
|
||||
|
||||
if (params?.containsKey('reql') == true &&
|
||||
params['reql'] is QueryCallback) {
|
||||
q = params['reql'](q) as RqlQuery;
|
||||
}
|
||||
|
||||
return q ?? initialQuery;
|
||||
}
|
||||
|
||||
RqlQuery _getQueryInner(RqlQuery query, Map params) {
|
||||
if (params == null || !params.containsKey('query')) {
|
||||
return query;
|
||||
} else {
|
||||
if (params['query'] is RqlQuery) {
|
||||
return params['query'] as RqlQuery;
|
||||
} else if (params['query'] is QueryCallback) {
|
||||
return params['query'](table) as RqlQuery;
|
||||
} else if (params['query'] is! Map || allowQuery != true) {
|
||||
return query;
|
||||
} else {
|
||||
var q = params['query'] as Map;
|
||||
return q.keys.fold<RqlQuery>(query, (out, key) {
|
||||
var val = q[key];
|
||||
|
||||
if (val is RequestContext ||
|
||||
val is ResponseContext ||
|
||||
key == 'provider' ||
|
||||
val is Providers) {
|
||||
return out;
|
||||
} else {
|
||||
return out.filter({key.toString(): val});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future _sendQuery(RqlQuery query) async {
|
||||
var result = await query.run(connection);
|
||||
|
||||
if (result is Cursor) {
|
||||
return await result.toList();
|
||||
} else if (result is Map && result['generated_keys'] is List) {
|
||||
if (result['generated_keys'].length == 1) {
|
||||
return await read(result['generated_keys'].first);
|
||||
}
|
||||
//return await Future.wait(result['generated_keys'].map(read));
|
||||
return await result['generated_keys'].map(read);
|
||||
} else {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
dynamic _serialize(data) {
|
||||
if (data is Map) {
|
||||
return data;
|
||||
} else if (data is Iterable) {
|
||||
return data.map(_serialize).toList();
|
||||
} else {
|
||||
return god.serializeObject(data);
|
||||
}
|
||||
}
|
||||
|
||||
dynamic _squeeze(data) {
|
||||
if (data is Map) {
|
||||
return data.keys.fold<Map>({}, (map, k) => map..[k.toString()] = data[k]);
|
||||
} else if (data is Iterable) {
|
||||
return data.map(_squeeze).toList();
|
||||
} else {
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void onHooked(HookedService hookedService) {
|
||||
if (listenForChanges == true) {
|
||||
listenToQuery(table, hookedService);
|
||||
}
|
||||
}
|
||||
|
||||
Future listenToQuery(RqlQuery query, HookedService hookedService) async {
|
||||
var feed =
|
||||
await query.changes({'include_types': true}).run(connection) as Feed;
|
||||
|
||||
feed.listen((Map event) {
|
||||
var type = event['type']?.toString();
|
||||
var newVal = event['new_val'], oldVal = event['old_val'];
|
||||
|
||||
if (type == 'add') {
|
||||
// Create
|
||||
hookedService.fireEvent(
|
||||
hookedService.afterCreated,
|
||||
HookedServiceEvent(
|
||||
true, null, null, this, HookedServiceEvent.created,
|
||||
result: newVal));
|
||||
} else if (type == 'change') {
|
||||
// Update
|
||||
hookedService.fireEvent(
|
||||
hookedService.afterCreated,
|
||||
HookedServiceEvent(
|
||||
true, null, null, this, HookedServiceEvent.updated,
|
||||
result: newVal, id: oldVal['id'], data: newVal));
|
||||
} else if (type == 'remove') {
|
||||
// Remove
|
||||
hookedService.fireEvent(
|
||||
hookedService.afterCreated,
|
||||
HookedServiceEvent(
|
||||
true, null, null, this, HookedServiceEvent.removed,
|
||||
result: oldVal, id: oldVal['id']));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: Invalid override method
|
||||
/*
|
||||
@override
|
||||
Future index([Map params]) async {
|
||||
var query = buildQuery(table, params);
|
||||
return await _sendQuery(query);
|
||||
}
|
||||
*/
|
||||
@override
|
||||
Future read(id, [Map params]) async {
|
||||
var query = buildQuery(table.get(id?.toString()), params);
|
||||
var found = await _sendQuery(query);
|
||||
//print('Found for $id: $found');
|
||||
|
||||
if (found == null) {
|
||||
throw AngelHttpException.notFound(message: 'No record found for ID $id');
|
||||
} else {
|
||||
return found;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future create(data, [Map params]) async {
|
||||
if (table is! Table) throw AngelHttpException.methodNotAllowed();
|
||||
|
||||
var d = _serialize(data);
|
||||
var q = table as Table;
|
||||
var query = buildQuery(q.insert(_squeeze(d)), params);
|
||||
return await _sendQuery(query);
|
||||
}
|
||||
|
||||
@override
|
||||
Future modify(id, data, [Map params]) async {
|
||||
var d = _serialize(data);
|
||||
|
||||
if (d is Map && d.containsKey('id')) {
|
||||
try {
|
||||
await read(d['id'], params);
|
||||
} on AngelHttpException catch (e) {
|
||||
if (e.statusCode == 404) {
|
||||
return await create(data, params);
|
||||
} else {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var query = buildQuery(table.get(id?.toString()), params).update(d);
|
||||
await _sendQuery(query);
|
||||
return await read(id, params);
|
||||
}
|
||||
|
||||
@override
|
||||
Future update(id, data, [Map params]) async {
|
||||
var d = _serialize(data);
|
||||
|
||||
if (d is Map && d.containsKey('id')) {
|
||||
try {
|
||||
await read(d['id'], params);
|
||||
} on AngelHttpException catch (e) {
|
||||
if (e.statusCode == 404) {
|
||||
return await create(data, params);
|
||||
} else {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (d is Map && !d.containsKey('id')) d['id'] = id.toString();
|
||||
var query = buildQuery(table.get(id?.toString()), params).replace(d);
|
||||
await _sendQuery(query);
|
||||
return await read(id, params);
|
||||
}
|
||||
|
||||
@override
|
||||
Future remove(id, [Map params]) async {
|
||||
if (id == null ||
|
||||
id == 'null' &&
|
||||
(allowRemoveAll == true ||
|
||||
params?.containsKey('provider') != true)) {
|
||||
return await _sendQuery(table.delete());
|
||||
} else {
|
||||
var prior = await read(id, params);
|
||||
var query = buildQuery(table.get(id), params).delete();
|
||||
await _sendQuery(query);
|
||||
return prior;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,35 +0,0 @@
|
|||
name: angel_rethink
|
||||
version: 2.0.0
|
||||
description: RethinkDB-enabled services for the Angel framework.
|
||||
publish_to: none
|
||||
environment:
|
||||
sdk: ">=2.10.0 <3.0.0"
|
||||
homepage: https://github.com/angel-dart/rethink
|
||||
dependencies:
|
||||
angel_framework:
|
||||
git:
|
||||
url: https://github.com/dukefirehawk/angel.git
|
||||
ref: sdk-2.12.x_nnbd
|
||||
path: packages/framework
|
||||
json_god:
|
||||
git:
|
||||
url: https://github.com/dukefirehawk/angel.git
|
||||
ref: sdk-2.12.x_nnbd
|
||||
path: packages/json_god
|
||||
rethinkdb_dart: ^2.3.2+6
|
||||
dev_dependencies:
|
||||
angel_client:
|
||||
git:
|
||||
url: https://github.com/dukefirehawk/angel.git
|
||||
ref: sdk-2.12.x_nnbd
|
||||
path: packages/client
|
||||
angel_test:
|
||||
git:
|
||||
url: https://github.com/dukefirehawk/angel.git
|
||||
ref: sdk-2.12.x_nnbd
|
||||
path: packages/test
|
||||
logging: ^1.0.0
|
||||
test: ^1.15.7
|
||||
lints: ^1.0.0
|
||||
dependency_overrides:
|
||||
crypto: ^3.0.0
|
|
@ -1,6 +0,0 @@
|
|||
# Tests
|
||||
|
||||
The tests expect you to have installed RethinkDB. You must have a `test` database
|
||||
available, and a server ready at the default port.
|
||||
|
||||
Also, the tests expect a table named `todos`.
|
|
@ -1,11 +0,0 @@
|
|||
import 'dart:io';
|
||||
import 'package:rethinkdb_dart/rethinkdb_dart.dart';
|
||||
|
||||
void main() async {
|
||||
var r = Rethinkdb();
|
||||
await r.connect().then((conn) {
|
||||
r.tableCreate('todos').run(conn);
|
||||
print('Done');
|
||||
exit(0);
|
||||
});
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
class Todo {
|
||||
String title;
|
||||
bool completed;
|
||||
|
||||
Todo({this.title, this.completed = false});
|
||||
|
||||
Map toJson() {
|
||||
return {'title': title, 'completed': completed == true};
|
||||
}
|
||||
}
|
|
@ -1,85 +0,0 @@
|
|||
import 'package:angel_client/angel_client.dart' as c;
|
||||
import 'package:angel_framework/angel_framework.dart';
|
||||
import 'package:angel_rethink/angel_rethink.dart';
|
||||
import 'package:angel_test/angel_test.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:rethinkdb_dart/rethinkdb_dart.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'common.dart';
|
||||
|
||||
void main() {
|
||||
Angel app;
|
||||
TestClient client;
|
||||
Rethinkdb r;
|
||||
c.Service todoService;
|
||||
|
||||
setUp(() async {
|
||||
r = Rethinkdb();
|
||||
var conn = await r.connect();
|
||||
|
||||
app = Angel();
|
||||
app.use('/todos', RethinkService(conn, r.table('todos')));
|
||||
|
||||
app.errorHandler = (e, req, res) async {
|
||||
print('Whoops: $e');
|
||||
};
|
||||
|
||||
app.logger = Logger.detached('angel')..onRecord.listen(print);
|
||||
|
||||
client = await connectTo(app);
|
||||
todoService = client.service('todos');
|
||||
});
|
||||
|
||||
tearDown(() => client.close());
|
||||
|
||||
test('index', () async {
|
||||
var result = await todoService.index();
|
||||
print('Response: $result');
|
||||
expect(result, isList);
|
||||
});
|
||||
|
||||
test('create+read', () async {
|
||||
var todo = Todo(title: 'Clean your room');
|
||||
var creation = await todoService.create(todo.toJson());
|
||||
print('Creation: $creation');
|
||||
|
||||
var id = creation['id'];
|
||||
var result = await todoService.read(id);
|
||||
|
||||
print('Response: $result');
|
||||
expect(result, isMap);
|
||||
expect(result['id'], equals(id));
|
||||
expect(result['title'], equals(todo.title));
|
||||
expect(result['completed'], equals(todo.completed));
|
||||
});
|
||||
|
||||
test('modify', () async {
|
||||
var todo = Todo(title: 'Clean your room');
|
||||
var creation = await todoService.create(todo.toJson());
|
||||
print('Creation: $creation');
|
||||
|
||||
var id = creation['id'];
|
||||
var result = await todoService.modify(id, {'title': 'Eat healthy'});
|
||||
|
||||
print('Response: $result');
|
||||
expect(result, isMap);
|
||||
expect(result['id'], equals(id));
|
||||
expect(result['title'], equals('Eat healthy'));
|
||||
expect(result['completed'], equals(todo.completed));
|
||||
});
|
||||
|
||||
test('remove', () async {
|
||||
var todo = Todo(title: 'Clean your room');
|
||||
var creation = await todoService.create(todo.toJson());
|
||||
print('Creation: $creation');
|
||||
|
||||
var id = creation['id'];
|
||||
var result = await todoService.remove(id);
|
||||
|
||||
print('Response: $result');
|
||||
expect(result, isMap);
|
||||
expect(result['id'], equals(id));
|
||||
expect(result['title'], equals(todo.title));
|
||||
expect(result['completed'], equals(todo.completed));
|
||||
});
|
||||
}
|
BIN
archived_packages/seeder.zip
Normal file
BIN
archived_packages/seeder.zip
Normal file
Binary file not shown.
27
archived_packages/seeder/.gitignore
vendored
27
archived_packages/seeder/.gitignore
vendored
|
@ -1,27 +0,0 @@
|
|||
# See https://www.dartlang.org/tools/private-files.html
|
||||
|
||||
# Files and directories created by pub
|
||||
.buildlog
|
||||
.packages
|
||||
.project
|
||||
.pub/
|
||||
build/
|
||||
**/packages/
|
||||
|
||||
# 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
|
|
@ -1 +0,0 @@
|
|||
language: dart
|
|
@ -1,12 +0,0 @@
|
|||
Primary Authors
|
||||
===============
|
||||
|
||||
* __[Thomas Hii](dukefirehawk.apps@gmail.com)__
|
||||
|
||||
Thomas is the current maintainer of the code base. He has refactored and migrated the
|
||||
code base to support NNBD.
|
||||
|
||||
* __[Tobe O](thosakwe@gmail.com)__
|
||||
|
||||
Tobe has written much of the original code prior to NNBD migration. He has moved on and
|
||||
is no longer involved with the project.
|
|
@ -1,9 +0,0 @@
|
|||
# Change Log
|
||||
|
||||
## 2.0.0
|
||||
|
||||
* Migrated to support Dart >= 2.12 NNBD
|
||||
|
||||
## 1.0.0
|
||||
|
||||
* Initial checkin
|
|
@ -1,29 +0,0 @@
|
|||
BSD 3-Clause License
|
||||
|
||||
Copyright (c) 2021, dukefirehawk.com
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@ -1,25 +0,0 @@
|
|||
# angel_seeder
|
||||
|
||||
[![version 1.0.](https://img.shields.io/pub/v/angel_seeder.svg)](https://pub.dartlang.org/packages/angel_seeder)
|
||||
[![build status](https://travis-ci.org/angel-dart/seeder.svg?branch=master)](https://travis-ci.org/angel-dart/seeder)
|
||||
|
||||
Straightforward data seeder for Angel services.
|
||||
This is an almost exact port of [feathers-seeder](https://github.com/thosakwe/feathers-seeder),
|
||||
so its documentation should almost exactly match up here.
|
||||
Fortunately, I was also the one who made `feathers-seeder`, so if you ever need assistance,
|
||||
file an issue.
|
||||
|
||||
# Example
|
||||
```dart
|
||||
var app = new Angel()..use('/todos', new TodoService());
|
||||
|
||||
await app.configure(seed(
|
||||
'todos',
|
||||
new SeederConfiguration<Todo>(delete: false, count: 10, template: {
|
||||
'text': (Faker faker) => 'Clean your room, ${faker.person.name()}!',
|
||||
'completed': false
|
||||
})));
|
||||
```
|
||||
|
||||
**NOTE**: Don't *await* seeding at application startup; that's too slow.
|
||||
Instead, run it asynchronously.
|
|
@ -1 +0,0 @@
|
|||
include: package:lints/recommended.yaml
|
|
@ -1,141 +0,0 @@
|
|||
import 'dart:math';
|
||||
import 'package:angel_framework/angel_framework.dart';
|
||||
import 'package:faker/faker.dart';
|
||||
export 'package:faker/faker.dart';
|
||||
|
||||
/// Generates data using a [Faker].
|
||||
typedef FakerCallback = Function(Faker faker);
|
||||
|
||||
/// Used to seed nested objects.
|
||||
typedef SeederCallback<T> = Function(T created,
|
||||
Function(Pattern path, SeederConfiguration configuration, {bool? verbose}));
|
||||
|
||||
/// Seeds the given service in development.
|
||||
AngelConfigurer seed<T>(
|
||||
Pattern servicePath,
|
||||
SeederConfiguration<T> configuration, {
|
||||
bool verbose = false,
|
||||
}) {
|
||||
return (Angel app) async {
|
||||
if (configuration.runInProduction != true) return;
|
||||
|
||||
if (!app.services.containsKey(servicePath)) {
|
||||
throw ArgumentError(
|
||||
"App does not contain a service at path '$servicePath'.");
|
||||
}
|
||||
|
||||
if (configuration.disabled == true) {
|
||||
print("Service '$servicePath' will not be seeded.");
|
||||
return;
|
||||
}
|
||||
|
||||
var service = app.findService(servicePath);
|
||||
var faker = Faker();
|
||||
|
||||
Map _buildTemplate(Map data) {
|
||||
return data.keys.fold({}, (map, key) {
|
||||
var value = data[key];
|
||||
|
||||
if (value is FakerCallback) {
|
||||
return map..[key] = value(faker);
|
||||
} else if (value is Function) {
|
||||
return map..[key] = value();
|
||||
} else if (value is Map) {
|
||||
return map..[key] = _buildTemplate(value);
|
||||
} else {
|
||||
return map..[key] = value;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Future<Null> Function(SeederConfiguration configuration) _buildSeeder(
|
||||
Service? service,
|
||||
{bool? verbose}) {
|
||||
return (SeederConfiguration configuration) async {
|
||||
if (configuration.delete == true) await service!.remove(null);
|
||||
|
||||
var count = configuration.count;
|
||||
var rnd = Random();
|
||||
if (count < 1) count = 1;
|
||||
|
||||
for (var i = 0; i < count; i++) {
|
||||
Future _gen(template) async {
|
||||
var data = template;
|
||||
|
||||
if (data is Map) {
|
||||
data = _buildTemplate(data);
|
||||
} else if (data is Faker) {
|
||||
data = template(faker);
|
||||
}
|
||||
|
||||
var params = <String, dynamic>{}..addAll(configuration.params);
|
||||
var result = await service!.create(data, params);
|
||||
|
||||
if (configuration.callback != null) {
|
||||
await configuration.callback!(result,
|
||||
(Pattern path, SeederConfiguration configuration,
|
||||
{bool? verbose}) {
|
||||
return _buildSeeder(app.findService(path),
|
||||
verbose: verbose == true)(configuration);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (configuration.template != null) {
|
||||
await _gen(configuration.template);
|
||||
} else if (configuration.templates.isNotEmpty == true) {
|
||||
var template = configuration.templates
|
||||
.elementAt(rnd.nextInt(configuration.templates.length));
|
||||
await _gen(template);
|
||||
} else {
|
||||
throw ArgumentError(
|
||||
'Configuration for service \'$servicePath\' must define at least one template.');
|
||||
}
|
||||
}
|
||||
|
||||
if (verbose == true) {
|
||||
print('Created $count object(s) in service \'$servicePath\'.');
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
await _buildSeeder(service, verbose: verbose == true)(configuration);
|
||||
};
|
||||
}
|
||||
|
||||
/// Configures the seeder.
|
||||
class SeederConfiguration<T> {
|
||||
/// Optional callback on creation.
|
||||
final SeederCallback<T>? callback;
|
||||
|
||||
/// Number of objects to seed.
|
||||
final int count;
|
||||
|
||||
/// If `true`, all records in the service are deleted before seeding.
|
||||
final bool delete;
|
||||
|
||||
/// If `true`, seeding will not occur.
|
||||
final bool disabled;
|
||||
|
||||
/// Optional service parameters to be passed.
|
||||
final Map<String, dynamic> params;
|
||||
|
||||
/// Unless this is `true`, the seeder will not run in production.
|
||||
final bool runInProduction;
|
||||
|
||||
/// A data template to build from.
|
||||
final template;
|
||||
|
||||
/// A set of templates to choose from.
|
||||
final Iterable templates;
|
||||
|
||||
SeederConfiguration(
|
||||
{this.callback,
|
||||
this.count = 1,
|
||||
this.delete = true,
|
||||
this.disabled = false,
|
||||
this.params = const {},
|
||||
this.runInProduction = false,
|
||||
this.template,
|
||||
this.templates = const []});
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
name: angel_seeder
|
||||
version: 2.0.0
|
||||
description: Straightforward data seeder for Angel services.
|
||||
publish_to: none
|
||||
environment:
|
||||
sdk: '>=2.12.0 <3.0.0'
|
||||
homepage: https://github.com/angel-dart/seeder
|
||||
dependencies:
|
||||
angel_framework:
|
||||
git:
|
||||
url: https://github.com/dukefirehawk/angel.git
|
||||
ref: sdk-2.12.x_nnbd
|
||||
path: packages/framework
|
||||
faker: ^2.0.0
|
||||
dev_dependencies:
|
||||
test: ^1.17.8
|
||||
lints: ^1.0.0
|
|
@ -1,69 +0,0 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:angel_framework/angel_framework.dart';
|
||||
import 'package:angel_container/mirrors.dart';
|
||||
import 'package:angel_seeder/angel_seeder.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
void main() {
|
||||
test('create one', () async {
|
||||
var app = Angel(reflector: MirrorsReflector())
|
||||
..use('/todos', TodoService());
|
||||
|
||||
await app.configure(seed(
|
||||
'todos',
|
||||
SeederConfiguration<Todo>(delete: false, count: 10, template: {
|
||||
'text': (Faker faker) => 'Clean your room, ${faker.person.name()}!',
|
||||
'completed': false
|
||||
})));
|
||||
|
||||
var todos = await app.findService('todos')!.index();
|
||||
print('Todos: \n${todos.map((todo) => " - $todo").join("\n")}');
|
||||
|
||||
expect(todos, isList);
|
||||
expect(todos, hasLength(10));
|
||||
});
|
||||
}
|
||||
|
||||
class TodoService extends Service {
|
||||
final List<Todo> todos = [];
|
||||
|
||||
@override
|
||||
Future<List<Todo>> index([params]) => myData();
|
||||
|
||||
Future<List<Todo>> myData() {
|
||||
var completer = Completer<List<Todo>>();
|
||||
completer.complete(todos);
|
||||
return completer.future;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Object> create(data, [params]) async {
|
||||
if (data is Todo) {
|
||||
todos.add(data..id = todos.length.toString());
|
||||
return data;
|
||||
} else if (data is Map) {
|
||||
todos.add(Todo.fromJson(data)..id = todos.length.toString());
|
||||
return data;
|
||||
} else {
|
||||
throw AngelHttpException.badRequest();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Todo extends Model {
|
||||
final String? text;
|
||||
final bool? completed;
|
||||
|
||||
Todo({String? id, this.text, this.completed = false}) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
factory Todo.fromJson(Map data) => Todo(
|
||||
id: data['id'] as String?,
|
||||
text: data['text'] as String?,
|
||||
completed: data['completed'] as bool?);
|
||||
|
||||
@override
|
||||
String toString() => '${completed! ? "Complete" : "Incomplete"}: $text';
|
||||
}
|
BIN
archived_packages/typed_service.zip
Normal file
BIN
archived_packages/typed_service.zip
Normal file
Binary file not shown.
13
archived_packages/typed_service/.gitignore
vendored
13
archived_packages/typed_service/.gitignore
vendored
|
@ -1,13 +0,0 @@
|
|||
# See https://www.dartlang.org/guides/libraries/private-files
|
||||
|
||||
# Files and directories created by pub
|
||||
.dart_tool/
|
||||
.packages
|
||||
.pub/
|
||||
build/
|
||||
# If you're building an application, you may want to check-in your pubspec.lock
|
||||
pubspec.lock
|
||||
|
||||
# Directory created by dartdoc
|
||||
# If you don't generate documentation locally you can remove this line.
|
||||
doc/api/
|
|
@ -1,12 +0,0 @@
|
|||
Primary Authors
|
||||
===============
|
||||
|
||||
* __[Thomas Hii](dukefirehawk.apps@gmail.com)__
|
||||
|
||||
Thomas is the current maintainer of the code base. He has refactored and migrated the
|
||||
code base to support NNBD.
|
||||
|
||||
* __[Tobe O](thosakwe@gmail.com)__
|
||||
|
||||
Tobe has written much of the original code prior to NNBD migration. He has moved on and
|
||||
is no longer involved with the project.
|
|
@ -1,16 +0,0 @@
|
|||
# Change Log
|
||||
|
||||
## 2.0.0
|
||||
|
||||
* Migrated to support Dart >= 2.12 NNBD
|
||||
|
||||
## 1.0.1
|
||||
|
||||
* Explicitly extend `Service<Id, T>`.
|
||||
* Override `readData`.
|
||||
* Use `Service<Id, Map<String, dynamic>>` for `inner`, instead of just
|
||||
`Service<Id, Map>`.
|
||||
|
||||
## 1.0.0
|
||||
|
||||
* Initial version.
|
|
@ -1,29 +0,0 @@
|
|||
BSD 3-Clause License
|
||||
|
||||
Copyright (c) 2021, dukefirehawk.com
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@ -1,29 +0,0 @@
|
|||
# typed_service
|
||||
Angel services that use reflection (via mirrors or codegen) to (de)serialize PODO's.
|
||||
Useful for quick prototypes.
|
||||
|
||||
Typically, [`package:angel_serialize`](https://github.com/angel-dart/serialize)
|
||||
is recommended.
|
||||
|
||||
## Brief Example
|
||||
```dart
|
||||
main() async {
|
||||
var app = Angel();
|
||||
var http = AngelHttp(app);
|
||||
var service = TypedService<String, Todo>(MapService());
|
||||
hierarchicalLoggingEnabled = true;
|
||||
app.use('/api/todos', service);
|
||||
|
||||
app
|
||||
..serializer = god.serialize
|
||||
..logger = Logger.detached('typed_service')
|
||||
..logger.onRecord.listen((rec) {
|
||||
print(rec);
|
||||
if (rec.error != null) print(rec.error);
|
||||
if (rec.stackTrace != null) print(rec.stackTrace);
|
||||
});
|
||||
|
||||
await http.startServer('127.0.0.1', 3000);
|
||||
print('Listening at ${http.uri}');
|
||||
}
|
||||
```
|
|
@ -1 +0,0 @@
|
|||
include: package:lints/recommended.yaml
|
|
@ -1,5 +0,0 @@
|
|||
Command:
|
||||
|
||||
```bash
|
||||
curl -X POST -d '@example/data.json' -H 'content-type: application/json' http://localhost:3000/api/todos; echo
|
||||
```
|
|
@ -1,22 +0,0 @@
|
|||
[
|
||||
{
|
||||
"id": "0",
|
||||
"createdAt": null,
|
||||
"updatedAt": null,
|
||||
"idAsInt": null,
|
||||
"text": "Yes",
|
||||
"completed": false,
|
||||
"created_at": "2019-04-26T09:51:27.494884",
|
||||
"updated_at": "2019-04-26T09:51:27.494884"
|
||||
},
|
||||
{
|
||||
"id": "1",
|
||||
"createdAt": null,
|
||||
"updatedAt": null,
|
||||
"idAsInt": null,
|
||||
"text": "nOPE",
|
||||
"completed": false,
|
||||
"created_at": "2019-04-26T09:51:37.847741",
|
||||
"updated_at": "2019-04-26T09:51:37.847741"
|
||||
}
|
||||
]
|
|
@ -1,42 +0,0 @@
|
|||
import 'dart:io';
|
||||
import 'package:angel_file_service/angel_file_service.dart';
|
||||
import 'package:angel_framework/angel_framework.dart';
|
||||
import 'package:angel_framework/http.dart';
|
||||
import 'package:angel_typed_service/angel_typed_service.dart';
|
||||
import 'package:file/local.dart';
|
||||
import 'package:json_god/json_god.dart' as god;
|
||||
import 'package:logging/logging.dart';
|
||||
|
||||
void main() async {
|
||||
var app = Angel();
|
||||
var http = AngelHttp(app);
|
||||
var fs = LocalFileSystem();
|
||||
var exampleDir = fs.file(Platform.script).parent;
|
||||
var dataJson = exampleDir.childFile('data.json');
|
||||
var service = TypedService<String, Todo>(JsonFileService(dataJson));
|
||||
hierarchicalLoggingEnabled = true;
|
||||
app.use('/api/todos', service);
|
||||
|
||||
app
|
||||
..serializer = god.serialize
|
||||
..logger = Logger.detached('typed_service')
|
||||
..logger!.onRecord.listen((rec) {
|
||||
print(rec);
|
||||
if (rec.error != null) print(rec.error);
|
||||
if (rec.stackTrace != null) print(rec.stackTrace);
|
||||
});
|
||||
|
||||
await http.startServer('127.0.0.1', 3000);
|
||||
print('Listening at ${http.uri}');
|
||||
}
|
||||
|
||||
class Todo extends Model {
|
||||
String? text;
|
||||
bool? completed;
|
||||
|
||||
@override
|
||||
DateTime? createdAt, updatedAt;
|
||||
|
||||
Todo({String? id, this.text, this.completed, this.createdAt, this.updatedAt})
|
||||
: super(id: id);
|
||||
}
|
|
@ -1,100 +0,0 @@
|
|||
import 'dart:async';
|
||||
import 'dart:mirrors';
|
||||
import 'package:angel_framework/angel_framework.dart';
|
||||
import 'package:json_god/json_god.dart' as god;
|
||||
|
||||
/// An Angel service that uses reflection to (de)serialize Dart objects.
|
||||
class TypedService<Id, T> extends Service<Id, T?> {
|
||||
/// The inner service.
|
||||
final Service<Id, Map<String, dynamic>> inner;
|
||||
|
||||
TypedService(this.inner) : super() {
|
||||
if (!reflectType(T).isAssignableTo(reflectType(Model))) {
|
||||
throw Exception(
|
||||
'If you specify a type for TypedService, it must extend Model.');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
FutureOr<T>? Function(RequestContext, ResponseContext) get readData =>
|
||||
_readData;
|
||||
|
||||
T? _readData(RequestContext req, ResponseContext res) =>
|
||||
deserialize(req.bodyAsMap);
|
||||
|
||||
/// Attempts to deserialize [x] into an instance of [T].
|
||||
T? deserialize(x) {
|
||||
// print('DESERIALIZE: $x (${x.runtimeType})');
|
||||
if (x is T) {
|
||||
return x;
|
||||
} else if (x is Map) {
|
||||
var data = x.keys.fold({}, (dynamic map, key) {
|
||||
var value = x[key];
|
||||
|
||||
if ((key == 'createdAt' ||
|
||||
key == 'updatedAt' ||
|
||||
key == 'created_at' ||
|
||||
key == 'updated_at') &&
|
||||
value is String) {
|
||||
return map..[key] = DateTime.parse(value);
|
||||
} else {
|
||||
return map..[key] = value;
|
||||
}
|
||||
});
|
||||
|
||||
var result = god.deserializeDatum(data, outputType: T);
|
||||
|
||||
if (data['createdAt'] is DateTime) {
|
||||
result.createdAt = data['createdAt'] as DateTime?;
|
||||
} else if (data['created_at'] is DateTime) {
|
||||
result.createdAt = data['created_at'] as DateTime?;
|
||||
}
|
||||
|
||||
if (data['updatedAt'] is DateTime) {
|
||||
result.updatedAt = data['updatedAt'] as DateTime?;
|
||||
} else if (data['updated_at'] is DateTime) {
|
||||
result.updatedAt = data['updated_at'] as DateTime?;
|
||||
}
|
||||
|
||||
// print('x: $x\nresult: $result');
|
||||
return result as T?;
|
||||
} else {
|
||||
throw ArgumentError('Cannot convert $x to $T');
|
||||
}
|
||||
}
|
||||
|
||||
/// Serializes [x] into a [Map].
|
||||
Map<String, dynamic> serialize(x) {
|
||||
if (x is Model) {
|
||||
return (god.serializeObject(x) as Map).cast<String, dynamic>();
|
||||
} else if (x is Map) {
|
||||
return x.cast<String, dynamic>();
|
||||
} else {
|
||||
throw ArgumentError('Cannot serialize ${x.runtimeType}');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<T?>> index([Map<String, dynamic>? params]) =>
|
||||
inner.index(params).then((it) => it.map(deserialize).toList());
|
||||
|
||||
@override
|
||||
Future<T> create(data, [Map<String, dynamic>? params]) =>
|
||||
inner.create(serialize(data), params).then(deserialize as FutureOr<T> Function(Map<String, dynamic>));
|
||||
|
||||
@override
|
||||
Future<T> read(Id id, [Map<String, dynamic>? params]) =>
|
||||
inner.read(id, params).then(deserialize as FutureOr<T> Function(Map<String, dynamic>));
|
||||
|
||||
@override
|
||||
Future<T> modify(Id id, T? data, [Map<String, dynamic>? params]) =>
|
||||
inner.modify(id, serialize(data), params).then(deserialize as FutureOr<T> Function(Map<String, dynamic>));
|
||||
|
||||
@override
|
||||
Future<T> update(Id id, T? data, [Map<String, dynamic>? params]) =>
|
||||
inner.update(id, serialize(data), params).then(deserialize as FutureOr<T> Function(Map<String, dynamic>));
|
||||
|
||||
@override
|
||||
Future<T> remove(Id id, [Map<String, dynamic>? params]) =>
|
||||
inner.remove(id, params).then(deserialize as FutureOr<T> Function(Map<String, dynamic>));
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
name: angel_typed_service
|
||||
version: 2.0.0
|
||||
description: Angel services that use reflection to (de)serialize Dart objects.
|
||||
homepage: https://github.com/angel-dart/typed_service
|
||||
publish_to: none
|
||||
environment:
|
||||
sdk: '>=2.12.0 <3.0.0'
|
||||
dependencies:
|
||||
angel_framework:
|
||||
git:
|
||||
url: https://github.com/dukefirehawk/angel.git
|
||||
ref: sdk-2.12.x_nnbd
|
||||
path: packages/framework
|
||||
json_god:
|
||||
git:
|
||||
url: https://github.com/dukefirehawk/angel.git
|
||||
ref: sdk-2.12.x_nnbd
|
||||
path: packages/json_god
|
||||
|
||||
dev_dependencies:
|
||||
angel_file_service:
|
||||
git:
|
||||
url: https://github.com/dukefirehawk/angel.git
|
||||
ref: sdk-2.12.x_nnbd
|
||||
path: packages/file_service
|
||||
file: ^6.0.0
|
||||
logging: ^1.0.0
|
||||
lints: ^1.0.0
|
||||
test: ^1.15.7
|
|
@ -1,57 +0,0 @@
|
|||
import 'package:angel_framework/angel_framework.dart';
|
||||
import 'package:angel_typed_service/angel_typed_service.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
void main() {
|
||||
var svc = TypedService<String?, Todo>(MapService());
|
||||
|
||||
test('force model', () {
|
||||
expect(() => TypedService<String?, int>(MapService()), throwsException);
|
||||
});
|
||||
|
||||
test('serialize', () {
|
||||
expect(svc.serialize({'foo': 'bar'}), {'foo': 'bar'});
|
||||
expect(() => svc.serialize(2), throwsArgumentError);
|
||||
var now = DateTime.now();
|
||||
var t = Todo(
|
||||
id: '3', text: 'a', completed: false, createdAt: now, updatedAt: now);
|
||||
var m = svc.serialize(t);
|
||||
print(m);
|
||||
expect(m..remove('_identityHashCode')..remove('idAsInt'), {
|
||||
'id': '3',
|
||||
'createdAt': now.toIso8601String(),
|
||||
'updatedAt': now.toIso8601String(),
|
||||
'text': 'a',
|
||||
'completed': false
|
||||
});
|
||||
});
|
||||
|
||||
test('deserialize date', () {
|
||||
var now = DateTime.now();
|
||||
var m = svc.deserialize({
|
||||
'createdAt': now.toIso8601String(),
|
||||
'updatedAt': now.toIso8601String()
|
||||
})!;
|
||||
expect(m, const TypeMatcher<Todo>());
|
||||
expect(m.createdAt!.millisecondsSinceEpoch, now.millisecondsSinceEpoch);
|
||||
});
|
||||
|
||||
test('deserialize date w/ underscore', () {
|
||||
var now = DateTime.now();
|
||||
var m = svc.deserialize({
|
||||
'created_at': now.toIso8601String(),
|
||||
'updated_at': now.toIso8601String()
|
||||
})!;
|
||||
expect(m, const TypeMatcher<Todo>());
|
||||
expect(m.createdAt!.millisecondsSinceEpoch, now.millisecondsSinceEpoch);
|
||||
});
|
||||
}
|
||||
|
||||
class Todo extends Model {
|
||||
String? text;
|
||||
bool? completed;
|
||||
@override
|
||||
DateTime? createdAt, updatedAt;
|
||||
Todo({String? id, this.text, this.completed, this.createdAt, this.updatedAt})
|
||||
: super(id: id);
|
||||
}
|
BIN
archived_packages/wings.zip
Normal file
BIN
archived_packages/wings.zip
Normal file
Binary file not shown.
|
@ -1,121 +0,0 @@
|
|||
---
|
||||
Language: Cpp
|
||||
# BasedOnStyle: LLVM
|
||||
AccessModifierOffset: -2
|
||||
AlignAfterOpenBracket: Align
|
||||
AlignConsecutiveAssignments: false
|
||||
AlignConsecutiveDeclarations: false
|
||||
AlignEscapedNewlines: Right
|
||||
AlignOperands: true
|
||||
AlignTrailingComments: true
|
||||
AllowAllParametersOfDeclarationOnNextLine: true
|
||||
AllowShortBlocksOnASingleLine: false
|
||||
AllowShortCaseLabelsOnASingleLine: false
|
||||
AllowShortFunctionsOnASingleLine: All
|
||||
AllowShortIfStatementsOnASingleLine: false
|
||||
AllowShortLoopsOnASingleLine: false
|
||||
AlwaysBreakAfterDefinitionReturnType: None
|
||||
AlwaysBreakAfterReturnType: None
|
||||
AlwaysBreakBeforeMultilineStrings: false
|
||||
AlwaysBreakTemplateDeclarations: MultiLine
|
||||
BinPackArguments: true
|
||||
BinPackParameters: true
|
||||
BraceWrapping:
|
||||
AfterClass: false
|
||||
AfterControlStatement: false
|
||||
AfterEnum: false
|
||||
AfterFunction: false
|
||||
AfterNamespace: false
|
||||
AfterObjCDeclaration: false
|
||||
AfterStruct: false
|
||||
AfterUnion: false
|
||||
AfterExternBlock: false
|
||||
BeforeCatch: false
|
||||
BeforeElse: false
|
||||
IndentBraces: false
|
||||
SplitEmptyFunction: true
|
||||
SplitEmptyRecord: true
|
||||
SplitEmptyNamespace: true
|
||||
BreakBeforeBinaryOperators: None
|
||||
BreakBeforeBraces: Attach
|
||||
BreakBeforeInheritanceComma: false
|
||||
BreakInheritanceList: BeforeColon
|
||||
BreakBeforeTernaryOperators: true
|
||||
BreakConstructorInitializersBeforeComma: false
|
||||
BreakConstructorInitializers: BeforeColon
|
||||
BreakAfterJavaFieldAnnotations: false
|
||||
BreakStringLiterals: true
|
||||
ColumnLimit: 80
|
||||
CommentPragmas: '^ IWYU pragma:'
|
||||
CompactNamespaces: false
|
||||
ConstructorInitializerAllOnOneLineOrOnePerLine: false
|
||||
ConstructorInitializerIndentWidth: 4
|
||||
ContinuationIndentWidth: 4
|
||||
Cpp11BracedListStyle: true
|
||||
DerivePointerAlignment: false
|
||||
DisableFormat: false
|
||||
ExperimentalAutoDetectBinPacking: false
|
||||
FixNamespaceComments: true
|
||||
ForEachMacros:
|
||||
- foreach
|
||||
- Q_FOREACH
|
||||
- BOOST_FOREACH
|
||||
IncludeBlocks: Preserve
|
||||
IncludeCategories:
|
||||
- Regex: '^"(llvm|llvm-c|clang|clang-c)/'
|
||||
Priority: 2
|
||||
- Regex: '^(<|"(gtest|gmock|isl|json)/)'
|
||||
Priority: 3
|
||||
- Regex: '.*'
|
||||
Priority: 1
|
||||
IncludeIsMainRegex: '(Test)?$'
|
||||
IndentCaseLabels: false
|
||||
IndentPPDirectives: None
|
||||
IndentWidth: 2
|
||||
IndentWrappedFunctionNames: false
|
||||
JavaScriptQuotes: Leave
|
||||
JavaScriptWrapImports: true
|
||||
KeepEmptyLinesAtTheStartOfBlocks: true
|
||||
MacroBlockBegin: ''
|
||||
MacroBlockEnd: ''
|
||||
MaxEmptyLinesToKeep: 1
|
||||
NamespaceIndentation: None
|
||||
ObjCBinPackProtocolList: Auto
|
||||
ObjCBlockIndentWidth: 2
|
||||
ObjCSpaceAfterProperty: false
|
||||
ObjCSpaceBeforeProtocolList: true
|
||||
PenaltyBreakAssignment: 2
|
||||
PenaltyBreakBeforeFirstCallParameter: 19
|
||||
PenaltyBreakComment: 300
|
||||
PenaltyBreakFirstLessLess: 120
|
||||
PenaltyBreakString: 1000
|
||||
PenaltyBreakTemplateDeclaration: 10
|
||||
PenaltyExcessCharacter: 1000000
|
||||
PenaltyReturnTypeOnItsOwnLine: 60
|
||||
PointerAlignment: Right
|
||||
ReflowComments: true
|
||||
SortIncludes: true
|
||||
SortUsingDeclarations: true
|
||||
SpaceAfterCStyleCast: false
|
||||
SpaceAfterTemplateKeyword: true
|
||||
SpaceBeforeAssignmentOperators: true
|
||||
SpaceBeforeCpp11BracedList: false
|
||||
SpaceBeforeCtorInitializerColon: true
|
||||
SpaceBeforeInheritanceColon: true
|
||||
SpaceBeforeParens: ControlStatements
|
||||
SpaceBeforeRangeBasedForLoopColon: true
|
||||
SpaceInEmptyParentheses: false
|
||||
SpacesBeforeTrailingComments: 1
|
||||
SpacesInAngles: false
|
||||
SpacesInContainerLiterals: true
|
||||
SpacesInCStyleCastParentheses: false
|
||||
SpacesInParentheses: false
|
||||
SpacesInSquareBrackets: false
|
||||
Standard: Cpp11
|
||||
StatementMacros:
|
||||
- Q_UNUSED
|
||||
- QT_REQUIRE_VERSION
|
||||
TabWidth: 8
|
||||
UseTab: Never
|
||||
...
|
||||
|
67
archived_packages/wings/.gitignore
vendored
67
archived_packages/wings/.gitignore
vendored
|
@ -1,67 +0,0 @@
|
|||
# See https://www.dartlang.org/guides/libraries/private-files
|
||||
|
||||
# Files and directories created by pub
|
||||
.dart_tool/
|
||||
.packages
|
||||
build/
|
||||
# If you're building an application, you may want to check-in your pubspec.lock
|
||||
pubspec.lock
|
||||
|
||||
# Directory created by dartdoc
|
||||
# If you don't generate documentation locally you can remove this line.
|
||||
doc/api/
|
||||
|
||||
# Avoid committing generated Javascript files:
|
||||
*.dart.js
|
||||
*.info.json # Produced by the --dump-info flag.
|
||||
*.js # When generated by dart2js. Don't specify *.js if your
|
||||
# project includes source files written in JavaScript.
|
||||
*.js_
|
||||
*.js.deps
|
||||
*.js.map
|
||||
|
||||
*.o
|
||||
# *.dylib
|
||||
*.a
|
||||
# *.so
|
||||
*.lib
|
||||
*.obj
|
||||
|
||||
.vscode/
|
||||
.idea/
|
||||
# Created by https://www.gitignore.io/api/cmake
|
||||
# Edit at https://www.gitignore.io/?templates=cmake
|
||||
|
||||
### CMake ###
|
||||
CMakeLists.txt.user
|
||||
CMakeCache.txt
|
||||
CMakeFiles
|
||||
CMakeScripts
|
||||
Testing
|
||||
Makefile
|
||||
cmake_install.cmake
|
||||
install_manifest.txt
|
||||
compile_commands.json
|
||||
CTestTestfile.cmake
|
||||
_deps
|
||||
|
||||
### CMake Patch ###
|
||||
# External projects
|
||||
*-prefix/
|
||||
|
||||
# End of https://www.gitignore.io/api/cmake
|
||||
|
||||
# Created by https://www.gitignore.io/api/vagrant
|
||||
# Edit at https://www.gitignore.io/?templates=vagrant
|
||||
|
||||
### Vagrant ###
|
||||
# General
|
||||
.vagrant/
|
||||
|
||||
# Log files (if you are creating logs in debug mode, uncomment this)
|
||||
# *.log
|
||||
|
||||
### Vagrant Patch ###
|
||||
*.box
|
||||
|
||||
# End of https://www.gitignore.io/api/vagrant
|
6
archived_packages/wings/.gitmodules
vendored
6
archived_packages/wings/.gitmodules
vendored
|
@ -1,6 +0,0 @@
|
|||
[submodule "cmake_dart_utils"]
|
||||
path = cmake_dart_utils
|
||||
url = https://github.com/thosakwe/cmake_dart_utils.git
|
||||
[submodule "lib/src/http-parser"]
|
||||
path = lib/src/http-parser
|
||||
url = https://github.com/nodejs/http-parser.git
|
|
@ -1,6 +0,0 @@
|
|||
cmake_minimum_required(VERSION 3.0)
|
||||
project(angel_wings)
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake_dart_utils/cmake")
|
||||
find_package(Dart REQUIRED)
|
||||
set(CMAKE_CXX_STANDARD 11)
|
||||
add_subdirectory(lib/src)
|
|
@ -1,29 +0,0 @@
|
|||
BSD 3-Clause License
|
||||
|
||||
Copyright (c) 2021, dukefirehawk.com
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@ -1,51 +0,0 @@
|
|||
# wings
|
||||
Native HTTP driver for Angel, for a nice speed boost.
|
||||
|
||||
Not ready for production.
|
||||
|
||||
## How does it work?
|
||||
Typically, Angel uses the `AngelHttp` driver, which is wrapper over the `HttpServer` functionality in
|
||||
`dart:io`, which in turns uses the `ServerSocket` and `Socket` functionality. This is great - Dart's standard
|
||||
library comes with an HTTP server, which saves a lot of difficult in implementation.
|
||||
|
||||
However, abstraction tends to come with a cost. Wings seeks to minimize abstraction entirely. Rather than
|
||||
using the built-in Dart network stack, Wings' HTTP server is implemented in C++ as a Dart native extension,
|
||||
and the `AngelWings` driver listens to events from the extension and converts them directly into
|
||||
`RequestContext` objects, without any additional abstraction within. This reduces the amount of computation
|
||||
performed on each request, and helps to minimize response latency. Sending data from the response buffer in plain
|
||||
Dart surprisingly is the most expensive operation, as is revealed by the Observatory.
|
||||
|
||||
By combining Dart's powerful VM with a native code server based on
|
||||
[the same one used in Node.js](https://github.com/nodejs/http-parser),
|
||||
`AngelWings` trims several milliseconds off every request, both saving resources and reducing
|
||||
load times for high-traffic applications.
|
||||
|
||||
## How can I use it?
|
||||
The intended way to use `AngelWings` is via
|
||||
[`package:build_native`](https://github.com/thosakwe/build_native);
|
||||
however, the situation surrounding distributing native extensions is yet far from ideal,
|
||||
so this package includes pre-built binaries out-of-the-box.
|
||||
|
||||
Thanks to this, you can use it like any other Dart package, by installing it via Pub.
|
||||
|
||||
## Brief example
|
||||
Using `AngelWings` is almost identical to using `AngelHttp`; however, it does
|
||||
not support SSL, and therefore should be placed behind a reverse proxy like `nginx` in production.
|
||||
|
||||
```dart
|
||||
main() async {
|
||||
var app = new Angel();
|
||||
var wings = new AngelWings(app, shared: true, useZone: false);
|
||||
|
||||
app.injectEncoders({'gzip': gzip.encoder, 'deflate': zlib.encoder});
|
||||
|
||||
app.get('/hello', 'Hello, native world! This is Angel WINGS.');
|
||||
|
||||
var fs = const LocalFileSystem();
|
||||
var vDir = new VirtualDirectory(app, fs, source: fs.directory('web'));
|
||||
app.use(vDir.handleRequest);
|
||||
|
||||
await wings.startServer('127.0.0.1', 3000);
|
||||
print('Listening at http://${wings.address.address}:${wings.port}');
|
||||
}
|
||||
```
|
6
archived_packages/wings/Vagrantfile
vendored
6
archived_packages/wings/Vagrantfile
vendored
|
@ -1,6 +0,0 @@
|
|||
# -*- mode: ruby -*-
|
||||
# vi: set ft=ruby :
|
||||
Vagrant.configure("2") do |config|
|
||||
config.vm.box = "ubuntu/bionic64"
|
||||
config.vm.provision "shell", path: "provision.sh"
|
||||
end
|
|
@ -1 +0,0 @@
|
|||
include: package:lints/recommended.yaml
|
|
@ -1,23 +0,0 @@
|
|||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
import 'package:angel_framework/angel_framework.dart';
|
||||
import 'util.dart';
|
||||
|
||||
const AngelBenchmark emptyBenchmark = _EmptyBenchmark();
|
||||
|
||||
void main() => runBenchmarks([emptyBenchmark]);
|
||||
|
||||
class _EmptyBenchmark implements AngelBenchmark {
|
||||
const _EmptyBenchmark();
|
||||
|
||||
@override
|
||||
String get name => 'empty';
|
||||
|
||||
@override
|
||||
FutureOr<void> rawHandler(HttpRequest req, HttpResponse res) {
|
||||
return res.close();
|
||||
}
|
||||
|
||||
@override
|
||||
void setupAngel(Angel app) {}
|
||||
}
|
|
@ -1,112 +0,0 @@
|
|||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
import 'dart:isolate';
|
||||
import 'package:angel_framework/angel_framework.dart';
|
||||
import 'package:angel_framework/http.dart';
|
||||
import 'package:angel_wings/angel_wings.dart';
|
||||
import 'package:io/ansi.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
|
||||
Future<Process> _runWrk(
|
||||
{ProcessStartMode mode = ProcessStartMode.inheritStdio}) async {
|
||||
return await Process.start('wrk', ['http://localhost:$testPort'], mode: mode);
|
||||
}
|
||||
|
||||
Future<void> _warmUp() async {
|
||||
var wrk = await _runWrk();
|
||||
await wrk.exitCode;
|
||||
// await wrk.stderr.drain();
|
||||
// await wrk.stdout.drain();
|
||||
}
|
||||
|
||||
Future _10s() => Future.delayed(Duration(seconds: 10));
|
||||
|
||||
const testPort = 8877;
|
||||
|
||||
Future<void> runBenchmarks(Iterable<AngelBenchmark> benchmarks,
|
||||
{Iterable<String> factories = const [
|
||||
// 'angel_http',
|
||||
'angel_wings',
|
||||
]}) async {
|
||||
for (var benchmark in benchmarks) {
|
||||
print(magenta.wrap('Entering benchmark: ${benchmark.name}'));
|
||||
|
||||
// // Run dart:io
|
||||
// print(lightGray.wrap('Booting dart:io server (waiting 10s)...'));
|
||||
// var isolates = <Isolate>[];
|
||||
// for (int i = 0; i < Platform.numberOfProcessors; i++) {
|
||||
// isolates.add(await Isolate.spawn(_httpIsolate, benchmark));
|
||||
// }
|
||||
|
||||
// await _10s();
|
||||
// print(lightGray.wrap('Warming up dart:io server...'));
|
||||
// await _warmUp();
|
||||
|
||||
// stdout
|
||||
// ..write(lightGray.wrap('Now running `wrk` for '))
|
||||
// ..write(cyan.wrap(benchmark.name))
|
||||
// ..writeln(lightGray.wrap(' (waiting 10s)...'));
|
||||
// var wrk = await _runWrk(mode: ProcessStartMode.inheritStdio);
|
||||
// await wrk.exitCode;
|
||||
// isolates.forEach((i) => i.kill(priority: Isolate.immediate));
|
||||
|
||||
// Run Angel HTTP, Wings
|
||||
for (var fac in factories) {
|
||||
print(lightGray.wrap('Booting $fac server...'));
|
||||
|
||||
var isolates = <Isolate>[];
|
||||
for (var i = 0; i < Platform.numberOfProcessors; i++) {
|
||||
isolates
|
||||
.add(await Isolate.spawn(_angelIsolate, Tuple2(benchmark, fac)));
|
||||
}
|
||||
|
||||
await _10s();
|
||||
print(lightGray.wrap('Warming up $fac server...'));
|
||||
await _warmUp();
|
||||
stdout
|
||||
..write(lightGray.wrap('Now running `wrk` for '))
|
||||
..write(cyan.wrap(benchmark.name))
|
||||
..writeln(lightGray.wrap('...'));
|
||||
var wrk = await _runWrk(mode: ProcessStartMode.inheritStdio);
|
||||
await wrk.exitCode;
|
||||
}
|
||||
}
|
||||
|
||||
exit(0);
|
||||
}
|
||||
|
||||
/*
|
||||
void _httpIsolate(AngelBenchmark benchmark) {
|
||||
Future(() async {
|
||||
var raw = await HttpServer.bind(InternetAddress.loopbackIPv4, testPort,
|
||||
shared: true);
|
||||
raw.listen((r) => benchmark.rawHandler(r, r.response));
|
||||
});
|
||||
}
|
||||
*/
|
||||
|
||||
void _angelIsolate(Tuple2<AngelBenchmark, String> args) {
|
||||
Future(() async {
|
||||
var app = Angel();
|
||||
late Driver driver;
|
||||
|
||||
if (args.item2 == 'angel_http') {
|
||||
driver = AngelHttp.custom(app, startShared);
|
||||
} else if (args.item2 == 'angel_wings') {
|
||||
driver = AngelWings.custom(app, startSharedWings);
|
||||
}
|
||||
|
||||
await app.configure(args.item1.setupAngel);
|
||||
await driver.startServer(InternetAddress.loopbackIPv4, testPort);
|
||||
});
|
||||
}
|
||||
|
||||
abstract class AngelBenchmark {
|
||||
const AngelBenchmark();
|
||||
|
||||
String get name;
|
||||
|
||||
FutureOr<void> setupAngel(Angel app);
|
||||
|
||||
FutureOr<void> rawHandler(HttpRequest req, HttpResponse res);
|
||||
}
|
|
@ -1,34 +0,0 @@
|
|||
import 'dart:io';
|
||||
import 'package:angel_framework/angel_framework.dart';
|
||||
import 'package:angel_static/angel_static.dart';
|
||||
import 'package:angel_wings/angel_wings.dart';
|
||||
import 'package:file/local.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:pretty_logging/pretty_logging.dart';
|
||||
|
||||
void main() async {
|
||||
hierarchicalLoggingEnabled = true;
|
||||
|
||||
var logger = Logger.detached('wings')
|
||||
..level = Level.ALL
|
||||
..onRecord.listen(prettyLog);
|
||||
var app = Angel(logger: logger);
|
||||
var wings = AngelWings(app);
|
||||
var fs = LocalFileSystem();
|
||||
var vDir = CachingVirtualDirectory(app, fs,
|
||||
source: fs.currentDirectory, allowDirectoryListing: true);
|
||||
|
||||
app.mimeTypeResolver.addExtension('yaml', 'text/x-yaml');
|
||||
|
||||
app.get('/', (req, res) => 'WINGS!!!');
|
||||
app.post('/', (req, res) async {
|
||||
await req.parseBody();
|
||||
return req.bodyAsMap;
|
||||
});
|
||||
|
||||
app.fallback(vDir.handleRequest);
|
||||
app.fallback((req, res) => throw AngelHttpException.notFound());
|
||||
|
||||
await wings.startServer(InternetAddress.loopbackIPv4, 3000);
|
||||
print('Listening at ${wings.uri}');
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue