@camstack/system 1.2.289 → 1.2.291

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 (62) hide show
  1. package/dist/addon-runner.js +1 -1
  2. package/dist/addon-runner.mjs +1 -1
  3. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
  4. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +1 -1
  5. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
  6. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +1 -1
  7. package/dist/builtins/alerts/alerts.addon.js +1 -1
  8. package/dist/builtins/alerts/alerts.addon.mjs +1 -1
  9. package/dist/builtins/autotrack/index.js +1 -1
  10. package/dist/builtins/autotrack/index.mjs +1 -1
  11. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +1 -1
  12. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +1 -1
  13. package/dist/builtins/camera-grid/index.js +1 -1
  14. package/dist/builtins/camera-grid/index.mjs +1 -1
  15. package/dist/builtins/console-logging/index.js +1 -1
  16. package/dist/builtins/console-logging/index.mjs +1 -1
  17. package/dist/builtins/core-blocks/core-blocks.addon.js +1 -1
  18. package/dist/builtins/core-blocks/core-blocks.addon.mjs +1 -1
  19. package/dist/builtins/device-manager/device-manager.addon.js +2 -2
  20. package/dist/builtins/device-manager/device-manager.addon.mjs +2 -2
  21. package/dist/builtins/doorbell/virtual-doorbell.addon.js +1 -1
  22. package/dist/builtins/doorbell/virtual-doorbell.addon.mjs +1 -1
  23. package/dist/builtins/hub-forwarder/index.js +1 -1
  24. package/dist/builtins/hub-forwarder/index.mjs +1 -1
  25. package/dist/builtins/liveness-monitor/liveness-monitor.addon.d.ts +9 -0
  26. package/dist/builtins/liveness-monitor/liveness-monitor.addon.js +18 -2
  27. package/dist/builtins/liveness-monitor/liveness-monitor.addon.mjs +18 -2
  28. package/dist/builtins/local-auth/local-auth.addon.js +1 -1
  29. package/dist/builtins/local-auth/local-auth.addon.mjs +1 -1
  30. package/dist/builtins/local-network/local-network.addon.js +1 -1
  31. package/dist/builtins/local-network/local-network.addon.mjs +1 -1
  32. package/dist/builtins/loki-logging/index.js +1 -1
  33. package/dist/builtins/loki-logging/index.mjs +1 -1
  34. package/dist/builtins/native-metrics/native-metrics.addon.js +1 -1
  35. package/dist/builtins/native-metrics/native-metrics.addon.mjs +1 -1
  36. package/dist/builtins/platform-probe/index.js +1 -1
  37. package/dist/builtins/platform-probe/index.mjs +1 -1
  38. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
  39. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
  40. package/dist/builtins/snapshot/index.js +1 -1
  41. package/dist/builtins/snapshot/index.mjs +1 -1
  42. package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
  43. package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
  44. package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +0 -0
  45. package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +0 -0
  46. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +11 -11
  47. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +11 -11
  48. package/dist/builtins/system-config/system-config.addon.js +1 -1
  49. package/dist/builtins/system-config/system-config.addon.mjs +1 -1
  50. package/dist/builtins/winston-logging/index.js +1 -1
  51. package/dist/builtins/winston-logging/index.mjs +1 -1
  52. package/dist/{dist-DKFb2936.mjs → dist-DiMNxNDh.mjs} +1474 -1017
  53. package/dist/{dist-BYnHJU96.js → dist-XnunBp0n.js} +1479 -1016
  54. package/dist/index.d.ts +1 -0
  55. package/dist/index.js +5 -3
  56. package/dist/index.mjs +4 -4
  57. package/dist/kernel/moleculer/addon-peer-bytes-facility.d.ts +25 -0
  58. package/dist/{manifest-system-deps-B8ZUclN4.js → manifest-system-deps-DTgkfBJy.js} +591 -0
  59. package/dist/{manifest-system-deps-C9T7784z.mjs → manifest-system-deps-ghRqcy_-.mjs} +585 -6
  60. package/dist/{retired-settings-keys-C6rFqHR7.js → retired-settings-keys-DQn9s_AI.js} +1 -1
  61. package/dist/{retired-settings-keys-tuNxNNpR.mjs → retired-settings-keys-DnCcgrCG.mjs} +1 -1
  62. package/package.json +1 -1
@@ -20,6 +20,7 @@ let node_perf_hooks = require("node:perf_hooks");
20
20
  let node_vm = require("node:vm");
21
21
  node_vm = require_chunk.__toESM(node_vm);
22
22
  let _camstack_types_addon = require("@camstack/types/addon");
23
+ let node_events = require("node:events");
23
24
  let node_net = require("node:net");
24
25
  let _trpc_client = require("@trpc/client");
25
26
  //#region src/kernel/event-plane-report.ts
@@ -2613,6 +2614,577 @@ function createAddonDataPlaneFacility(args) {
2613
2614
  };
2614
2615
  }
2615
2616
  //#endregion
2617
+ //#region src/kernel/moleculer/addon-peer-bytes-facility.ts
2618
+ /**
2619
+ * Kernel side of the addon→addon **byte transport** (`ctx.peerBytes`, D613).
2620
+ *
2621
+ * See {@link AddonPeerBytes} for what this exists for and what it deliberately
2622
+ * does not do. This file is the mechanism:
2623
+ *
2624
+ * - **One lazy loopback listener per addon process.** Bound on the first
2625
+ * `offer()`, never before, so an addon that never offers pays no port.
2626
+ * - **One token per offer.** 24 random bytes, base64url, ONE `GET`. A replay
2627
+ * is `offer-used` (410) for as long as the original ticket would have lived,
2628
+ * so a second take is told what happened rather than "no such thing".
2629
+ * `HEAD` answers the headers and consumes nothing.
2630
+ * - **Outstanding offers are bounded and swept on every mint**, which is also
2631
+ * what makes a per-frame misuse of this facility refuse by name instead of
2632
+ * working slowly.
2633
+ * - **Every write honours `write()`'s return and awaits `drain`** (D447), and
2634
+ * a peer that has taken nothing for {@link PEER_BYTES_STALL_MS} is cut. Every
2635
+ * exit — offered, taken, sent, cut, stalled, refused, disposed — is one log
2636
+ * line, carrying `tags: { deviceId }` when the offer named a device.
2637
+ * - **The consumer's response is handed over PAUSED**, which is the whole
2638
+ * reason `open()` lives in the framework rather than in each consumer: see
2639
+ * {@link PeerBytesBody}.
2640
+ */
2641
+ /** The HOST a node id belongs to. `hub` → `hub`; `hub/runner-3` → `hub`. */
2642
+ function hostNodeIdOf(nodeId) {
2643
+ const slash = nodeId.indexOf("/");
2644
+ return slash === -1 ? nodeId : nodeId.slice(0, slash);
2645
+ }
2646
+ /** A body chunk, without a cast. A readable in binary mode yields Buffers; in
2647
+ * string mode, strings. Anything else is a broken stream and says so. */
2648
+ function asBuffer(chunk) {
2649
+ if (Buffer.isBuffer(chunk)) return chunk;
2650
+ if (typeof chunk === "string") return Buffer.from(chunk);
2651
+ throw new Error("peer-bytes: a body chunk was neither a Buffer nor a string");
2652
+ }
2653
+ function tokenOf(req) {
2654
+ const raw = req.url ?? "/";
2655
+ const query = raw.indexOf("?");
2656
+ return (query === -1 ? raw : raw.slice(0, query)).replace(/^\/+/, "");
2657
+ }
2658
+ /**
2659
+ * Write `bytes` to `res` in bounded chunks, honouring backpressure.
2660
+ *
2661
+ * D447, the consuming half stated from the producing side: a raw HTTP stream
2662
+ * writes no chunk the socket has not taken. `write()` returning false means the
2663
+ * kernel buffer is full and everything further is heap in THIS process — so we
2664
+ * wait for `drain`, and a peer that never drains within `stallMs` is cut rather
2665
+ * than held. Returns how many bytes reached the socket and why it stopped.
2666
+ */
2667
+ async function writeBufferWithBackpressure(res, bytes, stallMs) {
2668
+ const waitForDrain = () => new Promise((resolve) => {
2669
+ const settle = (outcome) => {
2670
+ clearTimeout(timer);
2671
+ res.off("drain", onDrain);
2672
+ res.off("close", onClose);
2673
+ resolve(outcome);
2674
+ };
2675
+ const onDrain = () => {
2676
+ settle("drained");
2677
+ };
2678
+ const onClose = () => {
2679
+ settle("closed");
2680
+ };
2681
+ const timer = setTimeout(() => {
2682
+ settle("stalled");
2683
+ }, stallMs);
2684
+ timer.unref();
2685
+ res.on("drain", onDrain);
2686
+ res.on("close", onClose);
2687
+ });
2688
+ let sent = 0;
2689
+ while (sent < bytes.byteLength) {
2690
+ if (res.destroyed || res.writableEnded) return {
2691
+ sent,
2692
+ outcome: "peer-gone"
2693
+ };
2694
+ const end = Math.min(sent + _camstack_types_addon.PEER_BYTES_CHUNK_BYTES, bytes.byteLength);
2695
+ const took = res.write(bytes.subarray(sent, end));
2696
+ sent = end;
2697
+ if (took) continue;
2698
+ const waited = await waitForDrain();
2699
+ if (waited === "drained") continue;
2700
+ return {
2701
+ sent,
2702
+ outcome: waited === "closed" ? "peer-gone" : "stalled"
2703
+ };
2704
+ }
2705
+ return {
2706
+ sent,
2707
+ outcome: "sent"
2708
+ };
2709
+ }
2710
+ function createAddonPeerBytesFacility(args) {
2711
+ const now = args.now ?? (() => Date.now());
2712
+ const ttlMs = args.ttlMs ?? _camstack_types_addon.PEER_BYTES_TICKET_TTL_MS;
2713
+ const maxOutstanding = args.maxOutstanding ?? _camstack_types_addon.PEER_BYTES_MAX_OUTSTANDING;
2714
+ const stallMs = args.stallMs ?? _camstack_types_addon.PEER_BYTES_STALL_MS;
2715
+ const host = args.host ?? "127.0.0.1";
2716
+ const hostNodeId = hostNodeIdOf(args.nodeId);
2717
+ const offers = /* @__PURE__ */ new Map();
2718
+ /** Tokens already taken, kept for their TTL so a replay is `offer-used`. */
2719
+ const taken = /* @__PURE__ */ new Map();
2720
+ let server = null;
2721
+ let baseUrl = null;
2722
+ let listening = null;
2723
+ const sweep = () => {
2724
+ const at = now();
2725
+ for (const [token, offer] of offers) if (offer.expiresAtMs <= at) {
2726
+ offers.delete(token);
2727
+ args.logger.info("peer-bytes: an offer expired untaken", {
2728
+ ...offer.deviceId !== void 0 ? { tags: { deviceId: offer.deviceId } } : {},
2729
+ meta: {
2730
+ label: offer.label,
2731
+ outstanding: offers.size
2732
+ }
2733
+ });
2734
+ }
2735
+ for (const [token, expiresAtMs] of taken) if (expiresAtMs <= at) taken.delete(token);
2736
+ };
2737
+ const route = (req, res) => {
2738
+ const token = tokenOf(req);
2739
+ const offer = offers.get(token);
2740
+ if (offer === void 0) {
2741
+ const status = taken.has(token) ? 410 : 404;
2742
+ res.writeHead(status, { "x-camstack-peer-bytes": status === 410 ? "offer-used" : "no-offer" });
2743
+ res.end();
2744
+ return;
2745
+ }
2746
+ if (offer.expiresAtMs <= now()) {
2747
+ offers.delete(token);
2748
+ res.writeHead(410, { "x-camstack-peer-bytes": "offer-expired" }).end();
2749
+ return;
2750
+ }
2751
+ if (req.method === "GET") {
2752
+ offers.delete(token);
2753
+ taken.set(token, offer.expiresAtMs);
2754
+ args.logger.info("peer-bytes: an offer was taken", {
2755
+ ...offer.deviceId !== void 0 ? { tags: { deviceId: offer.deviceId } } : {},
2756
+ meta: {
2757
+ label: offer.label,
2758
+ outstanding: offers.size
2759
+ }
2760
+ });
2761
+ }
2762
+ Promise.resolve(offer.handler(req, res)).catch((err) => {
2763
+ args.logger.warn("peer-bytes: an offer handler threw", {
2764
+ ...offer.deviceId !== void 0 ? { tags: { deviceId: offer.deviceId } } : {},
2765
+ meta: {
2766
+ label: offer.label,
2767
+ error: err instanceof Error ? err.message : String(err)
2768
+ }
2769
+ });
2770
+ if (!res.headersSent) res.writeHead(500);
2771
+ res.end();
2772
+ });
2773
+ };
2774
+ const listen = () => {
2775
+ if (listening !== null) return listening;
2776
+ listening = new Promise((resolve, reject) => {
2777
+ const created = (0, node_http.createServer)((req, res) => {
2778
+ route(req, res);
2779
+ });
2780
+ created.on("error", (err) => {
2781
+ listening = null;
2782
+ reject(err);
2783
+ });
2784
+ created.listen(0, host, () => {
2785
+ const address = created.address();
2786
+ if (address === null || typeof address === "string") {
2787
+ created.close();
2788
+ listening = null;
2789
+ reject(/* @__PURE__ */ new Error("the peer-bytes listener bound no TCP port"));
2790
+ return;
2791
+ }
2792
+ server = created;
2793
+ baseUrl = `http://${host}:${address.port}`;
2794
+ args.logger.info("peer-bytes: listener bound", { meta: {
2795
+ addonId: args.addonId,
2796
+ host,
2797
+ port: address.port,
2798
+ hostNodeId,
2799
+ ttlMs
2800
+ } });
2801
+ resolve(baseUrl);
2802
+ });
2803
+ });
2804
+ return listening;
2805
+ };
2806
+ const mint = async (options) => {
2807
+ sweep();
2808
+ const deviceTags = options.deviceId !== void 0 ? { tags: { deviceId: options.deviceId } } : {};
2809
+ if (offers.size >= maxOutstanding) {
2810
+ args.logger.warn("peer-bytes: offer refused — too many outstanding", {
2811
+ ...deviceTags,
2812
+ meta: {
2813
+ label: options.label,
2814
+ outstanding: offers.size,
2815
+ maxOutstanding
2816
+ }
2817
+ });
2818
+ return {
2819
+ kind: "refused",
2820
+ code: "offer-exhausted",
2821
+ detail: `${String(offers.size)} offers outstanding against a bound of ${String(maxOutstanding)}`
2822
+ };
2823
+ }
2824
+ let base;
2825
+ try {
2826
+ base = baseUrl ?? await listen();
2827
+ } catch (err) {
2828
+ const detail = err instanceof Error ? err.message : String(err);
2829
+ args.logger.warn("peer-bytes: offer refused — no listener", {
2830
+ ...deviceTags,
2831
+ meta: {
2832
+ label: options.label,
2833
+ error: detail
2834
+ }
2835
+ });
2836
+ return {
2837
+ kind: "refused",
2838
+ code: "listener-failed",
2839
+ detail
2840
+ };
2841
+ }
2842
+ const token = (0, node_crypto.randomBytes)(24).toString("base64url");
2843
+ const expiresAtMs = now() + (options.ttlMs ?? ttlMs);
2844
+ offers.set(token, {
2845
+ handler: options.handler,
2846
+ label: options.label,
2847
+ deviceId: options.deviceId,
2848
+ expiresAtMs
2849
+ });
2850
+ args.logger.info("peer-bytes: offer minted", {
2851
+ ...deviceTags,
2852
+ meta: {
2853
+ label: options.label,
2854
+ declaredBytes: options.declaredBytes ?? null,
2855
+ expiresInMs: expiresAtMs - now(),
2856
+ outstanding: offers.size
2857
+ }
2858
+ });
2859
+ return {
2860
+ kind: "ticket",
2861
+ ticket: {
2862
+ url: `${base}/${token}`,
2863
+ hostNodeId,
2864
+ expiresAtMs,
2865
+ declaredBytes: options.declaredBytes ?? null
2866
+ }
2867
+ };
2868
+ };
2869
+ const peerBytes = {
2870
+ hostNodeId,
2871
+ get outstanding() {
2872
+ sweep();
2873
+ return offers.size;
2874
+ },
2875
+ offer: mint,
2876
+ offerBuffer: async (options) => {
2877
+ const deviceTags = options.deviceId !== void 0 ? { tags: { deviceId: options.deviceId } } : {};
2878
+ const handler = async (req, res) => {
2879
+ res.writeHead(200, {
2880
+ "content-type": options.contentType,
2881
+ "content-length": String(options.bytes.byteLength),
2882
+ "accept-ranges": "none"
2883
+ });
2884
+ if (req.method === "HEAD") {
2885
+ res.end();
2886
+ return;
2887
+ }
2888
+ const startedAt = now();
2889
+ const result = await writeBufferWithBackpressure(res, options.bytes, stallMs);
2890
+ if (result.outcome === "sent") {
2891
+ res.end();
2892
+ args.logger.info("peer-bytes: body sent", {
2893
+ ...deviceTags,
2894
+ meta: {
2895
+ label: options.label,
2896
+ bytes: result.sent,
2897
+ tookMs: now() - startedAt,
2898
+ source: "buffer"
2899
+ }
2900
+ });
2901
+ return;
2902
+ }
2903
+ args.logger.warn("peer-bytes: body cut before it was complete", {
2904
+ ...deviceTags,
2905
+ meta: {
2906
+ label: options.label,
2907
+ branch: result.outcome,
2908
+ sent: result.sent,
2909
+ of: options.bytes.byteLength,
2910
+ stallMs,
2911
+ source: "buffer"
2912
+ }
2913
+ });
2914
+ res.destroy();
2915
+ };
2916
+ return await mint({
2917
+ handler,
2918
+ label: options.label,
2919
+ declaredBytes: options.bytes.byteLength,
2920
+ ...options.deviceId !== void 0 ? { deviceId: options.deviceId } : {},
2921
+ ...options.ttlMs !== void 0 ? { ttlMs: options.ttlMs } : {}
2922
+ });
2923
+ },
2924
+ offerFile: async (options) => {
2925
+ const deviceTags = options.deviceId !== void 0 ? { tags: { deviceId: options.deviceId } } : {};
2926
+ let declaredBytes = null;
2927
+ try {
2928
+ declaredBytes = (await (0, node_fs_promises.stat)(options.path)).size;
2929
+ } catch {
2930
+ declaredBytes = null;
2931
+ }
2932
+ const handler = async (req, res) => {
2933
+ const headers = {
2934
+ "content-type": options.contentType,
2935
+ "accept-ranges": "none"
2936
+ };
2937
+ if (declaredBytes !== null) headers["content-length"] = String(declaredBytes);
2938
+ if (req.method === "HEAD") {
2939
+ res.writeHead(200, headers).end();
2940
+ return;
2941
+ }
2942
+ const startedAt = now();
2943
+ let stream;
2944
+ try {
2945
+ stream = (0, node_fs.createReadStream)(options.path);
2946
+ await (0, node_events.once)(stream, "open");
2947
+ } catch (err) {
2948
+ const detail = err instanceof Error ? err.message : String(err);
2949
+ args.logger.warn("peer-bytes: the offered file could not be opened", {
2950
+ ...deviceTags,
2951
+ meta: {
2952
+ label: options.label,
2953
+ error: detail
2954
+ }
2955
+ });
2956
+ if (!res.headersSent) res.writeHead(503, { "x-camstack-peer-bytes": "file-unreadable" });
2957
+ res.end();
2958
+ return;
2959
+ }
2960
+ res.writeHead(200, headers);
2961
+ let sent = 0;
2962
+ let lastProgressAt = now();
2963
+ stream.pipe(res);
2964
+ stream.on("data", (chunk) => {
2965
+ sent += typeof chunk === "string" ? Buffer.byteLength(chunk) : chunk.byteLength;
2966
+ lastProgressAt = now();
2967
+ });
2968
+ const stallTimer = setInterval(() => {
2969
+ if (now() - lastProgressAt < stallMs) return;
2970
+ clearInterval(stallTimer);
2971
+ args.logger.warn("peer-bytes: body cut before it was complete", {
2972
+ ...deviceTags,
2973
+ meta: {
2974
+ label: options.label,
2975
+ branch: "stalled",
2976
+ sent,
2977
+ stallMs,
2978
+ source: "file"
2979
+ }
2980
+ });
2981
+ stream.destroy();
2982
+ res.destroy();
2983
+ }, Math.max(1e3, Math.floor(stallMs / 4)));
2984
+ stallTimer.unref();
2985
+ const ended = await Promise.race([(0, node_events.once)(stream, "end").then(() => true), (0, node_events.once)(res, "close").then(() => false)]).catch(() => false);
2986
+ clearInterval(stallTimer);
2987
+ if (ended) {
2988
+ args.logger.info("peer-bytes: body sent", {
2989
+ ...deviceTags,
2990
+ meta: {
2991
+ label: options.label,
2992
+ bytes: sent,
2993
+ tookMs: now() - startedAt,
2994
+ source: "file"
2995
+ }
2996
+ });
2997
+ return;
2998
+ }
2999
+ stream.destroy();
3000
+ args.logger.warn("peer-bytes: body cut before it was complete", {
3001
+ ...deviceTags,
3002
+ meta: {
3003
+ label: options.label,
3004
+ branch: "peer-gone",
3005
+ sent,
3006
+ of: declaredBytes,
3007
+ source: "file"
3008
+ }
3009
+ });
3010
+ };
3011
+ return await mint({
3012
+ handler,
3013
+ label: options.label,
3014
+ ...declaredBytes !== null ? { declaredBytes } : {},
3015
+ ...options.deviceId !== void 0 ? { deviceId: options.deviceId } : {},
3016
+ ...options.ttlMs !== void 0 ? { ttlMs: options.ttlMs } : {}
3017
+ });
3018
+ },
3019
+ open: async (ticket) => {
3020
+ if (ticket.hostNodeId !== hostNodeId) {
3021
+ args.logger.warn("peer-bytes: refusing a ticket minted on another node", { meta: {
3022
+ mintedOn: ticket.hostNodeId,
3023
+ here: hostNodeId
3024
+ } });
3025
+ return {
3026
+ kind: "refused",
3027
+ code: "cross-node",
3028
+ detail: `this ticket was minted on node "${ticket.hostNodeId}" and this process runs on "${hostNodeId}" — peer-bytes is loopback only, so there is nothing to dial`
3029
+ };
3030
+ }
3031
+ if (ticket.expiresAtMs <= now()) return {
3032
+ kind: "refused",
3033
+ code: "ticket-expired",
3034
+ detail: `this ticket expired ${String(now() - ticket.expiresAtMs)} ms ago`
3035
+ };
3036
+ let url;
3037
+ try {
3038
+ url = new URL(ticket.url);
3039
+ } catch {
3040
+ return {
3041
+ kind: "refused",
3042
+ code: "ticket-malformed",
3043
+ detail: `"${ticket.url}" is not a URL`
3044
+ };
3045
+ }
3046
+ if (url.protocol !== "http:") return {
3047
+ kind: "refused",
3048
+ code: "ticket-malformed",
3049
+ detail: `peer-bytes dials http, not "${url.protocol}"`
3050
+ };
3051
+ const req = (0, node_http.request)({
3052
+ hostname: url.hostname,
3053
+ port: url.port,
3054
+ path: url.pathname,
3055
+ method: "GET"
3056
+ });
3057
+ let response;
3058
+ try {
3059
+ response = await new Promise((resolve, reject) => {
3060
+ req.once("response", resolve);
3061
+ req.once("error", reject);
3062
+ req.end();
3063
+ });
3064
+ } catch (err) {
3065
+ const detail = err instanceof Error ? err.message : String(err);
3066
+ req.destroy();
3067
+ args.logger.warn("peer-bytes: the dial failed", { meta: { error: detail } });
3068
+ return {
3069
+ kind: "refused",
3070
+ code: "unreachable",
3071
+ detail
3072
+ };
3073
+ }
3074
+ response.pause();
3075
+ const status = response.statusCode ?? 0;
3076
+ if (status < 200 || status >= 300) {
3077
+ const reason = response.headers["x-camstack-peer-bytes"];
3078
+ response.resume();
3079
+ req.destroy();
3080
+ return {
3081
+ kind: "refused",
3082
+ code: "producer-refused",
3083
+ detail: typeof reason === "string" ? `the producer answered ${String(status)} (${reason})` : `the producer answered ${String(status)}`,
3084
+ status
3085
+ };
3086
+ }
3087
+ const lengthHeader = response.headers["content-length"];
3088
+ const declaredBytes = typeof lengthHeader === "string" && /^\d+$/.test(lengthHeader) ? Number(lengthHeader) : null;
3089
+ const contentTypeHeader = response.headers["content-type"];
3090
+ const stream = response;
3091
+ return {
3092
+ kind: "body",
3093
+ body: {
3094
+ status,
3095
+ contentType: typeof contentTypeHeader === "string" ? contentTypeHeader : null,
3096
+ declaredBytes,
3097
+ stream,
3098
+ cancel: (reason) => {
3099
+ args.logger.info("peer-bytes: the consumer gave up on a body", { meta: { reason } });
3100
+ response.destroy();
3101
+ req.destroy();
3102
+ },
3103
+ writeToFile: async (path, options) => {
3104
+ await (0, node_fs_promises.mkdir)((0, node_path.dirname)(path), { recursive: true });
3105
+ const sink = (0, node_fs.createWriteStream)(path);
3106
+ const discard = async () => {
3107
+ sink.destroy();
3108
+ if (!sink.closed) await (0, node_events.once)(sink, "close").catch(() => void 0);
3109
+ await (0, node_fs_promises.rm)(path, { force: true });
3110
+ };
3111
+ let written = 0;
3112
+ let overrun = false;
3113
+ try {
3114
+ for await (const chunk of stream) {
3115
+ const buf = asBuffer(chunk);
3116
+ written += buf.byteLength;
3117
+ if (written > options.maxBytes) {
3118
+ overrun = true;
3119
+ break;
3120
+ }
3121
+ if (!sink.write(buf)) await (0, node_events.once)(sink, "drain");
3122
+ }
3123
+ } catch (err) {
3124
+ const detail = err instanceof Error ? err.message : String(err);
3125
+ response.destroy();
3126
+ req.destroy();
3127
+ await discard();
3128
+ return {
3129
+ kind: "refused",
3130
+ code: "transfer-failed",
3131
+ detail,
3132
+ bytes: written
3133
+ };
3134
+ }
3135
+ if (overrun) {
3136
+ response.destroy();
3137
+ req.destroy();
3138
+ await discard();
3139
+ return {
3140
+ kind: "refused",
3141
+ code: "too-large",
3142
+ detail: `the body passed ${String(options.maxBytes)} bytes and was aborted`,
3143
+ bytes: written
3144
+ };
3145
+ }
3146
+ try {
3147
+ sink.end();
3148
+ await (0, node_events.once)(sink, "close");
3149
+ } catch (err) {
3150
+ const detail = err instanceof Error ? err.message : String(err);
3151
+ await discard();
3152
+ return {
3153
+ kind: "refused",
3154
+ code: "write-failed",
3155
+ detail,
3156
+ bytes: written
3157
+ };
3158
+ }
3159
+ return {
3160
+ kind: "written",
3161
+ bytes: written
3162
+ };
3163
+ }
3164
+ }
3165
+ };
3166
+ }
3167
+ };
3168
+ const dispose = async () => {
3169
+ offers.clear();
3170
+ taken.clear();
3171
+ const open = server;
3172
+ server = null;
3173
+ baseUrl = null;
3174
+ listening = null;
3175
+ if (open === null) return;
3176
+ await new Promise((resolve) => {
3177
+ open.close(() => resolve());
3178
+ open.closeAllConnections();
3179
+ });
3180
+ args.logger.info("peer-bytes: listener closed", { meta: { addonId: args.addonId } });
3181
+ };
3182
+ return {
3183
+ peerBytes,
3184
+ dispose
3185
+ };
3186
+ }
3187
+ //#endregion
2616
3188
  //#region src/kernel/deps/npm-command.ts
2617
3189
  /**
2618
3190
  * npm invocation resolver — makes "is npm available?" a solved problem for
@@ -11190,6 +11762,12 @@ async function buildAddonContext(runtime, declaration, dataDir, options) {
11190
11762
  ...options?.dataPlaneSink !== void 0 ? { sink: options.dataPlaneSink } : {}
11191
11763
  });
11192
11764
  workerDisposerChain.add(() => dataPlaneFacility.dispose());
11765
+ const peerBytesFacility = createAddonPeerBytesFacility({
11766
+ addonId,
11767
+ logger: scopedLogger,
11768
+ nodeId
11769
+ });
11770
+ workerDisposerChain.add(() => peerBytesFacility.dispose());
11193
11771
  return {
11194
11772
  id: addonId,
11195
11773
  logger: scopedLogger,
@@ -11198,6 +11776,7 @@ async function buildAddonContext(runtime, declaration, dataDir, options) {
11198
11776
  dataDir: addonDataDir,
11199
11777
  nodeDataDir,
11200
11778
  dataPlane: dataPlaneFacility.dataPlane,
11779
+ peerBytes: peerBytesFacility.peerBytes,
11201
11780
  api,
11202
11781
  deps: new AddonDepsManager(addonDataDir, scopedLogger),
11203
11782
  settings: settingsView,
@@ -12120,6 +12699,12 @@ Object.defineProperty(exports, "createAddonDataPlaneFacility", {
12120
12699
  return createAddonDataPlaneFacility;
12121
12700
  }
12122
12701
  });
12702
+ Object.defineProperty(exports, "createAddonPeerBytesFacility", {
12703
+ enumerable: true,
12704
+ get: function() {
12705
+ return createAddonPeerBytesFacility;
12706
+ }
12707
+ });
12123
12708
  Object.defineProperty(exports, "createAddonService", {
12124
12709
  enumerable: true,
12125
12710
  get: function() {
@@ -12396,6 +12981,12 @@ Object.defineProperty(exports, "heapSpaceField", {
12396
12981
  return heapSpaceField;
12397
12982
  }
12398
12983
  });
12984
+ Object.defineProperty(exports, "hostNodeIdOf", {
12985
+ enumerable: true,
12986
+ get: function() {
12987
+ return hostNodeIdOf;
12988
+ }
12989
+ });
12399
12990
  Object.defineProperty(exports, "hubMainRssBudget", {
12400
12991
  enumerable: true,
12401
12992
  get: function() {