Comparing version 7.0.0-beta.45 to 7.0.0-beta.46
@@ -58,2 +58,4 @@ import { IAgent, IPluginMethodMap, IAgentPlugin, TAgent, IAgentPluginSchema } from './types/IAgent'; | ||
private protectedMethods; | ||
private readonly eventBus; | ||
private readonly eventQueue; | ||
/** | ||
@@ -103,2 +105,32 @@ * Constructs a new instance of the `Agent` class | ||
execute<P = any, R = any>(method: string, args: P): Promise<R>; | ||
/** | ||
* Broadcasts an `Event` to potential listeners. | ||
* | ||
* Listeners are `IEventListener` instances that declare `eventTypes` | ||
* and implement an `async onEvent({type, data}, context)` method. | ||
* Note that `IAgentPlugin` is also an `IEventListener` so plugins can be listeners for events. | ||
* | ||
* During creation, the agent automatically registers listener plugins | ||
* to the `eventTypes` that they declare. | ||
* | ||
* Events are processed asynchronously, so the general pattern to be used is fire-and-forget. | ||
* Ex: `agent.emit('foo', {eventData})` | ||
* | ||
* In situations where you need to make sure that all events in the queue have been exhausted, | ||
* the `Promise` returned by `emit` can be awaited. | ||
* Ex: `await agent.emit('foo', {eventData})` | ||
* | ||
* In case an error is thrown while processing an event, the error is re-emitted as an event | ||
* of type `error` with a `EventListenerError` as payload. | ||
* | ||
* Note that `await agent.emit()` will NOT throw an error. To process errors, use a listener | ||
* with `eventTypes: ["error"]` in the definition. | ||
* | ||
* @param eventType - the type of event being emitted | ||
* @param data - event payload. | ||
* Use the same `data` type for events of a particular `eventType`. | ||
* | ||
* @public | ||
*/ | ||
emit(eventType: string, data: any): Promise<void>; | ||
} | ||
@@ -105,0 +137,0 @@ /** |
@@ -68,2 +68,3 @@ "use strict"; | ||
var debug_1 = __importDefault(require("debug")); | ||
var events_1 = require("events"); | ||
/** | ||
@@ -120,3 +121,5 @@ * Filters unauthorized methods. By default all methods are authorized | ||
this.methods = {}; | ||
this.protectedMethods = ['execute', 'availableMethods']; | ||
this.protectedMethods = ['execute', 'availableMethods', 'emit']; | ||
this.eventBus = new events_1.EventEmitter(); | ||
this.eventQueue = []; | ||
this.context = options === null || options === void 0 ? void 0 : options.context; | ||
@@ -126,18 +129,53 @@ this.schema = { | ||
schemas: __assign({}, ValidationError_1.default.components.schemas), | ||
methods: {} | ||
} | ||
methods: {}, | ||
}, | ||
}; | ||
if (options === null || options === void 0 ? void 0 : options.plugins) { | ||
var _loop_1 = function (plugin) { | ||
var e_4, _a; | ||
this_1.methods = __assign(__assign({}, this_1.methods), filterUnauthorizedMethods(plugin.methods || {}, options.authorizedMethods)); | ||
if (plugin.schema) { | ||
this_1.schema = { | ||
components: { | ||
schemas: __assign(__assign({}, this_1.schema.components.schemas), plugin.schema.components.schemas), | ||
methods: __assign(__assign({}, this_1.schema.components.methods), plugin.schema.components.methods), | ||
}, | ||
}; | ||
} | ||
if ((plugin === null || plugin === void 0 ? void 0 : plugin.eventTypes) && (plugin === null || plugin === void 0 ? void 0 : plugin.onEvent)) { | ||
var _loop_3 = function (eventType) { | ||
this_1.eventBus.on(eventType, function (args) { | ||
var _a; | ||
var promise = (_a = plugin === null || plugin === void 0 ? void 0 : plugin.onEvent) === null || _a === void 0 ? void 0 : _a.call(plugin, { type: eventType, data: args }, __assign(__assign({}, _this.context), { agent: _this })); | ||
_this.eventQueue.push(promise); | ||
promise === null || promise === void 0 ? void 0 : promise.catch(function (rejection) { | ||
if (eventType !== 'error') { | ||
_this.eventBus.emit('error', rejection); | ||
} | ||
else { | ||
_this.eventQueue.push(Promise.reject(new Error('ErrorEventHandlerError: throwing an error in an error handler should crash'))); | ||
} | ||
}); | ||
}); | ||
}; | ||
try { | ||
for (var _b = (e_4 = void 0, __values(plugin.eventTypes)), _c = _b.next(); !_c.done; _c = _b.next()) { | ||
var eventType = _c.value; | ||
_loop_3(eventType); | ||
} | ||
} | ||
catch (e_4_1) { e_4 = { error: e_4_1 }; } | ||
finally { | ||
try { | ||
if (_c && !_c.done && (_a = _b.return)) _a.call(_b); | ||
} | ||
finally { if (e_4) throw e_4.error; } | ||
} | ||
} | ||
}; | ||
var this_1 = this; | ||
try { | ||
for (var _c = __values(options.plugins), _d = _c.next(); !_d.done; _d = _c.next()) { | ||
var plugin = _d.value; | ||
this.methods = __assign(__assign({}, this.methods), filterUnauthorizedMethods(plugin.methods, options.authorizedMethods)); | ||
if (plugin.schema) { | ||
this.schema = { | ||
components: { | ||
schemas: __assign(__assign({}, this.schema.components.schemas), plugin.schema.components.schemas), | ||
methods: __assign(__assign({}, this.schema.components.methods), plugin.schema.components.methods) | ||
} | ||
}; | ||
} | ||
_loop_1(plugin); | ||
} | ||
@@ -156,6 +194,6 @@ } | ||
} | ||
var _loop_1 = function (method) { | ||
if (!this_1.protectedMethods.includes(method)) { | ||
var _loop_2 = function (method) { | ||
if (!this_2.protectedMethods.includes(method)) { | ||
//@ts-ignore | ||
this_1[method] = function (args) { return __awaiter(_this, void 0, void 0, function () { return __generator(this, function (_a) { | ||
this_2[method] = function (args) { return __awaiter(_this, void 0, void 0, function () { return __generator(this, function (_a) { | ||
return [2 /*return*/, this.execute(method, args)]; | ||
@@ -165,7 +203,7 @@ }); }); }; | ||
}; | ||
var this_1 = this; | ||
var this_2 = this; | ||
try { | ||
for (var _e = __values(Object.keys(this.methods)), _f = _e.next(); !_f.done; _f = _e.next()) { | ||
var method = _f.value; | ||
_loop_1(method); | ||
_loop_2(method); | ||
} | ||
@@ -246,2 +284,61 @@ } | ||
}; | ||
/** | ||
* Broadcasts an `Event` to potential listeners. | ||
* | ||
* Listeners are `IEventListener` instances that declare `eventTypes` | ||
* and implement an `async onEvent({type, data}, context)` method. | ||
* Note that `IAgentPlugin` is also an `IEventListener` so plugins can be listeners for events. | ||
* | ||
* During creation, the agent automatically registers listener plugins | ||
* to the `eventTypes` that they declare. | ||
* | ||
* Events are processed asynchronously, so the general pattern to be used is fire-and-forget. | ||
* Ex: `agent.emit('foo', {eventData})` | ||
* | ||
* In situations where you need to make sure that all events in the queue have been exhausted, | ||
* the `Promise` returned by `emit` can be awaited. | ||
* Ex: `await agent.emit('foo', {eventData})` | ||
* | ||
* In case an error is thrown while processing an event, the error is re-emitted as an event | ||
* of type `error` with a `EventListenerError` as payload. | ||
* | ||
* Note that `await agent.emit()` will NOT throw an error. To process errors, use a listener | ||
* with `eventTypes: ["error"]` in the definition. | ||
* | ||
* @param eventType - the type of event being emitted | ||
* @param data - event payload. | ||
* Use the same `data` type for events of a particular `eventType`. | ||
* | ||
* @public | ||
*/ | ||
Agent.prototype.emit = function (eventType, data) { | ||
return __awaiter(this, void 0, void 0, function () { | ||
var e_5; | ||
return __generator(this, function (_a) { | ||
switch (_a.label) { | ||
case 0: | ||
this.eventBus.emit(eventType, data); | ||
_a.label = 1; | ||
case 1: | ||
if (!(this.eventQueue.length > 0)) return [3 /*break*/, 6]; | ||
_a.label = 2; | ||
case 2: | ||
_a.trys.push([2, 4, , 5]); | ||
return [4 /*yield*/, this.eventQueue.shift()]; | ||
case 3: | ||
_a.sent(); | ||
return [3 /*break*/, 5]; | ||
case 4: | ||
e_5 = _a.sent(); | ||
//nop | ||
if (e_5.message.startsWith('ErrorEventHandlerError')) { | ||
throw e_5; | ||
} | ||
return [3 /*break*/, 5]; | ||
case 5: return [3 /*break*/, 1]; | ||
case 6: return [2 /*return*/]; | ||
} | ||
}); | ||
}); | ||
}; | ||
return Agent; | ||
@@ -248,0 +345,0 @@ }()); |
@@ -15,2 +15,3 @@ /** | ||
execute: <A = any, R = any>(method: string, args: A) => Promise<R>; | ||
emit: (eventType: string, data: any) => Promise<void>; | ||
} | ||
@@ -41,7 +42,29 @@ /** | ||
/** | ||
* Describes a listener interface that needs to be implemented by components interested | ||
* in listening to events emitted by an agent. | ||
* | ||
* @public | ||
*/ | ||
export interface IEventListener { | ||
/** | ||
* Declares the event types that this listener is interested in. | ||
* @public | ||
*/ | ||
readonly eventTypes?: string[]; | ||
/** | ||
* Processes an event emitted by the agent. | ||
* @param context - Execution context. Requires agent with {@link daf-core#IDataStore} methods | ||
* @public | ||
*/ | ||
onEvent?(event: { | ||
type: string; | ||
data: any; | ||
}, context: IAgentContext<{}>): Promise<void>; | ||
} | ||
/** | ||
* Agent plugin interface | ||
* @public | ||
*/ | ||
export interface IAgentPlugin { | ||
readonly methods: IPluginMethodMap; | ||
export interface IAgentPlugin extends IEventListener { | ||
readonly methods?: IPluginMethodMap; | ||
readonly schema?: IAgentPluginSchema; | ||
@@ -48,0 +71,0 @@ } |
@@ -6,2 +6,13 @@ # Change Log | ||
# [7.0.0-beta.46](https://github.com/uport-project/daf/compare/v7.0.0-beta.45...v7.0.0-beta.46) (2020-11-19) | ||
### Features | ||
* Add event system to agent ([#262](https://github.com/uport-project/daf/issues/262)) ([9a6747e](https://github.com/uport-project/daf/commit/9a6747e84037613d396e14a6f68cb2de8275ddca)) | ||
# [7.0.0-beta.45](https://github.com/uport-project/daf/compare/v7.0.0-beta.44...v7.0.0-beta.45) (2020-10-22) | ||
@@ -8,0 +19,0 @@ |
{ | ||
"name": "daf-core", | ||
"description": "DID Agent Framework Core Logic & Interfaces.", | ||
"version": "7.0.0-beta.45", | ||
"version": "7.0.0-beta.46", | ||
"main": "build/index.js", | ||
@@ -42,3 +42,3 @@ "types": "build/index.d.ts", | ||
"keywords": [], | ||
"gitHead": "a50275b95d273399882aad4eba702b6169aea61d" | ||
"gitHead": "b97ff3b0a7c34d49215173e4ec76b2d093892c36" | ||
} |
@@ -17,5 +17,5 @@ import { Agent } from '../agent' | ||
await agent.doSomething({ foo: 'baz' }) | ||
expect(plugin.methods.doSomething).toBeCalledWith({ foo: 'baz' }, { agent }) | ||
expect(plugin.methods?.doSomething).toBeCalledWith({ foo: 'baz' }, { agent }) | ||
await agent.execute('doSomething', { foo: 'bar' }) | ||
expect(plugin.methods.doSomething).toBeCalledWith({ foo: 'bar' }, { agent }) | ||
expect(plugin.methods?.doSomething).toBeCalledWith({ foo: 'bar' }, { agent }) | ||
}) | ||
@@ -70,3 +70,3 @@ | ||
await agent.bar({ foo: 'baz' }) | ||
expect(plugin.methods.bar).toBeCalledWith({ foo: 'baz' }, { agent }) | ||
expect(plugin.methods?.bar).toBeCalledWith({ foo: 'baz' }, { agent }) | ||
await agent.execute('baz', { foo: 'bar' }) | ||
@@ -93,3 +93,3 @@ expect(baz).toBeCalledWith({ foo: 'bar' }, { agent }) | ||
await agent.doSomething({ foo: 'baz' }) | ||
expect(plugin.methods.doSomething).toBeCalledWith( | ||
expect(plugin.methods?.doSomething).toBeCalledWith( | ||
{ foo: 'baz' }, | ||
@@ -99,3 +99,3 @@ { agent, authorizedDid: 'did:example:123' }, | ||
await agent.execute('doSomething', { foo: 'bar' }) | ||
expect(plugin.methods.doSomething).toBeCalledWith( | ||
expect(plugin.methods?.doSomething).toBeCalledWith( | ||
{ foo: 'bar' }, | ||
@@ -102,0 +102,0 @@ { agent, authorizedDid: 'did:example:123' }, |
@@ -5,2 +5,3 @@ import { IAgent, IPluginMethodMap, IAgentPlugin, TAgent, IAgentPluginSchema } from './types/IAgent' | ||
import Debug from 'debug' | ||
import { EventEmitter } from 'events' | ||
@@ -86,7 +87,10 @@ /** | ||
readonly methods: IPluginMethodMap = {} | ||
private schema: IAgentPluginSchema | ||
private context?: Record<string, any> | ||
private protectedMethods = ['execute', 'availableMethods'] | ||
private protectedMethods = ['execute', 'availableMethods', 'emit'] | ||
private readonly eventBus: EventEmitter = new EventEmitter() | ||
private readonly eventQueue: (Promise<any> | undefined)[] = [] | ||
/** | ||
@@ -100,10 +104,10 @@ * Constructs a new instance of the `Agent` class | ||
this.context = options?.context | ||
this.schema = { | ||
components: { | ||
schemas: { | ||
...ValidationErrorSchema.components.schemas | ||
...ValidationErrorSchema.components.schemas, | ||
}, | ||
methods: {} | ||
} | ||
methods: {}, | ||
}, | ||
} | ||
@@ -115,3 +119,3 @@ | ||
...this.methods, | ||
...filterUnauthorizedMethods(plugin.methods, options.authorizedMethods), | ||
...filterUnauthorizedMethods(plugin.methods || {}, options.authorizedMethods), | ||
} | ||
@@ -128,7 +132,28 @@ if (plugin.schema) { | ||
...plugin.schema.components.methods, | ||
} | ||
} | ||
}, | ||
}, | ||
} | ||
} | ||
if (plugin?.eventTypes && plugin?.onEvent) { | ||
for (const eventType of plugin.eventTypes) { | ||
this.eventBus.on(eventType, (args) => { | ||
const promise = plugin?.onEvent?.( | ||
{ type: eventType, data: args }, | ||
{ ...this.context, agent: this }, | ||
) | ||
this.eventQueue.push(promise) | ||
promise?.catch((rejection) => { | ||
if (eventType !== 'error') { | ||
this.eventBus.emit('error', rejection) | ||
} else { | ||
this.eventQueue.push( | ||
Promise.reject( | ||
new Error('ErrorEventHandlerError: throwing an error in an error handler should crash'), | ||
), | ||
) | ||
} | ||
}) | ||
}) | ||
} | ||
} | ||
} | ||
@@ -208,2 +233,45 @@ } | ||
} | ||
/** | ||
* Broadcasts an `Event` to potential listeners. | ||
* | ||
* Listeners are `IEventListener` instances that declare `eventTypes` | ||
* and implement an `async onEvent({type, data}, context)` method. | ||
* Note that `IAgentPlugin` is also an `IEventListener` so plugins can be listeners for events. | ||
* | ||
* During creation, the agent automatically registers listener plugins | ||
* to the `eventTypes` that they declare. | ||
* | ||
* Events are processed asynchronously, so the general pattern to be used is fire-and-forget. | ||
* Ex: `agent.emit('foo', {eventData})` | ||
* | ||
* In situations where you need to make sure that all events in the queue have been exhausted, | ||
* the `Promise` returned by `emit` can be awaited. | ||
* Ex: `await agent.emit('foo', {eventData})` | ||
* | ||
* In case an error is thrown while processing an event, the error is re-emitted as an event | ||
* of type `error` with a `EventListenerError` as payload. | ||
* | ||
* Note that `await agent.emit()` will NOT throw an error. To process errors, use a listener | ||
* with `eventTypes: ["error"]` in the definition. | ||
* | ||
* @param eventType - the type of event being emitted | ||
* @param data - event payload. | ||
* Use the same `data` type for events of a particular `eventType`. | ||
* | ||
* @public | ||
*/ | ||
async emit(eventType: string, data: any): Promise<void> { | ||
this.eventBus.emit(eventType, data) | ||
while (this.eventQueue.length > 0) { | ||
try { | ||
await this.eventQueue.shift() | ||
} catch (e) { | ||
//nop | ||
if (e.message.startsWith('ErrorEventHandlerError')) { | ||
throw e | ||
} | ||
} | ||
} | ||
} | ||
} | ||
@@ -210,0 +278,0 @@ |
@@ -16,2 +16,3 @@ /** | ||
execute: <A = any, R = any>(method: string, args: A) => Promise<R> | ||
emit: (eventType: string, data: any) => Promise<void> | ||
} | ||
@@ -44,2 +45,21 @@ | ||
/** | ||
* Describes a listener interface that needs to be implemented by components interested | ||
* in listening to events emitted by an agent. | ||
* | ||
* @public | ||
*/ | ||
export interface IEventListener { | ||
/** | ||
* Declares the event types that this listener is interested in. | ||
* @public | ||
*/ | ||
readonly eventTypes?: string[] | ||
/** | ||
* Processes an event emitted by the agent. | ||
* @param context - Execution context. Requires agent with {@link daf-core#IDataStore} methods | ||
* @public | ||
*/ | ||
onEvent?(event: { type: string; data: any }, context: IAgentContext<{}>): Promise<void> | ||
} | ||
@@ -50,4 +70,4 @@ /** | ||
*/ | ||
export interface IAgentPlugin { | ||
readonly methods: IPluginMethodMap | ||
export interface IAgentPlugin extends IEventListener { | ||
readonly methods?: IPluginMethodMap | ||
readonly schema?: IAgentPluginSchema | ||
@@ -94,2 +114,1 @@ } | ||
} | ||
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
354420
92
8137