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 +26 -0
- package/dist/entrypoints/server.d.ts +1 -1
- package/dist/entrypoints/server.d.ts.map +1 -1
- package/dist/entrypoints/server.js +31 -17
- package/dist/entrypoints/telegram.js +156 -61
- package/dist/server/file.d.ts +12 -6
- package/dist/server/file.d.ts.map +1 -1
- package/dist/telegram/update-intake-work.d.ts +20 -1
- package/dist/telegram/update-intake-work.d.ts.map +1 -1
- package/dist/telegram/update-intake.d.ts +12 -3
- package/dist/telegram/update-intake.d.ts.map +1 -1
- package/dist/telegram/update-store-check.d.ts +1 -1
- package/dist/telegram/update-store-check.d.ts.map +1 -1
- package/dist/telegram/update-store-postgres.d.ts.map +1 -1
- package/dist/telegram/update-store-sqlite.d.ts.map +1 -1
- package/dist/telegram/update-store.d.ts +5 -1
- package/dist/telegram/update-store.d.ts.map +1 -1
- package/llms/server.2.txt +19 -6
- package/llms/server.5.txt +2 -1
- package/llms/stitchkit.txt +3 -3
- package/llms/telegram.txt +14 -3
- package/llms/testing.txt +3 -3
- package/llms/{upgrading-0.105.0-0.117.0.txt → upgrading-0.105.0-0.118.0.txt} +26 -3
- package/llms/upgrading.txt +1 -1
- package/llms-full.txt +64 -16
- package/llms.txt +3 -3
- package/package.json +1 -1
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 {
|
|
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
|
-
|
|
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
|
|
1025
|
-
if (!
|
|
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 =
|
|
1029
|
-
const mtimeMs =
|
|
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 :
|
|
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 :
|
|
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
|
|
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
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
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
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
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
|
|
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
|
|
1953
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2022
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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;
|
package/dist/server/file.d.ts
CHANGED
|
@@ -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
|
|
4
|
-
*
|
|
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
|
|
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
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
|
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":"AAAA
|
|
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
|
|
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;
|
|
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,
|
|
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;
|
|
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
|
-
/**
|
|
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":"AAEA
|
|
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
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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 |
|
package/llms/stitchkit.txt
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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.
|
|
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
|
|
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:
|
|
612
|
-
|
|
613
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
package/llms/upgrading.txt
CHANGED
|
@@ -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.
|
|
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
|
|
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
|
|
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
|
|
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
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
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:
|
|
15145
|
-
|
|
15146
|
-
|
|
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.
|
|
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
|
|
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.
|
|
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)
|
|
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.
|
|
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