2018-08-20 20:58:37 +00:00
|
|
|
library angel_framework.core.hooked_service;
|
2016-09-15 19:53:01 +00:00
|
|
|
|
|
|
|
import 'dart:async';
|
2018-09-11 20:34:29 +00:00
|
|
|
|
2016-09-15 19:53:01 +00:00
|
|
|
import '../util.dart';
|
2018-09-11 20:34:29 +00:00
|
|
|
import 'metadata.dart';
|
2017-01-20 22:11:20 +00:00
|
|
|
import 'request_context.dart';
|
|
|
|
import 'response_context.dart';
|
2018-08-20 20:21:06 +00:00
|
|
|
import 'routable.dart';
|
2018-08-21 02:44:32 +00:00
|
|
|
import 'server.dart';
|
2016-09-15 19:53:01 +00:00
|
|
|
import 'service.dart';
|
2016-05-02 22:28:14 +00:00
|
|
|
|
2016-06-19 05:02:41 +00:00
|
|
|
/// Wraps another service in a service that broadcasts events on actions.
|
2018-09-11 20:25:07 +00:00
|
|
|
class HookedService<Id, Data, T extends Service<Id, Data>>
|
|
|
|
extends Service<Id, Data> {
|
2017-04-04 08:35:36 +00:00
|
|
|
final List<StreamController<HookedServiceEvent>> _ctrl = [];
|
|
|
|
|
2016-06-21 22:56:04 +00:00
|
|
|
/// Tbe service that is proxied by this hooked one.
|
2018-09-11 20:25:07 +00:00
|
|
|
final T inner;
|
2016-05-02 22:28:14 +00:00
|
|
|
|
2018-09-11 20:34:29 +00:00
|
|
|
final HookedServiceEventDispatcher<Id, Data, T> beforeIndexed =
|
|
|
|
new HookedServiceEventDispatcher<Id, Data, T>();
|
|
|
|
final HookedServiceEventDispatcher<Id, Data, T> beforeRead =
|
|
|
|
new HookedServiceEventDispatcher<Id, Data, T>();
|
|
|
|
final HookedServiceEventDispatcher<Id, Data, T> beforeCreated =
|
|
|
|
new HookedServiceEventDispatcher<Id, Data, T>();
|
|
|
|
final HookedServiceEventDispatcher<Id, Data, T> beforeModified =
|
|
|
|
new HookedServiceEventDispatcher<Id, Data, T>();
|
|
|
|
final HookedServiceEventDispatcher<Id, Data, T> beforeUpdated =
|
|
|
|
new HookedServiceEventDispatcher<Id, Data, T>();
|
|
|
|
final HookedServiceEventDispatcher<Id, Data, T> beforeRemoved =
|
|
|
|
new HookedServiceEventDispatcher<Id, Data, T>();
|
|
|
|
final HookedServiceEventDispatcher<Id, Data, T> afterIndexed =
|
|
|
|
new HookedServiceEventDispatcher<Id, Data, T>();
|
|
|
|
final HookedServiceEventDispatcher<Id, Data, T> afterRead =
|
|
|
|
new HookedServiceEventDispatcher<Id, Data, T>();
|
|
|
|
final HookedServiceEventDispatcher<Id, Data, T> afterCreated =
|
|
|
|
new HookedServiceEventDispatcher<Id, Data, T>();
|
|
|
|
final HookedServiceEventDispatcher<Id, Data, T> afterModified =
|
|
|
|
new HookedServiceEventDispatcher<Id, Data, T>();
|
|
|
|
final HookedServiceEventDispatcher<Id, Data, T> afterUpdated =
|
|
|
|
new HookedServiceEventDispatcher<Id, Data, T>();
|
|
|
|
final HookedServiceEventDispatcher<Id, Data, T> afterRemoved =
|
|
|
|
new HookedServiceEventDispatcher<Id, Data, T>();
|
2016-06-21 04:19:43 +00:00
|
|
|
|
2018-09-11 20:25:07 +00:00
|
|
|
HookedService(this.inner) {
|
2016-06-24 19:19:02 +00:00
|
|
|
// Clone app instance
|
2016-10-22 20:41:36 +00:00
|
|
|
if (inner.app != null) this.app = inner.app;
|
2016-11-23 09:10:47 +00:00
|
|
|
}
|
2016-06-24 19:19:02 +00:00
|
|
|
|
2017-01-20 22:40:48 +00:00
|
|
|
RequestContext _getRequest(Map params) {
|
|
|
|
if (params == null) return null;
|
2018-06-23 03:29:38 +00:00
|
|
|
return params['__requestctx'] as RequestContext;
|
2017-01-20 22:40:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
ResponseContext _getResponse(Map params) {
|
|
|
|
if (params == null) return null;
|
2018-06-23 03:29:38 +00:00
|
|
|
return params['__responsectx'] as ResponseContext;
|
2017-01-20 22:40:48 +00:00
|
|
|
}
|
|
|
|
|
2018-09-11 20:25:07 +00:00
|
|
|
Map<String, dynamic> _stripReq(Map<String, dynamic> params) {
|
2017-01-20 22:11:20 +00:00
|
|
|
if (params == null)
|
|
|
|
return params;
|
|
|
|
else
|
2017-01-28 03:47:00 +00:00
|
|
|
return params.keys
|
|
|
|
.where((key) => key != '__requestctx' && key != '__responsectx')
|
2018-09-11 20:25:07 +00:00
|
|
|
.fold<Map<String, dynamic>>(
|
|
|
|
{}, (map, key) => map..[key] = params[key]);
|
2017-01-20 22:11:20 +00:00
|
|
|
}
|
|
|
|
|
2017-04-04 08:35:36 +00:00
|
|
|
/// Closes any open [StreamController]s on this instance. **Internal use only**.
|
2017-04-25 02:44:22 +00:00
|
|
|
@override
|
2018-06-08 07:06:26 +00:00
|
|
|
Future close() {
|
2017-04-04 08:35:36 +00:00
|
|
|
_ctrl.forEach((c) => c.close());
|
|
|
|
beforeIndexed._close();
|
|
|
|
beforeRead._close();
|
|
|
|
beforeCreated._close();
|
|
|
|
beforeModified._close();
|
|
|
|
beforeUpdated._close();
|
|
|
|
beforeRemoved._close();
|
|
|
|
afterIndexed._close();
|
|
|
|
afterRead._close();
|
|
|
|
afterCreated._close();
|
|
|
|
afterModified._close();
|
|
|
|
afterUpdated._close();
|
|
|
|
afterRemoved._close();
|
2018-06-08 07:06:26 +00:00
|
|
|
inner.close();
|
|
|
|
return new Future.value();
|
2017-04-04 08:35:36 +00:00
|
|
|
}
|
|
|
|
|
2017-01-20 22:11:20 +00:00
|
|
|
/// Adds hooks to this instance.
|
2018-08-21 02:44:32 +00:00
|
|
|
void addHooks(Angel app) {
|
|
|
|
Hooks hooks = getAnnotation(inner, Hooks, app.container.reflector);
|
2018-09-11 20:34:29 +00:00
|
|
|
List<HookedServiceEventListener<Id, Data, T>> before = [], after = [];
|
2016-12-10 14:05:40 +00:00
|
|
|
|
|
|
|
if (hooks != null) {
|
|
|
|
before.addAll(hooks.before);
|
|
|
|
after.addAll(hooks.after);
|
|
|
|
}
|
|
|
|
|
2018-09-11 20:34:29 +00:00
|
|
|
void applyListeners(
|
|
|
|
Function fn, HookedServiceEventDispatcher<Id, Data, T> dispatcher,
|
2016-12-10 14:05:40 +00:00
|
|
|
[bool isAfter]) {
|
2018-08-21 02:44:32 +00:00
|
|
|
Hooks hooks = getAnnotation(fn, Hooks, app.container.reflector);
|
2018-09-11 20:34:29 +00:00
|
|
|
final listeners = <HookedServiceEventListener<Id, Data, T>>[]
|
2018-06-23 03:29:38 +00:00
|
|
|
..addAll(isAfter == true ? after : before);
|
2016-12-10 14:05:40 +00:00
|
|
|
|
|
|
|
if (hooks != null)
|
|
|
|
listeners.addAll(isAfter == true ? hooks.after : hooks.before);
|
|
|
|
|
|
|
|
listeners.forEach(dispatcher.listen);
|
|
|
|
}
|
|
|
|
|
|
|
|
applyListeners(inner.index, beforeIndexed);
|
|
|
|
applyListeners(inner.read, beforeRead);
|
2017-08-03 16:40:21 +00:00
|
|
|
applyListeners(inner.create, beforeCreated);
|
2016-12-10 14:05:40 +00:00
|
|
|
applyListeners(inner.modify, beforeModified);
|
2017-08-03 16:40:21 +00:00
|
|
|
applyListeners(inner.update, beforeUpdated);
|
|
|
|
applyListeners(inner.remove, beforeRemoved);
|
2016-12-10 14:05:40 +00:00
|
|
|
applyListeners(inner.index, afterIndexed, true);
|
|
|
|
applyListeners(inner.read, afterRead, true);
|
2017-08-03 16:40:21 +00:00
|
|
|
applyListeners(inner.create, afterCreated, true);
|
2016-12-10 14:05:40 +00:00
|
|
|
applyListeners(inner.modify, afterModified, true);
|
2017-08-03 16:40:21 +00:00
|
|
|
applyListeners(inner.update, afterUpdated, true);
|
|
|
|
applyListeners(inner.remove, afterRemoved, true);
|
2016-12-10 14:05:40 +00:00
|
|
|
}
|
|
|
|
|
2018-08-21 18:50:43 +00:00
|
|
|
List<RequestHandler> get bootstrappers =>
|
|
|
|
new List<RequestHandler>.from(super.bootstrappers)
|
|
|
|
..add((RequestContext req, ResponseContext res) {
|
|
|
|
req.serviceParams
|
|
|
|
..['__requestctx'] = req
|
|
|
|
..['__responsectx'] = res;
|
|
|
|
return true;
|
|
|
|
});
|
2018-06-08 07:06:26 +00:00
|
|
|
|
|
|
|
void addRoutes([Service s]) {
|
|
|
|
super.addRoutes(s ?? inner);
|
2016-06-23 19:05:55 +00:00
|
|
|
}
|
2016-05-02 22:28:14 +00:00
|
|
|
|
2017-01-28 21:08:07 +00:00
|
|
|
/// Runs the [listener] before every service method specified.
|
2018-09-11 20:34:29 +00:00
|
|
|
void before(Iterable<String> eventNames,
|
|
|
|
HookedServiceEventListener<Id, Data, T> listener) {
|
2017-01-28 21:08:07 +00:00
|
|
|
eventNames.map((name) {
|
|
|
|
switch (name) {
|
2017-08-03 16:40:21 +00:00
|
|
|
case HookedServiceEvent.indexed:
|
2017-01-28 21:08:07 +00:00
|
|
|
return beforeIndexed;
|
2017-08-03 16:40:21 +00:00
|
|
|
case HookedServiceEvent.read:
|
2017-01-28 21:08:07 +00:00
|
|
|
return beforeRead;
|
2017-08-03 16:40:21 +00:00
|
|
|
case HookedServiceEvent.created:
|
2017-01-28 21:08:07 +00:00
|
|
|
return beforeCreated;
|
2017-08-03 16:40:21 +00:00
|
|
|
case HookedServiceEvent.modified:
|
2017-01-28 21:08:07 +00:00
|
|
|
return beforeModified;
|
2017-08-03 16:40:21 +00:00
|
|
|
case HookedServiceEvent.updated:
|
2017-01-28 21:08:07 +00:00
|
|
|
return beforeUpdated;
|
2017-08-03 16:40:21 +00:00
|
|
|
case HookedServiceEvent.removed:
|
2017-01-28 21:08:07 +00:00
|
|
|
return beforeRemoved;
|
|
|
|
default:
|
|
|
|
throw new ArgumentError('Invalid service method: ${name}');
|
|
|
|
}
|
2018-09-11 20:34:29 +00:00
|
|
|
}).forEach((HookedServiceEventDispatcher<Id, Data, T> dispatcher) =>
|
2017-01-28 21:08:07 +00:00
|
|
|
dispatcher.listen(listener));
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Runs the [listener] after every service method specified.
|
2018-09-11 20:34:29 +00:00
|
|
|
void after(Iterable<String> eventNames,
|
|
|
|
HookedServiceEventListener<Id, Data, T> listener) {
|
2017-01-28 21:08:07 +00:00
|
|
|
eventNames.map((name) {
|
|
|
|
switch (name) {
|
2017-08-03 16:40:21 +00:00
|
|
|
case HookedServiceEvent.indexed:
|
2017-01-28 21:08:07 +00:00
|
|
|
return afterIndexed;
|
2017-08-03 16:40:21 +00:00
|
|
|
case HookedServiceEvent.read:
|
2017-01-28 21:08:07 +00:00
|
|
|
return afterRead;
|
2017-08-03 16:40:21 +00:00
|
|
|
case HookedServiceEvent.created:
|
2017-01-28 21:08:07 +00:00
|
|
|
return afterCreated;
|
2017-08-03 16:40:21 +00:00
|
|
|
case HookedServiceEvent.modified:
|
2017-01-28 21:08:07 +00:00
|
|
|
return afterModified;
|
2017-08-03 16:40:21 +00:00
|
|
|
case HookedServiceEvent.updated:
|
2017-01-28 21:08:07 +00:00
|
|
|
return afterUpdated;
|
2017-08-03 16:40:21 +00:00
|
|
|
case HookedServiceEvent.removed:
|
2017-01-28 21:08:07 +00:00
|
|
|
return afterRemoved;
|
|
|
|
default:
|
|
|
|
throw new ArgumentError('Invalid service method: ${name}');
|
|
|
|
}
|
2018-09-11 20:34:29 +00:00
|
|
|
}).forEach((HookedServiceEventDispatcher<Id, Data, T> dispatcher) =>
|
2017-01-28 21:08:07 +00:00
|
|
|
dispatcher.listen(listener));
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Runs the [listener] before every service method.
|
2018-09-11 20:34:29 +00:00
|
|
|
void beforeAll(HookedServiceEventListener<Id, Data, T> listener) {
|
2017-01-28 03:47:00 +00:00
|
|
|
beforeIndexed.listen(listener);
|
|
|
|
beforeRead.listen(listener);
|
|
|
|
beforeCreated.listen(listener);
|
|
|
|
beforeModified.listen(listener);
|
|
|
|
beforeUpdated.listen(listener);
|
|
|
|
beforeRemoved.listen(listener);
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Runs the [listener] after every service method.
|
2018-09-11 20:34:29 +00:00
|
|
|
void afterAll(HookedServiceEventListener<Id, Data, T> listener) {
|
2017-01-28 03:47:00 +00:00
|
|
|
afterIndexed.listen(listener);
|
|
|
|
afterRead.listen(listener);
|
|
|
|
afterCreated.listen(listener);
|
|
|
|
afterModified.listen(listener);
|
|
|
|
afterUpdated.listen(listener);
|
|
|
|
afterRemoved.listen(listener);
|
|
|
|
}
|
|
|
|
|
2017-04-04 08:35:36 +00:00
|
|
|
/// Returns a [Stream] of all events fired before every service method.
|
|
|
|
///
|
|
|
|
/// *NOTE*: Only use this if you do not plan to modify events. There is no guarantee
|
|
|
|
/// that events coming out of this [Stream] will see changes you make within the [Stream]
|
|
|
|
/// callback.
|
2018-09-11 20:34:29 +00:00
|
|
|
Stream<HookedServiceEvent<Id, Data, T>> beforeAllStream() {
|
|
|
|
var ctrl = new StreamController<HookedServiceEvent<Id, Data, T>>();
|
2017-04-04 08:35:36 +00:00
|
|
|
_ctrl.add(ctrl);
|
2017-08-03 16:40:21 +00:00
|
|
|
before(HookedServiceEvent.all, ctrl.add);
|
2017-04-04 08:35:36 +00:00
|
|
|
return ctrl.stream;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns a [Stream] of all events fired after every service method.
|
|
|
|
///
|
|
|
|
/// *NOTE*: Only use this if you do not plan to modify events. There is no guarantee
|
|
|
|
/// that events coming out of this [Stream] will see changes you make within the [Stream]
|
|
|
|
/// callback.
|
2018-09-11 20:34:29 +00:00
|
|
|
Stream<HookedServiceEvent<Id, Data, T>> afterAllStream() {
|
|
|
|
var ctrl = new StreamController<HookedServiceEvent<Id, Data, T>>();
|
2017-04-04 08:35:36 +00:00
|
|
|
_ctrl.add(ctrl);
|
2017-08-03 16:40:21 +00:00
|
|
|
before(HookedServiceEvent.all, ctrl.add);
|
2017-04-04 08:35:36 +00:00
|
|
|
return ctrl.stream;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns a [Stream] of all events fired before every service method specified.
|
|
|
|
///
|
|
|
|
/// *NOTE*: Only use this if you do not plan to modify events. There is no guarantee
|
|
|
|
/// that events coming out of this [Stream] will see changes you make within the [Stream]
|
|
|
|
/// callback.
|
2018-09-11 20:34:29 +00:00
|
|
|
Stream<HookedServiceEvent<Id, Data, T>> beforeStream(
|
|
|
|
Iterable<String> eventNames) {
|
|
|
|
var ctrl = new StreamController<HookedServiceEvent<Id, Data, T>>();
|
2017-04-04 08:35:36 +00:00
|
|
|
_ctrl.add(ctrl);
|
|
|
|
before(eventNames, ctrl.add);
|
|
|
|
return ctrl.stream;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns a [Stream] of all events fired AFTER every service method specified.
|
|
|
|
///
|
|
|
|
/// *NOTE*: Only use this if you do not plan to modify events. There is no guarantee
|
|
|
|
/// that events coming out of this [Stream] will see changes you make within the [Stream]
|
|
|
|
/// callback.
|
2018-09-11 20:34:29 +00:00
|
|
|
Stream<HookedServiceEvent<Id, Data, T>> afterStream(
|
|
|
|
Iterable<String> eventNames) {
|
|
|
|
var ctrl = new StreamController<HookedServiceEvent<Id, Data, T>>();
|
2017-04-04 08:35:36 +00:00
|
|
|
_ctrl.add(ctrl);
|
|
|
|
after(eventNames, ctrl.add);
|
|
|
|
return ctrl.stream;
|
|
|
|
}
|
|
|
|
|
2017-01-28 03:47:00 +00:00
|
|
|
/// Runs the [listener] before [create], [modify] and [update].
|
2018-09-11 20:34:29 +00:00
|
|
|
void beforeModify(HookedServiceEventListener<Id, Data, T> listener) {
|
2017-01-28 03:47:00 +00:00
|
|
|
beforeCreated.listen(listener);
|
|
|
|
beforeModified.listen(listener);
|
|
|
|
beforeUpdated.listen(listener);
|
|
|
|
}
|
|
|
|
|
2016-05-02 22:28:14 +00:00
|
|
|
@override
|
2018-10-21 08:44:51 +00:00
|
|
|
Future<List<Data>> index([Map<String, dynamic> _params]) {
|
2017-01-20 22:11:20 +00:00
|
|
|
var params = _stripReq(_params);
|
2018-06-08 07:06:26 +00:00
|
|
|
return beforeIndexed
|
|
|
|
._emit(new HookedServiceEvent(false, _getRequest(_params),
|
2017-08-03 16:40:21 +00:00
|
|
|
_getResponse(_params), inner, HookedServiceEvent.indexed,
|
2018-06-08 07:06:26 +00:00
|
|
|
params: params))
|
|
|
|
.then((before) {
|
|
|
|
if (before._canceled) {
|
|
|
|
return beforeIndexed
|
|
|
|
._emit(new HookedServiceEvent(true, _getRequest(_params),
|
|
|
|
_getResponse(_params), inner, HookedServiceEvent.indexed,
|
|
|
|
params: params, result: before.result))
|
2018-10-21 08:53:09 +00:00
|
|
|
.then((after) => after.result as List<Data>);
|
2018-06-08 07:06:26 +00:00
|
|
|
}
|
2016-06-19 05:02:41 +00:00
|
|
|
|
2018-06-08 07:06:26 +00:00
|
|
|
return inner.index(params).then((result) {
|
|
|
|
return afterIndexed
|
|
|
|
._emit(new HookedServiceEvent(true, _getRequest(_params),
|
|
|
|
_getResponse(_params), inner, HookedServiceEvent.indexed,
|
|
|
|
params: params, result: result))
|
2018-10-21 08:53:09 +00:00
|
|
|
.then((after) => after.result as List<Data>);
|
2018-06-08 07:06:26 +00:00
|
|
|
});
|
|
|
|
});
|
2016-05-02 22:28:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
2018-10-21 08:44:51 +00:00
|
|
|
Future<Data> read(Id id, [Map<String, dynamic> _params]) {
|
2017-01-20 22:11:20 +00:00
|
|
|
var params = _stripReq(_params);
|
2018-06-08 07:06:26 +00:00
|
|
|
return beforeRead
|
|
|
|
._emit(new HookedServiceEvent(false, _getRequest(_params),
|
|
|
|
_getResponse(_params), inner, HookedServiceEvent.read,
|
|
|
|
id: id, params: params))
|
|
|
|
.then((before) {
|
|
|
|
if (before._canceled) {
|
|
|
|
return beforeRead
|
|
|
|
._emit(new HookedServiceEvent(true, _getRequest(_params),
|
|
|
|
_getResponse(_params), inner, HookedServiceEvent.read,
|
|
|
|
id: id, params: params, result: before.result))
|
2018-10-21 08:53:09 +00:00
|
|
|
.then((after) => after.result as Data);
|
2018-06-08 07:06:26 +00:00
|
|
|
}
|
2016-06-21 04:19:43 +00:00
|
|
|
|
2018-06-08 07:06:26 +00:00
|
|
|
return inner.read(id, params).then((result) {
|
|
|
|
return afterRead
|
|
|
|
._emit(new HookedServiceEvent(true, _getRequest(_params),
|
|
|
|
_getResponse(_params), inner, HookedServiceEvent.read,
|
|
|
|
id: id, params: params, result: result))
|
2018-10-21 08:53:09 +00:00
|
|
|
.then((after) => after.result as Data);
|
2018-06-08 07:06:26 +00:00
|
|
|
});
|
|
|
|
});
|
2016-06-21 04:19:43 +00:00
|
|
|
}
|
2016-05-02 22:28:14 +00:00
|
|
|
|
|
|
|
@override
|
2018-10-21 08:44:51 +00:00
|
|
|
Future<Data> create(Data data, [Map<String, dynamic> _params]) {
|
2017-01-20 22:11:20 +00:00
|
|
|
var params = _stripReq(_params);
|
2018-06-08 07:06:26 +00:00
|
|
|
return beforeCreated
|
|
|
|
._emit(new HookedServiceEvent(false, _getRequest(_params),
|
2017-08-03 16:40:21 +00:00
|
|
|
_getResponse(_params), inner, HookedServiceEvent.created,
|
2018-06-08 07:06:26 +00:00
|
|
|
data: data, params: params))
|
|
|
|
.then((before) {
|
|
|
|
if (before._canceled) {
|
|
|
|
return beforeCreated
|
|
|
|
._emit(new HookedServiceEvent(true, _getRequest(_params),
|
|
|
|
_getResponse(_params), inner, HookedServiceEvent.created,
|
|
|
|
data: data, params: params, result: before.result))
|
2018-10-21 08:53:09 +00:00
|
|
|
.then((after) => after.result as Data);
|
2018-06-08 07:06:26 +00:00
|
|
|
}
|
2016-06-21 04:19:43 +00:00
|
|
|
|
2018-06-08 07:06:26 +00:00
|
|
|
return inner.create(data, params).then((result) {
|
|
|
|
return afterCreated
|
|
|
|
._emit(new HookedServiceEvent(true, _getRequest(_params),
|
|
|
|
_getResponse(_params), inner, HookedServiceEvent.created,
|
|
|
|
data: data, params: params, result: result))
|
2018-10-21 08:53:09 +00:00
|
|
|
.then((after) => after.result as Data);
|
2018-06-08 07:06:26 +00:00
|
|
|
});
|
|
|
|
});
|
2016-05-02 22:28:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
2018-10-21 08:44:51 +00:00
|
|
|
Future<Data> modify(Id id, Data data, [Map<String, dynamic> _params]) {
|
2017-01-20 22:11:20 +00:00
|
|
|
var params = _stripReq(_params);
|
2018-06-08 07:06:26 +00:00
|
|
|
return beforeModified
|
|
|
|
._emit(new HookedServiceEvent(false, _getRequest(_params),
|
2017-08-03 16:40:21 +00:00
|
|
|
_getResponse(_params), inner, HookedServiceEvent.modified,
|
2018-06-08 07:06:26 +00:00
|
|
|
id: id, data: data, params: params))
|
|
|
|
.then((before) {
|
|
|
|
if (before._canceled) {
|
|
|
|
return beforeModified
|
|
|
|
._emit(new HookedServiceEvent(true, _getRequest(_params),
|
|
|
|
_getResponse(_params), inner, HookedServiceEvent.modified,
|
|
|
|
id: id, data: data, params: params, result: before.result))
|
2018-10-21 08:53:09 +00:00
|
|
|
.then((after) => after.result as Data);
|
2018-06-08 07:06:26 +00:00
|
|
|
}
|
2016-06-21 04:19:43 +00:00
|
|
|
|
2018-06-08 07:06:26 +00:00
|
|
|
return inner.modify(id, data, params).then((result) {
|
|
|
|
return afterModified
|
|
|
|
._emit(new HookedServiceEvent(true, _getRequest(_params),
|
|
|
|
_getResponse(_params), inner, HookedServiceEvent.created,
|
|
|
|
id: id, data: data, params: params, result: result))
|
2018-10-21 08:53:09 +00:00
|
|
|
.then((after) => after.result as Data);
|
2018-06-08 07:06:26 +00:00
|
|
|
});
|
|
|
|
});
|
2016-06-21 04:19:43 +00:00
|
|
|
}
|
2016-05-02 22:28:14 +00:00
|
|
|
|
|
|
|
@override
|
2018-10-21 08:44:51 +00:00
|
|
|
Future<Data> update(Id id, Data data, [Map<String, dynamic> _params]) {
|
2017-01-20 22:11:20 +00:00
|
|
|
var params = _stripReq(_params);
|
2018-06-08 07:06:26 +00:00
|
|
|
return beforeUpdated
|
|
|
|
._emit(new HookedServiceEvent(false, _getRequest(_params),
|
2017-08-03 16:40:21 +00:00
|
|
|
_getResponse(_params), inner, HookedServiceEvent.updated,
|
2018-06-08 07:06:26 +00:00
|
|
|
id: id, data: data, params: params))
|
|
|
|
.then((before) {
|
|
|
|
if (before._canceled) {
|
|
|
|
return beforeUpdated
|
|
|
|
._emit(new HookedServiceEvent(true, _getRequest(_params),
|
|
|
|
_getResponse(_params), inner, HookedServiceEvent.updated,
|
|
|
|
id: id, data: data, params: params, result: before.result))
|
2018-10-21 08:53:09 +00:00
|
|
|
.then((after) => after.result as Data);
|
2018-06-08 07:06:26 +00:00
|
|
|
}
|
2016-06-21 04:19:43 +00:00
|
|
|
|
2018-06-08 07:06:26 +00:00
|
|
|
return inner.update(id, data, params).then((result) {
|
|
|
|
return afterUpdated
|
|
|
|
._emit(new HookedServiceEvent(true, _getRequest(_params),
|
|
|
|
_getResponse(_params), inner, HookedServiceEvent.updated,
|
|
|
|
id: id, data: data, params: params, result: result))
|
2018-10-21 08:53:09 +00:00
|
|
|
.then((after) => after.result as Data);
|
2018-06-08 07:06:26 +00:00
|
|
|
});
|
|
|
|
});
|
2016-05-02 22:28:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
2018-10-21 08:44:51 +00:00
|
|
|
Future<Data> remove(Id id, [Map<String, dynamic> _params]) {
|
2017-01-20 22:11:20 +00:00
|
|
|
var params = _stripReq(_params);
|
2018-06-08 07:06:26 +00:00
|
|
|
return beforeRemoved
|
|
|
|
._emit(new HookedServiceEvent(false, _getRequest(_params),
|
2017-08-03 16:40:21 +00:00
|
|
|
_getResponse(_params), inner, HookedServiceEvent.removed,
|
2018-06-08 07:06:26 +00:00
|
|
|
id: id, params: params))
|
|
|
|
.then((before) {
|
|
|
|
if (before._canceled) {
|
|
|
|
return beforeRemoved
|
|
|
|
._emit(new HookedServiceEvent(true, _getRequest(_params),
|
|
|
|
_getResponse(_params), inner, HookedServiceEvent.removed,
|
|
|
|
id: id, params: params, result: before.result))
|
2018-10-21 08:53:09 +00:00
|
|
|
.then((after) => after.result) as Data;
|
2018-06-08 07:06:26 +00:00
|
|
|
}
|
2016-06-21 04:19:43 +00:00
|
|
|
|
2018-06-08 07:06:26 +00:00
|
|
|
return inner.remove(id, params).then((result) {
|
|
|
|
return afterRemoved
|
|
|
|
._emit(new HookedServiceEvent(true, _getRequest(_params),
|
|
|
|
_getResponse(_params), inner, HookedServiceEvent.removed,
|
|
|
|
id: id, params: params, result: result))
|
2018-10-21 08:53:09 +00:00
|
|
|
.then((after) => after.result as Data);
|
2018-06-08 07:06:26 +00:00
|
|
|
});
|
|
|
|
});
|
2016-05-02 22:28:14 +00:00
|
|
|
}
|
2017-01-20 22:11:20 +00:00
|
|
|
|
|
|
|
/// Fires an `after` event. This will not be propagated to clients,
|
|
|
|
/// but will be broadcasted to WebSockets, etc.
|
2018-09-11 20:34:29 +00:00
|
|
|
Future<HookedServiceEvent<Id, Data, T>> fire(String eventName, result,
|
|
|
|
[HookedServiceEventListener<Id, Data, T> callback]) {
|
|
|
|
HookedServiceEventDispatcher<Id, Data, T> dispatcher;
|
2017-01-20 22:11:20 +00:00
|
|
|
|
|
|
|
switch (eventName) {
|
2017-08-03 16:40:21 +00:00
|
|
|
case HookedServiceEvent.indexed:
|
2017-01-20 22:11:20 +00:00
|
|
|
dispatcher = afterIndexed;
|
|
|
|
break;
|
2017-08-03 16:40:21 +00:00
|
|
|
case HookedServiceEvent.read:
|
2017-01-20 22:11:20 +00:00
|
|
|
dispatcher = afterRead;
|
|
|
|
break;
|
2017-08-03 16:40:21 +00:00
|
|
|
case HookedServiceEvent.created:
|
2017-01-20 22:11:20 +00:00
|
|
|
dispatcher = afterCreated;
|
|
|
|
break;
|
2017-08-03 16:40:21 +00:00
|
|
|
case HookedServiceEvent.modified:
|
2017-01-20 22:11:20 +00:00
|
|
|
dispatcher = afterModified;
|
|
|
|
break;
|
2017-08-03 16:40:21 +00:00
|
|
|
case HookedServiceEvent.updated:
|
2017-01-20 22:11:20 +00:00
|
|
|
dispatcher = afterUpdated;
|
|
|
|
break;
|
2017-08-03 16:40:21 +00:00
|
|
|
case HookedServiceEvent.removed:
|
2017-01-20 22:11:20 +00:00
|
|
|
dispatcher = afterRemoved;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
throw new ArgumentError("Invalid service event name: '$eventName'");
|
|
|
|
}
|
|
|
|
|
2018-09-11 20:34:29 +00:00
|
|
|
var ev =
|
|
|
|
new HookedServiceEvent<Id, Data, T>(true, null, null, inner, eventName);
|
2018-06-08 07:06:26 +00:00
|
|
|
return fireEvent(dispatcher, ev, callback);
|
2017-02-23 00:37:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Sends an arbitrary event down the hook chain.
|
2018-09-11 20:34:29 +00:00
|
|
|
Future<HookedServiceEvent<Id, Data, T>> fireEvent(
|
|
|
|
HookedServiceEventDispatcher<Id, Data, T> dispatcher,
|
|
|
|
HookedServiceEvent<Id, Data, T> event,
|
|
|
|
[HookedServiceEventListener<Id, Data, T> callback]) {
|
2018-06-20 19:44:44 +00:00
|
|
|
Future f;
|
|
|
|
if (callback != null && event?._canceled != true)
|
|
|
|
f = new Future.sync(() => callback(event));
|
|
|
|
f ??= new Future.value();
|
|
|
|
return f.then((_) => dispatcher._emit(event));
|
2017-01-20 22:11:20 +00:00
|
|
|
}
|
2016-06-19 05:02:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Fired when a hooked service is invoked.
|
2018-09-11 20:34:29 +00:00
|
|
|
class HookedServiceEvent<Id, Data, T extends Service<Id, Data>> {
|
2017-08-03 16:40:21 +00:00
|
|
|
static const String indexed = 'indexed';
|
|
|
|
static const String read = 'read';
|
|
|
|
static const String created = 'created';
|
|
|
|
static const String modified = 'modified';
|
|
|
|
static const String updated = 'updated';
|
|
|
|
static const String removed = 'removed';
|
|
|
|
|
|
|
|
static const List<String> all = const [
|
2017-09-22 14:03:23 +00:00
|
|
|
indexed,
|
|
|
|
read,
|
|
|
|
created,
|
|
|
|
modified,
|
|
|
|
updated,
|
|
|
|
removed
|
2017-04-04 08:35:36 +00:00
|
|
|
];
|
2016-06-21 04:19:43 +00:00
|
|
|
|
2016-06-19 05:02:41 +00:00
|
|
|
/// Use this to end processing of an event.
|
2017-01-28 21:25:02 +00:00
|
|
|
void cancel([result]) {
|
2016-06-19 05:02:41 +00:00
|
|
|
_canceled = true;
|
2017-01-28 21:25:02 +00:00
|
|
|
this.result = result ?? this.result;
|
2016-06-19 05:02:41 +00:00
|
|
|
}
|
|
|
|
|
2017-02-26 21:31:09 +00:00
|
|
|
/// Resolves a service from the application.
|
2017-03-28 23:29:22 +00:00
|
|
|
///
|
2017-02-26 21:31:09 +00:00
|
|
|
/// Shorthand for `e.service.app.service(...)`.
|
2018-09-12 04:29:53 +00:00
|
|
|
Service getService(Pattern path) => service.app.findService(path);
|
2017-02-26 21:31:09 +00:00
|
|
|
|
2016-06-19 05:02:41 +00:00
|
|
|
bool _canceled = false;
|
2016-06-21 04:19:43 +00:00
|
|
|
String _eventName;
|
2018-09-11 20:34:29 +00:00
|
|
|
Id _id;
|
2017-01-28 21:08:07 +00:00
|
|
|
bool _isAfter;
|
2018-09-11 20:34:29 +00:00
|
|
|
Data data;
|
|
|
|
Map<String, dynamic> _params;
|
2017-01-20 22:11:20 +00:00
|
|
|
RequestContext _request;
|
|
|
|
ResponseContext _response;
|
2017-01-28 21:25:02 +00:00
|
|
|
var result;
|
2016-06-21 04:19:43 +00:00
|
|
|
|
|
|
|
String get eventName => _eventName;
|
|
|
|
|
2018-09-11 20:34:29 +00:00
|
|
|
Id get id => _id;
|
2016-06-21 04:19:43 +00:00
|
|
|
|
2017-01-28 21:08:07 +00:00
|
|
|
bool get isAfter => _isAfter == true;
|
|
|
|
|
|
|
|
bool get isBefore => !isAfter;
|
|
|
|
|
2016-06-21 04:19:43 +00:00
|
|
|
Map get params => _params;
|
|
|
|
|
2017-01-20 22:11:20 +00:00
|
|
|
RequestContext get request => _request;
|
|
|
|
|
|
|
|
ResponseContext get response => _response;
|
|
|
|
|
2016-06-19 05:02:41 +00:00
|
|
|
/// The inner service whose method was hooked.
|
2018-09-11 20:34:29 +00:00
|
|
|
T service;
|
2016-06-19 05:02:41 +00:00
|
|
|
|
2018-09-11 20:34:29 +00:00
|
|
|
HookedServiceEvent(this._isAfter, this._request, this._response, this.service,
|
|
|
|
String this._eventName,
|
|
|
|
{Id id, this.data, Map<String, dynamic> params, this.result}) {
|
2016-06-21 04:19:43 +00:00
|
|
|
_id = id;
|
|
|
|
_params = params ?? {};
|
2016-06-19 05:02:41 +00:00
|
|
|
}
|
2016-06-21 04:19:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Triggered on a hooked service event.
|
2018-09-11 20:34:29 +00:00
|
|
|
typedef FutureOr HookedServiceEventListener<Id, Data,
|
|
|
|
T extends Service<Id, Data>>(HookedServiceEvent<Id, Data, T> event);
|
2016-06-21 04:19:43 +00:00
|
|
|
|
|
|
|
/// Can be listened to, but events may be canceled.
|
2018-09-11 20:34:29 +00:00
|
|
|
class HookedServiceEventDispatcher<Id, Data, T extends Service<Id, Data>> {
|
|
|
|
final List<StreamController<HookedServiceEvent<Id, Data, T>>> _ctrl = [];
|
|
|
|
final List<HookedServiceEventListener<Id, Data, T>> listeners = [];
|
2017-04-04 08:35:36 +00:00
|
|
|
|
|
|
|
void _close() {
|
|
|
|
_ctrl.forEach((c) => c.close());
|
2017-10-28 08:50:16 +00:00
|
|
|
listeners.clear();
|
2017-04-04 08:35:36 +00:00
|
|
|
}
|
2016-06-21 04:19:43 +00:00
|
|
|
|
|
|
|
/// Fires an event, and returns it once it is either canceled, or all listeners have run.
|
2018-09-11 20:34:29 +00:00
|
|
|
Future<HookedServiceEvent<Id, Data, T>> _emit(
|
|
|
|
HookedServiceEvent<Id, Data, T> event) {
|
2018-06-20 19:44:44 +00:00
|
|
|
if (event?._canceled == true || event == null || listeners.isEmpty)
|
|
|
|
return new Future.value(event);
|
2016-06-21 04:19:43 +00:00
|
|
|
|
2018-09-11 20:34:29 +00:00
|
|
|
var f = new Future<HookedServiceEvent<Id, Data, T>>.value(event);
|
2018-06-20 19:44:44 +00:00
|
|
|
|
|
|
|
for (var listener in listeners) {
|
|
|
|
f = f.then((event) {
|
|
|
|
if (event._canceled) return event;
|
|
|
|
return new Future.sync(() => listener(event)).then((_) => event);
|
|
|
|
});
|
2016-06-21 04:19:43 +00:00
|
|
|
}
|
|
|
|
|
2018-06-20 19:44:44 +00:00
|
|
|
return f;
|
2016-06-21 04:19:43 +00:00
|
|
|
}
|
|
|
|
|
2017-04-04 08:35:36 +00:00
|
|
|
/// Returns a [Stream] containing all events fired by this dispatcher.
|
|
|
|
///
|
|
|
|
/// *NOTE*: Callbacks on the returned [Stream] cannot be guaranteed to run before other [listeners].
|
|
|
|
/// Use this only if you need a read-only stream of events.
|
2018-09-11 20:34:29 +00:00
|
|
|
Stream<HookedServiceEvent<Id, Data, T>> asStream() {
|
|
|
|
var ctrl = new StreamController<HookedServiceEvent<Id, Data, T>>();
|
2017-04-04 08:35:36 +00:00
|
|
|
_ctrl.add(ctrl);
|
|
|
|
listen(ctrl.add);
|
|
|
|
return ctrl.stream;
|
|
|
|
}
|
|
|
|
|
2016-06-21 04:19:43 +00:00
|
|
|
/// Registers the listener to be called whenever an event is triggered.
|
2018-09-11 20:34:29 +00:00
|
|
|
void listen(HookedServiceEventListener<Id, Data, T> listener) {
|
2016-06-21 04:19:43 +00:00
|
|
|
listeners.add(listener);
|
|
|
|
}
|
|
|
|
}
|