@pkcprotocol/pkc-js 0.0.88 → 0.0.89

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/README.md +1 -1
  2. package/dist/browser/generated-version.d.ts +1 -1
  3. package/dist/browser/generated-version.js +1 -1
  4. package/dist/browser/helia/helia-for-pkc.d.ts +2 -0
  5. package/dist/browser/helia/helia-for-pkc.js +158 -55
  6. package/dist/browser/helia/helia-for-pkc.js.map +1 -1
  7. package/dist/browser/helia/libp2pjsClient.d.ts +1 -1
  8. package/dist/browser/helia/lru-blockstore.d.ts +9 -8
  9. package/dist/browser/helia/lru-blockstore.js.map +1 -1
  10. package/dist/browser/helia/types.d.ts +10 -10
  11. package/dist/browser/rpc/src/schema.d.ts +148 -22
  12. package/dist/browser/runtime/node/community/local-community/cleanup.js +31 -1
  13. package/dist/browser/runtime/node/community/local-community/cleanup.js.map +1 -1
  14. package/dist/browser/schema.d.ts +8 -6
  15. package/dist/browser/schema.js +8 -1
  16. package/dist/browser/schema.js.map +1 -1
  17. package/dist/browser/test/test-util.js +3 -1
  18. package/dist/browser/test/test-util.js.map +1 -1
  19. package/dist/bundled/bundle-manifest.json +165 -146
  20. package/dist/bundled/chunks/{helia-for-pkc-CgG0H5g2.js → helia-for-pkc-BKXOmklx.js} +70 -39
  21. package/dist/bundled/chunks/{local-community-rFE0QOdJ.js → local-community-C7tTEYDD.js} +1 -1
  22. package/dist/bundled/chunks/{local-community-6_MVz1cc.js → local-community-D5fzgPCr.js} +32 -3
  23. package/dist/bundled/chunks/{node-BjSTVzbx.js → node-C9RpmtmM.js} +5 -5
  24. package/dist/bundled/chunks/{rpc-local-community-Bu6Wp-tO.js → rpc-local-community-Dq00OHRf.js} +432 -6
  25. package/dist/bundled/chunks/{schema-By3Sh-uW.js → schema-bDrzx9EZ.js} +3 -3
  26. package/dist/bundled/chunks/{src-WLgFbUwZ.js → src-629pKfxo.js} +2731 -415
  27. package/dist/bundled/chunks/{src-B2CwDTfy.js → src-Dz9wtvrK.js} +18 -18
  28. package/dist/bundled/chunks/{util-BxLdjyTF.js → util-DuJcVQ7Y.js} +1 -1
  29. package/dist/bundled/index.js +1 -1
  30. package/dist/bundled/rpc/src/index.js +4 -4
  31. package/dist/node/generated-version.d.ts +1 -1
  32. package/dist/node/generated-version.js +1 -1
  33. package/dist/node/helia/helia-for-pkc.d.ts +2 -0
  34. package/dist/node/helia/helia-for-pkc.js +158 -55
  35. package/dist/node/helia/helia-for-pkc.js.map +1 -1
  36. package/dist/node/helia/libp2pjsClient.d.ts +1 -1
  37. package/dist/node/helia/lru-blockstore.d.ts +9 -8
  38. package/dist/node/helia/lru-blockstore.js.map +1 -1
  39. package/dist/node/helia/types.d.ts +10 -10
  40. package/dist/node/rpc/src/schema.d.ts +148 -22
  41. package/dist/node/runtime/node/community/local-community/cleanup.js +31 -1
  42. package/dist/node/runtime/node/community/local-community/cleanup.js.map +1 -1
  43. package/dist/node/schema.d.ts +8 -6
  44. package/dist/node/schema.js +8 -1
  45. package/dist/node/schema.js.map +1 -1
  46. package/dist/node/test/test-util.js +3 -1
  47. package/dist/node/test/test-util.js.map +1 -1
  48. package/package.json +17 -16
@@ -5,10 +5,10 @@ import { CID } from "multiformats/cid";
5
5
  import { fromString } from "uint8arrays/from-string";
6
6
  import { concat } from "uint8arrays/concat";
7
7
  import { equals } from "uint8arrays/equals";
8
+ import { sha256 } from "multiformats/hashes/sha2";
8
9
  import * as raw from "multiformats/codecs/raw";
9
10
  import { alloc, allocUnsafe } from "uint8arrays/alloc";
10
11
  import { withArrayBuffer } from "uint8arrays/with-array-buffer";
11
- import { sha256 } from "multiformats/hashes/sha2";
12
12
  import { murmur3128 } from "@multiformats/murmur3";
13
13
  import { BlackHoleBlockstore } from "blockstore-core/black-hole";
14
14
  //#region node_modules/ipfs-unixfs-importer/node_modules/uint8arraylist/dist/src/index.js
@@ -485,7 +485,7 @@ const fixedSize = (options = {}) => {
485
485
  };
486
486
  };
487
487
  //#endregion
488
- //#region node_modules/ipfs-unixfs-importer/node_modules/ipfs-unixfs/dist/src/errors.js
488
+ //#region node_modules/ipfs-unixfs/dist/src/errors.js
489
489
  var InvalidTypeError = class InvalidTypeError extends Error {
490
490
  static name = "InvalidTypeError";
491
491
  static code = "ERR_INVALID_TYPE";
@@ -496,7 +496,7 @@ var InvalidTypeError = class InvalidTypeError extends Error {
496
496
  }
497
497
  };
498
498
  //#endregion
499
- //#region node_modules/ipfs-unixfs-importer/node_modules/uint8-varint/dist/src/index.js
499
+ //#region node_modules/ipfs-unixfs/node_modules/uint8-varint/dist/src/index.js
500
500
  const N1 = Math.pow(2, 7);
501
501
  const N2 = Math.pow(2, 14);
502
502
  const N3 = Math.pow(2, 21);
@@ -579,7 +579,7 @@ function decodeUint8Array(buf, offset) {
579
579
  throw new RangeError("Could not decode varint");
580
580
  }
581
581
  //#endregion
582
- //#region node_modules/ipfs-unixfs-importer/node_modules/protons-runtime/dist/src/utils/float.js
582
+ //#region node_modules/ipfs-unixfs/node_modules/protons-runtime/dist/src/utils/float.js
583
583
  const f32 = new Float32Array([-0]);
584
584
  const f8b = new Uint8Array(f32.buffer);
585
585
  /**
@@ -633,7 +633,7 @@ function readDoubleLE(buf, pos) {
633
633
  return f64[0];
634
634
  }
635
635
  //#endregion
636
- //#region node_modules/ipfs-unixfs-importer/node_modules/protons-runtime/dist/src/utils/longbits.js
636
+ //#region node_modules/ipfs-unixfs/node_modules/protons-runtime/dist/src/utils/longbits.js
637
637
  const MAX_SAFE_NUMBER_INTEGER = BigInt(Number.MAX_SAFE_INTEGER);
638
638
  const MIN_SAFE_NUMBER_INTEGER = BigInt(Number.MIN_SAFE_INTEGER);
639
639
  /**
@@ -777,7 +777,7 @@ zero.length = function() {
777
777
  };
778
778
  const TWO_32 = 4294967296n;
779
779
  //#endregion
780
- //#region node_modules/ipfs-unixfs-importer/node_modules/protons-runtime/dist/src/utils/utf8.js
780
+ //#region node_modules/ipfs-unixfs/node_modules/protons-runtime/dist/src/utils/utf8.js
781
781
  /**
782
782
  * Calculates the UTF8 byte length of a string
783
783
  */
@@ -853,7 +853,7 @@ function write(string, buffer, offset) {
853
853
  return offset - start;
854
854
  }
855
855
  //#endregion
856
- //#region node_modules/ipfs-unixfs-importer/node_modules/protons-runtime/dist/src/utils/reader.js
856
+ //#region node_modules/ipfs-unixfs/node_modules/protons-runtime/dist/src/utils/reader.js
857
857
  /* istanbul ignore next */
858
858
  function indexOutOfRange(reader, writeLength) {
859
859
  return RangeError(`index out of range: ${reader.pos} + ${writeLength ?? 1} > ${reader.len}`);
@@ -1153,13 +1153,13 @@ function createReader(buf) {
1153
1153
  return new Uint8ArrayReader(buf instanceof Uint8Array ? buf : buf.subarray());
1154
1154
  }
1155
1155
  //#endregion
1156
- //#region node_modules/ipfs-unixfs-importer/node_modules/protons-runtime/dist/src/decode.js
1156
+ //#region node_modules/ipfs-unixfs/node_modules/protons-runtime/dist/src/decode.js
1157
1157
  function decodeMessage(buf, codec, opts) {
1158
1158
  const reader = createReader(buf);
1159
1159
  return codec.decode(reader, void 0, opts);
1160
1160
  }
1161
1161
  //#endregion
1162
- //#region node_modules/ipfs-unixfs-importer/node_modules/protons-runtime/dist/src/utils/pool.js
1162
+ //#region node_modules/ipfs-unixfs/node_modules/protons-runtime/dist/src/utils/pool.js
1163
1163
  /**
1164
1164
  * A general purpose buffer pool
1165
1165
  */
@@ -1180,7 +1180,7 @@ function pool(size) {
1180
1180
  };
1181
1181
  }
1182
1182
  //#endregion
1183
- //#region node_modules/ipfs-unixfs-importer/node_modules/protons-runtime/dist/src/utils/writer.js
1183
+ //#region node_modules/ipfs-unixfs/node_modules/protons-runtime/dist/src/utils/writer.js
1184
1184
  /**
1185
1185
  * Constructs a new writer operation instance.
1186
1186
  *
@@ -1575,20 +1575,20 @@ function createWriter() {
1575
1575
  return new Uint8ArrayWriter();
1576
1576
  }
1577
1577
  //#endregion
1578
- //#region node_modules/ipfs-unixfs-importer/node_modules/protons-runtime/dist/src/encode.js
1578
+ //#region node_modules/ipfs-unixfs/node_modules/protons-runtime/dist/src/encode.js
1579
1579
  function encodeMessage(message, codec) {
1580
1580
  const w = createWriter();
1581
1581
  codec.encode(message, w, { lengthDelimited: false });
1582
1582
  return w.finish();
1583
1583
  }
1584
1584
  //#endregion
1585
- //#region node_modules/ipfs-unixfs-importer/node_modules/protons-runtime/dist/src/stream.js
1585
+ //#region node_modules/ipfs-unixfs/node_modules/protons-runtime/dist/src/stream.js
1586
1586
  function* streamMessage(buf, codec, opts) {
1587
1587
  const reader = createReader(buf);
1588
1588
  yield* codec.stream(reader, void 0, "$", opts);
1589
1589
  }
1590
1590
  //#endregion
1591
- //#region node_modules/ipfs-unixfs-importer/node_modules/protons-runtime/dist/src/codec.js
1591
+ //#region node_modules/ipfs-unixfs/node_modules/protons-runtime/dist/src/codec.js
1592
1592
  const CODEC_TYPES = {
1593
1593
  VARINT: 0,
1594
1594
  BIT64: 1,
@@ -1607,7 +1607,7 @@ function createCodec(name, type, encode, decode, stream) {
1607
1607
  };
1608
1608
  }
1609
1609
  //#endregion
1610
- //#region node_modules/ipfs-unixfs-importer/node_modules/protons-runtime/dist/src/codecs/enum.js
1610
+ //#region node_modules/ipfs-unixfs/node_modules/protons-runtime/dist/src/codecs/enum.js
1611
1611
  function enumeration(v) {
1612
1612
  function findValue(val) {
1613
1613
  if (v[val.toString()] == null) throw new Error("Invalid enum value");
@@ -1623,12 +1623,12 @@ function enumeration(v) {
1623
1623
  });
1624
1624
  }
1625
1625
  //#endregion
1626
- //#region node_modules/ipfs-unixfs-importer/node_modules/protons-runtime/dist/src/codecs/message.js
1626
+ //#region node_modules/ipfs-unixfs/node_modules/protons-runtime/dist/src/codecs/message.js
1627
1627
  function message(encode, decode, stream) {
1628
1628
  return createCodec("message", CODEC_TYPES.LENGTH_DELIMITED, encode, decode, stream);
1629
1629
  }
1630
1630
  //#endregion
1631
- //#region node_modules/ipfs-unixfs-importer/node_modules/protons-runtime/dist/src/index.js
1631
+ //#region node_modules/ipfs-unixfs/node_modules/protons-runtime/dist/src/index.js
1632
1632
  /**
1633
1633
  * Thrown when a repeated field has too many elements
1634
1634
  */
@@ -1642,7 +1642,7 @@ var MaxLengthError = class extends Error {
1642
1642
  name = "MaxLengthError";
1643
1643
  };
1644
1644
  //#endregion
1645
- //#region node_modules/ipfs-unixfs-importer/node_modules/ipfs-unixfs/dist/src/unixfs.js
1645
+ //#region node_modules/ipfs-unixfs/dist/src/unixfs.js
1646
1646
  var Data;
1647
1647
  (function(Data) {
1648
1648
  (function(DataType) {
@@ -1946,7 +1946,7 @@ var Metadata;
1946
1946
  Metadata.stream = stream;
1947
1947
  })(Metadata || (Metadata = {}));
1948
1948
  //#endregion
1949
- //#region node_modules/ipfs-unixfs-importer/node_modules/ipfs-unixfs/dist/src/index.js
1949
+ //#region node_modules/ipfs-unixfs/dist/src/index.js
1950
1950
  /**
1951
1951
  * @packageDocumentation
1952
1952
  *
@@ -1,4 +1,4 @@
1
- import { br as PKCError, et as pubsubTopicToDhtKeyCid } from "./schema-By3Sh-uW.js";
1
+ import { br as PKCError, et as pubsubTopicToDhtKeyCid } from "./schema-bDrzx9EZ.js";
2
2
  import { ipnsSelector } from "ipns/selector";
3
3
  import { equals } from "uint8arrays/equals";
4
4
  //#region dist/node/helia/util.js
@@ -1,2 +1,2 @@
1
- import { a as getShortCid, i as getShortAddress, n as challenges, o as nativeFunctions, r as createAnchorIpnsRecord, s as setNativeFunctions, t as PKC } from "./chunks/node-BjSTVzbx.js";
1
+ import { a as getShortCid, i as getShortAddress, n as challenges, o as nativeFunctions, r as createAnchorIpnsRecord, s as setNativeFunctions, t as PKC } from "./chunks/node-C9RpmtmM.js";
2
2
  export { challenges, createAnchorIpnsRecord, PKC as default, getShortAddress, getShortCid, nativeFunctions, setNativeFunctions };
@@ -1,7 +1,7 @@
1
- import { Ct as findStartedCommunity, L as hideClassPrivateProps, Sr as logger_default, br as PKCError, lt as replaceXWithY, rr as t, tr as n } from "../../chunks/schema-By3Sh-uW.js";
2
- import { _ as parseRpcPublishAnchorRecordParam, c as parseRpcAuthorNameParam, d as parseRpcCommentRepliesPageParam, f as parseRpcCommunityIdentifierParam, g as parseRpcExportCommunityParam, h as parseRpcExportCommunityModLogsParam, l as parseRpcCancelExportParam, m as parseRpcEditCommunityParam, p as parseRpcCommunityPageParam, t as PKC, u as parseRpcCidParam, v as parseRpcPublishChallengeAnswersParam, y as parseRpcUnsubscribeParam } from "../../chunks/node-BjSTVzbx.js";
3
- import { An as parseSetNewSettingsPKCWsServerSchemaWithPKCErrorIfItFails, Cn as parseDecryptedChallengeAnswerWithPKCErrorIfItFails, J as buildPagesRuntimeFields, ct as pRetry, dn as parseCommunityEditOptionsSchemaWithPKCErrorIfItFails, dt as require_lib, jn as parseVoteChallengeRequestToEncryptSchemaWithPKCErrorIfItFails, lt as pLimit, nn as parseCommentChallengeRequestToEncryptSchemaWithPKCErrorIfItFails, on as parseCommentModerationChallengeRequestToEncryptSchemaWithPKCErrorIfItFails, q as buildPageRuntimeFields, rn as parseCommentEditChallengeRequestToEncryptSchemaWithPKCErrorIfItFails, rr as t$1, rt as extractCrosspostRuntimeFields, un as parseCommunityEditChallengeRequestToEncryptSchemaWithPKCErrorIfItFails, ur as stringify, vn as parseCreateNewLocalCommunityUserOptionsSchemaWithPKCErrorIfItFails, yn as parseCreatePKCWsServerOptionsSchemaWithPKCErrorIfItFails } from "../../chunks/rpc-local-community-Bu6Wp-tO.js";
4
- import { t as LocalCommunity } from "../../chunks/local-community-6_MVz1cc.js";
1
+ import { Ct as findStartedCommunity, L as hideClassPrivateProps, Sr as logger_default, br as PKCError, lt as replaceXWithY, rr as t, tr as n } from "../../chunks/schema-bDrzx9EZ.js";
2
+ import { _ as parseRpcPublishAnchorRecordParam, c as parseRpcAuthorNameParam, d as parseRpcCommentRepliesPageParam, f as parseRpcCommunityIdentifierParam, g as parseRpcExportCommunityParam, h as parseRpcExportCommunityModLogsParam, l as parseRpcCancelExportParam, m as parseRpcEditCommunityParam, p as parseRpcCommunityPageParam, t as PKC, u as parseRpcCidParam, v as parseRpcPublishChallengeAnswersParam, y as parseRpcUnsubscribeParam } from "../../chunks/node-C9RpmtmM.js";
3
+ import { An as parseSetNewSettingsPKCWsServerSchemaWithPKCErrorIfItFails, Cn as parseDecryptedChallengeAnswerWithPKCErrorIfItFails, J as buildPagesRuntimeFields, ct as pRetry, dn as parseCommunityEditOptionsSchemaWithPKCErrorIfItFails, dt as require_lib, jn as parseVoteChallengeRequestToEncryptSchemaWithPKCErrorIfItFails, lt as pLimit, nn as parseCommentChallengeRequestToEncryptSchemaWithPKCErrorIfItFails, on as parseCommentModerationChallengeRequestToEncryptSchemaWithPKCErrorIfItFails, q as buildPageRuntimeFields, rn as parseCommentEditChallengeRequestToEncryptSchemaWithPKCErrorIfItFails, rr as t$1, rt as extractCrosspostRuntimeFields, un as parseCommunityEditChallengeRequestToEncryptSchemaWithPKCErrorIfItFails, ur as stringify, vn as parseCreateNewLocalCommunityUserOptionsSchemaWithPKCErrorIfItFails, yn as parseCreatePKCWsServerOptionsSchemaWithPKCErrorIfItFails } from "../../chunks/rpc-local-community-Dq00OHRf.js";
4
+ import { t as LocalCommunity } from "../../chunks/local-community-D5fzgPCr.js";
5
5
  import { fileURLToPath } from "node:url";
6
6
  import path from "path";
7
7
  import assert from "assert";
@@ -1 +1 @@
1
- export declare const version = "0.0.88";
1
+ export declare const version = "0.0.89";
@@ -1,3 +1,3 @@
1
1
  // Auto-generated by config/generate-version.js — do not edit
2
- export const version = "0.0.88";
2
+ export const version = "0.0.89";
3
3
  //# sourceMappingURL=generated-version.js.map
@@ -1,3 +1,5 @@
1
1
  import type { ParsedPKCOptions } from "../types.js";
2
2
  import { Libp2pJsClient } from "./libp2pjsClient.js";
3
+ export declare function ipnsRouterTag(router: unknown): string;
4
+ export declare function isLocalStoreIpnsRouter(router: unknown): boolean;
3
5
  export declare function createLibp2pJsClientOrUseExistingOne(pkcOptions: Required<Pick<ParsedPKCOptions, "httpRoutersOptions">> & Pick<ParsedPKCOptions, "dataPath"> & NonNullable<ParsedPKCOptions["libp2pJsClientsOptions"]>[number]): Promise<Libp2pJsClient>;
@@ -1,4 +1,6 @@
1
- import { createHelia, libp2pDefaults } from "helia";
1
+ import { createHeliaLight } from "helia";
2
+ import { libp2pDefaults, withLibp2p } from "@helia/libp2p";
3
+ import { withBitswap } from "@helia/bitswap";
2
4
  import { ipns } from "@helia/ipns";
3
5
  import { unmarshalIPNSRecord, multihashToIPNSRoutingKey } from "ipns";
4
6
  import { ipnsValidator } from "ipns/validator";
@@ -6,15 +8,15 @@ import { gossipsub } from "@libp2p/gossipsub";
6
8
  import { identify } from "@libp2p/identify";
7
9
  import extraLibp2pTransports from "../runtime/node/libp2p-extra-transports.js";
8
10
  import { CID } from "multiformats/cid";
11
+ import { sha512 } from "multiformats/hashes/sha2";
9
12
  import { peerIdFromString } from "@libp2p/peer-id";
10
- import { bitswap } from "@helia/block-brokers";
11
13
  import { createBlockstoreForLibp2pJsClient } from "../runtime/node/blockstore.js";
12
14
  import { LruBlockstore } from "./lru-blockstore.js";
13
15
  import { delegatedRoutingV1HttpApiClientContentRouting } from "@helia/delegated-routing-v1-http-api-client";
14
16
  import { NotFoundError } from "@libp2p/interface";
15
17
  import { unixfs } from "@helia/unixfs";
16
18
  import { fetch as libp2pFetch } from "@libp2p/fetch";
17
- import { pubsub as createIpnsPubusubRouter } from "@helia/ipns/routing";
19
+ import { pubSubIPNSRouting as createIpnsPubusubRouter } from "@helia/ipns";
18
20
  import Logger from "../logger.js";
19
21
  import { EventEmitter } from "events";
20
22
  import { PKCError } from "../pkc-error.js";
@@ -84,6 +86,16 @@ function getDelegatedRoutingFields(routers) {
84
86
  }
85
87
  return routersObj;
86
88
  }
89
+ // @helia/ipns routers identify themselves through a literal toString() tag ("LocalStoreRouting()",
90
+ // "HeliaRouting()", "PubSubRouting()"). Use that, never `constructor.name`: the classes are
91
+ // module-private and bundlers mangle their names, which turned the old class-name filter into a
92
+ // silent no-op in production builds.
93
+ export function ipnsRouterTag(router) {
94
+ return String(router);
95
+ }
96
+ export function isLocalStoreIpnsRouter(router) {
97
+ return ipnsRouterTag(router) === "LocalStoreRouting()";
98
+ }
87
99
  export async function createLibp2pJsClientOrUseExistingOne(pkcOptions) {
88
100
  if (!pkcOptions.httpRoutersOptions?.length)
89
101
  throw Error("You need to have pkc.httpRouterOptions to set up helia");
@@ -98,11 +110,16 @@ export async function createLibp2pJsClientOrUseExistingOne(pkcOptions) {
98
110
  client.countOfUsesOfInstance++;
99
111
  return client;
100
112
  }
113
+ // Both are set from inside the factory so that the catch handler after it can tear down
114
+ // whatever got started when the wiring throws part-way through: until the client lands in
115
+ // libp2pJsClients nothing else holds a reference to the node, and a started-but-orphaned
116
+ // libp2p keeps gossipsub heartbeats, bitswap and open sockets alive past pkc.destroy().
117
+ let startedHelia;
118
+ let closeBlockstore = async () => { };
101
119
  creatingLibp2pJsClients[pkcOptions.key] = (async () => {
102
120
  // A caller supplying their own heliaOptions.blockstore owns block storage entirely, so skip
103
121
  // ours rather than opening a directory or IndexedDB database nothing will read.
104
122
  let lruBlockstore;
105
- let closeBlockstore = async () => { };
106
123
  if (!pkcOptions.heliaOptions?.blockstore) {
107
124
  const { blockstore: childBlockstore, persistent, defaultMaxBytes, close } = await createBlockstoreForLibp2pJsClient({
108
125
  dataPath: pkcOptions.dataPath,
@@ -165,17 +182,45 @@ export async function createLibp2pJsClientOrUseExistingOne(pkcOptions) {
165
182
  // instance ever removes one, so an unbounded store grows for the life of the process.
166
183
  // See pkc-js#240 for the measurement behind the cap.
167
184
  blockstore: lruBlockstore,
168
- blockBrokers: [bitswap()],
169
- start: false,
170
185
  ...pkcOptions.heliaOptions
171
186
  };
172
- const helia = await createHelia(mergedHeliaInit);
173
- // Helia's default content routers are [Libp2pRouter, HTTPGatewayRouter]. We use our own
174
- // gateway-fan-out logic in base-client-manager, so the HTTP gateway router here is
175
- // redundant and adds latency. Filter by class name (not index) so a future helia release
176
- // that re-orders the array can't silently leave the gateway in or drop libp2p routing.
177
- //@ts-expect-error helia.routing.routers is internal
178
- helia.routing.routers = helia.routing.routers.filter((r) => r.constructor.name !== "HTTPGatewayRouter");
187
+ // helia 7's createHelia() composes withHTTP() in: a public trustless-gateway block broker
188
+ // plus public delegated HTTP routers and recursive gateways. We use our own gateway
189
+ // fan-out logic in base-client-manager and only the routers the user configured, so
190
+ // compose the node without any HTTP components (heliaOptions.http is excluded from the
191
+ // schema for that reason): bitswap is the only block broker and libp2p the only content
192
+ // router. withLibp2p applies libp2pDefaults with shallow per-key replace semantics (same
193
+ // as helia 6's createHelia): every key we set above wins outright (in particular our
194
+ // narrow `services` map fully replaces the default one), while keys we leave out
195
+ // (connectionEncrypters, streamMuxers, ...) come from helia's defaults.
196
+ //
197
+ // A caller-supplied heliaOptions.blockBrokers REPLACES bitswap, as it did under helia 6
198
+ // (`init.blockBrokers ?? [bitswap()]`): withBitswap() is additive, so applying it on top of
199
+ // the caller's list would hand a gateway-only/opt-out config bitswap sessions and dials anyway.
200
+ //
201
+ // createHeliaLight registers only sha2-256/identity and dag-pb/raw, where createHelia adds
202
+ // sha2-512 and the dag-cbor/dag-json/json codecs. pkc records are unixfs (dag-pb/raw) so
203
+ // the codecs are not needed, but nothing stops a community from publishing a sha2-512 CID
204
+ // (e.g. `ipfs add --hash sha2-512`) and helia looks the hasher up before the first block
205
+ // fetch, so keep sha2-512 to match what fetched under helia 6.
206
+ const { libp2p: mergedLibp2pInit, bitswap: bitswapInit, blockBrokers: callerBlockBrokers, ...heliaLightInit } = mergedHeliaInit;
207
+ const heliaWithLibp2p = withLibp2p(createHeliaLight({
208
+ ...heliaLightInit,
209
+ blockBrokers: callerBlockBrokers,
210
+ hashers: [sha512, ...(heliaLightInit.hashers ?? [])]
211
+ }), mergedLibp2pInit ?? {});
212
+ // The composition is typed as helia's default service map (dht, relay, upnp, ...) while our
213
+ // `services` above replaces it; HeliaWithLibp2pPubsub is the narrow map we actually run.
214
+ const helia = (callerBlockBrokers === undefined ? withBitswap(heliaWithLibp2p, bitswapInit) : heliaWithLibp2p);
215
+ // helia 7 creates libp2p lazily inside start() — the `helia.libp2p` getter throws
216
+ // NotStartedError until then — so start the node before wiring anything that reads it.
217
+ // Nothing can race the wiring below: no topic is subscribed and no dial is made until
218
+ // this factory returns the client.
219
+ // Assigned before start(): helia 7's start() has no rollback, so if a later mixin throws
220
+ // (e.g. bitswap's network.start() after withLibp2p already started libp2p) the node is
221
+ // half-started and must still be stopped by the catch below.
222
+ startedHelia = helia;
223
+ await helia.start();
179
224
  log("Initialized libp2pjs helia with key", pkcOptions.key, "peer id", helia.libp2p.peerId.toString());
180
225
  const pubsubEventHandler = new EventEmitter();
181
226
  helia.libp2p.services.pubsub.addEventListener("message", (evt) => {
@@ -185,10 +230,6 @@ export async function createLibp2pJsClientOrUseExistingOne(pkcOptions) {
185
230
  pubsubEventHandler.emit(evt.detail.topic, msgFormatted);
186
231
  });
187
232
  const heliaFs = unixfs(helia);
188
- const asHeliaCatCid = (ipfsPathOrCid) => {
189
- CID.parse(ipfsPathOrCid.split("/")[0]); // validate the root CID; throws on malformed input
190
- return ipfsPathOrCid;
191
- };
192
233
  // Issue #189: without a session, every block fetched over bitswap fires its own
193
234
  // network.findAndConnect — a findProviders query against ALL configured HTTP routers per
194
235
  // block, plus stray dials to whatever providers it discovers mid-fetch. cat() therefore
@@ -272,20 +313,51 @@ export async function createLibp2pJsClientOrUseExistingOne(pkcOptions) {
272
313
  signal.addEventListener("abort", onAbort, { once: true });
273
314
  });
274
315
  const ipnsPubsubRouter = createIpnsPubusubRouter(helia);
316
+ const ipnsNameResolver = ipns(helia, {
317
+ routers: [ipnsPubsubRouter]
318
+ });
319
+ // @helia/ipns constructs routers as [LocalStoreIPNSRouting, HeliaIPNSRouting, ...userRouters]
320
+ // and hands that SAME array to its resolver/publisher/republisher, so it must be mutated in
321
+ // place: reassigning `.routers` only changes what pkc's own loops below iterate, while
322
+ // ipnsNameResolver.resolve() keeps querying (and writing the local cache through) the
323
+ // original list. We drop LocalStoreIPNSRouting because pkc-js never publishes IPNS via
324
+ // @helia/ipns (kubo does that), so the local cache is always empty and just adds a wasted
325
+ // lookup. Keep HeliaIPNSRouting (HTTP delegated routing via helia.routing) and our pubsub
326
+ // router. Match by the router's toString() tag ("LocalStoreRouting()") rather than by
327
+ // `constructor.name`: the class is module-private and bundlers mangle its name (in 5chan's
328
+ // production build it becomes `dMe`), so a class-name match is a silent no-op there. The
329
+ // tag is a string literal and survives minification. Matching by identity (rather than
330
+ // by index) also means a future @helia/ipns release that re-orders the array can't make
331
+ // us drop the wrong router.
332
+ for (let i = ipnsNameResolver.routers.length - 1; i >= 0; i--) {
333
+ if (isLocalStoreIpnsRouter(ipnsNameResolver.routers[i]))
334
+ ipnsNameResolver.routers.splice(i, 1);
335
+ }
336
+ // The IPNS facade marks itself started (and runs its hourly republisher) from helia's
337
+ // 'start' event, which helia 7 dispatches exactly once at the end of start(), i.e. before the
338
+ // facade existed (its constructor walks helia's components, whose `libp2p` getter throws
339
+ // until start(), so it cannot be built earlier). helia 6 fell back to
340
+ // `libp2p.status === 'started'` and so ran the republisher; that fallback is gone in 7.
341
+ // Nothing in pkc depends on the facade being "started" (resolve() has no started check and
342
+ // pkc walks `routers` directly), so this only keeps helia-6 lifecycle parity for the
343
+ // republisher. start() is on the class but not on the exported IPNS interface.
344
+ ipnsNameResolver.start?.();
345
+ // The router is Startable but neither helia.start() (blockstore/datastore/routing/brokers
346
+ // only) nor the IPNS facade (republisher only) starts user-supplied routers. Its start()
347
+ // registers the libp2p/fetch topology that fills `fetchPeers`, which gates the
348
+ // subscription-change fast path: fetch the record over /libp2p/fetch from a server that
349
+ // joins the topic after our get() already ran (ipfs/helia#906) and republish it to the
350
+ // topic. NOTE: this path is newly enabled here, not restored. Under helia 6 nothing ever
351
+ // called start() on this router either, so `fetchPeers` stayed empty and the fast path
352
+ // never fired. It is largely redundant with the direct fetch below (issue #210) but bounded
353
+ // (one fetch per subscription-change event, into the router's own queue); its republish
354
+ // goes through raw libp2p pubsub, not pkc's mesh-gated wrapper, so a `could not publish
355
+ // record` error line is possible when no mesh peer exists. stop() is mirrored in stop() below.
356
+ await ipnsPubsubRouter.start?.();
275
357
  // The router's localStore is where gossipsub-delivered records get cached (handleRecord).
276
358
  // It's declared private but is a plain class field at runtime; the direct-fetch path below
277
359
  // writes to it to keep the cached-record invariant (issue #210).
278
360
  const ipnsPubsubLocalStore = ipnsPubsubRouter.localStore;
279
- const ipnsNameResolver = ipns(helia, {
280
- routers: [ipnsPubsubRouter]
281
- });
282
- // @helia/ipns constructs routers as [LocalStoreRouting, HeliaRouting, ...userRouters].
283
- // We drop LocalStoreRouting because pkc-js never publishes IPNS via @helia/ipns (kubo
284
- // does that), so the local cache is always empty and just adds a wasted lookup. Keep
285
- // HeliaRouting (HTTP delegated routing via helia.routing.routers) and our PubSubRouting.
286
- // Filter by class name so a future @helia/ipns release that re-orders the array can't
287
- // silently drop our pubsub router.
288
- ipnsNameResolver.routers = ipnsNameResolver.routers.filter((r) => r?.constructor?.name !== "LocalStoreRouting");
289
361
  // Side-channel awaitable warmup: gossipsub's pubsub.subscribe(topic) is sync and returns
290
362
  // void, so we can't make it awaitable without breaking @helia/ipns and other internal
291
363
  // callers that expect the sync contract. Instead, we expose `warmupForTopic(topic)` and
@@ -427,22 +499,17 @@ export async function createLibp2pJsClientOrUseExistingOne(pkcOptions) {
427
499
  // See docs/protocol/delegated-ipns.md.
428
500
  //
429
501
  // Why call routers directly instead of ipnsNameResolver.resolve():
430
- // - @helia/ipns 9.2.x has no public single-hop / non-recursive resolve API.
431
- // resolve() always recurses until it reaches an /ipfs/ value, and its single-hop
432
- // primitive (#findIpnsRecord) is private so the router layer is the only public
433
- // way to fetch exactly one record.
434
- // - resolve()'s ResolveProgressEvents type declares ipns:resolve:success (carrying
435
- // the per-hop IPNSRecord), but those events are never emitted in 9.2.x — only
436
- // routing-level events fire, none carrying a record value or next-hop name. So an
437
- // onProgress listener cannot reconstruct the hop chain either. Empirically pinned
438
- // in test/node/community/helia-ipns-resolve-equivalence.unit.test.ts.
439
- // Upstream main has since reworked resolve() into an async generator that yields each
440
- // hop's IPNSRecord (ipfs/helia#1041) — that would replace this manual walk and even let
441
- // us warm each pubsub topic between yields — but it's unreleased; npm latest 9.2.1 still
442
- // collapses the chain to the terminal CID. So the per-hop walk stays for now.
443
- // TODO: after the @helia/ipns upgrade, re-check this — once the generator resolve() is
444
- // released, replace this router.get + ipnsValidator loop with
445
- // `for await (const { record } of ipnsNameResolver.resolve(...))`.
502
+ // @helia/ipns 10 reworked resolve() into an async generator that yields one
503
+ // IPNSResolveResult per hop (ipfs/helia#1041), so a single-hop read is now
504
+ // technically reachable by taking the first yield. The per-hop walk stays anyway:
505
+ // - the direct-fetch fast path above (libp2p/fetch from subscribers + freshly
506
+ // discovered providers, with the issue #210 cache write) has no equivalent
507
+ // inside resolve();
508
+ // - per-hop pubsub warmup has to happen BEFORE that hop's record is requested, and
509
+ // resolve() requests the next hop's record internally as soon as it yields;
510
+ // - per-router error aggregation below is what the tests and callers report on.
511
+ // Equivalence of the resolved value with resolve() is pinned in
512
+ // test/node/community/helia-ipns-resolve-equivalence.unit.test.ts.
446
513
  // This does NOT bypass an active cache/TTL: all cache-read + TTL logic lives in the
447
514
  // resolver's #findIpnsRecord and is gated on `nocache !== true`, but pkc always
448
515
  // resolves IPNS with nocache:true (see resolveIpnsToCidP2P in base-client-manager),
@@ -497,7 +564,13 @@ export async function createLibp2pJsClientOrUseExistingOne(pkcOptions) {
497
564
  // see MAX_BITSWAP_SESSION_SEED_PEERS above for the why. UnixFSComponents only
498
565
  // needs a blockstore, and blocks fetched through the session land in the same
499
566
  // underlying blockstore helia uses, so nothing else changes.
500
- const rootCid = CID.parse(ipfsPath.split("/")[0]);
567
+ // ipfsPath is either a bare cid or a `<root-cid>/sub/path`; derive both parts here, once,
568
+ // so root and sub-path can never disagree. unixfs cat takes the root CID plus the sub-path
569
+ // via the `path` option. (The multiformats 13/14 split that used to force string-only cat
570
+ // input is gone: helia 7 and our tree share a single multiformats copy.)
571
+ const [rootCidString, ...ipfsSubPathSegments] = ipfsPath.split("/");
572
+ const rootCid = CID.parse(rootCidString);
573
+ const ipfsSubPath = ipfsSubPathSegments.length > 0 ? ipfsSubPathSegments.join("/") : undefined;
501
574
  const timeoutMs = parseKuboStyleTimeoutMs(options?.timeout); // throws on unparseable timeout at call time
502
575
  // Our own option, not kubo-rpc-client's — strip it so it never reaches unixfs cat.
503
576
  const { bitswapSessionSeedScopeIpnsPubsubTopic, ...unixfsCatOptions } = options ?? {};
@@ -520,7 +593,8 @@ export async function createLibp2pJsClientOrUseExistingOne(pkcOptions) {
520
593
  try {
521
594
  for (let attempt = 0;; attempt++) {
522
595
  const session = helia.blockstore.createSession(rootCid, {
523
- providers: getBitswapSessionSeedPeers(bitswapSessionSeedScopeIpnsPubsubTopic)
596
+ // helia 7 sessions take providers as libp2p-key CIDs (or multiaddrs), not PeerIds
597
+ providers: getBitswapSessionSeedPeers(bitswapSessionSeedScopeIpnsPubsubTopic).map((peerId) => peerId.toCID())
524
598
  });
525
599
  // Issue #218: the session broker waits on a single elected HAVE peer per
526
600
  // block with no stall timeout, so one slow seeder holding the only HAVE
@@ -542,12 +616,9 @@ export async function createLibp2pJsClientOrUseExistingOne(pkcOptions) {
542
616
  });
543
617
  let yieldedAnyBytes = false;
544
618
  try {
545
- // ipfsPath is either a bare cid or a `<root-cid>/sub/path`. Hand the whole
546
- // thing to cat as one string and never split out a `path` option — see
547
- // asHeliaCatCid above for why the `path` option would re-trip the
548
- // exporter's CID identity check.
549
- const catIterable = unixfs({ blockstore: session }).cat(asHeliaCatCid(ipfsPath), {
619
+ const catIterable = unixfs({ blockstore: session }).cat(rootCid, {
550
620
  ...unixfsCatOptions,
621
+ path: ipfsSubPath,
551
622
  signal: controller.signal
552
623
  });
553
624
  for await (const chunk of catIterable) {
@@ -664,8 +735,8 @@ export async function createLibp2pJsClientOrUseExistingOne(pkcOptions) {
664
735
  // createLibp2pJsClientOrUseExistingOne() can't grab a mid-stopping client.
665
736
  delete libp2pJsClients[pkcOptions.key];
666
737
  // Tear down the IPNS pubsub router's internal subscription state.
667
- // PubSubRouting (from @helia/ipns/routing) implements Startable and tracks its
668
- // own subscriptions list — without stop() those subscriptions leak past helia.
738
+ // PubSubIPNSRouting (@helia/ipns) implements Startable and tracks its own
739
+ // subscriptions list and fetch topology — without stop() those leak past helia.
669
740
  for (const router of ipnsNameResolver.routers) {
670
741
  const lifecycle = router;
671
742
  if (typeof lifecycle.stop === "function") {
@@ -673,7 +744,7 @@ export async function createLibp2pJsClientOrUseExistingOne(pkcOptions) {
673
744
  await lifecycle.stop();
674
745
  }
675
746
  catch (e) {
676
- log.error("Error stopping IPNS router", router?.constructor?.name, e);
747
+ log.error("Error stopping IPNS router", ipnsRouterTag(router), e);
677
748
  }
678
749
  }
679
750
  }
@@ -735,11 +806,43 @@ export async function createLibp2pJsClientOrUseExistingOne(pkcOptions) {
735
806
  key: pkcOptions.key
736
807
  };
737
808
  const client = new Libp2pJsClient(fullInstanceWithOptions);
738
- await helia.start();
739
809
  log("Helia/libp2p-js started with key", pkcOptions.key, "and peer id", helia.libp2p.peerId.toString());
740
810
  libp2pJsClients[pkcOptions.key] = client;
741
811
  return client;
742
- })();
812
+ })().catch(async (creationError) => {
813
+ // The client never reached libp2pJsClients, so stop() can't be called on it: release
814
+ // whatever this factory started before rethrowing (mirrors the final-release path in stop()).
815
+ if (startedHelia) {
816
+ // The `libp2p` getter throws NotStartedError if start() failed before the libp2p mixin
817
+ // ran, in which case there is nothing to abort.
818
+ let connections = [];
819
+ try {
820
+ connections = startedHelia.libp2p.getConnections();
821
+ }
822
+ catch { }
823
+ for (const connection of connections) {
824
+ try {
825
+ connection.abort(new Error("pkc-js libp2p instance failed to initialize"));
826
+ }
827
+ catch (e) {
828
+ log.error("Error aborting libp2p connection during failed initialization", e);
829
+ }
830
+ }
831
+ try {
832
+ await startedHelia.stop();
833
+ }
834
+ catch (e) {
835
+ log.error("Error stopping helia after failed initialization", e);
836
+ }
837
+ }
838
+ try {
839
+ await closeBlockstore();
840
+ }
841
+ catch (e) {
842
+ log.error("Error closing blockstore after failed initialization", e);
843
+ }
844
+ throw creationError;
845
+ });
743
846
  const createdClientPromise = creatingLibp2pJsClients[pkcOptions.key];
744
847
  if (!createdClientPromise)
745
848
  throw new Error("Missing creation promise after initialization");