@odla-ai/chapter 0.48.1 → 0.48.3

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.
@@ -287,7 +287,7 @@ interface LeaderFormationConfig {
287
287
  maxLen?: Record<string, number>;
288
288
  /** Per-string cap when `maxLen` has no field override. Default 4000. */
289
289
  defaultMaxLen?: number;
290
- /** Maximum JSON request size in characters. Default 32768. */
290
+ /** Maximum JSON request size in UTF-8 bytes, enforced while streaming. Default 32768. */
291
291
  bodyCap?: number;
292
292
  }
293
293
  /** Fully resolved formation intake policy. */
@@ -590,7 +590,7 @@ interface ChapterApplication {
590
590
  /** Per-field character cap. Fields not listed use `defaultMaxLen`. */
591
591
  maxLen?: Record<string, number>;
592
592
  defaultMaxLen?: number;
593
- /** Max JSON request body in bytes. Default 32768. */
593
+ /** Max JSON request body in UTF-8 bytes, enforced while streaming. Default 32768. */
594
594
  bodyCap?: number;
595
595
  /** Reject a submit that carries no truthy `disclaimerAck` (400), instead of
596
596
  * writing a row with no consent record. Default `true`. Set `false`
@@ -287,7 +287,7 @@ interface LeaderFormationConfig {
287
287
  maxLen?: Record<string, number>;
288
288
  /** Per-string cap when `maxLen` has no field override. Default 4000. */
289
289
  defaultMaxLen?: number;
290
- /** Maximum JSON request size in characters. Default 32768. */
290
+ /** Maximum JSON request size in UTF-8 bytes, enforced while streaming. Default 32768. */
291
291
  bodyCap?: number;
292
292
  }
293
293
  /** Fully resolved formation intake policy. */
@@ -590,7 +590,7 @@ interface ChapterApplication {
590
590
  /** Per-field character cap. Fields not listed use `defaultMaxLen`. */
591
591
  maxLen?: Record<string, number>;
592
592
  defaultMaxLen?: number;
593
- /** Max JSON request body in bytes. Default 32768. */
593
+ /** Max JSON request body in UTF-8 bytes, enforced while streaming. Default 32768. */
594
594
  bodyCap?: number;
595
595
  /** Reject a submit that carries no truthy `disclaimerAck` (400), instead of
596
596
  * writing a row with no consent record. Default `true`. Set `false`
@@ -3637,6 +3637,47 @@ async function memberSessionApplication(db, chapterId, runtime, identity) {
3637
3637
  ) };
3638
3638
  }
3639
3639
 
3640
+ // src/bounded-request-text.ts
3641
+ async function readBoundedRequestText(req, maxBytes) {
3642
+ if (!Number.isSafeInteger(maxBytes) || maxBytes < 0) throw new TypeError("invalid request body cap");
3643
+ const reader = req.body?.getReader();
3644
+ const cancel = () => {
3645
+ void reader?.cancel().catch(() => {
3646
+ });
3647
+ };
3648
+ const invalid2 = () => json({ error: "invalid JSON body" }, 400);
3649
+ let complete = false;
3650
+ req.signal.addEventListener("abort", cancel, { once: true });
3651
+ try {
3652
+ if (req.signal.aborted) return invalid2();
3653
+ const declared = req.headers.get("content-length");
3654
+ if (declared && /^\d+$/.test(declared) && Number(declared) > maxBytes) {
3655
+ return json({ error: "request body too large" }, 413);
3656
+ }
3657
+ if (!reader) return "";
3658
+ const decoder = new TextDecoder();
3659
+ let bytes = 0;
3660
+ let text6 = "";
3661
+ for (; ; ) {
3662
+ const { done, value } = await reader.read();
3663
+ if (req.signal.aborted) return invalid2();
3664
+ if (done) {
3665
+ complete = true;
3666
+ return text6 + decoder.decode();
3667
+ }
3668
+ bytes += value.byteLength;
3669
+ if (bytes > maxBytes) return json({ error: "request body too large" }, 413);
3670
+ text6 += decoder.decode(value, { stream: true });
3671
+ }
3672
+ } catch {
3673
+ return invalid2();
3674
+ } finally {
3675
+ req.signal.removeEventListener("abort", cancel);
3676
+ if (!complete) cancel();
3677
+ reader?.releaseLock();
3678
+ }
3679
+ }
3680
+
3640
3681
  // src/clerk.ts
3641
3682
  async function rejectedCreate(res) {
3642
3683
  const body = typeof res.json === "function" ? await res.json().catch(() => null) : null;
@@ -4055,8 +4096,8 @@ var handleMember = async (req, url, env, ctx) => {
4055
4096
  return resumeJoin(url, env, ctx);
4056
4097
  }
4057
4098
  if (req.method === "POST" && url.pathname === "/api/applications") {
4058
- const raw2 = await req.text();
4059
- if (raw2.length > chapter.application.bodyCap) return json({ error: "request body too large" }, 413);
4099
+ const raw2 = await readBoundedRequestText(req, chapter.application.bodyCap);
4100
+ if (raw2 instanceof Response) return raw2;
4060
4101
  let parsed;
4061
4102
  try {
4062
4103
  parsed = JSON.parse(raw2);
@@ -6951,8 +6992,8 @@ async function firstRow2(db, ns, q) {
6951
6992
  async function startSubscription(req, env, ctx) {
6952
6993
  let body;
6953
6994
  try {
6954
- const raw2 = await req.text();
6955
- if (raw2.length > ctx.chapter.application.bodyCap) return json({ error: "request body too large" }, 413);
6995
+ const raw2 = await readBoundedRequestText(req, ctx.chapter.application.bodyCap);
6996
+ if (raw2 instanceof Response) return raw2;
6956
6997
  body = JSON.parse(raw2);
6957
6998
  if (!body || typeof body !== "object" || Array.isArray(body)) throw new TypeError("body");
6958
6999
  } catch {
@@ -9292,6 +9333,9 @@ var handleAdminNetworkPush = async (req, url, env, ctx) => {
9292
9333
  if (req.method !== "POST" || url.pathname !== "/api/admin/network/push") return null;
9293
9334
  const got = await gate6(req, env, ctx);
9294
9335
  if ("response" in got) return got.response;
9336
+ if (!await networkWriteAllowed(req, got.db, got.user, ctx)) {
9337
+ return json({ error: "super-admin required", code: "super_admin_required" }, 403);
9338
+ }
9295
9339
  let body;
9296
9340
  try {
9297
9341
  body = await req.json();
@@ -11823,8 +11867,8 @@ var handleFormation = async (req, url, env, ctx) => {
11823
11867
  });
11824
11868
  }
11825
11869
  if (req.method !== "POST" || url.pathname !== "/api/formation/applications") return null;
11826
- const raw2 = await req.text();
11827
- if (raw2.length > formation.bodyCap) return json({ error: "request body too large" }, 413);
11870
+ const raw2 = await readBoundedRequestText(req, formation.bodyCap);
11871
+ if (raw2 instanceof Response) return raw2;
11828
11872
  let body;
11829
11873
  try {
11830
11874
  body = JSON.parse(raw2);