diff --git a/src/server/daemon/controller.js b/src/server/daemon/controller.js
index 36dca21..18a2635 100644
--- a/src/server/daemon/controller.js
+++ b/src/server/daemon/controller.js
@@ -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' );
diff --git a/src/server/service/TokenedService.js b/src/server/service/TokenedService.js
index 08a5e6d..ead005e 100644
--- a/src/server/service/TokenedService.js
+++ b/src/server/service/TokenedService.js
@@ -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' );
diff --git a/src/server/token/MongoTokenDao.ts b/src/server/token/MongoTokenDao.ts
new file mode 100644
index 0000000..f77a436
--- /dev/null
+++ b/src/server/token/MongoTokenDao.ts
@@ -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 .
+ */
+
+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
+ {
+ 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
+ {
+ 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 = data[ this._rootField ]
+ || {};
+
+ if ( !field[ ns ] )
+ {
+ resolve( null );
+ return;
+ }
+
+ const ns_data = 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 = ns_data[ 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;
+ }
+};
+
diff --git a/src/server/token/TokenDao.ts b/src/server/token/TokenDao.ts
index 75b7710..fcb06c2 100644
--- a/src/server/token/TokenDao.ts
+++ b/src/server/token/TokenDao.ts
@@ -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 .
+ *
+ * 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;
+
+
+ getToken(
+ doc_id: DocumentId,
+ ns: TokenNamespace,
+ token_id: TokenId
+ ): Promise;
+}
+
+
+/**
+ * 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
- {
- 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
- {
- 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 = data[ this._rootField ]
- || {};
-
- if ( !field[ ns ] )
- {
- resolve( null );
- return;
- }
-
- const ns_data = 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 = ns_data[ 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;
- }
-};
-
diff --git a/src/server/token/TokenQueryResult.ts b/src/server/token/TokenQueryResult.ts
deleted file mode 100644
index 5faeab4..0000000
--- a/src/server/token/TokenQueryResult.ts
+++ /dev/null
@@ -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 .
- *
- * 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,
-}
diff --git a/test/server/token/TokenDaoTest.ts b/test/server/token/MongoTokenDaoTest.ts
similarity index 98%
rename from test/server/token/TokenDaoTest.ts
rename to test/server/token/MongoTokenDaoTest.ts
index c992632..74f9c13 100644
--- a/test/server/token/TokenDaoTest.ts
+++ b/test/server/token/MongoTokenDaoTest.ts
@@ -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,