@camstack/system 1.2.288 → 1.2.290

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