wawesome 0.0.12 → 0.0.13

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.
package/README.md CHANGED
@@ -213,7 +213,7 @@ Three headers arrive or leave on it, and the stripping is what makes them worth
213
213
  | --- | --- | --- |
214
214
  | `x-wawesome-forwarded-prefix` | inbound | The mount that was stripped from the path. Join it to the path you observe to rebuild the caller's URL. |
215
215
  | `x-wawesome-invocation-id` | outbound | The id of this run — the key to fetch its logs with `npx wawesome logs --invocation <id>`. |
216
- | `x-wawesome-error` | outbound | Present only when the platform failed, never when your Function did. Its *absence* means the status on the wire is yours. |
216
+ | `x-wawesome-error` | outbound | Present only when the platform failed, never when your Function did. Its *absence* means the status on the wire is yours — up to the moment your response is committed, and no further. |
217
217
 
218
218
  ### Local Development / Gateway Overrides
219
219
 
package/dist/index.mjs CHANGED
@@ -166,7 +166,7 @@ async function buildJs(entryInput, options) {
166
166
  * that has to name this version — `--version`, the dependency a scaffolded
167
167
  * project pins — reads it here, so a release bumps one file.
168
168
  */
169
- const CLI_VERSION = "0.0.12";
169
+ const CLI_VERSION = "0.0.13";
170
170
  //#endregion
171
171
  //#region src/prompt.ts
172
172
  /**
@@ -549,6 +549,148 @@ async function whoami() {
549
549
  console.log(` Gateway: ${creds.gateway_url}\n`);
550
550
  }
551
551
  //#endregion
552
+ //#region src/usage.ts
553
+ const USAGE_TIMEOUT_MS = 2e3;
554
+ /**
555
+ * Bounded by its own timeout: every caller treats this as an aside to something
556
+ * that already succeeded, so a management API that hangs must not be able to
557
+ * hold that something open.
558
+ */
559
+ async function fetchTenantUsage(creds, timeoutMs = USAGE_TIMEOUT_MS) {
560
+ const controller = new AbortController();
561
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
562
+ try {
563
+ const res = await fetch(`${creds.gateway_url}/v1/tenant/usage`, {
564
+ headers: { Authorization: `Bearer ${creds.tenant_jwt}` },
565
+ signal: controller.signal
566
+ });
567
+ if (!res.ok) throw await asGatewayError(res, `Failed to read usage (HTTP ${res.status}).`);
568
+ return asTenantUsage(await res.json());
569
+ } finally {
570
+ clearTimeout(timer);
571
+ }
572
+ }
573
+ /**
574
+ * A payload that is missing any of this is one the plan could not be resolved
575
+ * for, and is refused here rather than rendered as half a block.
576
+ */
577
+ function asTenantUsage(value) {
578
+ const usage = value;
579
+ if (!(!!usage && typeof usage.plan?.name === "string" && typeof usage.plan?.limits?.app_slots === "number" && typeof usage.occupied_app_slots === "number" && !!usage.allowances && typeof usage.allowances === "object" && isInstant(usage.period?.start) && isInstant(usage.period?.end))) throw new Error("Usage payload does not carry a resolved plan.");
580
+ return usage;
581
+ }
582
+ function isInstant(value) {
583
+ return typeof value === "string" && !isNaN(new Date(value).getTime());
584
+ }
585
+ //#endregion
586
+ //#region src/headroom.ts
587
+ const KNOWN_LABELS = {
588
+ invocations: "Invocations",
589
+ caller_facing_bytes: "Caller-facing bytes"
590
+ };
591
+ const COUNT_UNITS = [
592
+ {
593
+ threshold: 1e9,
594
+ suffix: "B"
595
+ },
596
+ {
597
+ threshold: 1e6,
598
+ suffix: "M"
599
+ },
600
+ {
601
+ threshold: 1e3,
602
+ suffix: "k"
603
+ }
604
+ ];
605
+ const BYTE_UNITS = [
606
+ "B",
607
+ "KiB",
608
+ "MiB",
609
+ "GiB",
610
+ "TiB"
611
+ ];
612
+ const MONTHS = [
613
+ "January",
614
+ "February",
615
+ "March",
616
+ "April",
617
+ "May",
618
+ "June",
619
+ "July",
620
+ "August",
621
+ "September",
622
+ "October",
623
+ "November",
624
+ "December"
625
+ ];
626
+ /** Indented to sit inside the receipt the deploy already prints. */
627
+ function headroomLines(usage) {
628
+ const lines = [
629
+ ` Plan: ${usage.plan.name} — this deploy does not change your bill.`,
630
+ ` Apps: ${usage.occupied_app_slots} / ${usage.plan.limits.app_slots} slots`,
631
+ ` Usage: ${periodLabel(usage.period.start, usage.period.end)}`
632
+ ];
633
+ const entries = Object.entries(usage.allowances);
634
+ const labelWidth = widest(entries.map(([key]) => allowanceLabel(key)));
635
+ const amountWidth = widest(entries.map(([key, allowance]) => amount(key, allowance.used, allowance.limit)));
636
+ for (const [key, allowance] of entries) {
637
+ const percent = allowance.consumed_percent === null ? "" : ` (${allowance.consumed_percent}%)`;
638
+ const line = ` ${allowanceLabel(key).padEnd(labelWidth)} ${amount(key, allowance.used, allowance.limit).padEnd(amountWidth)}${percent}`;
639
+ lines.push(line.trimEnd());
640
+ }
641
+ return lines;
642
+ }
643
+ function allowanceLabel(key) {
644
+ if (KNOWN_LABELS[key]) return KNOWN_LABELS[key];
645
+ const words = key.replace(/[_-]+/g, " ").trim();
646
+ return words.charAt(0).toUpperCase() + words.slice(1);
647
+ }
648
+ function formatCount(value) {
649
+ for (let i = 0; i < COUNT_UNITS.length; i++) {
650
+ const { threshold, suffix } = COUNT_UNITS[i];
651
+ if (value < threshold) continue;
652
+ const scaled = round(value / threshold, 1);
653
+ if (scaled >= 1e3 && i > 0) {
654
+ const bigger = COUNT_UNITS[i - 1];
655
+ return `${round(value / bigger.threshold, 1)}${bigger.suffix}`;
656
+ }
657
+ return `${scaled}${suffix}`;
658
+ }
659
+ return String(Math.round(value));
660
+ }
661
+ function formatBytes(value) {
662
+ let scaled = value;
663
+ let unit = 0;
664
+ while (scaled >= 1024 && unit < BYTE_UNITS.length - 1) {
665
+ scaled /= 1024;
666
+ unit++;
667
+ }
668
+ return `${unit === 0 ? Math.round(scaled) : round(scaled, 2)} ${BYTE_UNITS[unit]}`;
669
+ }
670
+ /**
671
+ * Name the period rather than print its bounds. The end is exclusive, so a
672
+ * partial period names the last day inside it and not the instant after it.
673
+ */
674
+ function periodLabel(start, end) {
675
+ const from = new Date(start);
676
+ const to = new Date(end);
677
+ if (from.getTime() === Date.UTC(from.getUTCFullYear(), from.getUTCMonth(), 1) && to.getTime() === Date.UTC(from.getUTCFullYear(), from.getUTCMonth() + 1, 1)) return `${MONTHS[from.getUTCMonth()]} ${from.getUTCFullYear()} (UTC)`;
678
+ return `${dayLabel(from)} – ${dayLabel(/* @__PURE__ */ new Date(to.getTime() - 1))} (UTC)`;
679
+ }
680
+ function dayLabel(date) {
681
+ return `${date.getUTCDate()} ${MONTHS[date.getUTCMonth()].slice(0, 3)} ${date.getUTCFullYear()}`;
682
+ }
683
+ function amount(key, used, limit) {
684
+ const format = key.includes("bytes") ? formatBytes : formatCount;
685
+ return `${format(used)} / ${format(limit)}`;
686
+ }
687
+ function round(value, places) {
688
+ return Number(value.toFixed(places));
689
+ }
690
+ function widest(values) {
691
+ return values.reduce((longest, value) => Math.max(longest, value.length), 0);
692
+ }
693
+ //#endregion
552
694
  //#region ../shared/public-address.ts
553
695
  const INVOCATION_PREFIX = "/x";
554
696
  const SUBTREE_NOTE = "Every path beneath this address reaches the Function.";
@@ -668,6 +810,12 @@ async function deploy(entryInput, options) {
668
810
  } catch (err) {
669
811
  if (isVerbose) console.log(`[wawesome:verbose] Could not resolve the workspace address: ${err instanceof Error ? err.message : err}`);
670
812
  }
813
+ let headroom = null;
814
+ try {
815
+ headroom = headroomLines(await fetchTenantUsage(creds));
816
+ } catch (err) {
817
+ if (isVerbose) console.log(`[wawesome:verbose] Could not read the plan's usage: ${errorText(err)}`);
818
+ }
671
819
  console.log("\n======================================================");
672
820
  console.log("🚀 \x1B[32mDEPLOYED SUCCESSFULLY!\x1B[0m");
673
821
  console.log("======================================================");
@@ -678,6 +826,10 @@ async function deploy(entryInput, options) {
678
826
  console.log(`\n URL: \x1b[36m${address}\x1b[0m`);
679
827
  console.log(` ${SUBTREE_NOTE}`);
680
828
  }
829
+ if (headroom) {
830
+ console.log("");
831
+ for (const line of headroom) console.log(line);
832
+ }
681
833
  console.log("======================================================\n");
682
834
  return {
683
835
  app,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wawesome",
3
- "version": "0.0.12",
3
+ "version": "0.0.13",
4
4
  "description": "CLI tool for building and deploying serverless functions on wawesome.io platform",
5
5
  "type": "module",
6
6
  "bin": {