stitchkit 0.116.0 → 0.117.0
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 +42 -0
- package/dist/agent-runtime/run-acquisition.d.ts +9 -2
- package/dist/agent-runtime/run-acquisition.d.ts.map +1 -1
- package/dist/agent-runtime/run-execution.d.ts +3 -0
- package/dist/agent-runtime/run-execution.d.ts.map +1 -1
- package/dist/agent-runtime/run-failure.d.ts +4 -1
- package/dist/agent-runtime/run-failure.d.ts.map +1 -1
- package/dist/agent-runtime/run-store-retry.d.ts +8 -0
- package/dist/agent-runtime/run-store-retry.d.ts.map +1 -0
- package/dist/agent-runtime/run-terminal.d.ts.map +1 -1
- package/dist/agent-runtime/run-unacquired.d.ts +19 -0
- package/dist/agent-runtime/run-unacquired.d.ts.map +1 -0
- package/dist/agent-runtime/store-conformance.d.ts +8 -0
- package/dist/agent-runtime/store-conformance.d.ts.map +1 -1
- package/dist/agent-runtime/store-driver-contract.d.ts +5 -0
- package/dist/agent-runtime/store-driver-contract.d.ts.map +1 -1
- package/dist/agent-runtime/terminal-commit.d.ts +34 -4
- package/dist/agent-runtime/terminal-commit.d.ts.map +1 -1
- package/dist/entrypoints/agent-runtime/harness.js +2 -2
- package/dist/entrypoints/agent-runtime/sqlite/bun.js +3 -3
- package/dist/entrypoints/agent-runtime/sqlite/node.js +3 -3
- package/dist/entrypoints/agent-runtime/testing.js +22 -4
- package/dist/entrypoints/agent-runtime.d.ts +1 -1
- package/dist/entrypoints/agent-runtime.d.ts.map +1 -1
- package/dist/entrypoints/agent-runtime.js +13 -11
- package/dist/entrypoints/application/diagnostic-journal.js +1 -1
- package/dist/entrypoints/application/directory-inbox.js +3 -3
- package/dist/entrypoints/cli/publish.js +2 -2
- package/dist/entrypoints/files.js +2 -2
- package/dist/entrypoints/primitives.js +51 -154
- package/dist/entrypoints/server.js +6 -6
- package/dist/entrypoints/telegram.d.ts +1 -1
- package/dist/entrypoints/telegram.d.ts.map +1 -1
- package/dist/entrypoints/telegram.js +196 -76
- package/dist/entrypoints/tracking/contract.d.ts +11 -0
- package/dist/entrypoints/tracking/contract.d.ts.map +1 -0
- package/dist/entrypoints/tracking/contract.js +162 -0
- package/dist/entrypoints/tracking/performance.d.ts +8 -0
- package/dist/entrypoints/tracking/performance.d.ts.map +1 -0
- package/dist/entrypoints/tracking/performance.js +181 -0
- package/dist/entrypoints/tracking/server.d.ts +1 -0
- package/dist/entrypoints/tracking/server.d.ts.map +1 -1
- package/dist/entrypoints/tracking/server.js +40 -12
- package/dist/entrypoints/tracking.d.ts +4 -3
- package/dist/entrypoints/tracking.d.ts.map +1 -1
- package/dist/entrypoints/tracking.js +43 -143
- package/dist/entrypoints/xlsx.d.ts +4 -0
- package/dist/entrypoints/xlsx.d.ts.map +1 -0
- package/dist/entrypoints/xlsx.js +406 -0
- package/dist/{index-fa435yq2.js → index-073nw9qr.js} +8 -8
- package/dist/index-1f40z584.js +7 -0
- package/dist/{index-fdk4r4r6.js → index-75ns2ybj.js} +1 -1
- package/dist/{index-jq2ky8fw.js → index-cycp3r3e.js} +124 -181
- package/dist/{index-4g6qmgdx.js → index-ghtpfz4z.js} +9 -9
- package/dist/{index-n3v695sb.js → index-hdjmdwsj.js} +277 -96
- package/dist/{index-zqkz4af9.js → index-nqxrm176.js} +1 -1
- package/dist/index-qezhmp51.js +242 -0
- package/dist/index-vv1sch8a.js +94 -0
- package/dist/{index-fggxgbrk.js → index-xx1j310k.js} +165 -102
- package/dist/internal/exclusive-lock.d.ts.map +1 -1
- package/dist/primitives/decimal.d.ts +10 -0
- package/dist/primitives/decimal.d.ts.map +1 -1
- package/dist/primitives/index.d.ts +3 -1
- package/dist/primitives/index.d.ts.map +1 -1
- package/dist/primitives/money.d.ts +20 -0
- package/dist/primitives/money.d.ts.map +1 -1
- package/dist/primitives/quantity.d.ts.map +1 -1
- package/dist/primitives/rounding.d.ts +40 -0
- package/dist/primitives/rounding.d.ts.map +1 -0
- package/dist/telegram/budget-transformer.d.ts +2 -2
- package/dist/telegram/not-dispatched.d.ts +2 -2
- package/dist/telegram/not-dispatched.d.ts.map +1 -1
- package/dist/telegram/outbound-budget.d.ts +53 -16
- package/dist/telegram/outbound-budget.d.ts.map +1 -1
- package/dist/telegram/outbound-cost.d.ts +5 -0
- package/dist/telegram/outbound-cost.d.ts.map +1 -0
- package/dist/telegram/outbound-pauses.d.ts +17 -0
- package/dist/telegram/outbound-pauses.d.ts.map +1 -0
- package/dist/telegram/outbound-queue.d.ts +17 -0
- package/dist/telegram/outbound-queue.d.ts.map +1 -0
- package/dist/tracking/attribution.d.ts +1 -1
- package/dist/tracking/attribution.d.ts.map +1 -1
- package/dist/tracking/client-visit.d.ts +3 -1
- package/dist/tracking/client-visit.d.ts.map +1 -1
- package/dist/tracking/client.d.ts.map +1 -1
- package/dist/tracking/performance/collector.d.ts +61 -0
- package/dist/tracking/performance/collector.d.ts.map +1 -0
- package/dist/tracking/performance/schema.d.ts +45 -0
- package/dist/tracking/performance/schema.d.ts.map +1 -0
- package/dist/tracking/performance/summary.d.ts +12 -0
- package/dist/tracking/performance/summary.d.ts.map +1 -0
- package/dist/tracking/privacy.d.ts +5 -0
- package/dist/tracking/privacy.d.ts.map +1 -0
- package/dist/tracking/server/telemetry-health.d.ts +31 -0
- package/dist/tracking/server/telemetry-health.d.ts.map +1 -0
- package/dist/xlsx/cells.d.ts +17 -0
- package/dist/xlsx/cells.d.ts.map +1 -0
- package/dist/xlsx/render.d.ts +11 -0
- package/dist/xlsx/render.d.ts.map +1 -0
- package/dist/xlsx/schema.d.ts +119 -0
- package/dist/xlsx/schema.d.ts.map +1 -0
- package/dist/xlsx/zip.d.ts +13 -0
- package/dist/xlsx/zip.d.ts.map +1 -0
- package/llms/agent-runtime.2.txt +40 -16
- package/llms/agent-runtime.3.txt +17 -0
- package/llms/agent-runtime.txt +4 -1
- package/llms/primitives.txt +21 -0
- package/llms/stitchkit.txt +9 -6
- package/llms/telegram.txt +32 -12
- package/llms/tracking-contract.txt +32 -0
- package/llms/tracking-performance.txt +21 -0
- package/llms/tracking-server.txt +1 -0
- package/llms/tracking.txt +75 -12
- package/llms/{upgrading-0.105.0-0.116.0.txt → upgrading-0.105.0-0.117.0.txt} +50 -3
- package/llms/upgrading.txt +1 -1
- package/llms/xlsx.txt +107 -0
- package/llms-full.txt +355 -31
- package/llms.txt +11 -7
- package/package.json +27 -9
- package/dist/{index-z3d6smer.js → index-tdpmfks8.js} +5 -5
|
@@ -5,7 +5,7 @@ import"../index-2kbysk9s.js";
|
|
|
5
5
|
import"../index-n8z3rvzw.js";
|
|
6
6
|
import {
|
|
7
7
|
attemptExclusiveLock
|
|
8
|
-
} from "../index-
|
|
8
|
+
} from "../index-tdpmfks8.js";
|
|
9
9
|
import {
|
|
10
10
|
TELEGRAM_SEND_FAILURE_REASONS2,
|
|
11
11
|
classifyTelegramSendFailure2
|
|
@@ -908,11 +908,8 @@ function telegramOperatorSender(config) {
|
|
|
908
908
|
}
|
|
909
909
|
// src/telegram/outbound-budget.ts
|
|
910
910
|
import { z as z5 } from "zod";
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
var TOKEN = z5.string().min(1, "token must not be empty");
|
|
914
|
-
var MESSAGES_PER_SECOND = z5.number().positive().max(1000);
|
|
915
|
-
var RETRY_AFTER_FALLBACK_MS = z5.number().int().nonnegative().max(MAX_TIMER_MS);
|
|
911
|
+
|
|
912
|
+
// src/telegram/outbound-cost.ts
|
|
916
913
|
var SEND_METHODS = new Set([
|
|
917
914
|
"sendMessage",
|
|
918
915
|
"sendPhoto",
|
|
@@ -959,15 +956,69 @@ function sendCost(method, payload) {
|
|
|
959
956
|
const value = payload[field];
|
|
960
957
|
return Array.isArray(value) ? Math.max(1, value.length) : 1;
|
|
961
958
|
}
|
|
962
|
-
function
|
|
963
|
-
|
|
959
|
+
function chatOf(payload) {
|
|
960
|
+
if (!isRecord(payload))
|
|
961
|
+
return;
|
|
962
|
+
const chat = payload.chat_id;
|
|
963
|
+
return typeof chat === "number" || typeof chat === "string" ? String(chat) : undefined;
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
// src/telegram/outbound-pauses.ts
|
|
967
|
+
function createOutboundPauses({ tokenPauseChats, tokenPauseWindowMs }, now, onPause) {
|
|
968
|
+
const chatPauses = new Map;
|
|
969
|
+
const refusedChats = new Map;
|
|
970
|
+
let pausedUntil = Number.NEGATIVE_INFINITY;
|
|
971
|
+
const pauseEnd = (milliseconds) => {
|
|
972
|
+
if (Number.isNaN(milliseconds) || milliseconds < 0) {
|
|
973
|
+
throw new RangeError("pause must be a non-negative number of milliseconds");
|
|
974
|
+
}
|
|
975
|
+
return now() + (Number.isFinite(milliseconds) ? milliseconds : Number.MAX_SAFE_INTEGER);
|
|
976
|
+
};
|
|
977
|
+
const pauseToken = (milliseconds) => {
|
|
978
|
+
pausedUntil = Math.max(pausedUntil, pauseEnd(milliseconds));
|
|
979
|
+
onPause();
|
|
980
|
+
};
|
|
981
|
+
const forgetExpired = (current) => {
|
|
982
|
+
for (const [chat, until] of chatPauses)
|
|
983
|
+
if (until <= current)
|
|
984
|
+
chatPauses.delete(chat);
|
|
985
|
+
for (const [chat, at] of refusedChats) {
|
|
986
|
+
if (current - at > tokenPauseWindowMs)
|
|
987
|
+
refusedChats.delete(chat);
|
|
988
|
+
}
|
|
989
|
+
};
|
|
990
|
+
return {
|
|
991
|
+
resumesAt: (chat) => Math.max(pausedUntil, (chat === undefined ? undefined : chatPauses.get(chat)) ?? Number.NEGATIVE_INFINITY),
|
|
992
|
+
pauseToken,
|
|
993
|
+
pauseChat(chat, milliseconds) {
|
|
994
|
+
const until = pauseEnd(milliseconds);
|
|
995
|
+
const current = now();
|
|
996
|
+
forgetExpired(current);
|
|
997
|
+
chatPauses.set(chat, Math.max(chatPauses.get(chat) ?? until, until));
|
|
998
|
+
refusedChats.set(chat, current);
|
|
999
|
+
onPause();
|
|
1000
|
+
if (refusedChats.size < tokenPauseChats)
|
|
1001
|
+
return;
|
|
1002
|
+
refusedChats.clear();
|
|
1003
|
+
pauseToken(milliseconds);
|
|
1004
|
+
},
|
|
1005
|
+
pausedForMs: () => Math.max(0, pausedUntil - now()),
|
|
1006
|
+
pausedChats() {
|
|
1007
|
+
forgetExpired(now());
|
|
1008
|
+
return chatPauses.size;
|
|
1009
|
+
}
|
|
1010
|
+
};
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
// src/telegram/outbound-queue.ts
|
|
1014
|
+
function createOutboundQueue(interval, now, sleepFor, pauses) {
|
|
964
1015
|
const queue = [];
|
|
965
1016
|
let nextGrantAt = now();
|
|
966
|
-
let pausedUntil = Number.NEGATIVE_INFINITY;
|
|
967
1017
|
let granted = 0;
|
|
968
1018
|
let messages = 0;
|
|
969
1019
|
let draining = false;
|
|
970
|
-
let
|
|
1020
|
+
let waking;
|
|
1021
|
+
let wakingAt = Number.POSITIVE_INFINITY;
|
|
971
1022
|
const wait = async (milliseconds, signal) => {
|
|
972
1023
|
const delay = Math.min(MAX_TIMER_MS, Math.max(0, milliseconds));
|
|
973
1024
|
if (delay <= 0)
|
|
@@ -978,20 +1029,40 @@ function createBudget(rate, fallback, now, sleepFor) {
|
|
|
978
1029
|
else
|
|
979
1030
|
await pending;
|
|
980
1031
|
};
|
|
981
|
-
const
|
|
982
|
-
const current =
|
|
983
|
-
|
|
1032
|
+
const wake = () => {
|
|
1033
|
+
const current = waking;
|
|
1034
|
+
waking = undefined;
|
|
984
1035
|
current?.abort();
|
|
985
1036
|
};
|
|
986
|
-
const nextWaiter = () =>
|
|
1037
|
+
const nextWaiter = (current) => {
|
|
1038
|
+
let bulk;
|
|
1039
|
+
let held;
|
|
1040
|
+
for (const waiter of queue) {
|
|
1041
|
+
if (pauses.resumesAt(waiter.chat) > current) {
|
|
1042
|
+
if (held === undefined || pauses.resumesAt(waiter.chat) < pauses.resumesAt(held.chat)) {
|
|
1043
|
+
held = waiter;
|
|
1044
|
+
}
|
|
1045
|
+
} else if (waiter.lane === "interactive") {
|
|
1046
|
+
return waiter;
|
|
1047
|
+
} else {
|
|
1048
|
+
bulk ??= waiter;
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
return bulk ?? held;
|
|
1052
|
+
};
|
|
1053
|
+
const detach = (waiter) => {
|
|
1054
|
+
if (waiter.onAbort)
|
|
1055
|
+
waiter.signal?.removeEventListener("abort", waiter.onAbort);
|
|
1056
|
+
waiter.expiry?.abort();
|
|
1057
|
+
};
|
|
987
1058
|
const remove = (waiter) => {
|
|
988
1059
|
const index = queue.indexOf(waiter);
|
|
989
1060
|
if (index === -1)
|
|
990
1061
|
return;
|
|
991
|
-
const next = nextWaiter() === waiter;
|
|
1062
|
+
const next = nextWaiter(now()) === waiter;
|
|
992
1063
|
queue.splice(index, 1);
|
|
993
1064
|
if (next)
|
|
994
|
-
|
|
1065
|
+
wake();
|
|
995
1066
|
};
|
|
996
1067
|
const drain = async () => {
|
|
997
1068
|
if (draining)
|
|
@@ -999,38 +1070,38 @@ function createBudget(rate, fallback, now, sleepFor) {
|
|
|
999
1070
|
draining = true;
|
|
1000
1071
|
try {
|
|
1001
1072
|
while (queue.length > 0) {
|
|
1002
|
-
const
|
|
1073
|
+
const current = now();
|
|
1074
|
+
const head = nextWaiter(current);
|
|
1003
1075
|
if (head === undefined)
|
|
1004
1076
|
break;
|
|
1005
1077
|
if (head.signal?.aborted) {
|
|
1006
1078
|
remove(head);
|
|
1007
1079
|
continue;
|
|
1008
1080
|
}
|
|
1009
|
-
const
|
|
1010
|
-
const at = Math.max(current, nextGrantAt, pausedUntil);
|
|
1081
|
+
const at = Math.max(current, nextGrantAt, pauses.resumesAt(head.chat));
|
|
1011
1082
|
if (at > current) {
|
|
1012
1083
|
const controller = new AbortController;
|
|
1013
|
-
|
|
1084
|
+
waking = controller;
|
|
1085
|
+
wakingAt = at;
|
|
1014
1086
|
try {
|
|
1015
1087
|
await wait(at - current, controller.signal);
|
|
1016
1088
|
} catch (error) {
|
|
1017
1089
|
if (!controller.signal.aborted) {
|
|
1018
1090
|
for (const waiter of queue.splice(0)) {
|
|
1019
|
-
|
|
1020
|
-
waiter.signal?.removeEventListener("abort", waiter.onAbort);
|
|
1091
|
+
detach(waiter);
|
|
1021
1092
|
waiter.reject(error);
|
|
1022
1093
|
}
|
|
1023
1094
|
return;
|
|
1024
1095
|
}
|
|
1025
1096
|
} finally {
|
|
1026
|
-
if (
|
|
1027
|
-
|
|
1097
|
+
if (waking === controller)
|
|
1098
|
+
waking = undefined;
|
|
1099
|
+
wakingAt = Number.POSITIVE_INFINITY;
|
|
1028
1100
|
}
|
|
1029
1101
|
continue;
|
|
1030
1102
|
}
|
|
1031
1103
|
queue.splice(queue.indexOf(head), 1);
|
|
1032
|
-
|
|
1033
|
-
head.signal?.removeEventListener("abort", head.onAbort);
|
|
1104
|
+
detach(head);
|
|
1034
1105
|
nextGrantAt = at + head.cost * interval;
|
|
1035
1106
|
granted += 1;
|
|
1036
1107
|
messages += head.cost;
|
|
@@ -1042,67 +1113,115 @@ function createBudget(rate, fallback, now, sleepFor) {
|
|
|
1042
1113
|
drain();
|
|
1043
1114
|
}
|
|
1044
1115
|
};
|
|
1045
|
-
const
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
resolve,
|
|
1056
|
-
reject,
|
|
1057
|
-
...signal && { signal }
|
|
1058
|
-
};
|
|
1059
|
-
if (signal) {
|
|
1060
|
-
const onAbort = () => {
|
|
1061
|
-
remove(waiter);
|
|
1062
|
-
reject(signal.reason);
|
|
1063
|
-
};
|
|
1064
|
-
waiter.onAbort = onAbort;
|
|
1065
|
-
signal.addEventListener("abort", onAbort, { once: true });
|
|
1066
|
-
}
|
|
1067
|
-
queue.push(waiter);
|
|
1068
|
-
drain();
|
|
1116
|
+
const expireAfter = (waiter, maxWaitMs, reject) => {
|
|
1117
|
+
waiter.expiry = new AbortController;
|
|
1118
|
+
wait(maxWaitMs, waiter.expiry.signal).then(() => {
|
|
1119
|
+
if (!queue.includes(waiter))
|
|
1120
|
+
return;
|
|
1121
|
+
remove(waiter);
|
|
1122
|
+
detach(waiter);
|
|
1123
|
+
reject(new TelegramNotDispatchedError2("queue", "the Telegram request waited longer than maxWaitMs for its turn"));
|
|
1124
|
+
}, () => {
|
|
1125
|
+
return;
|
|
1069
1126
|
});
|
|
1070
1127
|
};
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1128
|
+
return {
|
|
1129
|
+
acquire(cost, signal, options) {
|
|
1130
|
+
if (!Number.isSafeInteger(cost) || cost < 1) {
|
|
1131
|
+
return Promise.reject(new RangeError("cost must be an integer of at least one message"));
|
|
1132
|
+
}
|
|
1133
|
+
if (signal?.aborted)
|
|
1134
|
+
return Promise.reject(signal.reason);
|
|
1135
|
+
const maxWaitMs = options?.maxWaitMs;
|
|
1136
|
+
if (maxWaitMs !== undefined && (!Number.isSafeInteger(maxWaitMs) || maxWaitMs < 1 || maxWaitMs > MAX_TIMER_MS)) {
|
|
1137
|
+
return Promise.reject(new RangeError(`maxWaitMs must be an integer from 1 to ${MAX_TIMER_MS}`));
|
|
1138
|
+
}
|
|
1139
|
+
const chat = options?.chatId === undefined ? undefined : String(options.chatId);
|
|
1140
|
+
return new Promise((resolve, reject) => {
|
|
1141
|
+
const waiter = {
|
|
1142
|
+
cost,
|
|
1143
|
+
lane: options?.lane ?? "interactive",
|
|
1144
|
+
chat,
|
|
1145
|
+
resolve,
|
|
1146
|
+
reject,
|
|
1147
|
+
...signal && { signal }
|
|
1148
|
+
};
|
|
1149
|
+
if (signal) {
|
|
1150
|
+
const onAbort = () => {
|
|
1151
|
+
remove(waiter);
|
|
1152
|
+
detach(waiter);
|
|
1153
|
+
reject(signal.reason);
|
|
1154
|
+
};
|
|
1155
|
+
waiter.onAbort = onAbort;
|
|
1156
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
1157
|
+
}
|
|
1158
|
+
if (maxWaitMs !== undefined)
|
|
1159
|
+
expireAfter(waiter, maxWaitMs, reject);
|
|
1160
|
+
queue.push(waiter);
|
|
1161
|
+
if (Math.max(now(), nextGrantAt, pauses.resumesAt(chat)) < wakingAt)
|
|
1162
|
+
wake();
|
|
1163
|
+
drain();
|
|
1164
|
+
});
|
|
1165
|
+
},
|
|
1166
|
+
wake,
|
|
1167
|
+
wait,
|
|
1168
|
+
counts: () => ({
|
|
1169
|
+
granted,
|
|
1170
|
+
messages,
|
|
1171
|
+
queued: queue.length,
|
|
1172
|
+
queuedBulk: queue.filter((waiter) => waiter.lane === "bulk").length
|
|
1173
|
+
})
|
|
1078
1174
|
};
|
|
1079
|
-
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
// src/telegram/outbound-budget.ts
|
|
1178
|
+
var DEFAULT_MESSAGES_PER_SECOND = 24;
|
|
1179
|
+
var DEFAULT_RETRY_AFTER_FALLBACK_MS = 1000;
|
|
1180
|
+
var DEFAULT_TOKEN_PAUSE_CHATS = 3;
|
|
1181
|
+
var DEFAULT_TOKEN_PAUSE_WINDOW_MS = 5000;
|
|
1182
|
+
var TOKEN = z5.string().min(1, "token must not be empty");
|
|
1183
|
+
var MESSAGES_PER_SECOND = z5.number().positive().max(1000);
|
|
1184
|
+
var RETRY_AFTER_FALLBACK_MS = z5.number().int().nonnegative().max(MAX_TIMER_MS);
|
|
1185
|
+
var TOKEN_PAUSE_CHATS = z5.number().int().min(2).max(1000);
|
|
1186
|
+
var TOKEN_PAUSE_WINDOW_MS = z5.number().int().positive().max(MAX_TIMER_MS);
|
|
1187
|
+
function createBudget(policy, now, sleepFor) {
|
|
1188
|
+
const { rate, fallback } = policy;
|
|
1189
|
+
const pauses = createOutboundPauses(policy, now, () => queue.wake());
|
|
1190
|
+
const queue = createOutboundQueue(1000 / rate, now, sleepFor, pauses);
|
|
1191
|
+
const pauseAfter = (error, options) => {
|
|
1080
1192
|
const failure = classifyTelegramSendFailure2(error);
|
|
1081
1193
|
if (failure.reason !== "rate-limited")
|
|
1082
1194
|
return;
|
|
1083
|
-
|
|
1195
|
+
const milliseconds = failure.retryAfterSeconds === undefined ? fallback : failure.retryAfterSeconds * 1000;
|
|
1196
|
+
if (options?.chatId === undefined)
|
|
1197
|
+
pauses.pauseToken(milliseconds);
|
|
1198
|
+
else
|
|
1199
|
+
pauses.pauseChat(String(options.chatId), milliseconds);
|
|
1084
1200
|
};
|
|
1085
|
-
const waitUntilResumed = async (signal) => {
|
|
1201
|
+
const waitUntilResumed = async (signal, options) => {
|
|
1202
|
+
const chat = options?.chatId === undefined ? undefined : String(options.chatId);
|
|
1086
1203
|
while (true) {
|
|
1087
1204
|
if (signal?.aborted)
|
|
1088
1205
|
throw signal.reason;
|
|
1089
|
-
const remaining =
|
|
1206
|
+
const remaining = pauses.resumesAt(chat) - now();
|
|
1090
1207
|
if (remaining <= 0)
|
|
1091
1208
|
return;
|
|
1092
|
-
await wait(Math.min(remaining, MAX_TIMER_MS), signal);
|
|
1209
|
+
await queue.wait(Math.min(remaining, MAX_TIMER_MS), signal);
|
|
1093
1210
|
}
|
|
1094
1211
|
};
|
|
1095
1212
|
const run = async (method, payload, signal, send, options) => {
|
|
1096
1213
|
if (signal?.aborted)
|
|
1097
1214
|
throw signal.reason;
|
|
1098
1215
|
const cost = sendCost(method, payload);
|
|
1216
|
+
const chatId = options?.chatId ?? chatOf(payload);
|
|
1217
|
+
const call = chatId === undefined ? {} : { chatId };
|
|
1099
1218
|
if (cost > 0)
|
|
1100
|
-
await acquire(cost, signal, options);
|
|
1219
|
+
await queue.acquire(cost, signal, { ...options, ...call });
|
|
1101
1220
|
if (signal?.aborted)
|
|
1102
1221
|
throw signal.reason;
|
|
1103
1222
|
const pauseOnRefusal = (refusal) => {
|
|
1104
|
-
if (cost > 0)
|
|
1105
|
-
pauseAfter(refusal);
|
|
1223
|
+
if (cost > 0 && options?.refusal !== "call")
|
|
1224
|
+
pauseAfter(refusal, call);
|
|
1106
1225
|
};
|
|
1107
1226
|
let result;
|
|
1108
1227
|
try {
|
|
@@ -1115,22 +1234,19 @@ function createBudget(rate, fallback, now, sleepFor) {
|
|
|
1115
1234
|
pauseOnRefusal(result);
|
|
1116
1235
|
return result;
|
|
1117
1236
|
};
|
|
1118
|
-
|
|
1237
|
+
return {
|
|
1119
1238
|
run,
|
|
1120
|
-
acquire,
|
|
1121
|
-
pause:
|
|
1239
|
+
acquire: queue.acquire,
|
|
1240
|
+
pause: pauses.pauseToken,
|
|
1122
1241
|
pauseAfter,
|
|
1123
1242
|
waitUntilResumed,
|
|
1124
1243
|
stats: () => ({
|
|
1125
|
-
|
|
1126
|
-
messages,
|
|
1127
|
-
queued: queue.length,
|
|
1128
|
-
queuedBulk: queue.filter((waiter) => waiter.lane === "bulk").length,
|
|
1244
|
+
...queue.counts(),
|
|
1129
1245
|
messagesPerSecond: rate,
|
|
1130
|
-
pausedForMs:
|
|
1246
|
+
pausedForMs: pauses.pausedForMs(),
|
|
1247
|
+
pausedChats: pauses.pausedChats()
|
|
1131
1248
|
})
|
|
1132
1249
|
};
|
|
1133
|
-
return budget;
|
|
1134
1250
|
}
|
|
1135
1251
|
var budgets = new Map;
|
|
1136
1252
|
function keyOf(token) {
|
|
@@ -1140,20 +1256,24 @@ function telegramOutboundBudget(config) {
|
|
|
1140
1256
|
const token = TOKEN.parse(config.token);
|
|
1141
1257
|
const rate = MESSAGES_PER_SECOND.parse(config.messagesPerSecond ?? DEFAULT_MESSAGES_PER_SECOND);
|
|
1142
1258
|
const fallback = RETRY_AFTER_FALLBACK_MS.parse(config.retryAfterFallbackMs ?? DEFAULT_RETRY_AFTER_FALLBACK_MS);
|
|
1259
|
+
const tokenPauseChats = TOKEN_PAUSE_CHATS.parse(config.tokenPauseChats ?? DEFAULT_TOKEN_PAUSE_CHATS);
|
|
1260
|
+
const tokenPauseWindowMs = TOKEN_PAUSE_WINDOW_MS.parse(config.tokenPauseWindowMs ?? DEFAULT_TOKEN_PAUSE_WINDOW_MS);
|
|
1143
1261
|
const key = keyOf(token);
|
|
1144
1262
|
const existing = budgets.get(key);
|
|
1145
1263
|
if (existing) {
|
|
1146
|
-
const same = existing.rate === rate && existing.fallback === fallback && existing.nowOption === config.now && existing.sleepOption === config.sleep;
|
|
1264
|
+
const same = existing.rate === rate && existing.fallback === fallback && existing.tokenPauseChats === tokenPauseChats && existing.tokenPauseWindowMs === tokenPauseWindowMs && existing.nowOption === config.now && existing.sleepOption === config.sleep;
|
|
1147
1265
|
if (!same) {
|
|
1148
1266
|
throw new Error("a Telegram outbound budget already exists for this token with different options");
|
|
1149
1267
|
}
|
|
1150
1268
|
return existing.budget;
|
|
1151
1269
|
}
|
|
1152
|
-
const budget = createBudget(rate, fallback, config.now ?? (() => performance.now()), config.sleep ?? sleep);
|
|
1270
|
+
const budget = createBudget({ rate, fallback, tokenPauseChats, tokenPauseWindowMs }, config.now ?? (() => performance.now()), config.sleep ?? sleep);
|
|
1153
1271
|
budgets.set(key, {
|
|
1154
1272
|
budget,
|
|
1155
1273
|
rate,
|
|
1156
1274
|
fallback,
|
|
1275
|
+
tokenPauseChats,
|
|
1276
|
+
tokenPauseWindowMs,
|
|
1157
1277
|
nowOption: config.now,
|
|
1158
1278
|
sleepOption: config.sleep
|
|
1159
1279
|
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `stitchkit/tracking/contract` — the wire shapes of tracking: the contract
|
|
3
|
+
* factory, the event and visit schemas, and the performance sample with its
|
|
4
|
+
* trust threshold. It brings `zod`, so it belongs to the server and to a shared
|
|
5
|
+
* contract, and browser code that sends events does not import it. → ADR 0280.
|
|
6
|
+
*/
|
|
7
|
+
export { createTrackingContract, type TrackingContractConfig, type TrackingContractEndpoints, } from '../../tracking/contract.js';
|
|
8
|
+
export { type PerformanceSample, PerformanceSampleSchema, type PerformanceVitals, PerformanceVitalsSchema, } from '../../tracking/performance/schema.js';
|
|
9
|
+
export { type PerformanceMetricSummary, summarizePerformanceMetric, } from '../../tracking/performance/summary.js';
|
|
10
|
+
export { type AttributionData, AttributionDataSchema, createTrackingSchemas, DEFAULT_BUILD_ID_PATTERN, type TrackEventsRequest, type TrackEventsResponse, TrackEventsResponseSchema, type TrackingDisposition, TrackingDispositionSchema, type TrackingDispositionStatus, TrackingDispositionStatusSchema, type TrackingEventEnvelope, type TrackingEventShape, type TrackingOutboxState, TrackingOutboxStateSchema, type TrackingSchemas, type TrackingSchemasConfig, type UtmData, UtmDataSchema, type VisitBootstrapResponse, VisitBootstrapResponseSchema, type VisitEntryContext, type VisitEntryContextShape, } from '../../tracking/schemas.js';
|
|
11
|
+
//# sourceMappingURL=contract.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../../../src/entrypoints/tracking/contract.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EACL,sBAAsB,EACtB,KAAK,sBAAsB,EAC3B,KAAK,yBAAyB,GAC/B,MAAM,yBAAyB,CAAC;AACjC,OAAO,EACL,KAAK,iBAAiB,EACtB,uBAAuB,EACvB,KAAK,iBAAiB,EACtB,uBAAuB,GACxB,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EACL,KAAK,wBAAwB,EAC7B,0BAA0B,GAC3B,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EACL,KAAK,eAAe,EACpB,qBAAqB,EACrB,qBAAqB,EACrB,wBAAwB,EACxB,KAAK,kBAAkB,EACvB,KAAK,mBAAmB,EACxB,yBAAyB,EACzB,KAAK,mBAAmB,EACxB,yBAAyB,EACzB,KAAK,yBAAyB,EAC9B,+BAA+B,EAC/B,KAAK,qBAAqB,EAC1B,KAAK,kBAAkB,EACvB,KAAK,mBAAmB,EACxB,yBAAyB,EACzB,KAAK,eAAe,EACpB,KAAK,qBAAqB,EAC1B,KAAK,OAAO,EACZ,aAAa,EACb,KAAK,sBAAsB,EAC3B,4BAA4B,EAC5B,KAAK,iBAAiB,EACtB,KAAK,sBAAsB,GAC5B,MAAM,wBAAwB,CAAC"}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import"../../index-m38ej7e5.js";
|
|
2
|
+
import {
|
|
3
|
+
defineContract2
|
|
4
|
+
} from "../../index-d9hq291n.js";
|
|
5
|
+
|
|
6
|
+
// src/tracking/schemas.ts
|
|
7
|
+
import { z } from "zod";
|
|
8
|
+
var UtmDataSchema = z.object({
|
|
9
|
+
source: z.string().max(200).optional(),
|
|
10
|
+
medium: z.string().max(200).optional(),
|
|
11
|
+
campaign: z.string().max(200).optional(),
|
|
12
|
+
content: z.string().max(200).optional(),
|
|
13
|
+
term: z.string().max(200).optional()
|
|
14
|
+
});
|
|
15
|
+
var AttributionDataSchema = z.object({
|
|
16
|
+
utm: UtmDataSchema.optional(),
|
|
17
|
+
referrer: z.string().optional(),
|
|
18
|
+
landingPage: z.string().optional()
|
|
19
|
+
});
|
|
20
|
+
var DEFAULT_BUILD_ID_PATTERN = /^(dev|[a-f0-9]{7,40})$/;
|
|
21
|
+
var TrackingDispositionStatusSchema = z.enum([
|
|
22
|
+
"accepted",
|
|
23
|
+
"duplicate",
|
|
24
|
+
"identity-invalid",
|
|
25
|
+
"excluded-bot"
|
|
26
|
+
]);
|
|
27
|
+
var TrackingDispositionSchema = z.object({
|
|
28
|
+
eventId: z.uuid(),
|
|
29
|
+
status: TrackingDispositionStatusSchema
|
|
30
|
+
});
|
|
31
|
+
var TrackEventsResponseSchema = z.object({
|
|
32
|
+
accepted: z.number().int().nonnegative(),
|
|
33
|
+
dispositions: z.array(TrackingDispositionSchema)
|
|
34
|
+
});
|
|
35
|
+
var VisitBootstrapResponseSchema = z.object({
|
|
36
|
+
visitId: z.uuid(),
|
|
37
|
+
expiresAt: z.iso.datetime()
|
|
38
|
+
});
|
|
39
|
+
var TrackingOutboxStateSchema = z.enum(["available", "unavailable"]);
|
|
40
|
+
function trackingEventShape(eventTypes) {
|
|
41
|
+
return {
|
|
42
|
+
eventId: z.uuid(),
|
|
43
|
+
visitId: z.uuid(),
|
|
44
|
+
browserStreamId: z.uuid(),
|
|
45
|
+
browserSequence: z.number().int().nonnegative(),
|
|
46
|
+
type: z.enum(eventTypes),
|
|
47
|
+
page: z.string().max(2000),
|
|
48
|
+
metadata: z.record(z.string(), z.unknown()).optional(),
|
|
49
|
+
clientTimestamp: z.number()
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
function visitEntryContextShape(buildIdPattern) {
|
|
53
|
+
return {
|
|
54
|
+
browserStreamId: z.uuid(),
|
|
55
|
+
previousVisitId: z.uuid().optional(),
|
|
56
|
+
origin: z.url().max(500),
|
|
57
|
+
landingPath: z.string().startsWith("/").max(300).regex(/^[^?#]*$/),
|
|
58
|
+
referrer: z.url().max(2000).optional(),
|
|
59
|
+
utm: UtmDataSchema.optional(),
|
|
60
|
+
displayMode: z.enum(["browser", "standalone"]),
|
|
61
|
+
screenWidth: z.number().int().positive().max(20000),
|
|
62
|
+
screenHeight: z.number().int().positive().max(20000),
|
|
63
|
+
buildId: z.string().regex(buildIdPattern),
|
|
64
|
+
outboxState: TrackingOutboxStateSchema,
|
|
65
|
+
outboxQueued: z.number().int().nonnegative().max(1000),
|
|
66
|
+
outboxDropped: z.number().int().nonnegative()
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
function createTrackingSchemas(config) {
|
|
70
|
+
const base = z.object(trackingEventShape(config.eventTypes));
|
|
71
|
+
const event = config.eventExtras ? base.extend(config.eventExtras.shape) : base;
|
|
72
|
+
const buildIdPattern = config.buildIdPattern ?? DEFAULT_BUILD_ID_PATTERN;
|
|
73
|
+
const maxEvents = config.maxEventsPerBatch ?? 50;
|
|
74
|
+
return {
|
|
75
|
+
event,
|
|
76
|
+
request: z.object({
|
|
77
|
+
buildId: z.string().regex(buildIdPattern),
|
|
78
|
+
events: z.array(event).min(1).max(maxEvents),
|
|
79
|
+
utm: UtmDataSchema.optional()
|
|
80
|
+
}),
|
|
81
|
+
response: TrackEventsResponseSchema,
|
|
82
|
+
disposition: TrackingDispositionSchema,
|
|
83
|
+
entry: z.object(visitEntryContextShape(buildIdPattern)),
|
|
84
|
+
bootstrap: VisitBootstrapResponseSchema
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// src/tracking/contract.ts
|
|
89
|
+
function createTrackingContract(config) {
|
|
90
|
+
const schemas = createTrackingSchemas(config);
|
|
91
|
+
return defineContract2({ prefix: config.prefix ?? "tracking", scope: config.scope }, {
|
|
92
|
+
bootstrap: {
|
|
93
|
+
method: "POST",
|
|
94
|
+
path: "/visit",
|
|
95
|
+
expose: ["HTTP"],
|
|
96
|
+
desc: "Issue or renew a browser visit lease",
|
|
97
|
+
input: schemas.entry,
|
|
98
|
+
output: schemas.bootstrap
|
|
99
|
+
},
|
|
100
|
+
track: {
|
|
101
|
+
method: "POST",
|
|
102
|
+
path: "/events",
|
|
103
|
+
expose: ["HTTP"],
|
|
104
|
+
desc: "Batch track browser events",
|
|
105
|
+
input: schemas.request,
|
|
106
|
+
output: schemas.response,
|
|
107
|
+
safelistedBody: true,
|
|
108
|
+
maxJsonBodyBytes: config.maxJsonBodyBytes ?? 256 * 1024
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
// src/tracking/performance/schema.ts
|
|
113
|
+
import { z as z2 } from "zod";
|
|
114
|
+
var BoundedMsSchema = z2.number().nonnegative().max(3600000).nullable();
|
|
115
|
+
var PerformanceVitalsSchema = z2.object({
|
|
116
|
+
lcpMs: BoundedMsSchema,
|
|
117
|
+
inpMs: BoundedMsSchema,
|
|
118
|
+
fcpMs: BoundedMsSchema,
|
|
119
|
+
ttfbMs: BoundedMsSchema,
|
|
120
|
+
cls: z2.number().nonnegative().max(100).nullable()
|
|
121
|
+
});
|
|
122
|
+
var PerformanceSampleSchema = z2.object({
|
|
123
|
+
schemaVersion: z2.literal(1),
|
|
124
|
+
id: z2.string().min(1).max(64),
|
|
125
|
+
kind: z2.enum(["early", "final"]),
|
|
126
|
+
route: z2.string().min(1).max(2000),
|
|
127
|
+
buildId: z2.string().min(1).max(64),
|
|
128
|
+
visitId: z2.string().min(1).max(128).nullable(),
|
|
129
|
+
deviceClass: z2.enum(["mobile", "desktop"]),
|
|
130
|
+
navigationType: z2.string().min(1).max(32).nullable(),
|
|
131
|
+
vitals: PerformanceVitalsSchema,
|
|
132
|
+
capturedAt: z2.number().int().nonnegative()
|
|
133
|
+
});
|
|
134
|
+
// src/tracking/performance/summary.ts
|
|
135
|
+
var DEFAULT_MIN_SAMPLES = 100;
|
|
136
|
+
function percentile(ascending, rank) {
|
|
137
|
+
return ascending[Math.ceil(ascending.length * rank) - 1] ?? null;
|
|
138
|
+
}
|
|
139
|
+
function summarizePerformanceMetric(values, options = {}) {
|
|
140
|
+
const present = values.filter((value) => value !== null).sort((left, right) => left - right);
|
|
141
|
+
const trusted = present.length >= (options.minSamples ?? DEFAULT_MIN_SAMPLES);
|
|
142
|
+
return {
|
|
143
|
+
samples: present.length,
|
|
144
|
+
p75: percentile(present, 0.75),
|
|
145
|
+
p95: trusted ? percentile(present, 0.95) : null
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
export {
|
|
149
|
+
AttributionDataSchema,
|
|
150
|
+
DEFAULT_BUILD_ID_PATTERN,
|
|
151
|
+
PerformanceSampleSchema,
|
|
152
|
+
PerformanceVitalsSchema,
|
|
153
|
+
TrackEventsResponseSchema,
|
|
154
|
+
TrackingDispositionSchema,
|
|
155
|
+
TrackingDispositionStatusSchema,
|
|
156
|
+
TrackingOutboxStateSchema,
|
|
157
|
+
UtmDataSchema,
|
|
158
|
+
VisitBootstrapResponseSchema,
|
|
159
|
+
createTrackingContract,
|
|
160
|
+
createTrackingSchemas,
|
|
161
|
+
summarizePerformanceMetric
|
|
162
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `stitchkit/tracking/performance` — Web Vitals from real visitors, collected
|
|
3
|
+
* after the page has loaded. The sample's schema and the report's trust threshold
|
|
4
|
+
* are `stitchkit/tracking/contract`; tables and reports stay the application's.
|
|
5
|
+
* Importing this needs the optional peer `web-vitals`. → ADR 0280.
|
|
6
|
+
*/
|
|
7
|
+
export { createPerformanceCollector, isLabUserAgent, type PerformanceCollector, type PerformanceCollectorConfig, type PerformanceHost, type VitalsLibrary, type VitalsMetric, type VitalsObserver, } from '../../tracking/performance/collector.js';
|
|
8
|
+
//# sourceMappingURL=performance.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"performance.d.ts","sourceRoot":"","sources":["../../../src/entrypoints/tracking/performance.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EACL,0BAA0B,EAC1B,cAAc,EACd,KAAK,oBAAoB,EACzB,KAAK,0BAA0B,EAC/B,KAAK,eAAe,EACpB,KAAK,aAAa,EAClB,KAAK,YAAY,EACjB,KAAK,cAAc,GACpB,MAAM,sCAAsC,CAAC"}
|