@getstrata/core 2.2.22 → 2.2.24

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/dist/index.js CHANGED
@@ -3893,6 +3893,7 @@ return redis.error_reply('Unknown cache operation')
3893
3893
  `;
3894
3894
 
3895
3895
  class RedisCacheStore {
3896
+ redisUrl;
3896
3897
  ttlMs;
3897
3898
  maxEntries;
3898
3899
  client;
@@ -3901,6 +3902,7 @@ class RedisCacheStore {
3901
3902
  fills = new Set;
3902
3903
  closed = false;
3903
3904
  constructor(redisUrl, ttlMs, maxEntries, options = {}) {
3905
+ this.redisUrl = redisUrl;
3904
3906
  this.ttlMs = ttlMs;
3905
3907
  this.maxEntries = maxEntries;
3906
3908
  if (!Number.isSafeInteger(ttlMs) || ttlMs < 0)
@@ -3925,7 +3927,10 @@ class RedisCacheStore {
3925
3927
  const namespace = namespacedRedisKey("cache:v3:");
3926
3928
  const slot = new Bun.CryptoHasher("sha256").update(namespace).digest("hex");
3927
3929
  this.keys = ["entries", "expiry", "order", "metadata", "metadata-expiry"].map((name) => `${namespace}{${slot}}:${name}`);
3928
- this.client = new RedisClient(redisUrl, {
3930
+ this.client = this.createClient();
3931
+ }
3932
+ createClient() {
3933
+ return new RedisClient(this.redisUrl, {
3929
3934
  connectionTimeout: this.options.commandTimeoutMs,
3930
3935
  maxRetries: 1
3931
3936
  });
@@ -3934,7 +3939,8 @@ class RedisCacheStore {
3934
3939
  this.closed = true;
3935
3940
  for (const fill of this.fills)
3936
3941
  fill.abort(new Error("Redis cache store is closed."));
3937
- this.client.close();
3942
+ this.client?.close();
3943
+ this.client = undefined;
3938
3944
  }
3939
3945
  async get(key) {
3940
3946
  const result = await this.execute("get", key);
@@ -4013,6 +4019,7 @@ class RedisCacheStore {
4013
4019
  Promise.resolve().then(() => loader(controller.signal)),
4014
4020
  aborted
4015
4021
  ]);
4022
+ controller.signal.throwIfAborted();
4016
4023
  done = true;
4017
4024
  clearInterval(renew);
4018
4025
  await this.execute("publish", key, {
@@ -4054,16 +4061,28 @@ class RedisCacheStore {
4054
4061
  const ttlMs = extra.ttlMs ?? this.ttlMs;
4055
4062
  if (typeof ttlMs !== "number" || !Number.isSafeInteger(ttlMs) || ttlMs < 0)
4056
4063
  throw new RangeError("ttlMs must be a non-negative safe integer.");
4064
+ if (!this.client)
4065
+ this.client = this.createClient();
4066
+ const client = this.client;
4067
+ const retire = (reason) => {
4068
+ if (this.client !== client)
4069
+ return;
4070
+ this.client = undefined;
4071
+ for (const fill of this.fills)
4072
+ fill.abort(reason);
4073
+ client.close();
4074
+ };
4057
4075
  let timer;
4058
4076
  const timeout = new Promise((_, reject) => {
4059
4077
  timer = setTimeout(() => {
4060
- reject(new Error("Redis cache command deadline exceeded; recreate the store."));
4061
- this.close();
4078
+ const error = new Error("Redis cache command deadline exceeded.");
4079
+ reject(error);
4080
+ retire(error);
4062
4081
  }, this.options.commandTimeoutMs);
4063
4082
  });
4064
4083
  try {
4065
4084
  const raw = await Promise.race([
4066
- this.client.send("EVAL", [
4085
+ client.send("EVAL", [
4067
4086
  CACHE_SCRIPT,
4068
4087
  String(this.keys.length),
4069
4088
  ...this.keys,
@@ -4081,7 +4100,13 @@ class RedisCacheStore {
4081
4100
  ]),
4082
4101
  timeout
4083
4102
  ]);
4103
+ if (this.closed || this.client !== client)
4104
+ throw new Error("Redis cache connection was retired.");
4084
4105
  return JSON.parse(String(raw));
4106
+ } catch (error) {
4107
+ if (error && typeof error === "object" && "code" in error && typeof error.code === "string" && error.code.startsWith("ERR_REDIS_CONNECTION"))
4108
+ retire(error);
4109
+ throw error;
4085
4110
  } finally {
4086
4111
  clearTimeout(timer);
4087
4112
  }
@@ -10641,8 +10666,8 @@ function resolveSmtpConfig() {
10641
10666
  if (!from) {
10642
10667
  throw new Error('MAIL_DRIVER="smtp" requires MAIL_FROM to be set.');
10643
10668
  }
10644
- const port = Number.parseInt(process.env.MAIL_PORT ?? "587", 10);
10645
- if (!Number.isInteger(port) || port <= 0) {
10669
+ const port = Number(process.env.MAIL_PORT ?? "587");
10670
+ if (!Number.isInteger(port) || port <= 0 || port > 65535) {
10646
10671
  throw new Error('Environment variable "MAIL_PORT" must be a positive integer.');
10647
10672
  }
10648
10673
  return {
@@ -10650,6 +10675,7 @@ function resolveSmtpConfig() {
10650
10675
  port,
10651
10676
  from,
10652
10677
  secure: (process.env.MAIL_SECURE ?? "false") === "true",
10678
+ ...process.env.MAIL_TIMEOUT_MS !== undefined ? { timeoutMs: smtpTimeout(Number(process.env.MAIL_TIMEOUT_MS)) } : {},
10653
10679
  ...process.env.MAIL_USERNAME?.trim() ? { username: process.env.MAIL_USERNAME.trim() } : {},
10654
10680
  ...process.env.MAIL_PASSWORD?.trim() ? { password: process.env.MAIL_PASSWORD.trim() } : {}
10655
10681
  };
@@ -10659,6 +10685,12 @@ function assertSafeSmtpField(value, field) {
10659
10685
  if (/[\r\n]/u.test(value)) {
10660
10686
  throw new Error(`Mail ${field} must not contain CR or LF.`);
10661
10687
  }
10688
+ for (const character of value) {
10689
+ const code = character.charCodeAt(0);
10690
+ if (code < 32 && code !== 9 || code === 127) {
10691
+ throw new Error(`Mail ${field} must not contain control characters.`);
10692
+ }
10693
+ }
10662
10694
  return value;
10663
10695
  }
10664
10696
  function smtpEnvelopeAddress(value, field) {
@@ -10679,148 +10711,307 @@ function redactMailBody(body) {
10679
10711
  function encodeBase64(value) {
10680
10712
  return Buffer.from(value, "utf8").toString("base64");
10681
10713
  }
10682
- function parseSmtpResponses(buffer) {
10683
- const responses = [];
10684
- let remainder = buffer;
10685
- while (remainder.includes(`\r
10686
- `)) {
10687
- const index = remainder.indexOf(`\r
10688
- `);
10689
- const line = remainder.slice(0, index);
10690
- remainder = remainder.slice(index + 2);
10691
- if (line.length >= 4 && line[3] === "-") {
10692
- continue;
10693
- }
10694
- responses.push(line);
10714
+ function smtpTimeout(value = 1e4) {
10715
+ if (!Number.isSafeInteger(value) || value < 1 || value > 2147483647) {
10716
+ throw new RangeError("SMTP timeoutMs must be a positive integer within the timer range.");
10695
10717
  }
10696
- return { responses, remainder };
10718
+ return value;
10697
10719
  }
10698
- async function waitForSmtpResponse(readResponse, expectedCodes) {
10699
- const response = await readResponse();
10700
- const code = response.slice(0, 3);
10701
- if (!expectedCodes.includes(code)) {
10702
- throw new Error(`Unexpected SMTP response: ${response}`);
10720
+ async function defaultSmtpTransport(config, message) {
10721
+ const timeoutMs = smtpTimeout(config.timeoutMs);
10722
+ if (!config.host.trim() || !Number.isInteger(config.port) || config.port < 1 || config.port > 65535) {
10723
+ throw new RangeError("SMTP requires a host and a port between 1 and 65535.");
10703
10724
  }
10704
- return response;
10705
- }
10706
- async function openSmtpConnection(config) {
10707
- let buffer = "";
10725
+ if (Boolean(config.username) !== Boolean(config.password)) {
10726
+ throw new Error("SMTP authentication requires both username and password.");
10727
+ }
10728
+ if (config.username)
10729
+ assertSafeSmtpField(config.username, "username");
10730
+ if (config.password)
10731
+ assertSafeSmtpField(config.password, "password");
10732
+ const fromHeader = assertSafeSmtpAddress(config.from, "from");
10733
+ const toHeader = assertSafeSmtpAddress(message.to, "to");
10734
+ assertSafeSmtpField(message.subject, "subject");
10735
+ const fromEnvelope = smtpEnvelopeAddress(config.from, "from");
10736
+ const toEnvelope = smtpEnvelopeAddress(message.to, "to");
10737
+ if (/[^\x21-\x7e]/u.test(fromEnvelope + toEnvelope)) {
10738
+ throw new Error("Default SMTP transport requires ASCII envelopes; SMTPUTF8 is not negotiated.");
10739
+ }
10740
+ for (const address of [fromEnvelope, toEnvelope]) {
10741
+ const at = address.lastIndexOf("@");
10742
+ if (Buffer.byteLength(address) > 254 || Buffer.byteLength(address.slice(0, at)) > 64) {
10743
+ throw new RangeError("SMTP envelope address exceeds the path/local-part length limit.");
10744
+ }
10745
+ }
10746
+ message.signal?.throwIfAborted();
10747
+ let socket;
10748
+ let finished = false;
10749
+ let failed = false;
10750
+ let failure;
10751
+ let rejectFailure;
10752
+ const failurePromise = new Promise((_, reject) => {
10753
+ rejectFailure = reject;
10754
+ });
10755
+ failurePromise.catch(() => {});
10756
+ const responses = [];
10708
10757
  const waiters = [];
10709
- const readResponse = () => new Promise((resolve, reject) => {
10710
- const parsed = parseSmtpResponses(buffer);
10711
- if (parsed.responses.length > 0) {
10712
- buffer = parsed.remainder;
10713
- resolve(parsed.responses.shift());
10758
+ let drain;
10759
+ let drainVersion = 0;
10760
+ let buffer = "";
10761
+ let multilineCode;
10762
+ let multilineLines = 0;
10763
+ const decoder = new TextDecoder;
10764
+ const fail = (reason) => {
10765
+ if (finished || failed)
10714
10766
  return;
10767
+ failed = true;
10768
+ failure = reason;
10769
+ rejectFailure(reason);
10770
+ waiters.length = 0;
10771
+ drain?.();
10772
+ drain = undefined;
10773
+ socket?.terminate();
10774
+ };
10775
+ const onAbort = () => fail(message.signal?.reason ?? new DOMException("Mail send aborted.", "AbortError"));
10776
+ const timer = setTimeout(() => fail(new DOMException("SMTP send deadline exceeded.", "TimeoutError")), timeoutMs);
10777
+ message.signal?.addEventListener("abort", onAbort, { once: true });
10778
+ if (message.signal?.aborted)
10779
+ onAbort();
10780
+ const readResponse = async (expected) => {
10781
+ const queued = responses.shift();
10782
+ const response = queued ?? await Promise.race([
10783
+ failed ? Promise.reject(failure) : new Promise((resolve) => waiters.push(resolve)),
10784
+ failurePromise
10785
+ ]);
10786
+ const code = response.slice(0, 3);
10787
+ if (!expected.includes(code))
10788
+ throw new Error(`Unexpected SMTP response code: ${code}`);
10789
+ };
10790
+ const write = async (text) => {
10791
+ const bytes = Buffer.from(text, "utf8");
10792
+ let offset = 0;
10793
+ while (offset < bytes.length) {
10794
+ if (failed)
10795
+ throw failure;
10796
+ const version = drainVersion;
10797
+ if (!socket)
10798
+ throw new Error("SMTP socket is not connected.");
10799
+ const written = socket.write(bytes.subarray(offset));
10800
+ if (written < 0)
10801
+ throw new Error("SMTP socket write failed.");
10802
+ offset += written;
10803
+ if (offset < bytes.length && version === drainVersion) {
10804
+ await Promise.race([
10805
+ new Promise((resolve) => {
10806
+ drain = resolve;
10807
+ }),
10808
+ failurePromise
10809
+ ]);
10810
+ }
10715
10811
  }
10716
- waiters.push({ resolve, reject });
10717
- });
10718
- const socket = await Bun.connect({
10719
- hostname: config.host,
10720
- port: config.port,
10721
- socket: {
10722
- open() {},
10723
- data(_socket, chunk) {
10724
- buffer += Buffer.from(chunk).toString("utf8");
10725
- const parsed = parseSmtpResponses(buffer);
10726
- buffer = parsed.remainder;
10727
- while (parsed.responses.length > 0 && waiters.length > 0) {
10728
- const response = parsed.responses.shift();
10729
- waiters.shift()?.resolve(response);
10812
+ };
10813
+ try {
10814
+ const connecting = Bun.connect({
10815
+ hostname: config.host,
10816
+ port: config.port,
10817
+ ...config.secure ? {
10818
+ tls: {
10819
+ ...config.tls,
10820
+ serverName: config.tls?.serverName ?? config.host,
10821
+ rejectUnauthorized: true
10730
10822
  }
10731
- },
10732
- error(_socket, error) {
10733
- const pending = waiters.splice(0);
10734
- for (const waiter of pending) {
10735
- waiter.reject(error instanceof Error ? error : new Error(String(error)));
10823
+ } : {},
10824
+ socket: {
10825
+ open(value) {
10826
+ socket = value;
10827
+ if (failed || finished)
10828
+ value.terminate();
10829
+ },
10830
+ data(_socket, chunk) {
10831
+ if (failed || finished)
10832
+ return;
10833
+ buffer += decoder.decode(chunk, { stream: true });
10834
+ if (Buffer.byteLength(buffer, "utf8") > 16384) {
10835
+ fail(new Error("SMTP response exceeded the buffer limit."));
10836
+ return;
10837
+ }
10838
+ while (buffer.includes(`\r
10839
+ `)) {
10840
+ const index = buffer.indexOf(`\r
10841
+ `);
10842
+ const line = buffer.slice(0, index);
10843
+ buffer = buffer.slice(index + 2);
10844
+ const match = /^([2-5]\d{2})([- ]|$)/.exec(line);
10845
+ if (!match || multilineCode !== undefined && match[1] !== multilineCode) {
10846
+ fail(new Error("Malformed SMTP reply."));
10847
+ return;
10848
+ }
10849
+ if (++multilineLines > 128) {
10850
+ fail(new Error("SMTP reply exceeded the line limit."));
10851
+ return;
10852
+ }
10853
+ if (match[2] === "-") {
10854
+ multilineCode = match[1];
10855
+ continue;
10856
+ }
10857
+ multilineCode = undefined;
10858
+ multilineLines = 0;
10859
+ const waiter = waiters.shift();
10860
+ if (waiter)
10861
+ waiter(line);
10862
+ else {
10863
+ responses.push(line);
10864
+ if (responses.length > 32) {
10865
+ fail(new Error("Too many buffered SMTP replies."));
10866
+ return;
10867
+ }
10868
+ }
10869
+ }
10870
+ },
10871
+ drain() {
10872
+ drainVersion++;
10873
+ const waiting = drain;
10874
+ drain = undefined;
10875
+ waiting?.();
10876
+ },
10877
+ end() {
10878
+ fail(new Error("SMTP peer closed before the send completed."));
10879
+ },
10880
+ close() {
10881
+ fail(new Error("SMTP connection closed before the send completed."));
10882
+ },
10883
+ error() {
10884
+ fail(new Error("SMTP socket failed."));
10885
+ },
10886
+ connectError() {
10887
+ fail(new Error("SMTP connection failed."));
10888
+ },
10889
+ handshake(_socket, success) {
10890
+ if (!success)
10891
+ fail(new Error("SMTP TLS verification failed."));
10736
10892
  }
10737
10893
  }
10738
- }
10739
- });
10740
- return { socket, readResponse };
10741
- }
10742
- async function defaultSmtpTransport(config, message) {
10743
- if (config.username) {
10744
- assertSafeSmtpField(config.username, "username");
10745
- }
10746
- if (config.password) {
10747
- assertSafeSmtpField(config.password, "password");
10748
- }
10749
- const { socket, readResponse } = await openSmtpConnection(config);
10750
- try {
10751
- await waitForSmtpResponse(readResponse, ["220"]);
10752
- await socket.write(`EHLO ${smtpEhloHost()}\r
10894
+ });
10895
+ connecting.then((value) => {
10896
+ socket = value;
10897
+ if (failed || finished)
10898
+ value.terminate();
10899
+ }, () => {});
10900
+ socket = await Promise.race([connecting, failurePromise]);
10901
+ await readResponse(["220"]);
10902
+ await write(`EHLO ${smtpEhloHost()}\r
10753
10903
  `);
10754
- await waitForSmtpResponse(readResponse, ["250"]);
10904
+ await readResponse(["250"]);
10755
10905
  if (config.username && config.password) {
10756
- await socket.write(`AUTH LOGIN\r
10906
+ await write(`AUTH LOGIN\r
10757
10907
  `);
10758
- await waitForSmtpResponse(readResponse, ["334"]);
10759
- await socket.write(`${encodeBase64(config.username)}\r
10908
+ await readResponse(["334"]);
10909
+ await write(`${encodeBase64(config.username)}\r
10760
10910
  `);
10761
- await waitForSmtpResponse(readResponse, ["334"]);
10762
- await socket.write(`${encodeBase64(config.password)}\r
10911
+ await readResponse(["334"]);
10912
+ await write(`${encodeBase64(config.password)}\r
10763
10913
  `);
10764
- await waitForSmtpResponse(readResponse, ["235"]);
10914
+ await readResponse(["235"]);
10765
10915
  }
10766
- const fromHeader = assertSafeSmtpAddress(config.from, "from");
10767
- const toHeader = assertSafeSmtpAddress(message.to, "to");
10768
- const from = smtpEnvelopeAddress(config.from, "from");
10769
- const to = smtpEnvelopeAddress(message.to, "to");
10770
- await socket.write(`MAIL FROM:<${from}>\r
10916
+ await write(`MAIL FROM:<${fromEnvelope}>\r
10771
10917
  `);
10772
- await waitForSmtpResponse(readResponse, ["250"]);
10773
- await socket.write(`RCPT TO:<${to}>\r
10918
+ await readResponse(["250"]);
10919
+ await write(`RCPT TO:<${toEnvelope}>\r
10774
10920
  `);
10775
- await waitForSmtpResponse(readResponse, ["250", "251"]);
10776
- await socket.write(`DATA\r
10921
+ await readResponse(["250", "251"]);
10922
+ await write(`DATA\r
10777
10923
  `);
10778
- await waitForSmtpResponse(readResponse, ["354"]);
10779
- const payload = buildSmtpPayload(fromHeader, {
10780
- ...message,
10781
- to: toHeader,
10782
- subject: assertSafeSmtpField(message.subject, "subject")
10783
- });
10784
- await socket.write(payload);
10785
- await waitForSmtpResponse(readResponse, ["250"]);
10786
- await socket.write(`QUIT\r
10924
+ await readResponse(["354"]);
10925
+ await write(buildSmtpPayload(fromHeader, { ...message, to: toHeader }));
10926
+ await readResponse(["250"]);
10927
+ finished = true;
10928
+ try {
10929
+ socket.write(`QUIT\r
10787
10930
  `);
10788
- await waitForSmtpResponse(readResponse, ["221"]);
10931
+ } catch {}
10789
10932
  } finally {
10790
- socket.end();
10933
+ finished = true;
10934
+ clearTimeout(timer);
10935
+ message.signal?.removeEventListener("abort", onAbort);
10936
+ waiters.length = 0;
10937
+ responses.length = 0;
10938
+ drain = undefined;
10939
+ socket?.end();
10791
10940
  }
10792
10941
  }
10942
+ function encodeSmtpHeader(value) {
10943
+ if (/^[\x20-\x7e]{0,60}$/u.test(value))
10944
+ return value;
10945
+ const words = [];
10946
+ let part = "";
10947
+ for (const character of value) {
10948
+ if (Buffer.byteLength(part + character, "utf8") > 45) {
10949
+ words.push(`=?UTF-8?B?${encodeBase64(part)}?=`);
10950
+ part = "";
10951
+ }
10952
+ part += character;
10953
+ }
10954
+ if (part)
10955
+ words.push(`=?UTF-8?B?${encodeBase64(part)}?=`);
10956
+ return words.join(`\r
10957
+ `);
10958
+ }
10959
+ function smtpAddressHeader(value) {
10960
+ if (!value.includes("<"))
10961
+ return value;
10962
+ const name = value.slice(0, value.lastIndexOf("<")).trim();
10963
+ const address = smtpEnvelopeAddress(value, "address");
10964
+ if (!name)
10965
+ return address;
10966
+ const encoded = encodeSmtpHeader(name);
10967
+ const display = encoded.startsWith("=?UTF-8?B?") ? encoded : `"${encoded.replace(/[\\"]/gu, "\\$&")}"`;
10968
+ return `${display} <${address}>`;
10969
+ }
10970
+ function smtpMimePart(type, value) {
10971
+ const normalized = value.replace(/\r\n|\r|\n/gu, `\r
10972
+ `);
10973
+ const base64 = /[^\t\r\n\x20-\x7e]/u.test(normalized) || normalized.split(`\r
10974
+ `).some((line) => line.length > 998);
10975
+ return [
10976
+ `Content-Type: ${type}; charset=utf-8`,
10977
+ `Content-Transfer-Encoding: ${base64 ? "base64" : "7bit"}`,
10978
+ "",
10979
+ base64 ? (encodeBase64(normalized).match(/.{1,76}/gu) ?? []).join(`\r
10980
+ `) : normalized
10981
+ ];
10982
+ }
10793
10983
  function buildSmtpPayload(from, message) {
10794
- const safeFrom = assertSafeSmtpAddress(from, "from");
10795
- const safeTo = assertSafeSmtpAddress(message.to, "to");
10796
- const safeSubject = assertSafeSmtpField(message.subject, "subject");
10797
10984
  const headers = [
10798
- `From: ${safeFrom}`,
10799
- `To: ${safeTo}`,
10800
- `Subject: ${safeSubject}`,
10985
+ `From: ${smtpAddressHeader(assertSafeSmtpAddress(from, "from"))}`,
10986
+ `To: ${smtpAddressHeader(assertSafeSmtpAddress(message.to, "to"))}`,
10987
+ `Subject: ${encodeSmtpHeader(assertSafeSmtpField(message.subject, "subject"))}`,
10801
10988
  "MIME-Version: 1.0"
10802
10989
  ];
10990
+ let lines;
10803
10991
  if (message.html) {
10804
- const boundary = `strata-${Date.now().toString(36)}`;
10805
- headers.push(`Content-Type: multipart/alternative; boundary="${boundary}"`);
10806
- const parts = [
10807
- `--${boundary}`,
10808
- "Content-Type: text/plain; charset=utf-8",
10992
+ let boundary;
10993
+ do {
10994
+ boundary = `strata-${crypto.randomUUID()}`;
10995
+ } while (message.body.includes(boundary) || message.html.includes(boundary));
10996
+ lines = [
10997
+ ...headers,
10998
+ `Content-Type: multipart/alternative; boundary="${boundary}"`,
10809
10999
  "",
10810
- message.body,
10811
11000
  `--${boundary}`,
10812
- "Content-Type: text/html; charset=utf-8",
10813
- "",
10814
- message.html,
11001
+ ...smtpMimePart("text/plain", message.body),
11002
+ `--${boundary}`,
11003
+ ...smtpMimePart("text/html", message.html),
10815
11004
  `--${boundary}--`,
10816
11005
  ""
10817
11006
  ];
10818
- return [...headers, "", ...parts, ".", ""].join(`\r
10819
- `);
10820
- }
10821
- headers.push("Content-Type: text/plain; charset=utf-8");
10822
- return [...headers, "", message.body, ".", ""].join(`\r
10823
- `);
11007
+ } else
11008
+ lines = [...headers, ...smtpMimePart("text/plain", message.body)];
11009
+ const content = lines.join(`\r
11010
+ `).replace(/(^|\r\n)\./gu, "$1..");
11011
+ return `${content}${content.endsWith(`\r
11012
+ `) ? "" : `\r
11013
+ `}.\r
11014
+ `;
10824
11015
  }
10825
11016
 
10826
11017
  class LogMailDriver {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getstrata/core",
3
- "version": "2.2.22",
3
+ "version": "2.2.24",
4
4
  "description": "Strata Bun framework public API",
5
5
  "type": "module",
6
6
  "license": "MIT",