diff --git a/packages/elements/custom-elements.json b/packages/elements/custom-elements.json
index 39b0360..7ab6708 100644
--- a/packages/elements/custom-elements.json
+++ b/packages/elements/custom-elements.json
@@ -842,8 +842,7 @@
"name": "defaultValue",
"type": {
"text": "Date | number | string | undefined"
- },
- "default": "new Date()"
+ }
},
{
"kind": "field",
diff --git a/packages/elements/demo/index.html b/packages/elements/demo/index.html
index 306daf6..2df61a7 100644
--- a/packages/elements/demo/index.html
+++ b/packages/elements/demo/index.html
@@ -89,6 +89,7 @@
>(
});
}
+/**
+ * Defines the behavior of the joining of the `AsyncReadables`
+ */
+export interface JoinAsyncOptions {
+ /**
+ * 'bubbles': the first error encountered in the collection of stores is going to be automatically returned
+ * 'filter_out': all errors encountered in the collection of stores are going to be filtered out, returning only those stores that completed successfully
+ */
+ errors?: "filter_out" | "bubble";
+ /**
+ * 'bubbles': the first pending status encountered in the collection of stores is going to be automatically returned
+ * 'filter_out': all pending status encountered in the collection of stores are going to be filtered out, returning only those stores that completed successfully
+ */
+ pendings?: "filter_out" | "bubble";
+}
+
/**
* Joins an array of `AsyncReadables` into a single `AsyncReadable` of the array of values
*/
-export function joinAsync(stores: [AsyncReadable]): AsyncReadable<[T]>;
+export function joinAsync(
+ stores: [AsyncReadable],
+ joinOptions?: JoinAsyncOptions
+): AsyncReadable<[T]>;
export function joinAsync(
- stores: [AsyncReadable, AsyncReadable]
+ stores: [AsyncReadable, AsyncReadable],
+ joinOptions?: JoinAsyncOptions
): AsyncReadable<[T, U]>;
export function joinAsync(
- stores: [AsyncReadable, AsyncReadable, AsyncReadable]
+ stores: [AsyncReadable, AsyncReadable, AsyncReadable],
+ joinOptions?: JoinAsyncOptions
): AsyncReadable<[T, U, V]>;
export function joinAsync(
stores: [
@@ -74,7 +95,8 @@ export function joinAsync(
AsyncReadable,
AsyncReadable,
AsyncReadable
- ]
+ ],
+ joinOptions?: JoinAsyncOptions
): AsyncReadable<[T, U, V, W]>;
export function joinAsync(
stores: [
@@ -83,7 +105,8 @@ export function joinAsync(
AsyncReadable,
AsyncReadable,
AsyncReadable
- ]
+ ],
+ joinOptions?: JoinAsyncOptions
): AsyncReadable<[T, U, V, W, X]>;
export function joinAsync(
stores: [
@@ -93,7 +116,8 @@ export function joinAsync(
AsyncReadable,
AsyncReadable,
AsyncReadable
- ]
+ ],
+ joinOptions?: JoinAsyncOptions
): AsyncReadable<[T, U, V, W, X, Y]>;
export function joinAsync(
stores: [
@@ -104,7 +128,8 @@ export function joinAsync(
AsyncReadable,
AsyncReadable,
AsyncReadable
- ]
+ ],
+ joinOptions?: JoinAsyncOptions
): AsyncReadable<[T, U, V, W, X, Y, Z]>;
export function joinAsync(
stores: [
@@ -116,7 +141,8 @@ export function joinAsync(
AsyncReadable,
AsyncReadable,
AsyncReadable
- ]
+ ],
+ joinOptions?: JoinAsyncOptions
): AsyncReadable<[T, U, V, W, X, Y, Z, A]>;
export function joinAsync(
stores: [
@@ -129,7 +155,8 @@ export function joinAsync(
AsyncReadable,
AsyncReadable,
AsyncReadable
- ]
+ ],
+ joinOptions?: JoinAsyncOptions
): AsyncReadable<[T, U, V, W, X, Y, Z, A, B]>;
export function joinAsync(
stores: [
@@ -143,29 +170,48 @@ export function joinAsync(
AsyncReadable,
AsyncReadable,
AsyncReadable
- ]
+ ],
+ joinOptions?: JoinAsyncOptions
): AsyncReadable<[T, U, V, W, X, Y, Z, A, B, C]>;
export function joinAsync(
- stores: Array>
+ stores: Array>,
+ joinOptions?: JoinAsyncOptions
): AsyncReadable>;
export function joinAsync(
- stores: Array>
+ stores: Array>,
+ joinOptions?: JoinAsyncOptions
): AsyncReadable> {
+ let options = {
+ errors: "bubble",
+ pendings: "bubble",
+ };
+ if (joinOptions) {
+ options = {
+ ...options,
+ ...joinOptions,
+ };
+ }
return derived(stores, (values): AsyncStatus => {
- const firstError = values.find(
- (v) => v && (v as AsyncStatus).status === "error"
- );
- if (firstError) {
- return firstError as AsyncStatus;
+ if (options.errors === "bubble") {
+ const firstError = values.find(
+ (v) => v && (v as AsyncStatus).status === "error"
+ );
+ if (firstError) {
+ return firstError as AsyncStatus;
+ }
}
- const firstLoading = values.find(
- (v) => v && (v as AsyncStatus).status === "pending"
- );
- if (firstLoading) {
- return firstLoading as AsyncStatus;
+ if (options.pendings === "bubble") {
+ const firstLoading = values.find(
+ (v) => v && (v as AsyncStatus).status === "pending"
+ );
+ if (firstLoading) {
+ return firstLoading as AsyncStatus;
+ }
}
- const v = values.map((v) => (v as any).value as T);
+ const v = values
+ .filter((v) => v.status === "complete")
+ .map((v) => (v as any).value as T);
return {
status: "complete",
value: v,
diff --git a/packages/stores/src/holochain.ts b/packages/stores/src/holochain.ts
index 30eccd3..b78120f 100644
--- a/packages/stores/src/holochain.ts
+++ b/packages/stores/src/holochain.ts
@@ -1,11 +1,17 @@
import {
ActionCommittedSignal,
EntryRecord,
+ getHashType,
+ HashType,
+ HoloHashMap,
LinkTypeForSignal,
+ retype,
ZomeClient,
} from "@holochain-open-dev/utils";
import {
+ Action,
ActionHash,
+ AgentPubKey,
CreateLink,
decodeHashFromBase64,
Delete,
@@ -28,12 +34,12 @@ import { retryUntilSuccess } from "./retry-until-success.js";
* Will do so by calling the given every 4 seconds calling the given fetch function,
* and listening to `LinkCreated` and `LinkDeleted` signals
*
- * Useful for link types
+ * Useful for link types that **don't** target AgentPubKeys (see liveLinksTargetsAgentPubKeysStore)
*/
export function liveLinksTargetsStore<
BASE extends HoloHash,
TARGET extends HoloHash,
- S extends ActionCommittedSignal
+ S extends ActionCommittedSignal & any
>(
client: ZomeClient,
baseAddress: BASE,
@@ -51,7 +57,10 @@ export function liveLinksTargetsStore<
};
await fetch();
const interval = setInterval(() => fetch(), 4000);
- const unsubs = client.onSignal((signal) => {
+ const unsubs = client.onSignal((originalSignal) => {
+ if (!(originalSignal as ActionCommittedSignal).type) return;
+ const signal = originalSignal as ActionCommittedSignal;
+
if (signal.type === "LinkCreated") {
if (
linkType in signal.link_type &&
@@ -99,7 +108,7 @@ export function liveLinksTargetsStore<
*/
export function collectionStore<
H extends HoloHash,
- S extends ActionCommittedSignal
+ S extends ActionCommittedSignal & any
>(
client: ZomeClient,
fetchCollection: () => Promise,
@@ -116,7 +125,10 @@ export function collectionStore<
};
await fetch();
const interval = setInterval(() => fetch(), 4000);
- const unsubs = client.onSignal((signal) => {
+ const unsubs = client.onSignal((originalSignal) => {
+ if (!(originalSignal as ActionCommittedSignal).type) return;
+ const signal = originalSignal as ActionCommittedSignal;
+
if (signal.type === "LinkCreated") {
if (linkType in signal.link_type) {
hashes = uniquify([
@@ -184,7 +196,7 @@ export function immutableEntryStore(
*/
export function latestVersionOfEntryStore<
T,
- S extends ActionCommittedSignal
+ S extends ActionCommittedSignal & any
>(
client: ZomeClient,
fetchLatestVersion: () => Promise | undefined>
@@ -217,7 +229,10 @@ export function latestVersionOfEntryStore<
};
fetch();
const interval = setInterval(() => fetch(), 4000);
- const unsubs = client.onSignal(async (signal) => {
+ const unsubs = client.onSignal((originalSignal) => {
+ if (!(originalSignal as ActionCommittedSignal).type) return;
+ const signal = originalSignal as ActionCommittedSignal;
+
if (
signal.type === "EntryUpdated" &&
latestVersion &&
@@ -246,6 +261,66 @@ export function latestVersionOfEntryStore<
});
}
+/**
+ * Keeps an up to date list of all the revisions for an entry
+ * Makes requests only while it has some subscriber
+ *
+ * Will do so by calling the given every 4 seconds calling the given fetch function,
+ * and listening to `EntryUpdated` signals
+ *
+ * Useful for entries that can be updated
+ */
+export function allRevisionsOfEntryStore<
+ T,
+ S extends ActionCommittedSignal & any
+>(
+ client: ZomeClient,
+ fetchAllRevisions: () => Promise>>
+): AsyncReadable>> {
+ return asyncReadable(async (set) => {
+ let allRevisions: Array>;
+ const fetch = async () => {
+ const nAllRevisions = await fetchAllRevisions();
+ if (!isEqual(allRevisions, nAllRevisions)) {
+ allRevisions = nAllRevisions;
+ set(allRevisions);
+ }
+ };
+ await fetch();
+ const interval = setInterval(() => fetch(), 4000);
+ const unsubs = client.onSignal(async (originalSignal) => {
+ if (!(originalSignal as ActionCommittedSignal).type) return;
+ const signal = originalSignal as ActionCommittedSignal;
+
+ if (
+ signal.type === "EntryUpdated" &&
+ allRevisions &&
+ allRevisions.find(
+ (revision) =>
+ revision.actionHash.toString() ===
+ signal.action.hashed.content.original_action_address.toString()
+ )
+ ) {
+ const newRevision = new EntryRecord({
+ entry: {
+ Present: {
+ entry_type: "App",
+ entry: encode(signal.app_entry),
+ },
+ },
+ signed_action: signal.action,
+ });
+ allRevisions.push(newRevision);
+ set(allRevisions);
+ }
+ });
+ return () => {
+ clearInterval(interval);
+ unsubs();
+ };
+ });
+}
+
/**
* Keeps an up to date list of the targets for the deleted links in this DHT
* Makes requests only while it has some subscriber
@@ -258,7 +333,7 @@ export function latestVersionOfEntryStore<
export function deletedLinksTargetsStore<
BASE extends HoloHash,
TARGET extends HoloHash,
- S extends ActionCommittedSignal
+ S extends ActionCommittedSignal & any
>(
client: ZomeClient,
baseAddress: BASE,
@@ -280,7 +355,10 @@ export function deletedLinksTargetsStore<
};
await fetch();
const interval = setInterval(() => fetch(), 4000);
- const unsubs = client.onSignal((signal) => {
+ const unsubs = client.onSignal((originalSignal) => {
+ if (!(originalSignal as ActionCommittedSignal).type) return;
+ const signal = originalSignal as ActionCommittedSignal;
+
if (signal.type === "LinkDeleted") {
if (
linkType in signal.link_type &&
@@ -321,7 +399,9 @@ export function deletedLinksTargetsStore<
*
* Useful for entries that can be deleted
*/
-export function deletesForEntryStore>(
+export function deletesForEntryStore<
+ S extends ActionCommittedSignal & any
+>(
client: ZomeClient,
originalActionHash: ActionHash,
fetchDeletes: () => Promise>>
@@ -337,7 +417,10 @@ export function deletesForEntryStore>(
};
await fetch();
const interval = setInterval(() => fetch(), 4000);
- const unsubs = client.onSignal(async (signal) => {
+ const unsubs = client.onSignal((originalSignal) => {
+ if (!(originalSignal as ActionCommittedSignal).type) return;
+ const signal = originalSignal as ActionCommittedSignal;
+
if (
signal.type === "EntryDeleted" &&
signal.action.hashed.content.deletes_address.toString() ===
@@ -359,3 +442,235 @@ export function uniquify(array: Array): Array {
const uniqueArray = [...new Set(strArray)];
return uniqueArray.map((h) => decodeHashFromBase64(h) as H);
}
+
+function uniquifyActions(
+ actions: Array>
+): Array> {
+ const map = new HoloHashMap>();
+ for (const a of actions) {
+ map.set(a.hashed.hash, a);
+ }
+
+ return Array.from(map.values());
+}
+
+/**
+ * Keeps an up to date list of the links for the non-deleted links in this DHT
+ * Makes requests only while it has some subscriber
+ *
+ * Will do so by calling the given every 4 seconds calling the given fetch function,
+ * and listening to `LinkCreated` and `LinkDeleted` signals
+ *
+ * Useful for link types
+ */
+export function liveLinksStore<
+ BASE extends HoloHash,
+ S extends ActionCommittedSignal & any
+>(
+ client: ZomeClient,
+ baseAddress: BASE,
+ fetchLinks: () => Promise>>,
+ linkType: LinkTypeForSignal
+): AsyncReadable>> {
+ let innerBaseAddress = baseAddress;
+ if (getHashType(innerBaseAddress) === HashType.AGENT) {
+ innerBaseAddress = retype(innerBaseAddress, HashType.ENTRY) as BASE;
+ }
+ return asyncReadable(async (set) => {
+ let links: SignedActionHashed[];
+ const fetch = async () => {
+ const nlinks = await fetchLinks();
+ if (!isEqual(nlinks, links)) {
+ links = uniquifyActions(nlinks);
+ set(links);
+ }
+ };
+ await fetch();
+ const interval = setInterval(() => fetch(), 4000);
+ const unsubs = client.onSignal((originalSignal) => {
+ if (!(originalSignal as ActionCommittedSignal).type) return;
+ const signal = originalSignal as ActionCommittedSignal;
+
+ if (signal.type === "LinkCreated") {
+ if (
+ linkType in signal.link_type &&
+ signal.action.hashed.content.base_address.toString() ===
+ innerBaseAddress.toString()
+ ) {
+ links = uniquifyActions([...links, signal.action]);
+ set(links);
+ }
+ } else if (signal.type === "LinkDeleted") {
+ if (
+ linkType in signal.link_type &&
+ signal.create_link_action.hashed.content.base_address.toString() ===
+ innerBaseAddress.toString()
+ ) {
+ links = uniquifyActions(
+ links.filter(
+ (h) =>
+ h.hashed.hash.toString() !==
+ signal.create_link_action.hashed.hash.toString()
+ )
+ );
+ set(links);
+ }
+ }
+ });
+ return () => {
+ clearInterval(interval);
+ unsubs();
+ };
+ });
+}
+
+/**
+ * Keeps an up to date list of the targets for the deleted links in this DHT
+ * Makes requests only while it has some subscriber
+ *
+ * Will do so by calling the given every 4 seconds calling the given fetch function,
+ * and listening to `LinkDeleted` signals
+ *
+ * Useful for link types and collections with some form of archive retrieving functionality
+ */
+export function deletedLinksStore<
+ BASE extends HoloHash,
+ S extends ActionCommittedSignal & any
+>(
+ client: ZomeClient,
+ baseAddress: BASE,
+ fetchDeletedTargets: () => Promise<
+ Array<
+ [SignedActionHashed, Array>]
+ >
+ >,
+ linkType: LinkTypeForSignal
+): AsyncReadable<
+ Array<[SignedActionHashed, Array>]>
+> {
+ let innerBaseAddress = baseAddress;
+ if (getHashType(innerBaseAddress) === HashType.AGENT) {
+ innerBaseAddress = retype(innerBaseAddress, HashType.ENTRY) as BASE;
+ }
+ return asyncReadable(async (set) => {
+ let deletedTargets: Array<
+ [SignedActionHashed, Array>]
+ >;
+ const fetch = async () => {
+ const ndeletedTargets = await fetchDeletedTargets();
+ if (!isEqual(deletedTargets, ndeletedTargets)) {
+ deletedTargets = ndeletedTargets;
+ set(deletedTargets);
+ }
+ };
+ await fetch();
+ const interval = setInterval(() => fetch(), 4000);
+ const unsubs = client.onSignal((originalSignal) => {
+ if (!(originalSignal as ActionCommittedSignal).type) return;
+ const signal = originalSignal as ActionCommittedSignal;
+
+ if (signal.type === "LinkDeleted") {
+ if (
+ linkType in signal.link_type &&
+ signal.create_link_action.hashed.content.base_address.toString() ===
+ innerBaseAddress.toString()
+ ) {
+ const alreadyDeletedTargetIndex = deletedTargets.findIndex(
+ ([cl]) =>
+ cl.hashed.hash.toString() ===
+ signal.create_link_action.hashed.hash.toString()
+ );
+
+ if (alreadyDeletedTargetIndex !== -1) {
+ deletedTargets[alreadyDeletedTargetIndex][1].push(signal.action);
+ } else {
+ deletedTargets = [
+ ...deletedTargets,
+ [signal.create_link_action, [signal.action]],
+ ];
+ }
+ set(deletedTargets);
+ }
+ }
+ });
+ return () => {
+ clearInterval(interval);
+ unsubs();
+ };
+ });
+}
+
+/**
+ * Keeps an up to date list of the target AgentPubKeys for the non-deleted links in this DHT
+ * Makes requests only while it has some subscriber
+ *
+ * Will do so by calling the given every 4 seconds calling the given fetch function,
+ * and listening to `LinkCreated` and `LinkDeleted` signals
+ *
+ * Useful for link types that target AgentPubKeys
+ */
+export function liveLinksAgentPubKeysTargetsStore<
+ BASE extends HoloHash,
+ S extends ActionCommittedSignal
+>(
+ client: ZomeClient,
+ baseAddress: BASE,
+ fetchTargets: () => Promise,
+ linkType: LinkTypeForSignal
+): AsyncReadable> {
+ return asyncReadable(async (set) => {
+ let hashes: AgentPubKey[];
+ const fetch = async () => {
+ const nhashes = await fetchTargets();
+ if (!isEqual(nhashes, hashes)) {
+ hashes = uniquify(nhashes);
+ set(hashes);
+ }
+ };
+ await fetch();
+ const interval = setInterval(() => fetch(), 4000);
+ const unsubs = client.onSignal((originalSignal) => {
+ if (!(originalSignal as ActionCommittedSignal).type) return;
+ const signal = originalSignal as ActionCommittedSignal;
+
+ if (signal.type === "LinkCreated") {
+ if (
+ linkType in signal.link_type &&
+ signal.action.hashed.content.base_address.toString() ===
+ baseAddress.toString()
+ ) {
+ hashes = uniquify([
+ ...hashes,
+ retype(
+ signal.action.hashed.content.target_address as AgentPubKey,
+ HashType.AGENT
+ ),
+ ]);
+ set(hashes);
+ }
+ } else if (signal.type === "LinkDeleted") {
+ if (
+ linkType in signal.link_type &&
+ signal.create_link_action.hashed.content.base_address.toString() ===
+ baseAddress.toString()
+ ) {
+ hashes = uniquify(
+ hashes.filter(
+ (h) =>
+ h.toString() !==
+ retype(
+ signal.create_link_action.hashed.content.target_address,
+ HashType.AGENT
+ ).toString()
+ )
+ );
+ set(hashes);
+ }
+ }
+ });
+ return () => {
+ clearInterval(interval);
+ unsubs();
+ };
+ });
+}
diff --git a/packages/stores/src/join-map.ts b/packages/stores/src/join-map.ts
index f1ba669..c50cac8 100644
--- a/packages/stores/src/join-map.ts
+++ b/packages/stores/src/join-map.ts
@@ -2,12 +2,12 @@ import { HoloHashMap } from "@holochain-open-dev/utils";
import { HoloHash } from "@holochain/client";
import { Readable } from "svelte/store";
import { derived } from "./derived.js";
-import { asyncDerived, joinAsync } from "./async-derived.js";
+import { asyncDerived, joinAsync, JoinAsyncOptions } from "./async-derived.js";
import { AsyncReadable } from "./async-readable.js";
-type StoreValue = T extends Readable ? U : never;
+export type StoreValue = T extends Readable ? U : never;
-type AsyncStoreValue = T extends AsyncReadable ? U : never;
+export type AsyncStoreValue = T extends AsyncReadable ? U : never;
/**
* Joins all the stores in a HoloHashMap of `Readables`
@@ -29,12 +29,13 @@ export function joinMap>(
* Joins all the stores in a HoloHashMap of `AsyncReadables`
*/
export function joinAsyncMap>(
- holoHashMap: ReadonlyMap
+ holoHashMap: ReadonlyMap,
+ joinOptions?: JoinAsyncOptions
): AsyncReadable>> {
const storeArray = Array.from(holoHashMap.entries()).map(([key, store]) =>
asyncDerived(store, (v) => [key, v] as [H, AsyncStoreValue])
);
- const arrayStore = joinAsync(storeArray);
+ const arrayStore = joinAsync(storeArray, joinOptions);
return asyncDerived(
arrayStore,
(entries) => new HoloHashMap>(entries)
diff --git a/packages/stores/src/map-and-join.ts b/packages/stores/src/map-and-join.ts
index bce0455..50be4a6 100644
--- a/packages/stores/src/map-and-join.ts
+++ b/packages/stores/src/map-and-join.ts
@@ -1,5 +1,6 @@
import { mapValues } from "@holochain-open-dev/utils";
import { HoloHash } from "@holochain/client";
+import { JoinAsyncOptions } from "async-derived.js";
import { AsyncReadable } from "./async-readable.js";
import { joinAsyncMap } from "./join-map.js";
@@ -9,7 +10,8 @@ import { joinAsyncMap } from "./join-map.js";
*/
export function mapAndJoin(
map: ReadonlyMap,
- fn: (value: T, key: H) => AsyncReadable
+ fn: (value: T, key: H) => AsyncReadable,
+ joinOptions?: JoinAsyncOptions
): AsyncReadable> {
- return joinAsyncMap(mapValues(map, fn));
+ return joinAsyncMap(mapValues(map, fn), joinOptions);
}
diff --git a/packages/stores/tests/join-map.test.js b/packages/stores/tests/join-map.test.js
index 2709d4e..ee01517 100644
--- a/packages/stores/tests/join-map.test.js
+++ b/packages/stores/tests/join-map.test.js
@@ -52,6 +52,38 @@ it("asyncJoinMap", async () => {
expect(Array.from(get(j).value.entries()).length).to.deep.equal(2);
});
+it("asyncJoinMap with error filtering", async () => {
+ let first = true;
+ const lazyStoreMap = new LazyHoloHashMap((hash) =>
+ asyncReadable(async (set) => {
+ await sleep(10);
+ if (first) {
+ first = false;
+ throw new Error("hi");
+ }
+ set(2);
+ })
+ );
+
+ const hashes = [new Uint8Array([0]), new Uint8Array([1])];
+
+ for (const h of hashes) {
+ lazyStoreMap.get(h);
+ }
+
+ const j = joinAsyncMap(lazyStoreMap, {
+ errors: "filter_out",
+ });
+
+ const subscriber = j.subscribe(() => {});
+
+ expect(get(j)).to.deep.equal({ status: "pending" });
+ await sleep(20);
+
+ expect(get(j).status).to.equal("complete");
+ expect(Array.from(get(j).value.entries()).length).to.deep.equal(1);
+});
+
it("mapAndJoin", async () => {
const lazyStoreMap = new LazyHoloHashMap((hash) =>
asyncReadable(async (set) => {
diff --git a/packages/utils/package.json b/packages/utils/package.json
index 7a5aa3b..dc188a5 100644
--- a/packages/utils/package.json
+++ b/packages/utils/package.json
@@ -1,6 +1,6 @@
{
"name": "@holochain-open-dev/utils",
- "version": "0.16.2",
+ "version": "0.16.3",
"description": "Common utilities to build Holochain web applications",
"author": "guillem.cordoba@gmail.com",
"main": "dist/index.js",
diff --git a/packages/utils/src/entry.ts b/packages/utils/src/entry.ts
new file mode 100644
index 0000000..347fe12
--- /dev/null
+++ b/packages/utils/src/entry.ts
@@ -0,0 +1,9 @@
+import { Entry } from "@holochain/client";
+import { encode } from "@msgpack/msgpack";
+
+export function encodeAppEntry(appEntry: any): Entry {
+ return {
+ entry_type: "App",
+ entry: encode(appEntry),
+ };
+}
diff --git a/packages/utils/src/hash.ts b/packages/utils/src/hash.ts
index 906dec1..579aecd 100644
--- a/packages/utils/src/hash.ts
+++ b/packages/utils/src/hash.ts
@@ -1,4 +1,9 @@
-import { encodeHashToBase64, HoloHash } from "@holochain/client";
+import {
+ encodeHashToBase64,
+ Entry,
+ EntryHash,
+ HoloHash,
+} from "@holochain/client";
// @ts-ignore
import blake from "blakejs";
import { encode } from "@msgpack/msgpack";
@@ -44,6 +49,11 @@ export function retype(hash: HoloHash, type: HashType): HoloHash {
]);
}
+export function hashEntry(entry: Entry): EntryHash {
+ if (entry.entry_type === "Agent") return entry.entry;
+ return hash(entry, HashType.ENTRY);
+}
+
export function isHash(hash: string): boolean {
return !![
AGENT_PREFIX,
diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts
index e956bc1..40162de 100644
--- a/packages/utils/src/index.ts
+++ b/packages/utils/src/index.ts
@@ -1,5 +1,6 @@
export * from "./hash.js";
export * from "./fake.js";
+export * from "./entry.js";
export * from "./action-committed-signal.js";
export * from "./zome-client.js";
export * from "./zome-mock.js";