futoin-eventstream
Version:
Neutral event stream interface
205 lines (181 loc) • 6.4 kB
JavaScript
;
/**
* @file
*
* Copyright 2017 FutoIn Project (https://futoin.org)
* Copyright 2017 Andrey Galkin <andrey@futoin.org>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
const _defaults = require( 'lodash/defaults' );
const PollService = require( './PollService' );
const { DB_IFACEVER, DB_EVTTABLE, DB_EVTCONSUMERS } = require( './common' );
/**
* Database-based Poll Service
*/
class DBPollService extends PollService
{
/**
* Please use DBPollService,register()
*
* @param {AsyncSteps} as - async step interface
* @param {Executor} executor - related Executor
* @param {object} [options={}] - options
* @param {string} [options.event_table=default] - events table
* @param {string} [options.consumer_table=default] - consumers table
*/
constructor( as, executor, options )
{
super( as, executor, options );
_defaults( options, {
event_table: DB_EVTTABLE,
consumer_table: DB_EVTCONSUMERS,
} );
const ccm = executor.ccm();
ccm.assertIface( '#db.evt', DB_IFACEVER );
const qb = ccm.db( 'evt' ).queryBuilder();
this._evt_table = qb.identifier( options.event_table );
this._consumer_table = qb.identifier( options.consumer_table );
}
_registerConsumer( as, executor, ident )
{
as.add(
( as ) =>
{
const db = executor.ccm().db( 'evt' );
const now = db.helpers().now();
db.insert( this._consumer_table )
.set( {
last_evt_id: 0,
reg_time: now,
last_time: now,
ident,
} )
.execute( as );
},
( as, err ) =>
{
if ( err === 'Duplicate' )
{
as.success();
}
}
);
}
_pollEvents( as, { executor, ident, last_id, want, is_reliable, chunk_size } )
{
const db = executor.ccm().db( 'evt' );
const xfer = db.newXfer( db.SERIALIZABLE );
const helpers = xfer.helpers();
// Update info
if ( is_reliable )
{
// check, if registered
xfer.select( this._consumer_table, { selected: 1 } )
.get( 'id' )
.where( { ident } )
.forUpdate();
xfer.update( this._consumer_table )
.set( {
last_evt_id: last_id,
last_time: helpers.now(),
} )
.where( {
ident,
'last_evt_id <=': last_id,
} );
// make sure to select actual last_id
last_id = db.select( this._consumer_table )
.get( 'last_evt_id' )
.where( { ident } );
}
else
{
last_id = parseInt( last_id );
}
// Actual select
const s = xfer.select( this._evt_table, { result: true } )
.get( [ 'id', 'type', 'data', 'ts' ] )
.where( 'id >', last_id )
.limit( chunk_size );
if ( want )
{
s.where( 'type IN', want );
}
as.add(
( as ) =>
{
xfer.execute( as );
as.add( ( as, res ) =>
{
const events = this._res2events( res[0].rows, helpers );
// Make sure we do not run through events again
// and again.
if ( !events.length && want )
{
const xfer = db.newXfer( db.SERIALIZABLE );
const sq = xfer.select(
[ db.select( this._evt_table )
.get( 'max_id', 'MAX(id)' ), 'ETM' ],
{ selected: 1 }
).where( 'max_id IS NOT NULL' );
xfer.select(
this._evt_table,
{ result: true,
selected: false } )
.get( [ 'id', 'type', 'data', 'ts' ] )
.where( 'id >', last_id )
.where( 'type IN', want )
.limit( chunk_size );
const uq = xfer.update( this._consumer_table );
uq.set( 'last_evt_id', uq.backref( sq, 'max_id' ) );
uq.where( { ident } );
as.add(
( as ) => xfer.execute( as ),
( as, err ) =>
{
if ( err === 'XferCondition' )
{
as.success();
}
}
);
as.add( ( as ) => as.success( events ) );
}
else
{
as.success( events );
}
} );
},
( as, err ) =>
{
if ( err === 'XferCondition' )
{
// it may get triggered on failed update as well...
as.error( 'NotRegistered' );
}
}
);
}
_res2events( rows, helpers )
{
return rows.map( ( v ) => ( {
id: `${v[0]}`,
type: v[1],
data: JSON.parse( v[2] ),
ts: helpers.nativeDate( v[3] ).format(),
} ) );
}
}
module.exports = DBPollService;