stitchkit 0.117.0 → 0.118.1

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/CHANGELOG.md CHANGED
@@ -15,6 +15,32 @@ additive**; the first breaking change landed in 0.10.0. Grep the file for
15
15
 
16
16
  ## [Unreleased]
17
17
 
18
+ ## [0.118.1] - 2026-11-03
19
+
20
+ ### Added
21
+
22
+ - `stitchkit/server` — `staticRoute(prefix, dir, { cacheControl })` sets `Cache-Control` on every file the route serves, its `304`s and ranges included. [ADR 0281](docs/decisions/0281-static-files-are-served-by-one-responder.md)
23
+ **Affects:** `stitchkit/server` staticRoute, StaticRouteOptions
24
+
25
+ ### Fixed
26
+
27
+ - `stitchkit/server` — `staticRoute` serves a file the way `serveFile` does. It read the whole file into memory and answered without `ETag`, `Last-Modified` or `Accept-Ranges`, so a browser re-downloaded every image and video on every visit, and a `<video>` that needs `206` (Safari, a Telegram Mini App on iOS) got the whole file. It now streams the body, revalidates to `304` and answers a single range with `206` or `416`; containment refusals are unchanged. `serveFile` runs on Node as well as Bun, and on both a directory is `404`. [ADR 0281](docs/decisions/0281-static-files-are-served-by-one-responder.md)
28
+ **Affects:** `stitchkit/server` staticRoute(behaviour), serveFile(behaviour)
29
+
30
+ ## [0.118.0] - 2026-11-03
31
+
32
+ ### ⚠️ Breaking changes
33
+
34
+ **Who must act:** an application with its own `TelegramUpdateStore`. A bot on a store shipped here needs no change. See the [migration](docs/guide/upgrading.md).
35
+
36
+ - `stitchkit/telegram` — **`TelegramUpdateStore.due` is paged by `afterUpdateId`.** A row stays due until it is claimed, so the first page of a long queue hid the rest. A store has to continue strictly after the cursor; one that ignores it fails the sweep by name rather than reading the same page for ever, and `checkTelegramUpdateStore` holds the rule. `// before: due({ now, pendingBefore, limit })` → `// after: due({ now, pendingBefore, limit, afterUpdateId })`
37
+ **Affects:** `stitchkit/telegram` TelegramUpdateStore(due), TelegramUpdateDueQuery, checkTelegramUpdateStore, createTelegramUpdateIntake(store)
38
+
39
+ ### Fixed
40
+
41
+ - `stitchkit/telegram` — after a crash the update intake keeps every chat in order however long the queue was. `start` took 100 rows and left the rest to `pendingGraceMs`, while a new update of the same chat ran at once; an update arriving during the start overtook even a short queue. `start` now schedules the whole queue, what arrives meanwhile waits behind it, and a sweep reaches past a full page of rows already scheduled. A `retry` that throws no longer breaks its chat's line; `onStoreError` reports it. `close` no longer works through the queue: it waits for the handlers running, as it says, and the rest stays recorded.
42
+ **Affects:** `stitchkit/telegram` createTelegramUpdateIntake(behaviour)
43
+
18
44
  ## [0.117.0] - 2026-11-02
19
45
 
20
46
  ### ⚠️ Breaking changes
@@ -9,7 +9,7 @@ export { type CacheOptions, cacheHeaders, createCache } from '../server/cache.js
9
9
  export { createHandler } from '../server/create.js';
10
10
  export { createErrorHook, type ErrorHookBase, type ErrorHookConfig, type ErrorHookMapping, type ResolvedError, } from '../server/error-hook.js';
11
11
  export { createEventBus, type DefaultEventMap, type EventBus, type EventBusOptions, type EventHandler, } from '../server/event-bus.js';
12
- export { type ByteRange, parseByteRange, type ServeFileOptions, serveFile, staticRoute, weakETag, } from '../server/file.js';
12
+ export { type ByteRange, parseByteRange, type ServeFileOptions, type StaticRouteOptions, serveFile, staticRoute, weakETag, } from '../server/file.js';
13
13
  export { createImplement, createMultipartStream, createScopedImplement, defineMultipartStream, type ImplementOptions, implement, type MissingHandlerPolicy, type MultipartStreamConfig, type StreamScope, } from '../server/implement.js';
14
14
  export { createImplementRegistry, createScopedImplementRegistry, type ExactRegistryHandlers, type ExactScopedRegistryHandlers, type ImplementationRegistry, implementRegistry, type KeyedServices, type RegistryHandlers, type ScopedImplementationRegistry, type ScopedRegistryHandlers, } from '../server/implement-registry.js';
15
15
  export { composeLifecycleHooks } from '../server/lifecycle.js';
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/entrypoints/server.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/entrypoints/server.ts"],"names":[],"mappings":"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"}
@@ -853,7 +853,8 @@ function createEventBus(options = {}) {
853
853
  };
854
854
  }
855
855
  // src/server/file.ts
856
- import { readFile, stat } from "node:fs/promises";
856
+ import { createReadStream } from "node:fs";
857
+ import { stat } from "node:fs/promises";
857
858
  import { resolve } from "node:path";
858
859
 
859
860
  // src/server/mime.ts
@@ -893,7 +894,7 @@ function mimeForPath(path) {
893
894
  }
894
895
 
895
896
  // src/server/file.ts
896
- function staticRoute(prefix, dir) {
897
+ function staticRoute(prefix, dir, options = {}) {
897
898
  const cleanPrefix = prefix.replace(/\/+$/, "");
898
899
  const root = resolve(dir.replace(/\/+$/, ""));
899
900
  return {
@@ -913,15 +914,7 @@ function staticRoute(prefix, dir) {
913
914
  const realTarget = await realPathWithinDir(root, target);
914
915
  if (realTarget === null)
915
916
  return new Response("Not found", { status: 404 });
916
- const info = await stat(realTarget).catch(() => null);
917
- if (!info?.isFile())
918
- return new Response("Not found", { status: 404 });
919
- return new Response(await readFile(realTarget), {
920
- headers: {
921
- "Content-Type": mimeForPath(realTarget),
922
- "X-Content-Type-Options": "nosniff"
923
- }
924
- });
917
+ return serveFile(req, { path: realTarget, cacheControl: options.cacheControl });
925
918
  }
926
919
  };
927
920
  }
@@ -1021,12 +1014,12 @@ async function serveFile(req, opts) {
1021
1014
  headers: { Allow: "GET, HEAD" }
1022
1015
  });
1023
1016
  }
1024
- const file = Bun.file(opts.path);
1025
- if (!await file.exists()) {
1017
+ const info = await stat(opts.path).catch(() => null);
1018
+ if (!info?.isFile()) {
1026
1019
  return new Response("Not found", { status: 404 });
1027
1020
  }
1028
- const size = file.size;
1029
- const mtimeMs = file.lastModified;
1021
+ const size = info.size;
1022
+ const mtimeMs = info.mtimeMs;
1030
1023
  const contentType = opts.contentType ?? mimeForPath(opts.path);
1031
1024
  const useEtag = opts.etag !== false;
1032
1025
  const useLastModified = opts.lastModified !== false;
@@ -1065,12 +1058,33 @@ async function serveFile(req, opts) {
1065
1058
  base.set("Content-Type", contentType);
1066
1059
  base.set("Content-Range", `bytes ${range.start}-${range.end}/${size}`);
1067
1060
  base.set("Content-Length", String(length));
1068
- const body = isHead ? null : file.slice(range.start, range.end + 1);
1061
+ const body = isHead ? null : fileBody(opts.path, range.start, range.end);
1069
1062
  return new Response(body, { status: 206, headers: base });
1070
1063
  }
1071
1064
  base.set("Content-Type", contentType);
1072
1065
  base.set("Content-Length", String(size));
1073
- return new Response(isHead ? null : file, { status: 200, headers: base });
1066
+ return new Response(isHead || size === 0 ? null : fileBody(opts.path, 0, size - 1), {
1067
+ status: 200,
1068
+ headers: base
1069
+ });
1070
+ }
1071
+ function fileBody(path, start, end) {
1072
+ if ("Bun" in globalThis)
1073
+ return Bun.file(path).slice(start, end + 1);
1074
+ const source = createReadStream(path, { start, end });
1075
+ const chunks = source[Symbol.asyncIterator]();
1076
+ return new ReadableStream({
1077
+ async pull(controller) {
1078
+ const next = await chunks.next();
1079
+ if (next.done)
1080
+ controller.close();
1081
+ else
1082
+ controller.enqueue(next.value);
1083
+ },
1084
+ cancel() {
1085
+ source.destroy();
1086
+ }
1087
+ });
1074
1088
  }
1075
1089
  // src/server/lifecycle.ts
1076
1090
  function composeLifecycleHooks(...hooks) {
@@ -1622,6 +1622,72 @@ function createTelegramUpdateWorkSlots(maxConcurrent) {
1622
1622
  }
1623
1623
  };
1624
1624
  }
1625
+ function lineOf(update) {
1626
+ for (const [key, payload] of Object.entries(update)) {
1627
+ if (key === "update_id" || !isRecord(payload))
1628
+ continue;
1629
+ const message = isRecord(payload.message) ? payload.message : undefined;
1630
+ for (const holder of [
1631
+ payload.chat,
1632
+ message?.chat,
1633
+ payload.from,
1634
+ payload.user,
1635
+ payload.voter_chat
1636
+ ]) {
1637
+ if (isRecord(holder) && (typeof holder.id === "number" || typeof holder.id === "string")) {
1638
+ return `chat:${holder.id}`;
1639
+ }
1640
+ }
1641
+ }
1642
+ return `update:${String(update.update_id)}`;
1643
+ }
1644
+ function createTelegramUpdateLines(run) {
1645
+ const lines = new Map;
1646
+ const scheduled = new Set;
1647
+ return {
1648
+ has: (updateId) => scheduled.has(updateId),
1649
+ schedule(update) {
1650
+ const updateId = Number(update.update_id);
1651
+ if (scheduled.has(updateId))
1652
+ return false;
1653
+ scheduled.add(updateId);
1654
+ const line = lineOf(update);
1655
+ const next = (lines.get(line) ?? Promise.resolve()).then(() => run(update, updateId)).finally(() => {
1656
+ scheduled.delete(updateId);
1657
+ if (lines.get(line) === next)
1658
+ lines.delete(line);
1659
+ });
1660
+ lines.set(line, next);
1661
+ return true;
1662
+ },
1663
+ pending: () => [...lines.values()]
1664
+ };
1665
+ }
1666
+ async function readTelegramUpdatesDue(store, read) {
1667
+ const { skip, parse, reading, ...query } = read;
1668
+ const rows = [];
1669
+ let afterUpdateId;
1670
+ for (;; ) {
1671
+ const due = await store.due({
1672
+ ...query,
1673
+ ...afterUpdateId !== undefined && { afterUpdateId }
1674
+ });
1675
+ for (const row of due) {
1676
+ if (afterUpdateId !== undefined && row.updateId <= afterUpdateId) {
1677
+ throw new TypeError("[stitchkit] telegram update store: due returned a row at or before afterUpdateId");
1678
+ }
1679
+ if (skip(row.updateId))
1680
+ continue;
1681
+ const update = parse(row.body);
1682
+ if (update)
1683
+ rows.push(update);
1684
+ }
1685
+ afterUpdateId = due.at(-1)?.updateId;
1686
+ if (due.length === 0 || !reading())
1687
+ return rows;
1688
+ await new Promise((resolve) => setTimeout(resolve, 0));
1689
+ }
1690
+ }
1625
1691
  function createTelegramUpdateExhaustionScheduler(options) {
1626
1692
  const { config, store, now, slots, parse, reportStoreError } = options;
1627
1693
  const handler = config.handleExhaustion;
@@ -1829,25 +1895,6 @@ function defaultRetry(error, attempt) {
1829
1895
  const backoff = Math.min(5000 * 4 ** (attempt - 1), 15 * 60000);
1830
1896
  return Math.max(backoff, (failure.retryAfterSeconds ?? 0) * 1000);
1831
1897
  }
1832
- function lineOf(update) {
1833
- for (const [key, payload] of Object.entries(update)) {
1834
- if (key === "update_id" || !isRecord(payload))
1835
- continue;
1836
- const message = isRecord(payload.message) ? payload.message : undefined;
1837
- for (const holder of [
1838
- payload.chat,
1839
- message?.chat,
1840
- payload.from,
1841
- payload.user,
1842
- payload.voter_chat
1843
- ]) {
1844
- if (isRecord(holder) && (typeof holder.id === "number" || typeof holder.id === "string")) {
1845
- return `chat:${holder.id}`;
1846
- }
1847
- }
1848
- }
1849
- return `update:${String(update.update_id)}`;
1850
- }
1851
1898
  function parsed(body) {
1852
1899
  try {
1853
1900
  const value = JSON.parse(body);
@@ -1868,7 +1915,7 @@ function isTelegramUpdateFencedStore(store) {
1868
1915
  function isTelegramUpdateDurableStore(store) {
1869
1916
  return isTelegramUpdateFencedStore(store) && "exhaust" in store && typeof store.exhaust === "function" && "dueExhaustions" in store && typeof store.dueExhaustions === "function" && "acknowledgeExhaustion" in store && typeof store.acknowledgeExhaustion === "function";
1870
1917
  }
1871
- function createTelegramUpdateIntake(config) {
1918
+ function storeCapabilities(config) {
1872
1919
  const { store } = config;
1873
1920
  const exhaustionHandler = config.handleExhaustion;
1874
1921
  const attemptAware = "handleAttempt" in config;
@@ -1880,6 +1927,11 @@ function createTelegramUpdateIntake(config) {
1880
1927
  if (exhaustionHandler && !durable) {
1881
1928
  throw new TypeError("[stitchkit] telegram update intake: handleExhaustion requires a TelegramUpdateDurableStore");
1882
1929
  }
1930
+ return { fenced, durable };
1931
+ }
1932
+ function createTelegramUpdateIntake(config) {
1933
+ const { store } = config;
1934
+ const { fenced, durable } = storeCapabilities(config);
1883
1935
  const now = config.now ?? Date.now;
1884
1936
  const retry = config.retry ?? defaultRetry;
1885
1937
  const maxAttempts = positive("maxAttempts", config.maxAttempts ?? 5);
@@ -1888,13 +1940,12 @@ function createTelegramUpdateIntake(config) {
1888
1940
  const sweepEveryMs = positive("sweepEveryMs", config.sweepEveryMs ?? 30000);
1889
1941
  const retainMs = config.retainMs ?? 86400000;
1890
1942
  const maxConcurrent = positive("maxConcurrent", config.maxConcurrent ?? 32);
1891
- const lines = new Map;
1892
- const scheduled = new Set;
1893
1943
  let started = false;
1894
1944
  let starting;
1895
1945
  let lifecycleGeneration = 0;
1896
1946
  let timer;
1897
1947
  let exhaustionCursor;
1948
+ let arrivals;
1898
1949
  const storeError = (error, step) => {
1899
1950
  try {
1900
1951
  config.onStoreError?.(error, step);
@@ -1924,34 +1975,38 @@ function createTelegramUpdateIntake(config) {
1924
1975
  },
1925
1976
  ownershipLostReason: (identity) => new TelegramUpdateOwnershipLostError(identity)
1926
1977
  });
1927
- const schedule = (update) => {
1928
- const updateId = Number(update.update_id);
1929
- if (!started || scheduled.has(updateId))
1930
- return false;
1931
- scheduled.add(updateId);
1932
- const line = lineOf(update);
1933
- const next = (lines.get(line) ?? Promise.resolve()).then(async () => {
1934
- await slots.take();
1935
- try {
1978
+ const lines = createTelegramUpdateLines(async (update, updateId) => {
1979
+ await slots.take();
1980
+ try {
1981
+ if (started)
1936
1982
  await attempt(update, updateId);
1937
- } finally {
1938
- slots.release();
1939
- }
1940
- }).finally(() => {
1941
- scheduled.delete(updateId);
1942
- if (lines.get(line) === next)
1943
- lines.delete(line);
1944
- });
1945
- lines.set(line, next);
1946
- return true;
1947
- };
1948
- const sweep = async (pendingBefore) => {
1983
+ } catch (error) {
1984
+ storeError(error, "settle");
1985
+ } finally {
1986
+ slots.release();
1987
+ }
1988
+ });
1989
+ const schedule = (update) => started && lines.schedule(update);
1990
+ const sweep = async (pendingBefore, waiting = []) => {
1949
1991
  const at = now();
1950
- const due = await store.due({ now: at, pendingBefore, limit: SWEEP_BATCH });
1992
+ const generation = lifecycleGeneration;
1993
+ const rows = await readTelegramUpdatesDue(store, {
1994
+ now: at,
1995
+ pendingBefore,
1996
+ limit: SWEEP_BATCH,
1997
+ skip: lines.has,
1998
+ parse: parsed,
1999
+ reading: () => started
2000
+ });
2001
+ if (generation !== lifecycleGeneration)
2002
+ return 0;
2003
+ if (arrivals === waiting)
2004
+ arrivals = undefined;
2005
+ const waited = new Set(waiting);
2006
+ const queue = [...rows, ...waiting].sort((left, right) => Number(left.update_id) - Number(right.update_id));
1951
2007
  let count = 0;
1952
- for (const row of due) {
1953
- const update = parsed(row.body);
1954
- if (update && schedule(update))
2008
+ for (const update of queue) {
2009
+ if (schedule(update) && !waited.has(update))
1955
2010
  count += 1;
1956
2011
  }
1957
2012
  if (durable) {
@@ -1972,6 +2027,11 @@ function createTelegramUpdateIntake(config) {
1972
2027
  await store.prune(at - retainMs);
1973
2028
  return count;
1974
2029
  };
2030
+ const drained = async () => {
2031
+ while (lines.pending().length > 0 || exhaustionScheduler.pending().length > 0) {
2032
+ await Promise.all([...lines.pending(), ...exhaustionScheduler.pending()]);
2033
+ }
2034
+ };
1975
2035
  return {
1976
2036
  async accept(body) {
1977
2037
  const update = parsed(body);
@@ -1981,7 +2041,10 @@ function createTelegramUpdateIntake(config) {
1981
2041
  const added = await store.add({ updateId, body, receivedAt: now() });
1982
2042
  if (!added)
1983
2043
  return "duplicate";
1984
- schedule(update);
2044
+ if (arrivals)
2045
+ arrivals.push(update);
2046
+ else
2047
+ schedule(update);
1985
2048
  return "accepted";
1986
2049
  },
1987
2050
  start() {
@@ -1991,13 +2054,18 @@ function createTelegramUpdateIntake(config) {
1991
2054
  return Promise.resolve();
1992
2055
  started = true;
1993
2056
  const generation = ++lifecycleGeneration;
2057
+ const waiting = [];
2058
+ arrivals = waiting;
1994
2059
  const operation = (async () => {
1995
2060
  try {
1996
- await sweep(now());
2061
+ await sweep(now(), waiting);
1997
2062
  } catch (error) {
1998
2063
  if (started && lifecycleGeneration === generation)
1999
2064
  started = false;
2000
2065
  throw error;
2066
+ } finally {
2067
+ if (arrivals === waiting)
2068
+ arrivals = undefined;
2001
2069
  }
2002
2070
  if (!started || lifecycleGeneration !== generation)
2003
2071
  return;
@@ -2018,9 +2086,10 @@ function createTelegramUpdateIntake(config) {
2018
2086
  },
2019
2087
  sweep: () => sweep(now() - pendingGraceMs),
2020
2088
  async idle() {
2021
- while (lines.size > 0 || exhaustionScheduler.pending().length > 0) {
2022
- await Promise.all([...lines.values(), ...exhaustionScheduler.pending()]);
2023
- }
2089
+ await starting?.catch(() => {
2090
+ return;
2091
+ });
2092
+ await drained();
2024
2093
  },
2025
2094
  async close() {
2026
2095
  started = false;
@@ -2036,9 +2105,7 @@ function createTelegramUpdateIntake(config) {
2036
2105
  return;
2037
2106
  });
2038
2107
  }
2039
- while (lines.size > 0 || exhaustionScheduler.pending().length > 0) {
2040
- await Promise.all([...lines.values(), ...exhaustionScheduler.pending()]);
2041
- }
2108
+ await drained();
2042
2109
  }
2043
2110
  };
2044
2111
  }
@@ -2121,7 +2188,7 @@ function memoryTelegramUpdateStore() {
2121
2188
  settleCurrent(updateId, attempt, settlement);
2122
2189
  },
2123
2190
  async due(query) {
2124
- return [...records.values()].filter((record) => record.state === "pending" ? record.receivedAt <= query.pendingBefore : isClaimable(record, query.now)).sort((left, right) => left.updateId - right.updateId).slice(0, query.limit).map(({ updateId, body }) => ({ updateId, body }));
2191
+ return [...records.values()].filter((record) => record.state === "pending" ? record.receivedAt <= query.pendingBefore : isClaimable(record, query.now)).filter((record) => query.afterUpdateId === undefined || record.updateId > query.afterUpdateId).sort((left, right) => left.updateId - right.updateId).slice(0, query.limit).map(({ updateId, body }) => ({ updateId, body }));
2125
2192
  },
2126
2193
  async owns(identity, at) {
2127
2194
  const record = holding(identity.updateId, identity.attempt, identity.claimId);
@@ -2254,6 +2321,27 @@ var RULES = {
2254
2321
  if (!same(await store.due({ now: 100, pendingBefore: 0, limit: 10 }), []))
2255
2322
  violated("an update past maxAttempts was not abandoned");
2256
2323
  },
2324
+ "due continues strictly after its cursor": async (store, violated) => {
2325
+ for (const updateId of [1, 2, 3]) {
2326
+ await store.add({ updateId, body: "b", receivedAt: 0 });
2327
+ }
2328
+ await store.claim(2, { ...claim, now: 0, leaseUntil: 10 });
2329
+ await store.settle(2, 1, { state: "failed", at: 1, retryAt: 5, error: "x" });
2330
+ const page = async (afterUpdateId) => (await store.due({
2331
+ now: 5,
2332
+ pendingBefore: 0,
2333
+ limit: 2,
2334
+ ...afterUpdateId !== undefined && { afterUpdateId }
2335
+ })).map(({ updateId }) => updateId);
2336
+ if (!same(await page(), [1, 2]))
2337
+ violated("due did not begin at the first row");
2338
+ if (!same(await page(1), [2, 3]))
2339
+ violated("due lost a retry after its cursor");
2340
+ if (!same(await page(2), [3]))
2341
+ violated("due did not continue after its cursor");
2342
+ if (!same(await page(3), []))
2343
+ violated("due listed a row at or before its cursor");
2344
+ },
2257
2345
  "settled updates are forgotten after they are pruned": async (store, violated) => {
2258
2346
  await store.add({ updateId: 3, body: "b", receivedAt: 0 });
2259
2347
  await store.claim(3, { ...claim, now: 0, leaseUntil: 100 });
@@ -2632,9 +2720,15 @@ function postgresTelegramUpdateStore(config) {
2632
2720
  },
2633
2721
  async due(options) {
2634
2722
  const rows = await run(`SELECT update_id, body FROM ${table}
2635
- WHERE (state = 'pending' AND received_at <= $2::bigint)
2636
- OR (state <> 'pending' AND ${CLAIMABLE})
2637
- ORDER BY update_id LIMIT $3::integer`, [options.now, options.pendingBefore, options.limit]);
2723
+ WHERE ((state = 'pending' AND received_at <= $2::bigint)
2724
+ OR (state <> 'pending' AND ${CLAIMABLE}))
2725
+ AND update_id > $4::bigint
2726
+ ORDER BY update_id LIMIT $3::integer`, [
2727
+ options.now,
2728
+ options.pendingBefore,
2729
+ options.limit,
2730
+ options.afterUpdateId ?? Number.MIN_SAFE_INTEGER
2731
+ ]);
2638
2732
  return rows.flatMap((row) => {
2639
2733
  const updateId = integer(field(row, "update_id"));
2640
2734
  const body = field(row, "body");
@@ -2743,7 +2837,8 @@ function prepareTelegramSqliteUpdateStatements(database, table) {
2743
2837
  WHERE update_id = ?5 AND attempts = ?6 AND state = 'processing' AND due_at >= ?7
2744
2838
  AND (?8 IS NULL OR claim_id = ?8)`),
2745
2839
  due: database.prepare(`SELECT update_id, body FROM ${table}
2746
- WHERE (state = 'pending' AND received_at <= ?2) OR (state <> 'pending' AND ${CLAIMABLE2})
2840
+ WHERE ((state = 'pending' AND received_at <= ?2) OR (state <> 'pending' AND ${CLAIMABLE2}))
2841
+ AND update_id > ?4
2747
2842
  ORDER BY update_id LIMIT ?3`),
2748
2843
  prune: database.prepare(`DELETE FROM ${table} WHERE state IN ('completed', 'abandoned') AND settled_at < ?1`),
2749
2844
  owns: database.prepare(`SELECT update_id FROM ${table}
@@ -2827,7 +2922,7 @@ function sqliteTelegramUpdateStore(config) {
2827
2922
  settleCurrent(updateId, attempt, settlement);
2828
2923
  },
2829
2924
  async due(query) {
2830
- return due.all(query.now, query.pendingBefore, query.limit).flatMap((row) => rowOf(row) ?? []);
2925
+ return due.all(query.now, query.pendingBefore, query.limit, query.afterUpdateId ?? Number.MIN_SAFE_INTEGER).flatMap((row) => rowOf(row) ?? []);
2831
2926
  },
2832
2927
  async owns(identity, at) {
2833
2928
  return owns.all(identity.updateId, identity.attempt, at, identity.claimId).length > 0;
@@ -1,9 +1,15 @@
1
1
  import type { RawRoute } from './types.js';
2
+ /** What a `staticRoute` adds to every file it serves. */
3
+ export interface StaticRouteOptions {
4
+ /** Value for the `Cache-Control` header, verbatim. */
5
+ cacheControl?: string;
6
+ }
2
7
  /**
3
- * Build a basic static-file route. URL-derived paths pass both lexical and
4
- * realpath containment checks, so an in-root symlink cannot escape `dir`.
8
+ * Build a static-file route. URL-derived paths pass both lexical and realpath
9
+ * containment checks, so an in-root symlink cannot escape `dir`; the file
10
+ * itself is answered by `serveFile` (Range, validators, `304`, streaming).
5
11
  */
6
- export declare function staticRoute(prefix: string, dir: string): RawRoute;
12
+ export declare function staticRoute(prefix: string, dir: string, options?: StaticRouteOptions): RawRoute;
7
13
  /** An inclusive byte range `[start, end]`. */
8
14
  export interface ByteRange {
9
15
  start: number;
@@ -49,10 +55,10 @@ export interface ServeFileOptions {
49
55
  * `405` (non GET/HEAD), `404` (missing), `304` (fresh per `If-None-Match` /
50
56
  * `If-Modified-Since`), `200` (full — no/ignored Range), `206` (range), or `416`
51
57
  * (unsatisfiable range). Always sets `Accept-Ranges: bytes` and `nosniff`; a
52
- * `HEAD` returns every header with an empty body.
58
+ * `HEAD` returns every header with an empty body. A directory is `404`.
53
59
  *
54
- * Bun-first (`Bun.file`). The caller is responsible for path containment — for
55
- * URL-derived paths use `staticRoute` or `isWithinDir`. → ADR 0023.
60
+ * Runs on Bun and Node. The caller is responsible for path containment — for
61
+ * URL-derived paths use `staticRoute` or `isWithinDir`. → ADR 0023, ADR 0281.
56
62
  */
57
63
  export declare function serveFile(req: Request, opts: ServeFileOptions): Promise<Response>;
58
64
  //# sourceMappingURL=file.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"file.d.ts","sourceRoot":"","sources":["../../src/server/file.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAExC;;;GAGG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,QAAQ,CA4BjE;AAED,8CAA8C;AAC9C,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;CACb;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAC5B,MAAM,EAAE,MAAM,GAAG,IAAI,EACrB,IAAI,EAAE,MAAM,GACX,SAAS,GAAG,eAAe,GAAG,IAAI,CAkCpC;AAED;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAE9D;AA6ED,MAAM,WAAW,gBAAgB;IAC/B,gFAAgF;IAChF,IAAI,EAAE,MAAM,CAAC;IACb,qEAAqE;IACrE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,qDAAqD;IACrD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,8EAA8E;IAC9E,WAAW,CAAC,EAAE,QAAQ,GAAG,YAAY,CAAC;IACtC,sDAAsD;IACtD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,2CAA2C;IAC3C,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,6CAA6C;IAC7C,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAED;;;;;;;;;GASG;AACH,wBAAsB,SAAS,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAAC,QAAQ,CAAC,CAwEvF"}
1
+ {"version":3,"file":"file.d.ts","sourceRoot":"","sources":["../../src/server/file.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAExC,yDAAyD;AACzD,MAAM,WAAW,kBAAkB;IACjC,sDAAsD;IACtD,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CACzB,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,MAAM,EACX,OAAO,GAAE,kBAAuB,GAC/B,QAAQ,CAqBV;AAED,8CAA8C;AAC9C,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;CACb;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAC5B,MAAM,EAAE,MAAM,GAAG,IAAI,EACrB,IAAI,EAAE,MAAM,GACX,SAAS,GAAG,eAAe,GAAG,IAAI,CAkCpC;AAED;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAE9D;AA6ED,MAAM,WAAW,gBAAgB;IAC/B,gFAAgF;IAChF,IAAI,EAAE,MAAM,CAAC;IACb,qEAAqE;IACrE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,qDAAqD;IACrD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,8EAA8E;IAC9E,WAAW,CAAC,EAAE,QAAQ,GAAG,YAAY,CAAC;IACtC,sDAAsD;IACtD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,2CAA2C;IAC3C,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,6CAA6C;IAC7C,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAED;;;;;;;;;GASG;AACH,wBAAsB,SAAS,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAAC,QAAQ,CAAC,CA0EvF"}
@@ -1,5 +1,5 @@
1
1
  import type { TelegramUpdateAttemptIntakeConfig, TelegramUpdateEnvelope, TelegramUpdateIntakeConfig, TelegramUpdateStoreStep } from './update-intake.js';
2
- import type { StoredTelegramUpdateExhaustion, TelegramUpdateAttemptIdentity, TelegramUpdateDurableStore, TelegramUpdateFencedStore, TelegramUpdateStore } from './update-store.js';
2
+ import type { StoredTelegramUpdateExhaustion, TelegramUpdateAttemptIdentity, TelegramUpdateDueQuery, TelegramUpdateDurableStore, TelegramUpdateFencedStore, TelegramUpdateStore } from './update-store.js';
3
3
  type TelegramUpdateAnyIntakeConfig<TUpdate extends TelegramUpdateEnvelope> = TelegramUpdateIntakeConfig<TUpdate> | TelegramUpdateAttemptIntakeConfig<TUpdate>;
4
4
  export type TelegramUpdateStoreErrorReporter = (error: unknown, step: TelegramUpdateStoreStep) => void;
5
5
  /** One shared concurrency budget for ordinary attempts and exhaustion delivery. */
@@ -8,6 +8,25 @@ export interface TelegramUpdateWorkSlots {
8
8
  release(): void;
9
9
  }
10
10
  export declare function createTelegramUpdateWorkSlots(maxConcurrent: number): TelegramUpdateWorkSlots;
11
+ /** The updates this process holds, each behind the earlier ones of its chat. */
12
+ export interface TelegramUpdateLines {
13
+ has(updateId: number): boolean;
14
+ /** Join the update's line; `false` when it is already in one. */
15
+ schedule(update: Record<string, unknown>): boolean;
16
+ pending(): readonly Promise<void>[];
17
+ }
18
+ export declare function createTelegramUpdateLines(run: (update: Record<string, unknown>, updateId: number) => Promise<void>): TelegramUpdateLines;
19
+ /**
20
+ * Every row due, read to the empty page. A scheduled row stays due until
21
+ * claimed, so only a cursor reaches past the first page. A store on a
22
+ * synchronous driver never leaves the microtask queue, so each page yields:
23
+ * webhooks and lease renewals run during a read.
24
+ */
25
+ export declare function readTelegramUpdatesDue(store: TelegramUpdateStore, read: Omit<TelegramUpdateDueQuery, 'afterUpdateId'> & {
26
+ readonly skip: (updateId: number) => boolean;
27
+ readonly parse: (body: string) => Record<string, unknown> | undefined;
28
+ readonly reading: () => boolean;
29
+ }): Promise<Record<string, unknown>[]>;
11
30
  export interface TelegramUpdateExhaustionScheduler {
12
31
  schedule(row: StoredTelegramUpdateExhaustion, active: boolean): boolean;
13
32
  pending(): readonly Promise<void>[];
@@ -1 +1 @@
1
- {"version":3,"file":"update-intake-work.d.ts","sourceRoot":"","sources":["../../src/telegram/update-intake-work.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAEV,iCAAiC,EACjC,sBAAsB,EAEtB,0BAA0B,EAC1B,uBAAuB,EACxB,MAAM,iBAAiB,CAAC;AACzB,OAAO,KAAK,EACV,8BAA8B,EAC9B,6BAA6B,EAC7B,0BAA0B,EAC1B,yBAAyB,EACzB,mBAAmB,EACpB,MAAM,gBAAgB,CAAC;AAExB,KAAK,6BAA6B,CAAC,OAAO,SAAS,sBAAsB,IACrE,0BAA0B,CAAC,OAAO,CAAC,GACnC,iCAAiC,CAAC,OAAO,CAAC,CAAC;AAE/C,MAAM,MAAM,gCAAgC,GAAG,CAC7C,KAAK,EAAE,OAAO,EACd,IAAI,EAAE,uBAAuB,KAC1B,IAAI,CAAC;AAEV,mFAAmF;AACnF,MAAM,WAAW,uBAAuB;IACtC,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACtB,OAAO,IAAI,IAAI,CAAC;CACjB;AAED,wBAAgB,6BAA6B,CAAC,aAAa,EAAE,MAAM,GAAG,uBAAuB,CAiB5F;AAED,MAAM,WAAW,iCAAiC;IAChD,QAAQ,CAAC,GAAG,EAAE,8BAA8B,EAAE,MAAM,EAAE,OAAO,GAAG,OAAO,CAAC;IACxE,OAAO,IAAI,SAAS,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;CACrC;AAED,uFAAuF;AACvF,wBAAgB,uCAAuC,CACrD,OAAO,SAAS,sBAAsB,EACtC,OAAO,EAAE;IACT,QAAQ,CAAC,MAAM,EAAE,6BAA6B,CAAC,OAAO,CAAC,CAAC;IACxD,QAAQ,CAAC,KAAK,EAAE,0BAA0B,GAAG,SAAS,CAAC;IACvD,QAAQ,CAAC,GAAG,EAAE,MAAM,MAAM,CAAC;IAC3B,QAAQ,CAAC,KAAK,EAAE,uBAAuB,CAAC;IACxC,QAAQ,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC;IACtE,QAAQ,CAAC,gBAAgB,EAAE,gCAAgC,CAAC;CAC7D,GAAG,iCAAiC,CA4CpC;AAYD,yEAAyE;AACzE,wBAAgB,iCAAiC,CAC/C,OAAO,SAAS,sBAAsB,EACtC,OAAO,EAAE;IACT,QAAQ,CAAC,MAAM,EAAE,6BAA6B,CAAC,OAAO,CAAC,CAAC;IACxD,QAAQ,CAAC,KAAK,EAAE,mBAAmB,CAAC;IACpC,QAAQ,CAAC,MAAM,EAAE,yBAAyB,GAAG,SAAS,CAAC;IACvD,QAAQ,CAAC,OAAO,EAAE,0BAA0B,GAAG,SAAS,CAAC;IACzD,QAAQ,CAAC,GAAG,EAAE,MAAM,MAAM,CAAC;IAC3B,QAAQ,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,KAAK,MAAM,GAAG,KAAK,CAAC;IACpE,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,gBAAgB,EAAE,gCAAgC,CAAC;IAC5D,QAAQ,CAAC,kBAAkB,EAAE,CAAC,GAAG,EAAE,8BAA8B,KAAK,IAAI,CAAC;IAC3E,QAAQ,CAAC,mBAAmB,EAAE,CAAC,QAAQ,EAAE,6BAA6B,KAAK,OAAO,CAAC;CACpF,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CA+JvE"}
1
+ {"version":3,"file":"update-intake-work.d.ts","sourceRoot":"","sources":["../../src/telegram/update-intake-work.ts"],"names":[],"mappings":"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"}
@@ -17,6 +17,12 @@
17
17
  * `pending` whose process never got to it, `failed` whose retry is due,
18
18
  * `processing` whose lease nobody renewed. Delivery is at least once: a
19
19
  * handler that finished but could not be recorded as finished runs again.
20
+ *
21
+ * The start reads the whole queue into the lines of its chats, page by page,
22
+ * and whatever arrives meanwhile waits for it: a line is ordered by when it was
23
+ * joined, so the only way to keep a chat in `update_id` order after a crash is
24
+ * for the rows the dead process left to join first. The attempt the crash
25
+ * interrupted stays under its lease and comes when that lapses.
20
26
  */
21
27
  import type { TelegramUpdateAttemptIdentity, TelegramUpdateFencedStore, TelegramUpdateStore } from './update-store.js';
22
28
  import type { TelegramUpdateAcceptance } from './webhook.js';
@@ -86,7 +92,7 @@ export interface TelegramUpdateIntakeOptions<TUpdate extends TelegramUpdateEnvel
86
92
  readonly handleExhaustion?: (exhaustion: TelegramUpdateExhaustion<TUpdate>) => unknown;
87
93
  /** An observational report that `handleExhaustion` threw; it never acknowledges the row. */
88
94
  readonly onExhaustionFailure?: (failure: TelegramUpdateExhaustionFailure<TUpdate>) => void;
89
- /** The store refused a step; the update is retried when its lease lapses. */
95
+ /** A step failed; the update stays recorded and a later sweep takes it. */
90
96
  readonly onStoreError?: (error: unknown, step: TelegramUpdateStoreStep) => void;
91
97
  /** Default `Date.now`. */
92
98
  readonly now?: () => number;
@@ -106,9 +112,12 @@ export interface TelegramUpdateAttemptIntakeConfig<TUpdate extends TelegramUpdat
106
112
  export interface TelegramUpdateIntake {
107
113
  /** Record one webhook body; handling starts after, once the intake is started. */
108
114
  accept(body: string): Promise<TelegramUpdateAcceptance>;
109
- /** Start handling, beginning with everything recorded before. */
115
+ /**
116
+ * Start handling, beginning with everything recorded before. Resolves once
117
+ * that queue is scheduled, not handled; `accept` works throughout.
118
+ */
110
119
  start(): Promise<void>;
111
- /** Take over what is due now; how many updates were scheduled. */
120
+ /** Take over everything due now; how many updates were scheduled. */
112
121
  sweep(): Promise<number>;
113
122
  /** Resolves once nothing is scheduled. */
114
123
  idle(): Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"update-intake.d.ts","sourceRoot":"","sources":["../../src/telegram/update-intake.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"update-intake.d.ts","sourceRoot":"","sources":["../../src/telegram/update-intake.ts"],"names":[],"mappings":"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"}
@@ -2,7 +2,7 @@
2
2
  * The rules of `TelegramUpdateStore`, as a check any store can be put
3
3
  * through — the ones shipped here and an application's own over its ORM.
4
4
  *
5
- * A store is five conditional statements, and each is easy to write almost
5
+ * A store is six conditional statements, and each is easy to write almost
6
6
  * right: a claim that does not check the lease, a settle that does not check
7
7
  * the attempt, a claim read and then written in two statements so that two
8
8
  * processes both take one update. Nothing fails in a test with one process;
@@ -1 +1 @@
1
- {"version":3,"file":"update-store-check.d.ts","sourceRoot":"","sources":["../../src/telegram/update-store-check.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAGV,mBAAmB,EACpB,MAAM,gBAAgB,CAAC;AAiXxB;;;;GAIG;AACH,wBAAsB,wBAAwB,CAC5C,IAAI,EAAE,MAAM,mBAAmB,GAAG,OAAO,CAAC,mBAAmB,CAAC,GAC7D,OAAO,CAAC,MAAM,EAAE,CAAC,CAYnB"}
1
+ {"version":3,"file":"update-store-check.d.ts","sourceRoot":"","sources":["../../src/telegram/update-store-check.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAGV,mBAAmB,EACpB,MAAM,gBAAgB,CAAC;AAqYxB;;;;GAIG;AACH,wBAAsB,wBAAwB,CAC5C,IAAI,EAAE,MAAM,mBAAmB,GAAG,OAAO,CAAC,mBAAmB,CAAC,GAC7D,OAAO,CAAC,MAAM,EAAE,CAAC,CAYnB"}
@@ -1 +1 @@
1
- {"version":3,"file":"update-store-postgres.d.ts","sourceRoot":"","sources":["../../src/telegram/update-store-postgres.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAGH,OAAO,KAAK,EAIV,0BAA0B,EAE3B,MAAM,gBAAgB,CAAC;AAExB,oFAAoF;AACpF,MAAM,MAAM,qBAAqB,GAAG,CAClC,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,SAAS,CAAC,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC,EAAE,KAC5C,OAAO,CAAC,SAAS,OAAO,EAAE,CAAC,CAAC;AAEjC,MAAM,WAAW,iCAAiC;IAChD,QAAQ,CAAC,KAAK,EAAE,qBAAqB,CAAC;IACtC,kCAAkC;IAClC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB;;;;OAIG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,OAAO,CAAC;CAChC;AAaD,mEAAmE;AACnE,wBAAgB,iCAAiC,CAAC,KAAK,SAAqB,GAAG,MAAM,CAepF;AA4HD,wBAAgB,2BAA2B,CACzC,MAAM,EAAE,iCAAiC,GACxC,0BAA0B,CAmK5B"}
1
+ {"version":3,"file":"update-store-postgres.d.ts","sourceRoot":"","sources":["../../src/telegram/update-store-postgres.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAGH,OAAO,KAAK,EAIV,0BAA0B,EAE3B,MAAM,gBAAgB,CAAC;AAExB,oFAAoF;AACpF,MAAM,MAAM,qBAAqB,GAAG,CAClC,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,SAAS,CAAC,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC,EAAE,KAC5C,OAAO,CAAC,SAAS,OAAO,EAAE,CAAC,CAAC;AAEjC,MAAM,WAAW,iCAAiC;IAChD,QAAQ,CAAC,KAAK,EAAE,qBAAqB,CAAC;IACtC,kCAAkC;IAClC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB;;;;OAIG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,OAAO,CAAC;CAChC;AAaD,mEAAmE;AACnE,wBAAgB,iCAAiC,CAAC,KAAK,SAAqB,GAAG,MAAM,CAepF;AA4HD,wBAAgB,2BAA2B,CACzC,MAAM,EAAE,iCAAiC,GACxC,0BAA0B,CAyK5B"}
@@ -1 +1 @@
1
- {"version":3,"file":"update-store-sqlite.d.ts","sourceRoot":"","sources":["../../src/telegram/update-store-sqlite.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,KAAK,EAKV,0BAA0B,EAE3B,MAAM,gBAAgB,CAAC;AAExB,gFAAgF;AAChF,MAAM,WAAW,sBAAsB;IACrC,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;IAC3B,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG;QACpB,GAAG,CAAC,GAAG,UAAU,EAAE,CAAC,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC,EAAE,GAAG;YAAE,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAAA;SAAE,CAAC;QACtF,GAAG,CAAC,GAAG,UAAU,EAAE,CAAC,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC,EAAE,GAAG,OAAO,EAAE,CAAC;KAC3D,CAAC;CACH;AAED,MAAM,WAAW,+BAA+B;IAC9C,QAAQ,CAAC,QAAQ,EAAE,sBAAsB,CAAC;IAC1C,wDAAwD;IACxD,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;CACzB;AA+ID,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,+BAA+B,GACtC,0BAA0B,CAyI5B"}
1
+ {"version":3,"file":"update-store-sqlite.d.ts","sourceRoot":"","sources":["../../src/telegram/update-store-sqlite.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,KAAK,EAKV,0BAA0B,EAE3B,MAAM,gBAAgB,CAAC;AAExB,gFAAgF;AAChF,MAAM,WAAW,sBAAsB;IACrC,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;IAC3B,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG;QACpB,GAAG,CAAC,GAAG,UAAU,EAAE,CAAC,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC,EAAE,GAAG;YAAE,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAAA;SAAE,CAAC;QACtF,GAAG,CAAC,GAAG,UAAU,EAAE,CAAC,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC,EAAE,GAAG,OAAO,EAAE,CAAC;KAC3D,CAAC;CACH;AAED,MAAM,WAAW,+BAA+B;IAC9C,QAAQ,CAAC,QAAQ,EAAE,sBAAsB,CAAC;IAC1C,wDAAwD;IACxD,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;CACzB;AAgJD,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,+BAA+B,GACtC,0BAA0B,CA8I5B"}
@@ -13,6 +13,8 @@
13
13
  * - `renew` and `settle` act only while the record is still `processing` under
14
14
  * the same attempt, so a process that lost its lease cannot overwrite the
15
15
  * one that took it over;
16
+ * - `due` lists a record until it is claimed, so a queue is read page by page,
17
+ * strictly after `afterUpdateId`;
16
18
  * - a durable abandonment keeps `settledAt` empty until the consumer has
17
19
  * handled its final obligation. It stays in this record and is never pruned
18
20
  * while it is pending.
@@ -43,6 +45,8 @@ export interface TelegramUpdateDueQuery {
43
45
  /** A `pending` record is due once received at or before this — its own process had its chance. */
44
46
  readonly pendingBefore: number;
45
47
  readonly limit: number;
48
+ /** Continue strictly after this `update_id`; omit to begin at the first row. */
49
+ readonly afterUpdateId?: number;
46
50
  }
47
51
  export interface StoredTelegramUpdate {
48
52
  readonly updateId: number;
@@ -78,7 +82,7 @@ export interface TelegramUpdateStore {
78
82
  now?: number): Promise<boolean>;
79
83
  /** End this attempt; ignored when the attempt no longer holds the update. */
80
84
  settle(updateId: number, attempt: number, settlement: TelegramUpdateSettlement): Promise<void>;
81
- /** Claimable updates in `update_id` order. */
85
+ /** One page of claimable updates in `update_id` order; a row stays due until it is claimed. */
82
86
  due(query: TelegramUpdateDueQuery): Promise<readonly StoredTelegramUpdate[]>;
83
87
  /** Forget `completed` and `abandoned` updates settled before `before`; how many were forgotten. */
84
88
  prune(before: number): Promise<number>;
@@ -1 +1 @@
1
- {"version":3,"file":"update-store.d.ts","sourceRoot":"","sources":["../../src/telegram/update-store.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"update-store.d.ts","sourceRoot":"","sources":["../../src/telegram/update-store.ts"],"names":[],"mappings":"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"}
package/llms/server.2.txt CHANGED
@@ -97,11 +97,24 @@ contract route does.
97
97
 
98
98
  ### Serving files & Range requests
99
99
 
100
- `staticRoute` is basic on purpose — it reads the whole file into memory and has
101
- no `Range` or conditional support; put a CDN in front, or use it for small web
102
- assets. To serve **media** (video / audio / large downloads) that a browser must
103
- seek and cache, use **`serveFile`** (Bun) — it streams the requested byte range
104
- and speaks the conditional-request half of RFC 7233 / 9110:
100
+ `staticRoute(prefix, dir, { cacheControl })` serves a directory: it checks
101
+ containment, then answers the file through `serveFile`, so a static image or
102
+ video carries `ETag`, `Last-Modified` and `Accept-Ranges`, revalidates to `304`
103
+ and seeks with `206`. `cacheControl` sets `Cache-Control` on every answer; by
104
+ default none is sent.
105
+
106
+ ```ts
107
+ import { createServer, staticRoute } from 'stitchkit/server'
108
+
109
+ createServer({
110
+ rawRoutes: [staticRoute('/static', './public', { cacheControl: 'public, max-age=3600' })],
111
+ })
112
+ ```
113
+
114
+ The route answers `GET`. To serve a file chosen by your own logic — through a
115
+ contract, with `HEAD`, a download name or a custom type — call **`serveFile`**
116
+ directly. It runs on Bun and Node, streams the requested byte range and speaks
117
+ the conditional-request half of RFC 7233 / 9110:
105
118
 
106
119
  ```ts
107
120
  import { defineContract } from 'stitchkit/contract'
@@ -151,7 +164,7 @@ emit none.
151
164
  `serveFile` takes an explicit `path` and trusts it — **the caller owns
152
165
  containment**. For a URL-derived path use `staticRoute` (which enforces it) or
153
166
  `isWithinDir` first. The byte-range parser is exported on its own as
154
- `parseByteRange(header, size)` for direct use and testing. → ADR 0023.
167
+ `parseByteRange(header, size)` for direct use and testing. → ADR 0023, ADR 0281.
155
168
 
156
169
  ## Server primitives
157
170
 
package/llms/server.5.txt CHANGED
@@ -47,7 +47,8 @@ Also re-exports the error helpers from `stitchkit/contract`.
47
47
  | `KeyedServices` | _type_ | registry result: the mount-ordered `ServiceDef[]` carrying the same services under `.byKey` |
48
48
  | `RegistryHandlers` | _type_ | exact backend handler registry inferred from a contract registry |
49
49
  | `ExactRegistryHandlers` | _type_ | fail-first handler shape that rejects extra registry and endpoint keys |
50
- | `staticRoute` | function | a raw route that serves a directory |
50
+ | `staticRoute` | function | a raw route that serves a contained directory through `serveFile` (`Range` / `304` / validators) |
51
+ | `StaticRouteOptions` | _type_ | `{ cacheControl }` — what `staticRoute` adds to every file it serves |
51
52
  | `serveFile` | function | serve a file with `Range` / `304` / `HEAD` — [guide](../guide/server.md#serving-files--range-requests) |
52
53
  | `parseByteRange` | function | parse a single `Range` header → range / `unsatisfiable` / `null` |
53
54
  | `isWithinDir` | function | path containment — `(root, resolvedTarget) => boolean`; call it before `serveFile` on any URL-derived path |
@@ -89,13 +89,13 @@ own, recorded as an ADR.
89
89
  | `stitchkit/xlsx` | browser **and** server | evolving | a workbook from rows and typed columns, with no dependency; text never runs as a formula |
90
90
  | `stitchkit/oauth` | browser **and** server | evolving | provider-neutral Authorization Code + PKCE request and one-shot callback transaction mechanics |
91
91
  | `stitchkit/google` | server (Bun or Node) | evolving | optional Google code exchange and verified OIDC identity adapter |
92
- | `stitchkit/observability` | server | stable<br>_redefined in 2 of the 62 minors since 0.56.2, most recently 0.92.0_ | request/tool event projections — `createObservability`, trace context, sanitisation |
92
+ | `stitchkit/observability` | server | stable<br>_redefined in 2 of the 63 minors since 0.56.2, most recently 0.92.0_ | request/tool event projections — `createObservability`, trace context, sanitisation |
93
93
  | `stitchkit/testing` | tests on Bun or Node | stable | in-process generated clients over a real Fetch handler, the managed-resource conformance kit and manual time for schedules and retries |
94
94
  | `stitchkit/declaration` | browser + build and deployment tooling (Bun or Node) | evolving | `ProjectDeclarationSchema` — the one machine-readable statement a repository makes about itself |
95
95
  | `stitchkit/react` | browser + server rendering | stable | `createCursorQuery`, `createCacheBridge`, QueryClient and `ApiError` retry policy |
96
96
  | `stitchkit/react/keyboard` | browser | evolving | which part of a screen gets a key — ordered layers, one Escape per level, list zones with real focus; see [keyboard guide](keyboard.md) |
97
97
  | `stitchkit/react/keyboard/testing` | server (Bun or Node) | evolving | the keyboard layers and zones without a DOM — fakes for the window, elements and key events beside the two engines, to test the order of layers, `scope`/`inert` and the edges of a zone in milliseconds |
98
- | `stitchkit/agent-runtime` | server | evolving<br>_redefined in 25 of the 62 minors since 0.56.2, most recently 0.115.0_ | optional durable conversation/run loop, history, models, prompts, fencing and events |
98
+ | `stitchkit/agent-runtime` | server | evolving<br>_redefined in 25 of the 63 minors since 0.56.2, most recently 0.115.0_ | optional durable conversation/run loop, history, models, prompts, fencing and events |
99
99
  | `stitchkit/agent-runtime/sandbox` | server | evolving | optional Linux Bubblewrap sessions, durable workspaces and a host HTTP credential gateway; see [sandbox guide](sandbox.md) |
100
100
  | `stitchkit/agent-runtime/testing` | tests on Bun or Node | evolving | credential-free replay, scripted provider faults, deterministic race controls and the agent-store conformance kit |
101
101
  | `stitchkit/agent-runtime/harness` | server | evolving | resource-aware process-local facade over the canonical Agent runtime; supervision stays outside |
@@ -107,7 +107,7 @@ own, recorded as an ADR.
107
107
  | `stitchkit/agent-runtime/sqlite/bun` | server (Bun) | evolving | durable built-in SQLite store for the agent runtime |
108
108
  | `stitchkit/agent-runtime/sqlite/node` | server (Node ≥ 22.5) | evolving | durable built-in SQLite store for the agent runtime |
109
109
  | `stitchkit-tui` | terminal (Bun) | evolving | optional official OpenTUI host over a caller-composed headless runtime |
110
- | `stitchkit/application` | browser + server | evolving<br>_redefined in 14 of the 62 minors since 0.56.2, most recently 0.110.0_ | managed resource graph, readiness, admission, schedules, subtree restart and bounded shutdown |
110
+ | `stitchkit/application` | browser + server | evolving<br>_redefined in 14 of the 63 minors since 0.56.2, most recently 0.110.0_ | managed resource graph, readiness, admission, schedules, subtree restart and bounded shutdown |
111
111
  | `stitchkit/application/grammy` | server | evolving | a grammY bot as application resources — configuration, then polling with updates admitted by batch — and the webhook adapter |
112
112
  | `stitchkit/application/opentelemetry` | server | evolving | maps application snapshots onto an injected OpenTelemetry `Meter` |
113
113
  | `stitchkit/application/schemas` | browser + server | evolving | the application's snapshot, health and shutdown schemas alone, without the kernel |
package/llms/telegram.txt CHANGED
@@ -478,7 +478,17 @@ intake recorded the body; the handler runs after, outside the request. Updates
478
478
  of one chat run in order, other chats alongside (`maxConcurrent`, 32). An
479
479
  attempt holds a lease it renews while the handler lives; a sweep takes over what
480
480
  was left — `pending` whose process never got to it (`pendingGraceMs`), `failed`
481
- whose retry is due, `processing` whose process died. A failure is retried with
481
+ whose retry is due, `processing` whose process died. `start` schedules the whole
482
+ queue the last process left, page by page and without waiting for
483
+ `pendingGraceMs`, and an update accepted meanwhile takes its place behind it, so
484
+ a crash keeps each chat in order however long the queue was. The queue sits in
485
+ memory while it drains, as it did in the process that accepted it. `close`
486
+ waits for the handlers running and leaves the rest of the queue recorded. Order
487
+ holds inside one process for updates handled in their turn: the attempt a crash
488
+ interrupted comes when its lease lapses, a failed update at its retry and one
489
+ the store refused to claim at a later sweep, each after later updates of its
490
+ chat. An old release draining beside a new one shares its queue, so the two can
491
+ run updates of one chat side by side. A failure is retried with
482
492
  backoff or after Telegram's `retry_after`; one Telegram said repeating cannot
483
493
  fix (a refused message, a blocked user) is abandoned; `maxAttempts` (5) ends
484
494
  the rest. Delivery is at least once: a handler that finished but could not be
@@ -528,7 +538,8 @@ in a `BotError`; the intake unwraps it, so `retry`, `onFailure` and the error
528
538
  stored with the update are what the middleware threw — a `retry` that returns
529
539
  `false` for the bot's own terminal error sees that error.
530
540
 
531
- The base store remains the six atomic steps of `TelegramUpdateStore`. A
541
+ The base store remains the six atomic steps of `TelegramUpdateStore`; `due` is
542
+ paged, continuing strictly after `afterUpdateId`. A
532
543
  `TelegramUpdateFencedStore` adds `claimOwned`, `renewOwned`, `owns` and
533
544
  `settleOwned`; `TelegramUpdateDurableStore` adds the final-obligation methods.
534
545
  Reference stores implement both, so the bot's own database holds the fence and
@@ -659,7 +670,7 @@ injected grammY bot. → ADR 0143, ADR 0201, ADR 0274 — [guide](../guide/teleg
659
670
  | `TelegramUpdateIntakeConfig` / `TelegramUpdateAttemptIntakeConfig` / `TelegramUpdateIntakeOptions` / `TelegramUpdateIntake` / `TelegramUpdateEnvelope` / `TelegramUpdateFailure` / `TelegramUpdateStoreStep` | _type_ | `store`, unchanged `handle(update)` or attempt-aware `handleAttempt(update, context)` (a grammY `BotError` is unwrapped), shared `retry`, `maxAttempts` (5), `leaseMs` (120 000), `pendingGraceMs` (60 000), `sweepEveryMs` (30 000), `retainMs` (24 h), `maxConcurrent` (32), `onFailure`, durable `handleExhaustion`, `onExhaustionFailure`, `onStoreError`, `now`; `accept(body)`, `start()`, `sweep()`, `idle()`, `close()` |
660
671
  | `TelegramUpdateAttemptContext` / `TelegramUpdateAttemptIdentity` / `TelegramUpdateOwnershipLostError` | _type_ / class | immutable `{ updateId, attempt, claimId }` fence plus `ownerLost`; the persisted opaque claim token prevents a stale capability from matching after prune/re-add, while a store check in the consumer's DB transaction fences a domain write |
661
672
  | `TelegramUpdateExhaustion` / `TelegramUpdateExhaustionFailure` / `StoredTelegramUpdateExhaustion` / `TelegramUpdateExhaustionQuery` | _type_ | a terminal update/attempt/error retained in the update store until `handleExhaustion` resolves; ordered `{ limit, afterUpdateId? }` pages prevent a failing first batch from starving later rows; delivery failures are observational and leave it due |
662
- | `TelegramUpdateStore` / `StoredTelegramUpdate` / `TelegramUpdateState` / `TelegramUpdateSettlement` / `TelegramUpdateClaimOptions` / `TelegramUpdateDueQuery` | _type_ | the base store an intake runs on: `add`, `claim`, `renew`, `settle`, `due`, `prune`, each atomic on one record — a claim takes `pending`, a due `failed` or a lapsed `processing`; renew and settle only by the attempt holding it |
673
+ | `TelegramUpdateStore` / `StoredTelegramUpdate` / `TelegramUpdateState` / `TelegramUpdateSettlement` / `TelegramUpdateClaimOptions` / `TelegramUpdateDueQuery` | _type_ | the base store an intake runs on: `add`, `claim`, `renew`, `settle`, `due`, `prune`, each atomic on one record — a claim takes `pending`, a due `failed` or a lapsed `processing`; renew and settle only by the attempt holding it; `due` returns one page and continues strictly after `afterUpdateId` |
663
674
  | `TelegramUpdateFencedStore` / `TelegramUpdateDurableStore` | _type_ | reference-store capabilities: `claimOwned` creates and returns the persisted identity, `renewOwned`, `owns(identity, at)` and `settleOwned(identity, settlement)` act only on that exact unexpired claim; `exhaust`, `dueExhaustions`, `acknowledgeExhaustion` keep a final consumer obligation in the same row until exact acknowledgement |
664
675
  | `memoryTelegramUpdateStore` | function | the store in this process's memory — tests, or a bot that accepts losing unhandled updates to a restart |
665
676
  | `sqliteTelegramUpdateStore` / `SqliteTelegramUpdateStoreConfig` / `TelegramSqliteDatabase` | function / _type_ | the store in SQLite over the handle the application opened — `bun:sqlite` `Database` or `node:sqlite` `DatabaseSync`; `table` (default `telegram_updates`) created when missing |
package/llms/testing.txt CHANGED
@@ -608,9 +608,9 @@ Notes for a Node host:
608
608
  - **Socket.IO** attaches to the Node HTTP server via `serveNode({ socket })`, and
609
609
  on Node the default transport is `['websocket']` — set the client to match
610
610
  (`transports: ['websocket']`). See [realtime](./realtime.md).
611
- - **Bun-only helpers** do not run on Node: `serveFile` (uses `Bun.file`) and the
612
- raw WebSocket lane. `staticRoute` is runtime-neutral (`node:fs`) and works on
613
- both, but in production prefer a CDN / the static front-end below.
611
+ - **Bun-only helpers** do not run on Node: the raw WebSocket lane. `serveFile`
612
+ and `staticRoute` work on both — on Node the body is a `node:fs` read stream —
613
+ but in production prefer a CDN / the static front-end below.
614
614
 
615
615
  ### Production checklist
616
616
 
@@ -1,14 +1,37 @@
1
- # Upgrading 0.105.0 → 0.117.0 — stitchkit docs slice
1
+ # Upgrading 0.105.0 → 0.118.0 — stitchkit docs slice
2
2
 
3
3
  > Generated from the `docs/` tree by `bun run gen:llms`. The index of all slices is
4
4
  > `llms.txt` in this package.
5
5
 
6
- Contains: Guide: Upgrading, 0.105.0 → 0.117.0 (docs/guide/upgrading.md)
6
+ Contains: Guide: Upgrading, 0.105.0 → 0.118.0 (docs/guide/upgrading.md)
7
7
 
8
8
  ==============================================================================
9
- # Guide: Upgrading, 0.105.0 → 0.117.0 (docs/guide/upgrading.md)
9
+ # Guide: Upgrading, 0.105.0 → 0.118.0 (docs/guide/upgrading.md)
10
10
  ==============================================================================
11
11
 
12
+ ## Released migration: 0.118.0
13
+
14
+ ### telegram-due-cursor
15
+
16
+ **Who must act:** an application with its own `TelegramUpdateStore`. A bot on `memoryTelegramUpdateStore`,
17
+ `sqliteTelegramUpdateStore` or `postgresTelegramUpdateStore` needs no change.
18
+
19
+ `due` returns one page of a queue whose rows stay due until claimed, and the intake reads the next page from
20
+ `afterUpdateId`, until a page comes back empty. A store that ignores the cursor fails `start` and every sweep
21
+ with a `TypeError`, reported to `onStoreError`, as soon as a row is due. Continue strictly after it, in
22
+ `update_id` order:
23
+
24
+ ```ts
25
+ // before
26
+ where: { OR: dueRows }
27
+ // after
28
+ where: { OR: dueRows, ...(query.afterUpdateId !== undefined && { updateId: { gt: query.afterUpdateId } }) }
29
+ ```
30
+
31
+ `checkTelegramUpdateStore` reports the rule as `due continues strictly after its cursor`.
32
+
33
+ Search pattern: an object with a `due(` method passed as `store` to `createTelegramUpdateIntake`.
34
+
12
35
  ## Released migration: 0.117.0
13
36
 
14
37
  ### telegram-chat-pause
@@ -11,7 +11,7 @@ Contains: Guide: Upgrading, general (docs/guide/upgrading.md)
11
11
 
12
12
  Load this slice, then every range slice between your installed version and the target:
13
13
 
14
- - 0.105.0 → 0.117.0: `llms/upgrading-0.105.0-0.117.0.txt`
14
+ - 0.105.0 → 0.118.0: `llms/upgrading-0.105.0-0.118.0.txt`
15
15
  - 0.84.0 → 0.104.0: `llms/upgrading-0.84.0-0.104.0.txt`
16
16
  - 0.63.0 → 0.83.0: `llms/upgrading-0.63.0-0.83.0.txt`
17
17
  - 0.44.0 → 0.62.0: `llms/upgrading-0.44.0-0.62.0.txt`
package/llms-full.txt CHANGED
@@ -81,13 +81,13 @@ own, recorded as an ADR.
81
81
  | `stitchkit/xlsx` | browser **and** server | evolving | a workbook from rows and typed columns, with no dependency; text never runs as a formula |
82
82
  | `stitchkit/oauth` | browser **and** server | evolving | provider-neutral Authorization Code + PKCE request and one-shot callback transaction mechanics |
83
83
  | `stitchkit/google` | server (Bun or Node) | evolving | optional Google code exchange and verified OIDC identity adapter |
84
- | `stitchkit/observability` | server | stable<br>_redefined in 2 of the 62 minors since 0.56.2, most recently 0.92.0_ | request/tool event projections — `createObservability`, trace context, sanitisation |
84
+ | `stitchkit/observability` | server | stable<br>_redefined in 2 of the 63 minors since 0.56.2, most recently 0.92.0_ | request/tool event projections — `createObservability`, trace context, sanitisation |
85
85
  | `stitchkit/testing` | tests on Bun or Node | stable | in-process generated clients over a real Fetch handler, the managed-resource conformance kit and manual time for schedules and retries |
86
86
  | `stitchkit/declaration` | browser + build and deployment tooling (Bun or Node) | evolving | `ProjectDeclarationSchema` — the one machine-readable statement a repository makes about itself |
87
87
  | `stitchkit/react` | browser + server rendering | stable | `createCursorQuery`, `createCacheBridge`, QueryClient and `ApiError` retry policy |
88
88
  | `stitchkit/react/keyboard` | browser | evolving | which part of a screen gets a key — ordered layers, one Escape per level, list zones with real focus; see [keyboard guide](keyboard.md) |
89
89
  | `stitchkit/react/keyboard/testing` | server (Bun or Node) | evolving | the keyboard layers and zones without a DOM — fakes for the window, elements and key events beside the two engines, to test the order of layers, `scope`/`inert` and the edges of a zone in milliseconds |
90
- | `stitchkit/agent-runtime` | server | evolving<br>_redefined in 25 of the 62 minors since 0.56.2, most recently 0.115.0_ | optional durable conversation/run loop, history, models, prompts, fencing and events |
90
+ | `stitchkit/agent-runtime` | server | evolving<br>_redefined in 25 of the 63 minors since 0.56.2, most recently 0.115.0_ | optional durable conversation/run loop, history, models, prompts, fencing and events |
91
91
  | `stitchkit/agent-runtime/sandbox` | server | evolving | optional Linux Bubblewrap sessions, durable workspaces and a host HTTP credential gateway; see [sandbox guide](sandbox.md) |
92
92
  | `stitchkit/agent-runtime/testing` | tests on Bun or Node | evolving | credential-free replay, scripted provider faults, deterministic race controls and the agent-store conformance kit |
93
93
  | `stitchkit/agent-runtime/harness` | server | evolving | resource-aware process-local facade over the canonical Agent runtime; supervision stays outside |
@@ -99,7 +99,7 @@ own, recorded as an ADR.
99
99
  | `stitchkit/agent-runtime/sqlite/bun` | server (Bun) | evolving | durable built-in SQLite store for the agent runtime |
100
100
  | `stitchkit/agent-runtime/sqlite/node` | server (Node ≥ 22.5) | evolving | durable built-in SQLite store for the agent runtime |
101
101
  | `stitchkit-tui` | terminal (Bun) | evolving | optional official OpenTUI host over a caller-composed headless runtime |
102
- | `stitchkit/application` | browser + server | evolving<br>_redefined in 14 of the 62 minors since 0.56.2, most recently 0.110.0_ | managed resource graph, readiness, admission, schedules, subtree restart and bounded shutdown |
102
+ | `stitchkit/application` | browser + server | evolving<br>_redefined in 14 of the 63 minors since 0.56.2, most recently 0.110.0_ | managed resource graph, readiness, admission, schedules, subtree restart and bounded shutdown |
103
103
  | `stitchkit/application/grammy` | server | evolving | a grammY bot as application resources — configuration, then polling with updates admitted by batch — and the webhook adapter |
104
104
  | `stitchkit/application/opentelemetry` | server | evolving | maps application snapshots onto an injected OpenTelemetry `Meter` |
105
105
  | `stitchkit/application/schemas` | browser + server | evolving | the application's snapshot, health and shutdown schemas alone, without the kernel |
@@ -2236,11 +2236,24 @@ contract route does.
2236
2236
 
2237
2237
  ### Serving files & Range requests
2238
2238
 
2239
- `staticRoute` is basic on purpose — it reads the whole file into memory and has
2240
- no `Range` or conditional support; put a CDN in front, or use it for small web
2241
- assets. To serve **media** (video / audio / large downloads) that a browser must
2242
- seek and cache, use **`serveFile`** (Bun) — it streams the requested byte range
2243
- and speaks the conditional-request half of RFC 7233 / 9110:
2239
+ `staticRoute(prefix, dir, { cacheControl })` serves a directory: it checks
2240
+ containment, then answers the file through `serveFile`, so a static image or
2241
+ video carries `ETag`, `Last-Modified` and `Accept-Ranges`, revalidates to `304`
2242
+ and seeks with `206`. `cacheControl` sets `Cache-Control` on every answer; by
2243
+ default none is sent.
2244
+
2245
+ ```ts
2246
+ import { createServer, staticRoute } from 'stitchkit/server'
2247
+
2248
+ createServer({
2249
+ rawRoutes: [staticRoute('/static', './public', { cacheControl: 'public, max-age=3600' })],
2250
+ })
2251
+ ```
2252
+
2253
+ The route answers `GET`. To serve a file chosen by your own logic — through a
2254
+ contract, with `HEAD`, a download name or a custom type — call **`serveFile`**
2255
+ directly. It runs on Bun and Node, streams the requested byte range and speaks
2256
+ the conditional-request half of RFC 7233 / 9110:
2244
2257
 
2245
2258
  ```ts
2246
2259
  import { defineContract } from 'stitchkit/contract'
@@ -2290,7 +2303,7 @@ emit none.
2290
2303
  `serveFile` takes an explicit `path` and trusts it — **the caller owns
2291
2304
  containment**. For a URL-derived path use `staticRoute` (which enforces it) or
2292
2305
  `isWithinDir` first. The byte-range parser is exported on its own as
2293
- `parseByteRange(header, size)` for direct use and testing. → ADR 0023.
2306
+ `parseByteRange(header, size)` for direct use and testing. → ADR 0023, ADR 0281.
2294
2307
 
2295
2308
  ## Server primitives
2296
2309
 
@@ -15141,9 +15154,9 @@ Notes for a Node host:
15141
15154
  - **Socket.IO** attaches to the Node HTTP server via `serveNode({ socket })`, and
15142
15155
  on Node the default transport is `['websocket']` — set the client to match
15143
15156
  (`transports: ['websocket']`). See [realtime](./realtime.md).
15144
- - **Bun-only helpers** do not run on Node: `serveFile` (uses `Bun.file`) and the
15145
- raw WebSocket lane. `staticRoute` is runtime-neutral (`node:fs`) and works on
15146
- both, but in production prefer a CDN / the static front-end below.
15157
+ - **Bun-only helpers** do not run on Node: the raw WebSocket lane. `serveFile`
15158
+ and `staticRoute` work on both — on Node the body is a `node:fs` read stream —
15159
+ but in production prefer a CDN / the static front-end below.
15147
15160
 
15148
15161
  ### Production checklist
15149
15162
 
@@ -16407,7 +16420,17 @@ intake recorded the body; the handler runs after, outside the request. Updates
16407
16420
  of one chat run in order, other chats alongside (`maxConcurrent`, 32). An
16408
16421
  attempt holds a lease it renews while the handler lives; a sweep takes over what
16409
16422
  was left — `pending` whose process never got to it (`pendingGraceMs`), `failed`
16410
- whose retry is due, `processing` whose process died. A failure is retried with
16423
+ whose retry is due, `processing` whose process died. `start` schedules the whole
16424
+ queue the last process left, page by page and without waiting for
16425
+ `pendingGraceMs`, and an update accepted meanwhile takes its place behind it, so
16426
+ a crash keeps each chat in order however long the queue was. The queue sits in
16427
+ memory while it drains, as it did in the process that accepted it. `close`
16428
+ waits for the handlers running and leaves the rest of the queue recorded. Order
16429
+ holds inside one process for updates handled in their turn: the attempt a crash
16430
+ interrupted comes when its lease lapses, a failed update at its retry and one
16431
+ the store refused to claim at a later sweep, each after later updates of its
16432
+ chat. An old release draining beside a new one shares its queue, so the two can
16433
+ run updates of one chat side by side. A failure is retried with
16411
16434
  backoff or after Telegram's `retry_after`; one Telegram said repeating cannot
16412
16435
  fix (a refused message, a blocked user) is abandoned; `maxAttempts` (5) ends
16413
16436
  the rest. Delivery is at least once: a handler that finished but could not be
@@ -16457,7 +16480,8 @@ in a `BotError`; the intake unwraps it, so `retry`, `onFailure` and the error
16457
16480
  stored with the update are what the middleware threw — a `retry` that returns
16458
16481
  `false` for the bot's own terminal error sees that error.
16459
16482
 
16460
- The base store remains the six atomic steps of `TelegramUpdateStore`. A
16483
+ The base store remains the six atomic steps of `TelegramUpdateStore`; `due` is
16484
+ paged, continuing strictly after `afterUpdateId`. A
16461
16485
  `TelegramUpdateFencedStore` adds `claimOwned`, `renewOwned`, `owns` and
16462
16486
  `settleOwned`; `TelegramUpdateDurableStore` adds the final-obligation methods.
16463
16487
  Reference stores implement both, so the bot's own database holds the fence and
@@ -17037,6 +17061,29 @@ charts, no merged cells and no streaming: an application that needs them takes a
17037
17061
 
17038
17062
  # Upgrading stitchkit
17039
17063
 
17064
+ ## Released migration: 0.118.0
17065
+
17066
+ ### telegram-due-cursor
17067
+
17068
+ **Who must act:** an application with its own `TelegramUpdateStore`. A bot on `memoryTelegramUpdateStore`,
17069
+ `sqliteTelegramUpdateStore` or `postgresTelegramUpdateStore` needs no change.
17070
+
17071
+ `due` returns one page of a queue whose rows stay due until claimed, and the intake reads the next page from
17072
+ `afterUpdateId`, until a page comes back empty. A store that ignores the cursor fails `start` and every sweep
17073
+ with a `TypeError`, reported to `onStoreError`, as soon as a row is due. Continue strictly after it, in
17074
+ `update_id` order:
17075
+
17076
+ ```ts
17077
+ // before
17078
+ where: { OR: dueRows }
17079
+ // after
17080
+ where: { OR: dueRows, ...(query.afterUpdateId !== undefined && { updateId: { gt: query.afterUpdateId } }) }
17081
+ ```
17082
+
17083
+ `checkTelegramUpdateStore` reports the rule as `due continues strictly after its cursor`.
17084
+
17085
+ Search pattern: an object with a `due(` method passed as `store` to `createTelegramUpdateIntake`.
17086
+
17040
17087
  ## Released migration: 0.117.0
17041
17088
 
17042
17089
  ### telegram-chat-pause
@@ -22524,7 +22571,8 @@ Also re-exports the error helpers from `stitchkit/contract`.
22524
22571
  | `KeyedServices` | _type_ | registry result: the mount-ordered `ServiceDef[]` carrying the same services under `.byKey` |
22525
22572
  | `RegistryHandlers` | _type_ | exact backend handler registry inferred from a contract registry |
22526
22573
  | `ExactRegistryHandlers` | _type_ | fail-first handler shape that rejects extra registry and endpoint keys |
22527
- | `staticRoute` | function | a raw route that serves a directory |
22574
+ | `staticRoute` | function | a raw route that serves a contained directory through `serveFile` (`Range` / `304` / validators) |
22575
+ | `StaticRouteOptions` | _type_ | `{ cacheControl }` — what `staticRoute` adds to every file it serves |
22528
22576
  | `serveFile` | function | serve a file with `Range` / `304` / `HEAD` — [guide](../guide/server.md#serving-files--range-requests) |
22529
22577
  | `parseByteRange` | function | parse a single `Range` header → range / `unsatisfiable` / `null` |
22530
22578
  | `isWithinDir` | function | path containment — `(root, resolvedTarget) => boolean`; call it before `serveFile` on any URL-derived path |
@@ -24178,7 +24226,7 @@ injected grammY bot. → ADR 0143, ADR 0201, ADR 0274 — [guide](../guide/teleg
24178
24226
  | `TelegramUpdateIntakeConfig` / `TelegramUpdateAttemptIntakeConfig` / `TelegramUpdateIntakeOptions` / `TelegramUpdateIntake` / `TelegramUpdateEnvelope` / `TelegramUpdateFailure` / `TelegramUpdateStoreStep` | _type_ | `store`, unchanged `handle(update)` or attempt-aware `handleAttempt(update, context)` (a grammY `BotError` is unwrapped), shared `retry`, `maxAttempts` (5), `leaseMs` (120 000), `pendingGraceMs` (60 000), `sweepEveryMs` (30 000), `retainMs` (24 h), `maxConcurrent` (32), `onFailure`, durable `handleExhaustion`, `onExhaustionFailure`, `onStoreError`, `now`; `accept(body)`, `start()`, `sweep()`, `idle()`, `close()` |
24179
24227
  | `TelegramUpdateAttemptContext` / `TelegramUpdateAttemptIdentity` / `TelegramUpdateOwnershipLostError` | _type_ / class | immutable `{ updateId, attempt, claimId }` fence plus `ownerLost`; the persisted opaque claim token prevents a stale capability from matching after prune/re-add, while a store check in the consumer's DB transaction fences a domain write |
24180
24228
  | `TelegramUpdateExhaustion` / `TelegramUpdateExhaustionFailure` / `StoredTelegramUpdateExhaustion` / `TelegramUpdateExhaustionQuery` | _type_ | a terminal update/attempt/error retained in the update store until `handleExhaustion` resolves; ordered `{ limit, afterUpdateId? }` pages prevent a failing first batch from starving later rows; delivery failures are observational and leave it due |
24181
- | `TelegramUpdateStore` / `StoredTelegramUpdate` / `TelegramUpdateState` / `TelegramUpdateSettlement` / `TelegramUpdateClaimOptions` / `TelegramUpdateDueQuery` | _type_ | the base store an intake runs on: `add`, `claim`, `renew`, `settle`, `due`, `prune`, each atomic on one record — a claim takes `pending`, a due `failed` or a lapsed `processing`; renew and settle only by the attempt holding it |
24229
+ | `TelegramUpdateStore` / `StoredTelegramUpdate` / `TelegramUpdateState` / `TelegramUpdateSettlement` / `TelegramUpdateClaimOptions` / `TelegramUpdateDueQuery` | _type_ | the base store an intake runs on: `add`, `claim`, `renew`, `settle`, `due`, `prune`, each atomic on one record — a claim takes `pending`, a due `failed` or a lapsed `processing`; renew and settle only by the attempt holding it; `due` returns one page and continues strictly after `afterUpdateId` |
24182
24230
  | `TelegramUpdateFencedStore` / `TelegramUpdateDurableStore` | _type_ | reference-store capabilities: `claimOwned` creates and returns the persisted identity, `renewOwned`, `owns(identity, at)` and `settleOwned(identity, settlement)` act only on that exact unexpired claim; `exhaust`, `dueExhaustions`, `acknowledgeExhaustion` keep a final consumer obligation in the same row until exact acknowledgement |
24183
24231
  | `memoryTelegramUpdateStore` | function | the store in this process's memory — tests, or a bot that accepts losing unhandled updates to a restart |
24184
24232
  | `sqliteTelegramUpdateStore` / `SqliteTelegramUpdateStoreConfig` / `TelegramSqliteDatabase` | function / _type_ | the store in SQLite over the handle the application opened — `bun:sqlite` `Database` or `node:sqlite` `DatabaseSync`; `table` (default `telegram_updates`) created when missing |
package/llms.txt CHANGED
@@ -24,7 +24,7 @@ Load the slice for each entrypoint your code imports — `import … from 'stitc
24
24
  - **stitchkit/remote**: [llms/remote.txt](llms/remote.txt) 1.0 KB
25
25
  - **stitchkit/contract**: [llms/contract.txt](llms/contract.txt) 31.9 KB
26
26
  - **stitchkit/primitives**: [llms/primitives.txt](llms/primitives.txt) 13.2 KB
27
- - **stitchkit/server**: [llms/server.txt](llms/server.txt) 44.2 KB → [llms/server.2.txt](llms/server.2.txt) 20.5 KB → [llms/server.3.txt](llms/server.3.txt) 47.0 KB → [llms/server.4.txt](llms/server.4.txt) 38.6 KB → [llms/server.5.txt](llms/server.5.txt) 22.9 KB
27
+ - **stitchkit/server**: [llms/server.txt](llms/server.txt) 44.2 KB → [llms/server.2.txt](llms/server.2.txt) 20.9 KB → [llms/server.3.txt](llms/server.3.txt) 47.0 KB → [llms/server.4.txt](llms/server.4.txt) 38.6 KB → [llms/server.5.txt](llms/server.5.txt) 23.1 KB
28
28
  - **stitchkit/observability**: [llms/observability.txt](llms/observability.txt) 46.9 KB
29
29
  - **stitchkit/agent-runtime**: [llms/agent-runtime.txt](llms/agent-runtime.txt) 44.3 KB → [llms/agent-runtime.2.txt](llms/agent-runtime.2.txt) 46.7 KB → [llms/agent-runtime.3.txt](llms/agent-runtime.3.txt) 43.4 KB
30
30
  - **stitchkit/agent-runtime/testing**: [llms/agent-runtime-testing.txt](llms/agent-runtime-testing.txt) 2.6 KB
@@ -43,7 +43,7 @@ Load the slice for each entrypoint your code imports — `import … from 'stitc
43
43
  - **stitchkit/testing**: [llms/testing.txt](llms/testing.txt) 33.6 KB
44
44
  - **stitchkit/agent-runtime/sandbox**: [llms/agent-runtime-sandbox.txt](llms/agent-runtime-sandbox.txt) 8.3 KB
45
45
  - **stitchkit/files**: [llms/files.txt](llms/files.txt) 5.7 KB
46
- - **stitchkit/telegram**: [llms/telegram.txt](llms/telegram.txt) 43.9 KB
46
+ - **stitchkit/telegram**: [llms/telegram.txt](llms/telegram.txt) 44.8 KB
47
47
  - **stitchkit/telegram/grammy**: [llms/telegram-grammy.txt](llms/telegram-grammy.txt) 1.2 KB
48
48
  - **stitchkit/telegram/html**: [llms/telegram-html.txt](llms/telegram-html.txt) 2.7 KB
49
49
  - **stitchkit/telegram/screens**: [llms/telegram-screens.txt](llms/telegram-screens.txt) 21.2 KB
@@ -69,7 +69,7 @@ Load the slice for each entrypoint your code imports — `import … from 'stitc
69
69
 
70
70
  ## Upgrading slices
71
71
  - **Upgrading — how to move across versions**: [llms/upgrading.txt](llms/upgrading.txt) 12.7 KB
72
- - **Upgrading 0.105.0 → 0.117.0**: [llms/upgrading-0.105.0-0.117.0.txt](llms/upgrading-0.105.0-0.117.0.txt) 42.1 KB
72
+ - **Upgrading 0.105.0 → 0.118.0**: [llms/upgrading-0.105.0-0.118.0.txt](llms/upgrading-0.105.0-0.118.0.txt) 43.0 KB
73
73
  - **Upgrading 0.84.0 → 0.104.0**: [llms/upgrading-0.84.0-0.104.0.txt](llms/upgrading-0.84.0-0.104.0.txt) 46.1 KB
74
74
  - **Upgrading 0.63.0 → 0.83.0**: [llms/upgrading-0.63.0-0.83.0.txt](llms/upgrading-0.63.0-0.83.0.txt) 44.0 KB
75
75
  - **Upgrading 0.44.0 → 0.62.0**: [llms/upgrading-0.44.0-0.62.0.txt](llms/upgrading-0.44.0-0.62.0.txt) 46.5 KB
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stitchkit",
3
- "version": "0.117.0",
3
+ "version": "0.118.1",
4
4
  "description": "Contract-first backend framework — one defineContract() into an HTTP API, MCP tools, AI-agent tools and a typed client. Bun and Node.",
5
5
  "keywords": [
6
6
  "bun",