TokenDao: class=>interface
TokenDao has been renamed to MongoTokenDao. While it's good for this to have its own interface anyway, the immediate motivation was for unit tests: I started playing with mocking with TypeScript and researching some libraries, but I don't have time to research enough to commit to any of them at the moment. Interfaces remove the need to mock at all. This also stops using `export default' in favor of just importing by name. Using defaults only saves us a few characters, and it makes for awkward syntax in various cases (e.g. with multiple exports). But I'm still new to TS, so who knows if I'll be flip-flopping on this decision in the future. If we kept to our normal 1:1 file:definition convention, it wouldn't cause problems, but based on the types I've had to define so far, that'd cause way too much bloat and boilerplate. * src/server/daemon/controller.js: No long import `default'. Use `MongoTokenDao'. * src/server/token/TokenedService.js: Stop checking type (since TS interfaces do not result in compiler output, easejs cannot validate against them.) * src/server/token/MongoTokenDao.ts: Rename from `TokenDao.ts'. * src/server/token/TokenDao.ts: Rename from `TokenQueryResult.ts'. (TokenDao): New interface. * src/server/token/TokenQueryResult.ts: Rename to `TokenDao.ts'. * test/server/token/MongoTokenDaoTest.ts: Rename from `TokenDaoTest.ts'.master
parent
37f1b86ac1
commit
fb88ceeae6
|
@ -90,8 +90,8 @@ const {
|
|||
},
|
||||
|
||||
token: {
|
||||
TokenDao: {
|
||||
default: TokenDao,
|
||||
MongoTokenDao: {
|
||||
MongoTokenDao
|
||||
},
|
||||
},
|
||||
|
||||
|
@ -246,7 +246,7 @@ function _initExportService( db, callback )
|
|||
ExportService
|
||||
.use( TokenedService(
|
||||
'c1import',
|
||||
new TokenDao( collection, "exports", getUnixTimestamp ),
|
||||
new MongoTokenDao( collection, "exports", getUnixTimestamp ),
|
||||
function tokgen()
|
||||
{
|
||||
var shasum = crypto.createHash( 'sha1' );
|
||||
|
|
|
@ -21,8 +21,7 @@
|
|||
|
||||
var Trait = require( 'easejs' ).Trait,
|
||||
Class = require( 'easejs' ).Class,
|
||||
Service = require( './Service' ),
|
||||
TokenDao = require( '../token/TokenDao' ).default;
|
||||
Service = require( './Service' );
|
||||
|
||||
|
||||
/**
|
||||
|
@ -96,11 +95,6 @@ module.exports = Trait( 'TokenedService' )
|
|||
*/
|
||||
__mixin: function( namespace, dao, tokgen, capture_gen )
|
||||
{
|
||||
if ( !Class.isA( TokenDao, dao ) )
|
||||
{
|
||||
throw TypeError( 'Instance of TokenDao expected' );
|
||||
}
|
||||
|
||||
if ( typeof tokgen !== 'function' )
|
||||
{
|
||||
throw TypeError( 'Token generator must be a function' );
|
||||
|
|
|
@ -0,0 +1,269 @@
|
|||
/**
|
||||
* Token state management
|
||||
*
|
||||
* Copyright (C) 2010-2019 R-T Specialty, LLC.
|
||||
*
|
||||
* This file is part of the Liza Data Collection Framework.
|
||||
*
|
||||
* liza is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {
|
||||
TokenDao,
|
||||
TokenData,
|
||||
TokenEntry,
|
||||
TokenNamespaceData,
|
||||
TokenNamespaceResults,
|
||||
TokenQueryResult,
|
||||
TokenStatus,
|
||||
TokenType,
|
||||
} from "./TokenDao";
|
||||
|
||||
import { TokenId, TokenNamespace } from "./Token";
|
||||
import { DocumentId } from "../../document/Document";
|
||||
|
||||
|
||||
/**
|
||||
* Manages token updates
|
||||
*
|
||||
* This uses MongoDB as the underlying database.
|
||||
*/
|
||||
export class MongoTokenDao implements TokenDao
|
||||
{
|
||||
/**
|
||||
* Mongo database collection
|
||||
*/
|
||||
private readonly _collection: MongoCollection;
|
||||
|
||||
/**
|
||||
* Field storing token data, relative to document root
|
||||
*/
|
||||
private readonly _rootField: string;
|
||||
|
||||
/**
|
||||
* Retrieve a Unix timestamp
|
||||
*
|
||||
* This is used for timestampping token updates.
|
||||
*/
|
||||
private readonly _getTimestamp: () => UnixTimestamp;
|
||||
|
||||
|
||||
/**
|
||||
* Initialize connection
|
||||
*
|
||||
* @param collection Mongo collection
|
||||
* @param root_field topmost field in mongo document
|
||||
* @param date_ctor Date constructor
|
||||
*/
|
||||
constructor(
|
||||
collection: MongoCollection,
|
||||
root_field: string,
|
||||
getTimestamp: () => UnixTimestamp,
|
||||
)
|
||||
{
|
||||
this._collection = collection;
|
||||
this._rootField = root_field;
|
||||
this._getTimestamp = getTimestamp;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create or update a token record
|
||||
*
|
||||
* The token entry is entered in the token log, and then the current
|
||||
* entry is updated to reflect the changes. The operation is atomic.
|
||||
*
|
||||
* @param doc_id unique document identifier
|
||||
* @param ns token namespace
|
||||
* @param token token value
|
||||
* @param data token data, if any
|
||||
* @param status arbitrary token type
|
||||
*/
|
||||
updateToken(
|
||||
doc_id: DocumentId,
|
||||
ns: TokenNamespace,
|
||||
token_id: TokenId,
|
||||
type: TokenType,
|
||||
data: string | null,
|
||||
): Promise<void>
|
||||
{
|
||||
const root = this._genRoot( ns ) + '.';
|
||||
|
||||
const token_entry: TokenStatus = {
|
||||
type: type,
|
||||
timestamp: this._getTimestamp(),
|
||||
data: data,
|
||||
};
|
||||
|
||||
const token_data = {
|
||||
[ root + 'last' ]: token_id,
|
||||
[ root + 'lastStatus' ]: token_entry,
|
||||
[ root + token_id + '.status' ]: token_entry,
|
||||
};
|
||||
|
||||
const token_log = {
|
||||
[ root + token_id + '.statusLog' ]: token_entry,
|
||||
};
|
||||
|
||||
return new Promise( ( resolve, reject ) =>
|
||||
{
|
||||
this._collection.update(
|
||||
{ id: +doc_id },
|
||||
{
|
||||
$set: token_data,
|
||||
$push: token_log
|
||||
},
|
||||
{ upsert: true },
|
||||
|
||||
function ( err: Error|null )
|
||||
{
|
||||
if ( err )
|
||||
{
|
||||
reject( err );
|
||||
return;
|
||||
}
|
||||
|
||||
resolve();
|
||||
}
|
||||
);
|
||||
} );
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve existing token under the namespace NS, if any, for the doc
|
||||
* identified by DOC_ID
|
||||
*
|
||||
* If a TOKEN_ID is provided, only that token will be queried; otherwise,
|
||||
* the most recently created token will be the subject of the query.
|
||||
*
|
||||
* @param doc_id document identifier
|
||||
* @param ns token namespace
|
||||
* @param token_id token identifier (unique to NS)
|
||||
*
|
||||
* @return token data
|
||||
*/
|
||||
getToken( doc_id: DocumentId, ns: TokenNamespace, token_id: TokenId ):
|
||||
Promise<TokenData|null>
|
||||
{
|
||||
const root = this._genRoot( ns ) + '.';
|
||||
const fields: any = {};
|
||||
|
||||
fields[ root + 'last' ] = 1;
|
||||
fields[ root + 'lastStatus' ] = 1;
|
||||
|
||||
if ( token_id )
|
||||
{
|
||||
// XXX: injectable
|
||||
fields[ root + token_id ] = 1;
|
||||
}
|
||||
|
||||
return new Promise( ( resolve, reject ) =>
|
||||
{
|
||||
this._collection.findOne(
|
||||
{ id: +doc_id },
|
||||
{ fields: fields },
|
||||
( err: Error|null, data: TokenQueryResult ) =>
|
||||
{
|
||||
if ( err || !data )
|
||||
{
|
||||
reject( err );
|
||||
return;
|
||||
}
|
||||
|
||||
const field = <TokenNamespaceResults>data[ this._rootField ]
|
||||
|| {};
|
||||
|
||||
if ( !field[ ns ] )
|
||||
{
|
||||
resolve( null );
|
||||
return;
|
||||
}
|
||||
|
||||
const ns_data = <TokenNamespaceData>field[ ns ];
|
||||
|
||||
resolve( ( token_id )
|
||||
? this._getRequestedToken( token_id, ns_data )
|
||||
: this._getLatestToken( ns_data )
|
||||
);
|
||||
}
|
||||
);
|
||||
} );
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve latest token data, or `null` if none
|
||||
*
|
||||
* @param ns_data namespace data
|
||||
*
|
||||
* @return data of latest token in namespace
|
||||
*/
|
||||
private _getLatestToken( ns_data: TokenNamespaceData ): TokenData | null
|
||||
{
|
||||
var last = ns_data.last;
|
||||
|
||||
if ( !last )
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
id: last,
|
||||
status: ns_data.lastStatus,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve latest token data, or `null` if none
|
||||
*
|
||||
* @param token_id token identifier for namespace associated with NS_DATA
|
||||
* @param ns_data namespace data
|
||||
*
|
||||
* @return data of requested token
|
||||
*/
|
||||
private _getRequestedToken(
|
||||
token_id: TokenId,
|
||||
ns_data: TokenNamespaceData
|
||||
): TokenData | null
|
||||
{
|
||||
const reqtok = <TokenEntry>ns_data[ <string>token_id ];
|
||||
|
||||
if ( !reqtok )
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
id: token_id,
|
||||
status: reqtok.status,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Determine token root for the given namespace
|
||||
*
|
||||
* @param ns token namespace
|
||||
*
|
||||
* @return token root for namespace NS
|
||||
*/
|
||||
private _genRoot( ns: TokenNamespace ): string
|
||||
{
|
||||
// XXX: injectable
|
||||
return this._rootField + '.' + ns;
|
||||
}
|
||||
};
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
/**
|
||||
* Token state management
|
||||
* Token data access
|
||||
*
|
||||
* Copyright (C) 2010-2019 R-T Specialty, LLC.
|
||||
*
|
||||
|
@ -17,21 +17,148 @@
|
|||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* These types are used to describe the structure of the token data as it
|
||||
* is stored in Mongo. It has a number of undesirable properties and
|
||||
* duplicates data---this was intended to make querying easier and work
|
||||
* around Mongo limitations.
|
||||
*
|
||||
* This structure can be changed in the future, but we'll need to maintain
|
||||
* compatibility with the existing data.
|
||||
*/
|
||||
|
||||
import {
|
||||
TokenEntry,
|
||||
TokenNamespaceData,
|
||||
TokenNamespaceResults,
|
||||
TokenQueryResult,
|
||||
TokenStatus,
|
||||
TokenType,
|
||||
} from "./TokenQueryResult";
|
||||
|
||||
import { TokenId, TokenNamespace } from "./Token";
|
||||
import { DocumentId } from "../../document/Document";
|
||||
|
||||
|
||||
/** Manage token updates */
|
||||
export interface TokenDao
|
||||
{
|
||||
updateToken(
|
||||
doc_id: DocumentId,
|
||||
ns: TokenNamespace,
|
||||
token_id: TokenId,
|
||||
type: TokenType,
|
||||
data: string | null,
|
||||
): Promise<void>;
|
||||
|
||||
|
||||
getToken(
|
||||
doc_id: DocumentId,
|
||||
ns: TokenNamespace,
|
||||
token_id: TokenId
|
||||
): Promise<TokenData|null>;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Token status types as stored in the database
|
||||
*/
|
||||
export type TokenType = 'ACTIVE' | 'DONE' | 'ACCEPTED' | 'DEAD';
|
||||
|
||||
|
||||
/**
|
||||
* Result of a Mongo query
|
||||
*
|
||||
* The returned property depends on the actual query.
|
||||
*/
|
||||
export interface TokenQueryResult
|
||||
{
|
||||
readonly [propName: string]: TokenNamespaceResults | null,
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Token data for requested namespaces
|
||||
*/
|
||||
export interface TokenNamespaceResults
|
||||
{
|
||||
readonly [propName: string]: TokenNamespaceData | null,
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Token data associated with the given namespace
|
||||
*
|
||||
* This contains duplicate information in order to work around inconvenient
|
||||
* limitations in [earlier] versions of Mongo.
|
||||
*/
|
||||
export interface TokenNamespaceData
|
||||
{
|
||||
/**
|
||||
* Identifier of last token touched in this namespace
|
||||
*/
|
||||
readonly last: TokenId,
|
||||
|
||||
/**
|
||||
* Most recent token status
|
||||
*
|
||||
* This is a duplicate of the last entry in `TokenEntry#statusLog`.
|
||||
*/
|
||||
readonly lastStatus: TokenStatus,
|
||||
|
||||
/**
|
||||
* Tokens indexed by identifier
|
||||
*
|
||||
* These data are inconveniently placed---the type definition here is to
|
||||
* accommodate the above fields. Anything using this should cast to
|
||||
* `TokenEntry`.
|
||||
*/
|
||||
readonly [propName: string]: TokenEntry | TokenStatus | TokenId | null,
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Information about a given token
|
||||
*/
|
||||
export interface TokenEntry
|
||||
{
|
||||
/**
|
||||
* Current token status
|
||||
*
|
||||
* This is a duplicate of the last element of `statusLog`.
|
||||
*/
|
||||
readonly status: TokenStatus,
|
||||
|
||||
/**
|
||||
* Log of all past status changes and any associated data
|
||||
*
|
||||
* This is pushed to on each status change. The last element is
|
||||
* duplicated in `status`.
|
||||
*/
|
||||
readonly statusLog: TokenStatus[],
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Status of the token (past or present)
|
||||
*
|
||||
* A status is a `TokenType`, along with a timestamp of occurrence and
|
||||
* optional data.
|
||||
*/
|
||||
export interface TokenStatus
|
||||
{
|
||||
/**
|
||||
* State of the token
|
||||
*/
|
||||
readonly type: TokenType,
|
||||
|
||||
/**
|
||||
* Unix timestamp representing when the status change occurred
|
||||
*/
|
||||
readonly timestamp: UnixTimestamp,
|
||||
|
||||
/**
|
||||
* Arbitrary data associated with the status change
|
||||
*
|
||||
* For example, a token of status `DONE` may be associated with the
|
||||
* fulfillment of a request, in which case this may contain the response
|
||||
* data.
|
||||
*/
|
||||
readonly data: string | null,
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Token information
|
||||
*/
|
||||
|
@ -40,238 +167,3 @@ export interface TokenData
|
|||
id: TokenId,
|
||||
status: TokenStatus,
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Manages token updates
|
||||
*
|
||||
* Note that this is tightly coupled with MongoDB.
|
||||
*/
|
||||
export default class TokenDao
|
||||
{
|
||||
/**
|
||||
* Mongo database collection
|
||||
*/
|
||||
private readonly _collection: MongoCollection;
|
||||
|
||||
/**
|
||||
* Field storing token data, relative to document root
|
||||
*/
|
||||
private readonly _rootField: string;
|
||||
|
||||
/**
|
||||
* Retrieve a Unix timestamp
|
||||
*
|
||||
* This is used for timestampping token updates.
|
||||
*/
|
||||
private readonly _getTimestamp: () => UnixTimestamp;
|
||||
|
||||
|
||||
/**
|
||||
* Initialize connection
|
||||
*
|
||||
* @param collection Mongo collection
|
||||
* @param root_field topmost field in mongo document
|
||||
* @param date_ctor Date constructor
|
||||
*/
|
||||
constructor(
|
||||
collection: MongoCollection,
|
||||
root_field: string,
|
||||
getTimestamp: () => UnixTimestamp,
|
||||
)
|
||||
{
|
||||
this._collection = collection;
|
||||
this._rootField = root_field;
|
||||
this._getTimestamp = getTimestamp;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create or update a token record
|
||||
*
|
||||
* The token entry is entered in the token log, and then the current
|
||||
* entry is updated to reflect the changes. The operation is atomic.
|
||||
*
|
||||
* @param doc_id unique document identifier
|
||||
* @param ns token namespace
|
||||
* @param token token value
|
||||
* @param data token data, if any
|
||||
* @param status arbitrary token type
|
||||
*/
|
||||
updateToken(
|
||||
doc_id: DocumentId,
|
||||
ns: TokenNamespace,
|
||||
token_id: TokenId,
|
||||
type: TokenType,
|
||||
data: string | null,
|
||||
): Promise<void>
|
||||
{
|
||||
const root = this._genRoot( ns ) + '.';
|
||||
|
||||
const token_entry: TokenStatus = {
|
||||
type: type,
|
||||
timestamp: this._getTimestamp(),
|
||||
data: data,
|
||||
};
|
||||
|
||||
const token_data = {
|
||||
[ root + 'last' ]: token_id,
|
||||
[ root + 'lastStatus' ]: token_entry,
|
||||
[ root + token_id + '.status' ]: token_entry,
|
||||
};
|
||||
|
||||
const token_log = {
|
||||
[ root + token_id + '.statusLog' ]: token_entry,
|
||||
};
|
||||
|
||||
return new Promise( ( resolve, reject ) =>
|
||||
{
|
||||
this._collection.update(
|
||||
{ id: +doc_id },
|
||||
{
|
||||
$set: token_data,
|
||||
$push: token_log
|
||||
},
|
||||
{ upsert: true },
|
||||
|
||||
function ( err: Error|null )
|
||||
{
|
||||
if ( err )
|
||||
{
|
||||
reject( err );
|
||||
return;
|
||||
}
|
||||
|
||||
resolve();
|
||||
}
|
||||
);
|
||||
} );
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve existing token under the namespace NS, if any, for the doc
|
||||
* identified by DOC_ID
|
||||
*
|
||||
* If a TOKEN_ID is provided, only that token will be queried; otherwise,
|
||||
* the most recently created token will be the subject of the query.
|
||||
*
|
||||
* @param doc_id document identifier
|
||||
* @param ns token namespace
|
||||
* @param token_id token identifier (unique to NS)
|
||||
*
|
||||
* @return token data
|
||||
*/
|
||||
getToken( doc_id: DocumentId, ns: TokenNamespace, token_id: TokenId ):
|
||||
Promise<TokenData|null>
|
||||
{
|
||||
const root = this._genRoot( ns ) + '.';
|
||||
const fields: any = {};
|
||||
|
||||
fields[ root + 'last' ] = 1;
|
||||
fields[ root + 'lastStatus' ] = 1;
|
||||
|
||||
if ( token_id )
|
||||
{
|
||||
// XXX: injectable
|
||||
fields[ root + token_id ] = 1;
|
||||
}
|
||||
|
||||
return new Promise( ( resolve, reject ) =>
|
||||
{
|
||||
this._collection.findOne(
|
||||
{ id: +doc_id },
|
||||
{ fields: fields },
|
||||
( err: Error|null, data: TokenQueryResult ) =>
|
||||
{
|
||||
if ( err || !data )
|
||||
{
|
||||
reject( err );
|
||||
return;
|
||||
}
|
||||
|
||||
const field = <TokenNamespaceResults>data[ this._rootField ]
|
||||
|| {};
|
||||
|
||||
if ( !field[ ns ] )
|
||||
{
|
||||
resolve( null );
|
||||
return;
|
||||
}
|
||||
|
||||
const ns_data = <TokenNamespaceData>field[ ns ];
|
||||
|
||||
resolve( ( token_id )
|
||||
? this._getRequestedToken( token_id, ns_data )
|
||||
: this._getLatestToken( ns_data )
|
||||
);
|
||||
}
|
||||
);
|
||||
} );
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve latest token data, or `null` if none
|
||||
*
|
||||
* @param ns_data namespace data
|
||||
*
|
||||
* @return data of latest token in namespace
|
||||
*/
|
||||
private _getLatestToken( ns_data: TokenNamespaceData ): TokenData | null
|
||||
{
|
||||
var last = ns_data.last;
|
||||
|
||||
if ( !last )
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
id: last,
|
||||
status: ns_data.lastStatus,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve latest token data, or `null` if none
|
||||
*
|
||||
* @param token_id token identifier for namespace associated with NS_DATA
|
||||
* @param ns_data namespace data
|
||||
*
|
||||
* @return data of requested token
|
||||
*/
|
||||
private _getRequestedToken(
|
||||
token_id: TokenId,
|
||||
ns_data: TokenNamespaceData
|
||||
): TokenData | null
|
||||
{
|
||||
const reqtok = <TokenEntry>ns_data[ <string>token_id ];
|
||||
|
||||
if ( !reqtok )
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
id: token_id,
|
||||
status: reqtok.status,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Determine token root for the given namespace
|
||||
*
|
||||
* @param ns token namespace
|
||||
*
|
||||
* @return token root for namespace NS
|
||||
*/
|
||||
private _genRoot( ns: TokenNamespace ): string
|
||||
{
|
||||
// XXX: injectable
|
||||
return this._rootField + '.' + ns;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -1,138 +0,0 @@
|
|||
/**
|
||||
* Result of querying for a token from Mongo
|
||||
*
|
||||
* Copyright (C) 2010-2019 R-T Specialty, LLC.
|
||||
*
|
||||
* This file is part of the Liza Data Collection Framework.
|
||||
*
|
||||
* liza is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* These types are used to describe the structure of the token data as it
|
||||
* is stored in Mongo. It has a number of undesirable properties and
|
||||
* duplicates data---this was intended to make querying easier and work
|
||||
* around Mongo limitations.
|
||||
*
|
||||
* This structure can be changed in the future, but we'll need to maintain
|
||||
* compatibility with the existing data.
|
||||
*/
|
||||
|
||||
import { TokenId } from "./Token";
|
||||
|
||||
|
||||
/**
|
||||
* Token status types as stored in the database
|
||||
*/
|
||||
export type TokenType = 'ACTIVE' | 'DONE' | 'ACCEPTED' | 'DEAD';
|
||||
|
||||
|
||||
/**
|
||||
* Result of a Mongo query
|
||||
*
|
||||
* The returned property depends on the actual query.
|
||||
*/
|
||||
export interface TokenQueryResult
|
||||
{
|
||||
readonly [propName: string]: TokenNamespaceResults | null,
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Token data for requested namespaces
|
||||
*/
|
||||
export interface TokenNamespaceResults
|
||||
{
|
||||
readonly [propName: string]: TokenNamespaceData | null,
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Token data associated with the given namespace
|
||||
*
|
||||
* This contains duplicate information in order to work around inconvenient
|
||||
* limitations in [earlier] versions of Mongo.
|
||||
*/
|
||||
export interface TokenNamespaceData
|
||||
{
|
||||
/**
|
||||
* Identifier of last token touched in this namespace
|
||||
*/
|
||||
readonly last: TokenId,
|
||||
|
||||
/**
|
||||
* Most recent token status
|
||||
*
|
||||
* This is a duplicate of the last entry in `TokenEntry#statusLog`.
|
||||
*/
|
||||
readonly lastStatus: TokenStatus,
|
||||
|
||||
/**
|
||||
* Tokens indexed by identifier
|
||||
*
|
||||
* These data are inconveniently placed---the type definition here is to
|
||||
* accommodate the above fields. Anything using this should cast to
|
||||
* `TokenEntry`.
|
||||
*/
|
||||
readonly [propName: string]: TokenEntry | TokenStatus | TokenId | null,
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Information about a given token
|
||||
*/
|
||||
export interface TokenEntry
|
||||
{
|
||||
/**
|
||||
* Current token status
|
||||
*
|
||||
* This is a duplicate of the last element of `statusLog`.
|
||||
*/
|
||||
readonly status: TokenStatus,
|
||||
|
||||
/**
|
||||
* Log of all past status changes and any associated data
|
||||
*
|
||||
* This is pushed to on each status change. The last element is
|
||||
* duplicated in `status`.
|
||||
*/
|
||||
readonly statusLog: TokenStatus[],
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Status of the token (past or present)
|
||||
*
|
||||
* A status is a `TokenType`, along with a timestamp of occurrence and
|
||||
* optional data.
|
||||
*/
|
||||
export interface TokenStatus
|
||||
{
|
||||
/**
|
||||
* State of the token
|
||||
*/
|
||||
readonly type: TokenType,
|
||||
|
||||
/**
|
||||
* Unix timestamp representing when the status change occurred
|
||||
*/
|
||||
readonly timestamp: UnixTimestamp,
|
||||
|
||||
/**
|
||||
* Arbitrary data associated with the status change
|
||||
*
|
||||
* For example, a token of status `DONE` may be associated with the
|
||||
* fulfillment of a request, in which case this may contain the response
|
||||
* data.
|
||||
*/
|
||||
readonly data: string | null,
|
||||
}
|
|
@ -20,15 +20,13 @@
|
|||
*/
|
||||
|
||||
import {
|
||||
TokenData,
|
||||
TokenQueryResult,
|
||||
TokenStatus,
|
||||
} from "../../../src/server/token/TokenQueryResult";
|
||||
|
||||
import {
|
||||
default as Sut,
|
||||
TokenData,
|
||||
} from "../../../src/server/token/TokenDao";
|
||||
|
||||
import { MongoTokenDao as Sut } from "../../../src/server/token/MongoTokenDao";
|
||||
|
||||
import {
|
||||
TokenId,
|
||||
TokenNamespace,
|
Loading…
Reference in New Issue