refactor: Functionize identity provider. (#74)

* refactor: Functionize identity provider.

* docs: Building a custom identity provider.

* refactor: Functionize clock.
This commit is contained in:
Hayden Young 2023-05-25 00:48:01 +08:00 committed by GitHub
parent 60fbe47ee3
commit 794136c762
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 331 additions and 309 deletions

View File

@ -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
*/

View File

@ -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

View File

@ -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 = () => {

View File

@ -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

View File

@ -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
*/

View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -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) => {

View File

@ -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'

View File

@ -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'

View File

@ -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

View File

@ -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 }

View File

@ -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
*/

View File

@ -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.

View File

@ -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 }

View File

@ -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) {

View File

@ -21,7 +21,7 @@ const hashStringEncoding = base58btc
* @param {Clock} [clock] The clock
* @param {Array<string|Entry>} [next=[]] An array of CIDs as base58btc encoded strings
* @param {Array<string|Entry>} [refs=[]] An array of CIDs as base58btc encoded strings
* @returns {Promise<Entry>}
* @return {Promise<Entry>}
* @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,

View File

@ -76,7 +76,7 @@ const Heads = async ({ storage, heads }) => {
* This function is private and not exposed in the Log API
*
* @param {Array<Entry>} entries Entries to search heads from
* @returns {Array<Entry>}
* @return {Array<Entry>}
*/
const findHeads = (entries) => {
entries = new Set(entries)

View File

@ -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<module:Log~Entry>}
* @return {Array<module:Log~Entry>}
* @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<module:Log~Entry>}
* @return {Array<module:Log~Entry>}
* @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
*/

View File

@ -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
*/

View File

@ -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

View File

@ -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
*/

View File

@ -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
*/

View File

@ -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
*/

View File

@ -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
*/

View File

@ -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 => {

View File

@ -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)
})

View File

@ -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)
})
})
})

View File

@ -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)

View File

@ -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)

View File

@ -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()