From 794136c76281e37e7eb13814ac8ef60120f7e48e Mon Sep 17 00:00:00 2001 From: Hayden Young Date: Thu, 25 May 2023 00:48:01 +0800 Subject: [PATCH] refactor: Functionize identity provider. (#74) * refactor: Functionize identity provider. * docs: Building a custom identity provider. * refactor: Functionize clock. --- src/access-controllers/ipfs.js | 4 +- src/access-controllers/orbitdb.js | 10 +-- src/address.js | 4 +- src/db/documents.js | 16 ++-- src/db/events.js | 20 ++--- src/db/keyvalue-indexed.js | 8 +- src/db/keyvalue.js | 12 +-- src/identities/identities.js | 22 ++--- src/identities/identity.js | 6 +- src/identities/index.js | 3 +- src/identities/providers/index.js | 52 +++++++++++- src/identities/providers/interface.js | 26 ------ src/identities/providers/publickey.js | 79 +++++++++++++----- src/key-store.js | 16 ++-- src/manifest.js | 6 +- src/oplog/clock.js | 41 ++++------ src/oplog/conflict-resolution.js | 12 +-- src/oplog/entry.js | 12 +-- src/oplog/heads.js | 2 +- src/oplog/log.js | 18 ++--- src/storage/composed.js | 2 +- src/storage/ipfs-block.js | 2 +- src/storage/level.js | 2 +- src/storage/lru.js | 2 +- src/storage/memory.js | 2 +- src/utils/create-id.js | 2 +- src/utils/ensure-ac-address.js | 2 +- test/identities/identities.test.js | 26 ++++-- test/oplog/clock.test.js | 94 +++++++-------------- test/oplog/conflict-resolution.test.js | 108 ++++++++++++------------- test/oplog/entry.test.js | 3 +- test/oplog/join.test.js | 26 +++--- 32 files changed, 331 insertions(+), 309 deletions(-) delete mode 100644 src/identities/providers/interface.js diff --git a/src/access-controllers/ipfs.js b/src/access-controllers/ipfs.js index e530f20..5e3ff66 100644 --- a/src/access-controllers/ipfs.js +++ b/src/access-controllers/ipfs.js @@ -47,7 +47,7 @@ const type = 'ipfs' * @param {Array} [params.write] An array of identity ids who can write to the * database. * @param {module:Storage} [params.storage] An instance of a compatible storage. - * @returns {module:AccessControllers.AccessControllers-IPFS} An + * @return {module:AccessControllers.AccessControllers-IPFS} An * IPFSAccessController function. * @memberof module:AccessControllers */ @@ -70,7 +70,7 @@ const IPFSAccessController = ({ write, storage } = {}) => async ({ orbitdb, iden /** * Verifies the write permission of an entry. * @param {module:Log~Entry} entry An entry to verify. - * @returns {boolean} True if the entry's identity has write permission, + * @return {boolean} True if the entry's identity has write permission, * false otherwise. * @memberof module:AccessControllers.AccessControllers-IPFS */ diff --git a/src/access-controllers/orbitdb.js b/src/access-controllers/orbitdb.js index bc2a2e9..e47fac0 100644 --- a/src/access-controllers/orbitdb.js +++ b/src/access-controllers/orbitdb.js @@ -27,7 +27,7 @@ const type = 'orbitdb' * IPFSAccessController. * @param {Array} [params.write] An array of identity ids who can write to the * database. - * @returns {module:AccessControllers.AccessControllers-OrbitDB} An + * @return {module:AccessControllers.AccessControllers-OrbitDB} An * IPFSAccessController function. * @memberof module:AccessControllers */ @@ -42,7 +42,7 @@ const OrbitDBAccessController = ({ write } = {}) => async ({ orbitdb, identities /** * Verifies the write permission of an entry. * @param {module:Log~Entry} entry An entry to verify. - * @returns {boolean} True if the entry's identity has write permission, + * @return {boolean} True if the entry's identity has write permission, * false otherwise. * @memberof module:AccessControllers.AccessControllers-OrbitDB * @instance @@ -68,7 +68,7 @@ const OrbitDBAccessController = ({ write } = {}) => async ({ orbitdb, identities * * The returned capabilities will be a mixture of admin and write access * addresses. - * @returns {Array} A list of addresses with admin and write access. + * @return {Array} A list of addresses with admin and write access. * @memberof module:AccessControllers.AccessControllers-OrbitDB * @instance */ @@ -98,7 +98,7 @@ const OrbitDBAccessController = ({ write } = {}) => async ({ orbitdb, identities /** * Gets a list of addresses with the specified capability. * @param {string} capability A capability (e.g. write). - * @returns {Array} One or more addresses with the spcified capability. + * @return {Array} One or more addresses with the spcified capability. * @memberof module:AccessControllers.AccessControllers-OrbitDB * @instance */ @@ -120,7 +120,7 @@ const OrbitDBAccessController = ({ write } = {}) => async ({ orbitdb, identities * Checks whether an address has a capability. * @param {string} capability A capability (e.g. write). * @param {string} key An address. - * @returns {boolean} True if the address has the capability, false + * @return {boolean} True if the address has the capability, false * otherwise. * @memberof module:AccessControllers.AccessControllers-OrbitDB * @instance diff --git a/src/address.js b/src/address.js index ebc8586..1ffb558 100644 --- a/src/address.js +++ b/src/address.js @@ -61,7 +61,7 @@ const parseAddress = (address) => { * Creates an instance of OrbitDBAddress. * @function * @param {OrbitDBAddress|string} address A valid OrbitDB database address. - * @returns {OrbitDBAddress} An instance of OrbitDBAddress. + * @return {OrbitDBAddress} An instance of OrbitDBAddress. * @instance */ const OrbitDBAddress = (address) => { @@ -89,7 +89,7 @@ const OrbitDBAddress = (address) => { /** * Returns OrbitDBAddress as a string. * @function - * @returns {string} The string form of OrbitDBAddress. + * @return {string} The string form of OrbitDBAddress. * @memberof module:Address~OrbitDBAddress */ const toString = () => { diff --git a/src/db/documents.js b/src/db/documents.js index 2780f0a..99be777 100644 --- a/src/db/documents.js +++ b/src/db/documents.js @@ -54,7 +54,7 @@ const DefaultOptions = { indexBy: '_id' } * Defines a Documents database. * @param {Object} options Various options for configuring the Document store. * @param {string} [params.indexBy=_id] An index. - * @returns {module:Database.Database-Documents} A Documents function. + * @return {module:Database.Database-Documents} A Documents function. * @memberof module:Database */ const Documents = ({ indexBy } = DefaultOptions) => async ({ ipfs, identity, address, name, access, directory, meta, headsStorage, entryStorage, indexStorage, referencesCount, syncAutomatically, onUpdate }) => { @@ -66,7 +66,7 @@ const Documents = ({ indexBy } = DefaultOptions) => async ({ ipfs, identity, add * Stores a document to the store. * @function * @param {Object} doc An object representing a key/value list of fields. - * @returns {string} The hash of the new oplog entry. + * @return {string} The hash of the new oplog entry. * @memberof module:Database.Database-Documents * @instance */ @@ -82,7 +82,7 @@ const Documents = ({ indexBy } = DefaultOptions) => async ({ ipfs, identity, add * Deletes a document from the store. * @function * @param {string} key The key of the doc to delete. - * @returns {string} The hash of the new oplog entry. + * @return {string} The hash of the new oplog entry. * @memberof module:Database.Database-Documents * @instance */ @@ -96,7 +96,7 @@ const Documents = ({ indexBy } = DefaultOptions) => async ({ ipfs, identity, add * Gets a document from the store by key. * @function * @param {string} key The key of the doc to get. - * @returns {Object} The doc corresponding to key or null. + * @return {Object} The doc corresponding to key or null. * @memberof module:Database.Database-Documents * @instance */ @@ -113,7 +113,7 @@ const Documents = ({ indexBy } = DefaultOptions) => async ({ ipfs, identity, add * @function * @param {function(Object)} findFn A function for querying for specific * results. - * @returns {Array} Found documents. + * @return {Array} Found documents. * @memberof module:Database.Database-Documents * @instance */ @@ -132,8 +132,8 @@ const Documents = ({ indexBy } = DefaultOptions) => async ({ ipfs, identity, add /** * Iterates over documents. * @function - * @params {Object} [filters={}] Various filters to apply to the iterator. - * @params {string} [filters.amount=-1] The number of results to fetch. + * @param {Object} [filters={}] Various filters to apply to the iterator. + * @param {string} [filters.amount=-1] The number of results to fetch. * @yields [string, string, string] The next document as hash/key/value. * @memberof module:Database.Database-Documents * @instance @@ -160,7 +160,7 @@ const Documents = ({ indexBy } = DefaultOptions) => async ({ ipfs, identity, add /** * Returns all documents. * @function - * @returns [][string, string, string] An array of documents as hash/key + * @return [][string, string, string] An array of documents as hash/key * value entries. * @memberof module:Database.Database-Documents * @instance diff --git a/src/db/events.js b/src/db/events.js index 35d56f6..6f42fd9 100644 --- a/src/db/events.js +++ b/src/db/events.js @@ -37,7 +37,7 @@ import Database from '../database.js' /** * Defines an Events database. - * @returns {module:Database.Database-Events} A Events function. + * @return {module:Database.Database-Events} A Events function. * @memberof module:Database */ const Events = () => async ({ ipfs, identity, address, name, access, directory, meta, headsStorage, entryStorage, indexStorage, referencesCount, syncAutomatically, onUpdate }) => { @@ -49,7 +49,7 @@ const Events = () => async ({ ipfs, identity, address, name, access, directory, * Adds an event to the store. * @function * @param {*} value The event to be added. - * @returns {string} The hash of the new oplog entry. + * @return {string} The hash of the new oplog entry. * @memberof module:Database.Database-Events * @instance */ @@ -61,7 +61,7 @@ const Events = () => async ({ ipfs, identity, address, name, access, directory, * Gets an event from the store by hash. * @function * @param {string} hash The hash of the event to get. - * @returns {*} The value corresponding to hash or null. + * @return {*} The value corresponding to hash or null. * @memberof module:Database.Database-Events * @instance */ @@ -73,16 +73,16 @@ const Events = () => async ({ ipfs, identity, address, name, access, directory, /** * Iterates over events. * @function - * @params {Object} [filters={}] Various filters to apply to the iterator. - * @params {string} [filters.gt] All events which are greater than the + * @param {Object} [filters={}] Various filters to apply to the iterator. + * @param {string} [filters.gt] All events which are greater than the * given hash. - * @params {string} [filters.gte] All events which are greater than or equal + * @param {string} [filters.gte] All events which are greater than or equal * to the given hash. - * @params {string} [filters.lt] All events which are less than the given + * @param {string} [filters.lt] All events which are less than the given * hash. - * @params {string} [filters.lte] All events which are less than or equal to + * @param {string} [filters.lte] All events which are less than or equal to * the given hash. - * @params {string} [filters.amount=-1] The number of results to fetch. + * @param {string} [filters.amount=-1] The number of results to fetch. * @yields [string, string] The next event as hash/value. * @memberof module:Database.Database-Events * @instance @@ -99,7 +99,7 @@ const Events = () => async ({ ipfs, identity, address, name, access, directory, /** * Returns all events. * @function - * @returns [][string, string] An array of events as hash/value entries. + * @return [][string, string] An array of events as hash/value entries. * @memberof module:Database.Database-Events * @instance */ diff --git a/src/db/keyvalue-indexed.js b/src/db/keyvalue-indexed.js index 3c0716c..ded37df 100644 --- a/src/db/keyvalue-indexed.js +++ b/src/db/keyvalue-indexed.js @@ -54,7 +54,7 @@ const valueEncoding = 'json' * @param {Object} options Various options for configuring the KeyValueIndexed * store. * @param {module:Storage} [storage=LevelStorage] A compatible storage. - * @returns {module:Database.Database-KeyValueIndexed} A KeyValueIndexed + * @return {module:Database.Database-KeyValueIndexed} A KeyValueIndexed * function. * @memberof module:Database */ @@ -93,7 +93,7 @@ const KeyValueIndexed = ({ storage } = {}) => async ({ ipfs, identity, address, * Gets a value from the store by key. * @function * @param {string} key The key of the value to get. - * @returns {*} The value corresponding to key or null. + * @return {*} The value corresponding to key or null. * @memberof module:Database.Database-KeyValueIndexed * @instance */ @@ -108,8 +108,8 @@ const KeyValueIndexed = ({ storage } = {}) => async ({ ipfs, identity, address, /** * Iterates over keyvalue pairs. * @function - * @params {Object} [filters={}] Various filters to apply to the iterator. - * @params {string} [filters.amount=-1] The number of results to fetch. + * @param {Object} [filters={}] Various filters to apply to the iterator. + * @param {string} [filters.amount=-1] The number of results to fetch. * @yields [string, string, string] The next key/value as key/value/hash. * @memberof module:Database.Database-KeyValueIndexed * @instance diff --git a/src/db/keyvalue.js b/src/db/keyvalue.js index 7df4f1c..ba356fd 100644 --- a/src/db/keyvalue.js +++ b/src/db/keyvalue.js @@ -37,7 +37,7 @@ import Database from '../database.js' /** * Defines an KeyValue database. - * @returns {module:Database.Database-KeyValue} A KeyValue function. + * @return {module:Database.Database-KeyValue} A KeyValue function. * @memberof module:Database */ const KeyValue = () => async ({ ipfs, identity, address, name, access, directory, meta, headsStorage, entryStorage, indexStorage, referencesCount, syncAutomatically, onUpdate }) => { @@ -50,7 +50,7 @@ const KeyValue = () => async ({ ipfs, identity, address, name, access, directory * @function * @param {string} key The key to store. * @param {*} value The value to store. - * @returns {string} The hash of the new oplog entry. + * @return {string} The hash of the new oplog entry. * @memberof module:Database.Database-KeyValue * @instance */ @@ -73,7 +73,7 @@ const KeyValue = () => async ({ ipfs, identity, address, name, access, directory * Gets a value from the store by key. * @function * @param {string} key The key of the value to get. - * @returns {*} The value corresponding to key or null. + * @return {*} The value corresponding to key or null. * @memberof module:Database.Database-KeyValue * @instance */ @@ -91,8 +91,8 @@ const KeyValue = () => async ({ ipfs, identity, address, name, access, directory /** * Iterates over keyvalue pairs. * @function - * @params {Object} [filters={}] Various filters to apply to the iterator. - * @params {string} [filters.amount=-1] The number of results to fetch. + * @param {Object} [filters={}] Various filters to apply to the iterator. + * @param {string} [filters.amount=-1] The number of results to fetch. * @yields [string, string, string] The next key/value as key/value/hash. * @memberof module:Database.Database-KeyValue * @instance @@ -119,7 +119,7 @@ const KeyValue = () => async ({ ipfs, identity, address, name, access, directory /** * Returns all key/value pairs. * @function - * @returns [][string, string, string] An array of key/value pairs as + * @return [][string, string, string] An array of key/value pairs as * key/value/hash entries. * @memberof module:Database.Database-KeyValue * @instance diff --git a/src/identities/identities.js b/src/identities/identities.js index b0bf19f..024711e 100644 --- a/src/identities/identities.js +++ b/src/identities/identities.js @@ -13,7 +13,7 @@ import KeyStore, { signMessage, verifyMessage } from '../key-store.js' import { LRUStorage, IPFSBlockStorage, MemoryStorage, ComposedStorage } from '../storage/index.js' import pathJoin from '../utils/path-join.js' -const DefaultProviderType = PublicKeyIdentityProvider.type +const DefaultProviderType = 'publickey' const DefaultIdentityKeysPath = pathJoin('./orbitdb', 'identities') const supportedTypes = { @@ -36,7 +36,7 @@ const supportedTypes = { * module. * @param {IPFS} [params.ipfs] An instance of IPFS. This param is not required * if storage is provided. - * @returns {module:Identities~Identities} An instance of Identities. + * @return {module:Identities~Identities} An instance of Identities. * @instance */ const Identities = async ({ keystore, path, storage, ipfs } = {}) => { @@ -58,7 +58,7 @@ const Identities = async ({ keystore, path, storage, ipfs } = {}) => { /** * Gets an identity by hash. * @param {string} hash An identity hash. - * @returns {Identity} An instance of identity. + * @return {Identity} An instance of identity. * @memberof module:Identities~Identities * @instance */ @@ -72,8 +72,8 @@ const Identities = async ({ keystore, path, storage, ipfs } = {}) => { /** * Creates an identity, adding it to storage. * @param {Object} options Various options for configuring a new identity. - * @param {string} [options.type=PublicKeyIdentityProvider.type] The type of provider to use for generating an identity. - * @returns {Identity} An instance of identity. + * @param {string} [options.type=publickey] The type of provider to use for generating an identity. + * @return {Identity} An instance of identity. * @memberof module:Identities~Identities * @instance */ @@ -81,8 +81,8 @@ const Identities = async ({ keystore, path, storage, ipfs } = {}) => { options.keystore = keystore const type = options.type || DefaultProviderType - const Provider = getProviderFor(type) - const identityProvider = new Provider(options) + const Provider = getProviderFor(type).default + const identityProvider = Provider(options) const id = await identityProvider.getId(options) const privateKey = await keystore.getKey(id) || await keystore.createKey(id) const publicKey = keystore.getPublic(privateKey) @@ -103,7 +103,7 @@ const Identities = async ({ keystore, path, storage, ipfs } = {}) => { /** * Verifies an identity using the identity's provider. * @param {Identity} identity The identity to verify. - * @returns {boolean} True the identity is valid, false otherwise. + * @return {boolean} True the identity is valid, false otherwise. * @memberof module:Identities~Identities */ const verifyIdentity = async (identity) => { @@ -137,7 +137,7 @@ const Identities = async ({ keystore, path, storage, ipfs } = {}) => { * Signs data using an identity. * @param {Identity} identity The identity to use for signing. * @param {string} data The data to sign. - * @returns {string} The signed data. + * @return {string} The signed data. * @throws Private signing key not fund from KeyStore when no signing key can * be retrieved. * @memberof module:Identities~Identities @@ -170,7 +170,7 @@ const Identities = async ({ keystore, path, storage, ipfs } = {}) => { /** * Checks whether an identity provider is supported. * @param {string} type The identity provider type. - * @returns {boolean} True if the identity provider is supported, false + * @return {boolean} True if the identity provider is supported, false * otherwise. * @static */ @@ -181,7 +181,7 @@ const isProviderSupported = (type) => { /** * Gets an identity provider. * @param {string} type The identity provider type. - * @returns {IdentityProvider} The IdentityProvider module corresponding to + * @return {IdentityProvider} The IdentityProvider module corresponding to * type. * @throws IdentityProvider type is not supported if the identity provider is * not supported. diff --git a/src/identities/identity.js b/src/identities/identity.js index 9596a9e..f57066d 100644 --- a/src/identities/identity.js +++ b/src/identities/identity.js @@ -23,7 +23,7 @@ const hashStringEncoding = base58btc * @param {string} params.type The type of identity provider. * @param {function} params.sign A sign function. * @param {function} params.verify A verify function. - * @returns {module:Identity~Identity} An instance of Identity. + * @return {module:Identity~Identity} An instance of Identity. * @throws Identity id is required if id is not provided. * @throws Invalid public key if publicKey is not provided. * @throws Signatures object is required if signature is not provided. @@ -62,7 +62,7 @@ const Identity = async ({ id, publicKey, signatures, type, sign, verify } = {}) /** * Encode an Identity to a serializable form. * @param {Identity} identity Identity to encode, - * @returns {Object} Object with fields hash and bytes. + * @return {Object} Object with fields hash and bytes. * @static */ const _encodeIdentity = async (identity) => { @@ -76,7 +76,7 @@ const _encodeIdentity = async (identity) => { /** * Decode an Identity from bytes * @param {Uint8Array} bytes Bytes from which to decode an Identity from - * @returns {Identity} + * @return {Identity} * @static */ const decodeIdentity = async (bytes) => { diff --git a/src/identities/index.js b/src/identities/index.js index 688b441..e2b6610 100644 --- a/src/identities/index.js +++ b/src/identities/index.js @@ -1,4 +1,3 @@ -/** @module IdentityProviders */ export { default as Identities, addIdentityProvider, @@ -12,4 +11,4 @@ export { isEqual } from './identity.js' -export { default as PublicKeyIdentityProvider } from './providers/publickey.js' +export { PublicKeyIdentityProvider } from './providers/index.js' diff --git a/src/identities/providers/index.js b/src/identities/providers/index.js index d772903..35620ef 100644 --- a/src/identities/providers/index.js +++ b/src/identities/providers/index.js @@ -1,4 +1,52 @@ +/** + * @module IdentityProviders + * @description + * Identity providers. + * + * ## Custom Providers + * + * An identity provider provides a method for signing and verifying an + * identity using a particular cryptographic mechanism. + * + * A custom identity provider can be used provided the module takes the + * following form: + * ```javascript + * // A unique name for the identity provider + * const type = 'custom' + * + * // check whether the identity was signed by the identity's id. + * const verifyIdentity = identity => { + * + * } + * + * // The identity provider. + * const MyCustomIdentityProvider = ({ keystore }) => { + * const getId = async ({ id } = {}) => { + * + * } + * + * const signIdentity = async (data, { id } = {}) => { + * + * } + * + * return { + * getId, + * signIdentity + * } + * } + * + * export { MyCustomIdentityProvider as default, verifyIdentity, type } + * ``` + * + * To use it, add it to the list of known identity providers: + * ```javascript + * import * as MyCustomIdentityProvider from 'my-custom-identity-provider' + * addIdentityProvider(MyCustomIdentityProvider) + * ``` + * + * where my-custom-identity-provider is the custom module. + */ + // export { default as DIDIdentityProvider } from './did.js' // export { default as EthIdentityProvider } from './ethereum.js' -export { default as IdentityProvider } from './interface.js' -export { default as PublicKeyIdentityProvider } from './publickey.js' +export * as PublicKeyIdentityProvider from './publickey.js' diff --git a/src/identities/providers/interface.js b/src/identities/providers/interface.js deleted file mode 100644 index a524f16..0000000 --- a/src/identities/providers/interface.js +++ /dev/null @@ -1,26 +0,0 @@ -class IdentityProvider { - /* Return id of identity (to be signed by orbit-db public key) */ - async getId (options) {} - - /* Return signature of OrbitDB public key signature */ - async signIdentity (data, options) {} - - /* Verify a signature of OrbitDB public key signature */ - static async verifyIdentity (identity) {} - - /* Return the type for this identity provider */ - static get type () { - throw new Error('\'static get type ()\' needs to be defined in the inheriting class') - } - - /* - Return the type for this identity-proider - NOTE! This is the only property of the interface that - shouldn't be overridden in the inherited IdentityProvider - */ - get type () { - return this.constructor.type - } -} - -export default IdentityProvider diff --git a/src/identities/providers/publickey.js b/src/identities/providers/publickey.js index cc4c468..67f6dad 100644 --- a/src/identities/providers/publickey.js +++ b/src/identities/providers/publickey.js @@ -1,41 +1,77 @@ /** - * @namespace module:IdentityProviders.IdentityProviders-PublicKey - * @description PublicKey Identity Provider + * @module PublicKeyIdentityProvider + * @memberof module:IdentityProviders + * @description + * The PublicKey Identity Provider signs and verifies an identity using the + * public key of a private/public key pair. */ import { toString as uint8ArrayToString } from 'uint8arrays/to-string' -import IdentityProvider from './interface.js' import { signMessage, verifyMessage } from '../../key-store.js' +/** + * The type of identity provider. + * @return string + * @const + */ const type = 'publickey' -class PublicKeyIdentityProvider extends IdentityProvider { - constructor ({ keystore }) { - super() +/** + * Verifies an identity using the identity's id. + * @param {module:Identity} identity + * @return {boolean} True if the identity is valid, false otherwise. + * @static + */ +const verifyIdentity = identity => { + const { id, publicKey, signatures } = identity + return verifyMessage(signatures.publicKey, id, publicKey + signatures.id) +} - if (!keystore) { - throw new Error('PublicKeyIdentityProvider requires a keystore parameter') - } +/** + * Instantiates the publickey identity provider. + * @return {module:IdentityProviders.IdentityProvider-PublicKey} A public key identity provider function. + */ +const PublicKeyIdentityProvider = ({ keystore }) => { + /** + * @namespace module:IdentityProviders.IdentityProvider-PublicKey + * @memberof module:IdentityProviders + * @description The instance returned by {@link module:IdentityProviders.IdentityProvider-PublicKey}. + */ - this._keystore = keystore + if (!keystore) { + throw new Error('PublicKeyIdentityProvider requires a keystore parameter') } - // Returns the type of the identity provider - static get type () { return type } - - async getId ({ id } = {}) { + /** + * Gets the id. + * @memberof module:IdentityProviders.IdentityProvider-PublicKey + * @param {String} id The id to retrieve. + * @return {String} The identity's id. + * @instance + */ + const getId = async ({ id } = {}) => { if (!id) { throw new Error('id is required') } - const key = await this._keystore.getKey(id) || await this._keystore.createKey(id) + + const key = await keystore.getKey(id) || await keystore.createKey(id) return uint8ArrayToString(key.public.marshal(), 'base16') } - async signIdentity (data, { id } = {}) { + /** + * Signs an identity using the identity's id. + * @memberof module:IdentityProviders.IdentityProvider-PublicKey + * @param {*} data The identity data to sign. + * @param {Object} params One or more parameters for configuring Database. + * @param {string} [params.id] The identity's id. + * @return {string} A signature. + * @instance + */ + const signIdentity = async (data, { id } = {}) => { if (!id) { throw new Error('id is required') } - const key = await this._keystore.getKey(id) + const key = await keystore.getKey(id) if (!key) { throw new Error(`Signing key for '${id}' not found`) } @@ -43,11 +79,10 @@ class PublicKeyIdentityProvider extends IdentityProvider { return signMessage(key, data) } - static async verifyIdentity (identity) { - const { id, publicKey, signatures } = identity - // Verify that identity was signed by the ID - return verifyMessage(signatures.publicKey, id, publicKey + signatures.id) + return { + getId, + signIdentity } } -export default PublicKeyIdentityProvider +export { PublicKeyIdentityProvider as default, verifyIdentity, type } diff --git a/src/key-store.js b/src/key-store.js index 04c3254..8a09a56 100644 --- a/src/key-store.js +++ b/src/key-store.js @@ -21,9 +21,9 @@ const unmarshalPubKey = crypto.keys.supportedKeys.secp256k1.unmarshalSecp256k1Pu /** * Verifies a signature used for signing data. - * @params {string} signature The generated signature. - * @params {string} publicKey The derived public key of the key pair. - * @params {string} data The data to be verified. + * @param {string} signature The generated signature. + * @param {string} publicKey The derived public key of the key pair. + * @param {string} data The data to be verified. * @return {boolean} True if the signature is valid, false otherwise. * @throws No signature given if no signature is provided. * @throws Given publicKey was undefined if no publicKey is provided. @@ -60,8 +60,8 @@ const verifySignature = async (signature, publicKey, data) => { /** * Signs data using a key pair. - * @params {string} key The key to use for signing data. - * @params {string} data The data to sign. + * @param {string} key The key to use for signing data. + * @param {string} data The data to sign. * @return {string} A signature. * @throws No signing key given if no key is provided. * @throws Given input data was undefined if no data is provided. @@ -87,9 +87,9 @@ const verifiedCachePromise = LRUStorage({ size: 1000 }) /** * Verifies input data against a cached version of the signed message. - * @params {string} signature The generated signature. - * @params {string} publicKey The derived public key of the key pair. - * @params {string} data The data to be verified. + * @param {string} signature The generated signature. + * @param {string} publicKey The derived public key of the key pair. + * @param {string} data The data to be verified. * @return {boolean} True if the the data and cache match, false otherwise. * @static */ diff --git a/src/manifest.js b/src/manifest.js index d389673..deef420 100644 --- a/src/manifest.js +++ b/src/manifest.js @@ -20,7 +20,7 @@ const hashStringEncoding = base58btc * @param {Object} params One or more parameters for configuring Manifest. * @param {IPFS} params.ipfs An instance of IPFS. * @param {module:Storage} [param.storage=module:Storage.Storage-ComposedStorage] An instance of Storage. - * @returns {module:Manifest} An instance of Manifest. + * @return {module:Manifest} An instance of Manifest. * @instance */ const Manifest = async ({ ipfs, storage } = {}) => { @@ -37,7 +37,7 @@ const Manifest = async ({ ipfs, storage } = {}) => { /** * Gets the manifest data from the underlying storage. * @param {string} address The address of the manifest. - * @returns {*} The manifest data. + * @return {*} The manifest data. * @memberof module:Manifest~Manifest */ const get = async (address) => { @@ -53,7 +53,7 @@ const Manifest = async ({ ipfs, storage } = {}) => { * @param {string} type The type of the database. * @param {string} accessController The type of access controller. * @param {Object} meta Metadata. - * @returns {Object} A hash and manifest. + * @return {Object} A hash and manifest. * @throws name is required if no name is provided. * @throws type is required if no type is provided. * @throws accessController is required if no access controller is provided. diff --git a/src/oplog/clock.js b/src/oplog/clock.js index 9753970..038f5fc 100644 --- a/src/oplog/clock.js +++ b/src/oplog/clock.js @@ -1,33 +1,26 @@ /* Lamport Clock */ -class Clock { - constructor (id, time) { - this.id = id - this.time = time || 0 - } +const compareClocks = (a, b) => { + // Calculate the "distance" based on the clock, ie. lower or greater + const dist = a.time - b.time - tick () { - return new Clock(this.id, ++this.time) - } + // If the sequence number is the same (concurrent events), + // and the IDs are different, take the one with a "lower" id + if (dist === 0 && a.id !== b.id) return a.id < b.id ? -1 : 1 - merge (clock) { - this.time = Math.max(this.time, clock.time) - return new Clock(this.id, this.time) - } + return dist +} - clone () { - return new Clock(this.id, this.time) - } +const tickClock = (clock) => { + return Clock(clock.id, ++clock.time) +} - static compare (a, b) { - // Calculate the "distance" based on the clock, ie. lower or greater - const dist = a.time - b.time +const Clock = (id, time) => { + time = time || 0 - // If the sequence number is the same (concurrent events), - // and the IDs are different, take the one with a "lower" id - if (dist === 0 && a.id !== b.id) return a.id < b.id ? -1 : 1 - - return dist + return { + id, + time } } -export default Clock +export { Clock as default, compareClocks, tickClock } diff --git a/src/oplog/conflict-resolution.js b/src/oplog/conflict-resolution.js index 06b52a4..d4b4339 100644 --- a/src/oplog/conflict-resolution.js +++ b/src/oplog/conflict-resolution.js @@ -1,4 +1,4 @@ -import Clock from './clock.js' +import { compareClocks } from './clock.js' /** * Sort two entries as Last-Write-Wins (LWW). @@ -8,7 +8,7 @@ import Clock from './clock.js' * * @param {Entry} a First entry * @param {Entry} b Second entry - * @returns {number} 1 if a is latest, -1 if b is latest + * @return {number} 1 if a is latest, -1 if b is latest */ function LastWriteWins (a, b) { // Ultimate conflict resolution (take the first/left arg) @@ -27,11 +27,11 @@ function LastWriteWins (a, b) { * @param {Entry} a First entry to compare * @param {Entry} b Second entry to compare * @param {function(a, b)} resolveConflict A function to call if entries are concurrent (happened at the same time). The function should take in two entries and return 1 if the first entry should be chosen and -1 if the second entry should be chosen. - * @returns {number} 1 if a is greater, -1 if b is greater + * @return {number} 1 if a is greater, -1 if b is greater */ function SortByClocks (a, b, resolveConflict) { // Compare the clocks - const diff = Clock.compare(a.clock, b.clock) + const diff = compareClocks(a.clock, b.clock) // If the clocks are concurrent, use the provided // conflict resolution function to determine which comes first return diff === 0 ? resolveConflict(a, b) : diff @@ -42,7 +42,7 @@ function SortByClocks (a, b, resolveConflict) { * @param {Entry} a First entry to compare * @param {Entry} b Second entry to compare * @param {function(a, b)} resolveConflict A function to call if the clocks ids are the same. The function should take in two entries and return 1 if the first entry should be chosen and -1 if the second entry should be chosen. - * @returns {number} 1 if a is greater, -1 if b is greater + * @return {number} 1 if a is greater, -1 if b is greater */ function SortByClockId (a, b, resolveConflict) { // Sort by ID if clocks are concurrent, @@ -55,7 +55,7 @@ function SortByClockId (a, b, resolveConflict) { /** * A wrapper function to throw an error if the results of a passed function return zero * @param {function(a, b)} [tiebreaker] The tiebreaker function to validate. - * @returns {function(a, b)} 1 if a is greater, -1 if b is greater + * @return {function(a, b)} 1 if a is greater, -1 if b is greater * @throws {Error} if func ever returns 0 */ function NoZeroes (func) { diff --git a/src/oplog/entry.js b/src/oplog/entry.js index 46ae6af..e215194 100644 --- a/src/oplog/entry.js +++ b/src/oplog/entry.js @@ -21,7 +21,7 @@ const hashStringEncoding = base58btc * @param {Clock} [clock] The clock * @param {Array} [next=[]] An array of CIDs as base58btc encoded strings * @param {Array} [refs=[]] An array of CIDs as base58btc encoded strings - * @returns {Promise} + * @return {Promise} * @example * const entry = await Entry.create(identity, 'log1', 'hello') * console.log(entry) @@ -33,7 +33,7 @@ const create = async (identity, id, payload, clock = null, next = [], refs = []) if (payload == null) throw new Error('Entry requires a payload') if (next == null || !Array.isArray(next)) throw new Error("'next' argument is not an array") - clock = clock || new Clock(identity.publicKey) + clock = clock || Clock(identity.publicKey) const entry = { id, // For determining a unique chain @@ -84,7 +84,7 @@ const verify = async (identities, entry) => { /** * Check if an object is an Entry. * @param {Entry} obj - * @returns {boolean} + * @return {boolean} */ const isEntry = (obj) => { return obj && obj.id !== undefined && @@ -102,7 +102,7 @@ const isEqual = (a, b) => { /** * Decode a serialized Entry from bytes * @param {Uint8Array} bytes - * @returns {Entry} + * @return {Entry} */ const decode = async (bytes) => { const { value } = await Block.decode({ bytes, codec, hasher }) @@ -112,12 +112,12 @@ const decode = async (bytes) => { /** * Encode an Entry to a serializable form * @param {Entry} entry - * @returns {TODO} + * @return {TODO} */ const _encodeEntry = async (entry) => { const { cid, bytes } = await Block.encode({ value: entry, codec, hasher }) const hash = cid.toString(hashStringEncoding) - const clock = new Clock(entry.clock.id, entry.clock.time) + const clock = Clock(entry.clock.id, entry.clock.time) return { ...entry, clock, diff --git a/src/oplog/heads.js b/src/oplog/heads.js index f9cb23e..df908ba 100644 --- a/src/oplog/heads.js +++ b/src/oplog/heads.js @@ -76,7 +76,7 @@ const Heads = async ({ storage, heads }) => { * This function is private and not exposed in the Log API * * @param {Array} entries Entries to search heads from - * @returns {Array} + * @return {Array} */ const findHeads = (entries) => { entries = new Set(entries) diff --git a/src/oplog/log.js b/src/oplog/log.js index 730648f..41c3ef9 100644 --- a/src/oplog/log.js +++ b/src/oplog/log.js @@ -9,7 +9,7 @@ */ import LRU from 'lru' import Entry from './entry.js' -import Clock from './clock.js' +import Clock, { tickClock } from './clock.js' import Heads from './heads.js' import ConflictResolution from './conflict-resolution.js' import MemoryStorage from '../storage/memory.js' @@ -80,20 +80,20 @@ const Log = async (identity, { logId, logHeads, access, entryStorage, headsStora /** * Returns the clock of the log. - * @returns {Clock} + * @return {Clock} * @memberof module:Log~Log * @instance */ const clock = async () => { // Find the latest clock from the heads const maxTime = Math.max(0, (await heads()).reduce(maxClockTimeReducer, 0)) - return new Clock(identity.publicKey, maxTime) + return Clock(identity.publicKey, maxTime) } /** * Returns the current heads of the log * - * @returns {Array} + * @return {Array} * @memberof module:Log~Log * @instance */ @@ -105,7 +105,7 @@ const Log = async (identity, { logId, logHeads, access, entryStorage, headsStora /** * Returns all entries in the log * - * @returns {Array} + * @return {Array} * @memberof module:Log~Log * @instance */ @@ -121,7 +121,7 @@ const Log = async (identity, { logId, logHeads, access, entryStorage, headsStora * Retrieve an entry * * @param {string} hash The hash of the entry to retrieve - * @returns {module:Log~Entry} + * @return {module:Log~Entry} * @memberof module:Log~Log * @instance */ @@ -169,7 +169,7 @@ const Log = async (identity, { logId, logHeads, access, entryStorage, headsStora identity, id, data, - (await clock()).tick(), + tickClock(await clock()), nexts, refs ) @@ -353,7 +353,7 @@ const Log = async (identity, { logId, logHeads, access, entryStorage, headsStora * @param {string} options.gte Beginning hash of the iterator, inclusive * @param {string} options.lt Ending hash of the iterator, non-inclusive * @param {string} options.lte Ending hash of the iterator, inclusive - * @returns {Symbol.asyncIterator} Iterator object of log entries + * @return {Symbol.asyncIterator} Iterator object of log entries * * @examples * @@ -473,7 +473,7 @@ const Log = async (identity, { logId, logHeads, access, entryStorage, headsStora /** * Check if an object is a Log. * @param {Log} obj - * @returns {boolean} + * @return {boolean} * @memberof module:Log~Log * @instance */ diff --git a/src/storage/composed.js b/src/storage/composed.js index a382842..388d33d 100644 --- a/src/storage/composed.js +++ b/src/storage/composed.js @@ -17,7 +17,7 @@ * @function * @param {module:Storage} storage1 A storage instance. * @param {module:Storage} storage2 A storage instance. - * @returns {module:Storage.Storage-Composed} An instance of ComposedStorage. + * @return {module:Storage.Storage-Composed} An instance of ComposedStorage. * @memberof module:Storage * @instance */ diff --git a/src/storage/ipfs-block.js b/src/storage/ipfs-block.js index 6801c4e..e63d20f 100644 --- a/src/storage/ipfs-block.js +++ b/src/storage/ipfs-block.js @@ -18,7 +18,7 @@ const defaultTimeout = 30000 * @param {number} [params.timeout=defaultTimeout] A timeout in ms. * @param {boolean} [params.pin=false] True, if the block should be pinned, * false otherwise. - * @returns {module:Storage.Storage-IPFS} An instance of IPFSBlockStorage. + * @return {module:Storage.Storage-IPFS} An instance of IPFSBlockStorage. * @memberof module:Storage * @throw An instance of ipfs is required if params.ipfs is not specified. * @instance diff --git a/src/storage/level.js b/src/storage/level.js index fcdcef4..02fe9c6 100644 --- a/src/storage/level.js +++ b/src/storage/level.js @@ -18,7 +18,7 @@ const defaultValueEncoding = 'view' * LevelStorage. * @param {string} [params.path=defaultPath] The Level path. * @param {string} [params.valueEncoding=defaultValueEncoding] Value encoding. - * @returns {module:Storage.Storage-Level} An instance of LevelStorage. + * @return {module:Storage.Storage-Level} An instance of LevelStorage. * @memberof module:Storage * @instance */ diff --git a/src/storage/lru.js b/src/storage/lru.js index 4384cb6..bbe05f5 100644 --- a/src/storage/lru.js +++ b/src/storage/lru.js @@ -14,7 +14,7 @@ const defaultSize = 1000000 * @param {Object} [params={}] One or more parameters for configuring * IPFSBlockStorage. * @param {string} [params.size=defaultSize] The number of elements to store. - * @returns {module:Storage.Storage-LRU} An instance of LRUStorage. + * @return {module:Storage.Storage-LRU} An instance of LRUStorage. * @memberof module:Storage * @instance */ diff --git a/src/storage/memory.js b/src/storage/memory.js index ff96549..239a9f4 100644 --- a/src/storage/memory.js +++ b/src/storage/memory.js @@ -8,7 +8,7 @@ /** * Creates an instance of MemoryStorage. * @function - * @returns {module:Storage.Storage-Memory} An instance of LRUStorage. + * @return {module:Storage.Storage-Memory} An instance of LRUStorage. * @memberof module:Storage * @instance */ diff --git a/src/utils/create-id.js b/src/utils/create-id.js index 4c8d774..a1e2bed 100644 --- a/src/utils/create-id.js +++ b/src/utils/create-id.js @@ -1,7 +1,7 @@ /** * Creates an id from an alphanumeric character list. * @param {number} [length=32] The length of the id. - * @returns {string} An id. + * @return {string} An id. * @see {@link https://stackoverflow.com/questions/1349404/generate-random-string-characters-in-javascript} * @memberof module:Utils */ diff --git a/src/utils/ensure-ac-address.js b/src/utils/ensure-ac-address.js index 9ed1660..fe39bf1 100644 --- a/src/utils/ensure-ac-address.js +++ b/src/utils/ensure-ac-address.js @@ -4,7 +4,7 @@ import pathJoin from './path-join.js' * Checks that the given address has '/_access' as the last part. * @function * @param {string} address The address to check. - * @returns {string} The address appended with /_access. + * @return {string} The address appended with /_access. * @memberof module:Utils */ export default address => { diff --git a/test/identities/identities.test.js b/test/identities/identities.test.js index 116e956..71f83a3 100644 --- a/test/identities/identities.test.js +++ b/test/identities/identities.test.js @@ -213,18 +213,30 @@ describe('Identities', function () { }) it('false signature doesn\'t verify', async () => { - class IP { - async getId () { return 'pubKey' } + const IP = () => { + const verifyIdentity = async (data) => { return false } - async signIdentity (data) { return `false signature '${data}'` } + const FakeIdentityProvider = () => { + const getId = () => { return 'pubKey' } - static async verifyIdentity (data) { return false } + const signIdentity = (data) => { return `false signature '${data}'` } - static get type () { return 'fake' } + return { + getId, + signIdentity, + type: 'fake' + } + } + + return { + default: FakeIdentityProvider, + verifyIdentity, + type: 'fake' + } } - addIdentityProvider(IP) - identity = await identities.createIdentity({ type: IP.type }) + addIdentityProvider(IP()) + identity = await identities.createIdentity({ type: IP().type }) const verified = await identities.verifyIdentity(identity) assert.strictEqual(verified, false) }) diff --git a/test/oplog/clock.test.js b/test/oplog/clock.test.js index 1afac73..67ebef7 100644 --- a/test/oplog/clock.test.js +++ b/test/oplog/clock.test.js @@ -1,11 +1,11 @@ -import Clock from '../../src/oplog/clock.js' +import Clock, { tickClock, compareClocks } from '../../src/oplog/clock.js' import { strictEqual } from 'assert' describe('Clock', () => { it('creates a new clock', () => { const id = 'A' const time = 0 - const clock = new Clock(id, time) + const clock = Clock(id, time) strictEqual(clock.id, id) strictEqual(clock.time, time) }) @@ -13,7 +13,7 @@ describe('Clock', () => { it('creates a new clock with default time', () => { const id = 'A' const time = 0 - const clock = new Clock(id) + const clock = Clock(id) strictEqual(clock.id, id) strictEqual(clock.time, time) }) @@ -21,7 +21,7 @@ describe('Clock', () => { it('creates a new clock with time starting at 1', () => { const id = 'A' const time = 1 - const clock = new Clock(id, time) + const clock = Clock(id, time) strictEqual(clock.id, id) strictEqual(clock.time, time) }) @@ -29,144 +29,104 @@ describe('Clock', () => { it('advances clock forward 1 tick', () => { const id = 'A' const time = 1 - const clock = new Clock(id) - clock.tick() + const clock = tickClock(Clock(id)) strictEqual(clock.time, time) }) it('advances clock forward 2 ticks', () => { const id = 'A' const time = 2 - const clock = new Clock(id) - clock.tick() - clock.tick() + const clock = tickClock(tickClock(Clock(id))) strictEqual(clock.time, time) }) - it('merges clock2 into clock1', () => { - const id1 = 'A' - const clock1 = new Clock(id1) - const id2 = 'B' - const time2 = 2 - const clock2 = new Clock(id2, time2) - - clock1.merge(clock2) - - strictEqual(clock1.id, id1) - strictEqual(clock1.time, time2) - }) - - it('merges two clocks with the same time', () => { - const id1 = 'A' - const time1 = 1 - const clock1 = new Clock(id1, time1) - const id2 = 'B' - const time2 = 1 - const clock2 = new Clock(id2, time2) - - clock1.merge(clock2) - - strictEqual(clock1.id, id1) - strictEqual(clock1.time, time1) - }) - - it('clones a clock', () => { - const id = 'A' - const time = 1 - const clock = new Clock(id, time) - const clonedClock = clock.clone() - - strictEqual(clonedClock.id, id) - strictEqual(clonedClock.time, time) - }) - describe('Compare clocks', () => { it('compares clocks when clock1\'s time is 1 less than clock2\'s', () => { const id1 = 'A' const time1 = 1 - const clock1 = new Clock(id1, time1) + const clock1 = Clock(id1, time1) const id2 = 'B' const time2 = 2 - const clock2 = new Clock(id2, time2) + const clock2 = Clock(id2, time2) const expected = -1 - strictEqual(Clock.compare(clock1, clock2), expected) + strictEqual(compareClocks(clock1, clock2), expected) }) it('compares clocks when clock1\'s time is 3 less than clock2\'s', () => { const id1 = 'A' const time1 = 1 - const clock1 = new Clock(id1, time1) + const clock1 = Clock(id1, time1) const id2 = 'B' const time2 = 4 - const clock2 = new Clock(id2, time2) + const clock2 = Clock(id2, time2) const expected = -3 - strictEqual(Clock.compare(clock1, clock2), expected) + strictEqual(compareClocks(clock1, clock2), expected) }) it('compares clocks when clock1\'s time is 1 more than clock2\'s', () => { const id1 = 'A' const time1 = 2 - const clock1 = new Clock(id1, time1) + const clock1 = Clock(id1, time1) const id2 = 'B' const time2 = 1 - const clock2 = new Clock(id2, time2) + const clock2 = Clock(id2, time2) const expected = 1 - strictEqual(Clock.compare(clock1, clock2), expected) + strictEqual(compareClocks(clock1, clock2), expected) }) it('compares clocks when clock1\'s time is 3 more than clock2\'s', () => { const id1 = 'A' const time1 = 4 - const clock1 = new Clock(id1, time1) + const clock1 = Clock(id1, time1) const id2 = 'B' const time2 = 1 - const clock2 = new Clock(id2, time2) + const clock2 = Clock(id2, time2) const expected = 3 - strictEqual(Clock.compare(clock1, clock2), expected) + strictEqual(compareClocks(clock1, clock2), expected) }) it('compares clocks when clock1\'s id is less than clock2\'s', () => { const id1 = 'A' const time1 = 1 - const clock1 = new Clock(id1, time1) + const clock1 = Clock(id1, time1) const id2 = 'B' const time2 = 1 - const clock2 = new Clock(id2, time2) + const clock2 = Clock(id2, time2) const expected = -1 - strictEqual(Clock.compare(clock1, clock2), expected) + strictEqual(compareClocks(clock1, clock2), expected) }) it('compares clocks when clock1\'s id is more than clock2\'s', () => { const id1 = 'B' const time1 = 1 - const clock1 = new Clock(id1, time1) + const clock1 = Clock(id1, time1) const id2 = 'A' const time2 = 1 - const clock2 = new Clock(id2, time2) + const clock2 = Clock(id2, time2) const expected = 1 - strictEqual(Clock.compare(clock1, clock2), expected) + strictEqual(compareClocks(clock1, clock2), expected) }) it('compares clocks when clock1 is the same as clock2', () => { const id = 'A' const time = 1 - const clock1 = new Clock(id, time) - const clock2 = new Clock(id, time) + const clock1 = Clock(id, time) + const clock2 = Clock(id, time) const expected = 0 - strictEqual(Clock.compare(clock1, clock2), expected) + strictEqual(compareClocks(clock1, clock2), expected) }) }) }) diff --git a/test/oplog/conflict-resolution.test.js b/test/oplog/conflict-resolution.test.js index 615a949..d328ba8 100644 --- a/test/oplog/conflict-resolution.test.js +++ b/test/oplog/conflict-resolution.test.js @@ -31,22 +31,22 @@ describe('ConflictResolution', () => { it('returns -1 when first clock\'s id is less than second clock\'s', () => { const expected = -1 - const record1 = { clock: new Clock('A') } - const record2 = { clock: new Clock('B') } + const record1 = { clock: Clock('A') } + const record2 = { clock: Clock('B') } strictEqual(ConflictResolution.SortByClockId(record1, record2, fallbackFn), expected) }) it('returns 1 when first clock\'s id is greater than second clock\'s', () => { const expected = 1 - const record1 = { clock: new Clock('B') } - const record2 = { clock: new Clock('A') } + const record1 = { clock: Clock('B') } + const record2 = { clock: Clock('A') } strictEqual(ConflictResolution.SortByClockId(record1, record2, fallbackFn), expected) }) it('returns the clock when clocks have the same id', () => { - const expected = { clock: new Clock('A') } - const record1 = { clock: new Clock('A') } - const record2 = { clock: new Clock('A') } + const expected = { clock: Clock('A') } + const record1 = { clock: Clock('A') } + const record2 = { clock: Clock('A') } deepStrictEqual(ConflictResolution.SortByClockId(record1, record2, fallbackFn), expected) }) }) @@ -59,22 +59,22 @@ describe('ConflictResolution', () => { it('returns -1 when a\'s time is less than b\'s', () => { const expected = -1 - const record1 = { clock: new Clock('A', 1) } - const record2 = { clock: new Clock('B', 2) } + const record1 = { clock: Clock('A', 1) } + const record2 = { clock: Clock('B', 2) } strictEqual(ConflictResolution.SortByClocks(record1, record2, fallbackFn), expected) }) it('returns 1 when a\'s time is greater than b\'s', () => { const expected = 1 - const record1 = { clock: new Clock('A', 2) } - const record2 = { clock: new Clock('B', 1) } + const record1 = { clock: Clock('A', 2) } + const record2 = { clock: Clock('B', 1) } strictEqual(ConflictResolution.SortByClocks(record1, record2, fallbackFn), expected) }) it('returns -1 when a\'s time is equal to b\'s', () => { const expected = -1 - const record1 = { clock: new Clock('A', 1) } - const record2 = { clock: new Clock('B', 1) } + const record1 = { clock: Clock('A', 1) } + const record2 = { clock: Clock('B', 1) } strictEqual(ConflictResolution.SortByClocks(record1, record2, fallbackFn), expected) }) }) @@ -82,29 +82,29 @@ describe('ConflictResolution', () => { describe('Last write wins', () => { it('returns -1 when a\'s time is less than b\'s', () => { const expected = -1 - const record1 = { clock: new Clock('A', 1) } - const record2 = { clock: new Clock('B', 2) } + const record1 = { clock: Clock('A', 1) } + const record2 = { clock: Clock('B', 2) } strictEqual(ConflictResolution.LastWriteWins(record1, record2), expected) }) it('returns 1 when a\'s time is greater than b\'s', () => { const expected = 1 - const record1 = { clock: new Clock('A', 2) } - const record2 = { clock: new Clock('B', 1) } + const record1 = { clock: Clock('A', 2) } + const record2 = { clock: Clock('B', 1) } strictEqual(ConflictResolution.LastWriteWins(record1, record2), expected) }) it('returns -1 when a\'s time is equal to b\'s', () => { const expected = -1 - const record1 = { clock: new Clock('A', 1) } - const record2 = { clock: new Clock('B', 1) } + const record1 = { clock: Clock('A', 1) } + const record2 = { clock: Clock('B', 1) } strictEqual(ConflictResolution.LastWriteWins(record1, record2), expected) }) it('returns the clock when a and b are the same', () => { - const expected = { clock: new Clock('A') } - const record1 = { clock: new Clock('A') } - const record2 = { clock: new Clock('A') } + const expected = { clock: Clock('A') } + const record1 = { clock: Clock('A') } + const record2 = { clock: Clock('A') } deepStrictEqual(ConflictResolution.LastWriteWins(record1, record2), expected) }) }) @@ -112,17 +112,17 @@ describe('ConflictResolution', () => { describe('ConflictResolution records', () => { it('sorts by clock time', () => { const expected = [ - { clock: new Clock('A', 1) }, - { clock: new Clock('B', 2) }, - { clock: new Clock('C', 3) }, - { clock: new Clock('D', 4) } + { clock: Clock('A', 1) }, + { clock: Clock('B', 2) }, + { clock: Clock('C', 3) }, + { clock: Clock('D', 4) } ] const records = [ - { clock: new Clock('C', 3) }, - { clock: new Clock('A', 1) }, - { clock: new Clock('D', 4) }, - { clock: new Clock('B', 2) } + { clock: Clock('C', 3) }, + { clock: Clock('A', 1) }, + { clock: Clock('D', 4) }, + { clock: Clock('B', 2) } ] deepStrictEqual(records.sort(ConflictResolution.LastWriteWins), expected) @@ -130,17 +130,17 @@ describe('ConflictResolution', () => { it('sorts by clock time when id is the same', () => { const expected = [ - { clock: new Clock('A', 1) }, - { clock: new Clock('A', 2) }, - { clock: new Clock('A', 3) }, - { clock: new Clock('A', 4) } + { clock: Clock('A', 1) }, + { clock: Clock('A', 2) }, + { clock: Clock('A', 3) }, + { clock: Clock('A', 4) } ] const records = [ - { clock: new Clock('A', 3) }, - { clock: new Clock('A', 1) }, - { clock: new Clock('A', 4) }, - { clock: new Clock('A', 2) } + { clock: Clock('A', 3) }, + { clock: Clock('A', 1) }, + { clock: Clock('A', 4) }, + { clock: Clock('A', 2) } ] deepStrictEqual(records.sort(ConflictResolution.LastWriteWins), expected) @@ -148,17 +148,17 @@ describe('ConflictResolution', () => { it('sorts by clock id', () => { const expected = [ - { clock: new Clock('A') }, - { clock: new Clock('B') }, - { clock: new Clock('C') }, - { clock: new Clock('D') } + { clock: Clock('A') }, + { clock: Clock('B') }, + { clock: Clock('C') }, + { clock: Clock('D') } ] const records = [ - { clock: new Clock('C') }, - { clock: new Clock('A') }, - { clock: new Clock('D') }, - { clock: new Clock('B') } + { clock: Clock('C') }, + { clock: Clock('A') }, + { clock: Clock('D') }, + { clock: Clock('B') } ] deepStrictEqual(records.sort(ConflictResolution.LastWriteWins), expected) @@ -166,17 +166,17 @@ describe('ConflictResolution', () => { it('sorts the same clock', () => { const expected = [ - { clock: new Clock('A') }, - { clock: new Clock('A') }, - { clock: new Clock('B') }, - { clock: new Clock('B') } + { clock: Clock('A') }, + { clock: Clock('A') }, + { clock: Clock('B') }, + { clock: Clock('B') } ] const records = [ - { clock: new Clock('B') }, - { clock: new Clock('A') }, - { clock: new Clock('B') }, - { clock: new Clock('A') } + { clock: Clock('B') }, + { clock: Clock('A') }, + { clock: Clock('B') }, + { clock: Clock('A') } ] deepStrictEqual(records.sort(ConflictResolution.LastWriteWins), expected) diff --git a/test/oplog/entry.test.js b/test/oplog/entry.test.js index bb04e1a..d382ca7 100644 --- a/test/oplog/entry.test.js +++ b/test/oplog/entry.test.js @@ -3,6 +3,7 @@ import rmrf from 'rimraf' import { copy } from 'fs-extra' import { Entry, Identities, KeyStore } from '../../src/index.js' import testKeysPath from '../fixtures/test-keys-path.js' +import { tickClock } from '../../src/oplog/clock.js' const { create, isEntry } = Entry const keysPath = './testkeys' @@ -79,7 +80,7 @@ describe('Entry', function () { const payload1 = 'hello world' const payload2 = 'hello again' const entry1 = await create(testIdentity, 'A', payload1) - entry1.clock.tick() + entry1.clock = tickClock(entry1.clock) const entry2 = await create(testIdentity, 'A', payload2, entry1.clock, [entry1]) strictEqual(entry2.payload, payload2) strictEqual(entry2.next.length, 1) diff --git a/test/oplog/join.test.js b/test/oplog/join.test.js index 524ce66..c54d1fa 100644 --- a/test/oplog/join.test.js +++ b/test/oplog/join.test.js @@ -350,19 +350,19 @@ describe('Log - Join', async function () { strictEqual((await log4.clock()).time, 8) const expectedData = [ - { payload: 'helloA1', id: 'X', clock: new Clock(testIdentity.publicKey, 1) }, - { payload: 'helloB1', id: 'X', clock: new Clock(testIdentity2.publicKey, 1) }, - { payload: 'helloD1', id: 'X', clock: new Clock(testIdentity4.publicKey, 1) }, - { payload: 'helloA2', id: 'X', clock: new Clock(testIdentity.publicKey, 2) }, - { payload: 'helloB2', id: 'X', clock: new Clock(testIdentity2.publicKey, 2) }, - { payload: 'helloD2', id: 'X', clock: new Clock(testIdentity4.publicKey, 2) }, - { payload: 'helloC1', id: 'X', clock: new Clock(testIdentity3.publicKey, 3) }, - { payload: 'helloC2', id: 'X', clock: new Clock(testIdentity3.publicKey, 4) }, - { payload: 'helloD3', id: 'X', clock: new Clock(testIdentity4.publicKey, 5) }, - { payload: 'helloD4', id: 'X', clock: new Clock(testIdentity4.publicKey, 6) }, - { payload: 'helloA5', id: 'X', clock: new Clock(testIdentity.publicKey, 7) }, - { payload: 'helloD5', id: 'X', clock: new Clock(testIdentity4.publicKey, 7) }, - { payload: 'helloD6', id: 'X', clock: new Clock(testIdentity4.publicKey, 8) } + { payload: 'helloA1', id: 'X', clock: Clock(testIdentity.publicKey, 1) }, + { payload: 'helloB1', id: 'X', clock: Clock(testIdentity2.publicKey, 1) }, + { payload: 'helloD1', id: 'X', clock: Clock(testIdentity4.publicKey, 1) }, + { payload: 'helloA2', id: 'X', clock: Clock(testIdentity.publicKey, 2) }, + { payload: 'helloB2', id: 'X', clock: Clock(testIdentity2.publicKey, 2) }, + { payload: 'helloD2', id: 'X', clock: Clock(testIdentity4.publicKey, 2) }, + { payload: 'helloC1', id: 'X', clock: Clock(testIdentity3.publicKey, 3) }, + { payload: 'helloC2', id: 'X', clock: Clock(testIdentity3.publicKey, 4) }, + { payload: 'helloD3', id: 'X', clock: Clock(testIdentity4.publicKey, 5) }, + { payload: 'helloD4', id: 'X', clock: Clock(testIdentity4.publicKey, 6) }, + { payload: 'helloA5', id: 'X', clock: Clock(testIdentity.publicKey, 7) }, + { payload: 'helloD5', id: 'X', clock: Clock(testIdentity4.publicKey, 7) }, + { payload: 'helloD6', id: 'X', clock: Clock(testIdentity4.publicKey, 8) } ] const values = await log4.values()