@particle-academy/fancy-connector-core 0.6.1 → 0.7.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 CHANGED
@@ -6,6 +6,54 @@ All notable changes to `fancy-connector-core` are documented here, in
6
6
  **This package is pre-1.0, so breaking changes land in MINOR releases.** The
7
7
  version number is not a promise it can keep yet; the entries below are.
8
8
 
9
+ ## [0.7.0] - 2026-09-15
10
+
11
+ Two additions to the connector vocabulary, both approved by the owner on
12
+ 2026-09-14 and both meeting the two-provider bar before shipping: the
13
+ subscription lease is for Google Calendar `events.watch` and Microsoft Graph
14
+ subscriptions; the MIME transform is for inbound email, provider-agnostic,
15
+ with Resend first.
16
+
17
+ ### Added
18
+
19
+ - **`SubscriptionLease`** (`src/lease.ts`, `SubscriptionLease.php` with
20
+ `LeaseState` and `LeaseAction`) — the duty a `subscription` trigger carries,
21
+ as one value: the provider's expiry as an RFC 3339 instant, the connector's
22
+ `renewBeforeSeconds`, and the `renewOperation` the host calls when due.
23
+ `leaseState` says where it is (active / due / expired — both boundaries
24
+ inclusive, expired wins) and `leaseAction` what the host does (none / renew /
25
+ resync; a missed lease is resync, never a quiet re-create). Both runtimes
26
+ read `fixtures/subscription-lease/cases.json`. A host runs ONE renewal
27
+ scheduler for every expiring trigger instead of one per connector.
28
+ - **`parseMime` / `Mime::parse`** — raw MIME in, `headers`, `subject`,
29
+ `messageId`, `text`, `html`, `parts` and `attachments` out, identical in both
30
+ runtimes and held to the authored corpus under `fixtures/mime/` (CRLF and
31
+ LF; folded headers; RFC 2047 B and Q words; quoted-printable and base64;
32
+ nested multipart; RFC 2231 `filename*`; utf-8, us-ascii, iso-8859-1,
33
+ iso-8859-15 and windows-1252 — any other charset leaves a part with no
34
+ text). Attachments carry their bytes as base64. **What to do:** nothing
35
+ unless you want them; neither touches an existing surface.
36
+
37
+ ## [0.6.2] - 2026-09-14
38
+
39
+ ### Fixed
40
+
41
+ - **Three remedies that could not be followed are gone, and a test now refuses
42
+ any string that names a command, package or script that does not exist.**
43
+ `ConnectorApiMismatch` for a connector behind the window told the reader to
44
+ run `npx fancy-cli@latest add connector <id>` — there is no such command; it
45
+ now says to move the connector to a release written for this core (upgrade
46
+ its `-js`/`-php` package, or regenerate a vendored copy from that release).
47
+ `index.ts` opened by naming `@particle-academy/fancy-connectors`, a package
48
+ that was never published and whose vendored catalogue is retired (the owner,
49
+ 2026-09-14 — every connector is a generated package from the Fancy-Friends
50
+ estate, and this core is Weaver's). And the banner `scripts/vendor.mjs`
51
+ writes into every generated file said to re-run `php artisan flow:build`,
52
+ which regenerates nothing; it now names `vendor.mjs` itself. **What to do:**
53
+ nothing at runtime; a host carrying a vendored `_connector` copy regenerates
54
+ it with `node scripts/vendor.mjs --target <dir>` on its next core update, and
55
+ its drift check will say so.
56
+
9
57
  ## [0.6.1] - 2026-09-13
10
58
 
11
59
  ### Fixed
package/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # fancy-connector-core
2
2
 
3
+ [![Fancified](art/fancified.svg)](https://particle.academy)
4
+
3
5
  **The runtime under every Fancy connector.** Modes and estates, connections, one
4
6
  call path, a failure taxonomy that knows the difference between *it never
5
7
  arrived* and *nobody can tell*, deterministic fakers, webhook verification, pure
@@ -220,7 +222,9 @@ entirely. That is the whole argument.
220
222
 
221
223
  - [`AGENTS.md`](./AGENTS.md) — the invariants, the traps, and what a change here
222
224
  breaks.
223
- - `.ai/plans/fancy-connectors.md` in the envelope — the architecture, the
224
- Socialite seam, the drift research, and every decision with its reason.
225
+ - `RULES.md` in [`Fancy-Friends/weaver.agi`](https://github.com/Fancy-Friends/weaver.agi)
226
+ — the estate every connector is generated from (one definition → four
227
+ packages), its invariants, and the process rules this core follows since it
228
+ became Weaver's on 2026-09-14.
225
229
 
226
230
  MIT.
package/dist/index.cjs CHANGED
@@ -64,15 +64,19 @@ __export(src_exports, {
64
64
  fakeValues: () => fakeValues,
65
65
  getConnectionHost: () => getConnectionHost,
66
66
  graphemes: () => graphemes,
67
- header: () => header,
67
+ header: () => header2,
68
68
  hmac: () => hmac,
69
69
  httpFailure: () => httpFailure,
70
70
  idempotencyKeyFor: () => idempotencyKeyFor,
71
71
  isEmptyPayload: () => isEmptyPayload,
72
72
  isRemote: () => isRemote,
73
73
  isUnconditionallyRetryable: () => isUnconditionallyRetryable,
74
+ leaseAction: () => leaseAction,
75
+ leaseRenewAt: () => leaseRenewAt,
76
+ leaseState: () => leaseState,
74
77
  linkRanges: () => linkRanges,
75
78
  measure: () => measure,
79
+ parseMime: () => parseMime,
76
80
  payloadHash: () => payloadHash,
77
81
  postChain: () => postChain,
78
82
  probe: () => probe,
@@ -97,6 +101,7 @@ __export(src_exports, {
97
101
  shouldRetry: () => shouldRetry,
98
102
  sliceByteRange: () => sliceByteRange,
99
103
  splitToFit: () => splitToFit,
104
+ subscriptionLease: () => subscriptionLease,
100
105
  triggerEvent: () => triggerEvent,
101
106
  unchecked: () => unchecked,
102
107
  utf8Length: () => utf8Length,
@@ -142,7 +147,7 @@ function assertConnectorApi(connectorId, declared) {
142
147
  if (SUPPORTED_CONNECTOR_API.includes(declared)) return;
143
148
  const supported = SUPPORTED_CONNECTOR_API.join(", ");
144
149
  throw new ConnectorApiMismatch(
145
- declared > CONNECTOR_API_VERSION ? `"${connectorId}" was written against connector API ${declared}, and this core implements ${supported}. The CONNECTOR is newer: upgrade @particle-academy/fancy-connector-core. Vendoring a connector ahead of the core it needs is the one direction that cannot be made to work by trying.` : `"${connectorId}" was written against connector API ${declared}, which is older than anything this core still runs (${supported}). The CONNECTOR is behind: re-vendor it \u2014 \`npx fancy-cli@latest add connector ${connectorId}\` \u2014 which fetches the copy written for this core. Nothing is adapted automatically, because guessing what a two-version-old connector meant is how a connector quietly starts doing something else.`,
150
+ declared > CONNECTOR_API_VERSION ? `"${connectorId}" was written against connector API ${declared}, and this core implements ${supported}. The CONNECTOR is newer: upgrade @particle-academy/fancy-connector-core. Vendoring a connector ahead of the core it needs is the one direction that cannot be made to work by trying.` : `"${connectorId}" was written against connector API ${declared}, which is older than anything this core still runs (${supported}). The CONNECTOR is behind: move it to a release written for this core \u2014 upgrade its package (@particle-academy/${connectorId}-js, particle-academy/${connectorId}-php), or regenerate a vendored copy from that release; its CHANGELOG names the connector API each release targets. Nothing is adapted automatically, because guessing what a two-version-old connector meant is how a connector quietly starts doing something else.`,
146
151
  connectorId,
147
152
  declared
148
153
  );
@@ -703,21 +708,21 @@ async function prepare(service, connection, options) {
703
708
  }
704
709
  function encodeForm(form) {
705
710
  const pairs = [];
706
- const walk = (prefix, value) => {
711
+ const walk2 = (prefix, value) => {
707
712
  if (value === void 0 || value === null) return;
708
713
  if (Array.isArray(value)) {
709
- value.forEach((item, index) => walk(`${prefix}[${index}]`, item));
714
+ value.forEach((item, index) => walk2(`${prefix}[${index}]`, item));
710
715
  return;
711
716
  }
712
717
  if (typeof value === "object") {
713
718
  for (const [key, nested] of Object.entries(value)) {
714
- walk(prefix === "" ? key : `${prefix}[${key}]`, nested);
719
+ walk2(prefix === "" ? key : `${prefix}[${key}]`, nested);
715
720
  }
716
721
  return;
717
722
  }
718
723
  pairs.push(`${encodeURIComponent(prefix)}=${encodeURIComponent(String(value))}`);
719
724
  };
720
- walk("", form);
725
+ walk2("", form);
721
726
  return pairs.join("&");
722
727
  }
723
728
  var fetchTransport = async (request) => {
@@ -1021,6 +1026,59 @@ function fnv1a(value) {
1021
1026
  return hash.toString(16).padStart(8, "0");
1022
1027
  }
1023
1028
 
1029
+ // src/lease.ts
1030
+ var INSTANT = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,9})?(?:Z|[+-]\d{2}:\d{2})$/;
1031
+ var DEFAULT_CONTEXT = { service: "", operation: "subscription" };
1032
+ function instantMs(value, field, at) {
1033
+ if (typeof value !== "string" || !INSTANT.test(value)) {
1034
+ throw new ConnectorConfigError(
1035
+ `${field} must be an RFC 3339 instant such as "2026-09-22T00:00:00Z", got ${JSON.stringify(value)}. A provider's own shape \u2014 Google's epoch milliseconds, say \u2014 is converted by the connector, never guessed at here.`,
1036
+ at
1037
+ );
1038
+ }
1039
+ const ms = Date.parse(value);
1040
+ if (Number.isNaN(ms)) {
1041
+ throw new ConnectorConfigError(`${field} is not a real instant: ${JSON.stringify(value)}`, at);
1042
+ }
1043
+ return ms;
1044
+ }
1045
+ function subscriptionLease(input, at = DEFAULT_CONTEXT) {
1046
+ instantMs(input.expiresAt, "expiresAt", at);
1047
+ if (!Number.isInteger(input.renewBeforeSeconds) || input.renewBeforeSeconds <= 0) {
1048
+ throw new ConnectorConfigError(
1049
+ `renewBeforeSeconds must be a positive whole number of seconds, got ${JSON.stringify(input.renewBeforeSeconds)}. Zero would make the lease due only at the instant it expires, which is a lease nobody renews.`,
1050
+ at
1051
+ );
1052
+ }
1053
+ if (typeof input.renewOperation !== "string" || input.renewOperation.trim() === "") {
1054
+ throw new ConnectorConfigError(
1055
+ "renewOperation must name the operation the host calls when the lease is due; a due lease with nothing to call is one nobody renews.",
1056
+ at
1057
+ );
1058
+ }
1059
+ return { expiresAt: input.expiresAt, renewBeforeSeconds: input.renewBeforeSeconds, renewOperation: input.renewOperation };
1060
+ }
1061
+ function leaseRenewAt(lease) {
1062
+ return new Date(instantMs(lease.expiresAt, "expiresAt", DEFAULT_CONTEXT) - lease.renewBeforeSeconds * 1e3).toISOString();
1063
+ }
1064
+ function leaseState(lease, now) {
1065
+ const at = now instanceof Date ? now.getTime() : instantMs(now, "now", DEFAULT_CONTEXT);
1066
+ const expires = instantMs(lease.expiresAt, "expiresAt", DEFAULT_CONTEXT);
1067
+ if (at >= expires) return "expired";
1068
+ if (at >= expires - lease.renewBeforeSeconds * 1e3) return "due";
1069
+ return "active";
1070
+ }
1071
+ function leaseAction(lease, now) {
1072
+ switch (leaseState(lease, now)) {
1073
+ case "active":
1074
+ return "none";
1075
+ case "due":
1076
+ return "renew";
1077
+ case "expired":
1078
+ return "resync";
1079
+ }
1080
+ }
1081
+
1024
1082
  // src/metrics.ts
1025
1083
  function compareShape(connectorId, declared, produced) {
1026
1084
  const declaredKeys = new Set((declared ?? []).map((metric) => metric.key));
@@ -1115,6 +1173,245 @@ function providerSandboxIsSelectable(provider) {
1115
1173
  return sandboxIsSelectable(provider.sandbox);
1116
1174
  }
1117
1175
 
1176
+ // src/mime.ts
1177
+ var CR = 13;
1178
+ var LF = 10;
1179
+ function latin1(bytes) {
1180
+ let s = "";
1181
+ for (let i = 0; i < bytes.length; i++) s += String.fromCharCode(bytes[i]);
1182
+ return s;
1183
+ }
1184
+ function bytesOfLatin1(s) {
1185
+ const out = new Uint8Array(s.length);
1186
+ for (let i = 0; i < s.length; i++) out[i] = s.charCodeAt(i) & 255;
1187
+ return out;
1188
+ }
1189
+ function splitEntity(bytes) {
1190
+ for (let i = 0; i < bytes.length; i++) {
1191
+ if (bytes[i] === LF) {
1192
+ if (bytes[i + 1] === LF) return [i, i + 2];
1193
+ if (bytes[i + 1] === CR && bytes[i + 2] === LF) return [i, i + 3];
1194
+ }
1195
+ }
1196
+ return [bytes.length, bytes.length];
1197
+ }
1198
+ function parseHeaders(block) {
1199
+ const lines = block.split(/\r?\n/);
1200
+ const unfolded = [];
1201
+ for (const line of lines) {
1202
+ if (line === "") continue;
1203
+ if ((line.startsWith(" ") || line.startsWith(" ")) && unfolded.length > 0) {
1204
+ unfolded[unfolded.length - 1] += " " + line.trim();
1205
+ } else {
1206
+ unfolded.push(line);
1207
+ }
1208
+ }
1209
+ const headers = [];
1210
+ for (const line of unfolded) {
1211
+ const colon = line.indexOf(":");
1212
+ if (colon <= 0) continue;
1213
+ headers.push({ name: line.slice(0, colon).trim().toLowerCase(), value: decodeWords(line.slice(colon + 1).trim()) });
1214
+ }
1215
+ return headers;
1216
+ }
1217
+ function header(headers, name) {
1218
+ return headers.find((h) => h.name === name)?.value ?? null;
1219
+ }
1220
+ var ENCODED_WORD = /=\?([^?\s]+)\?([BbQq])\?([^?\s]*)\?=/g;
1221
+ function decodeWords(value) {
1222
+ const joined = value.replace(/(\?=)[ \t\r\n]+(=\?)/g, "$1$2");
1223
+ return joined.replace(ENCODED_WORD, (whole, charset, encoding, text) => {
1224
+ const bytes = encoding.toUpperCase() === "B" ? base64Bytes(text) : bytesOfLatin1(text.replace(/_/g, " ").replace(/=([0-9A-Fa-f]{2})/g, (_m, h) => String.fromCharCode(parseInt(h, 16))));
1225
+ const decoded = decodeCharset(bytes, charset);
1226
+ return decoded ?? whole;
1227
+ });
1228
+ }
1229
+ function parseParams(value) {
1230
+ const [head, ...rest] = value.split(";");
1231
+ const params = {};
1232
+ const continued = {};
1233
+ for (const piece of rest) {
1234
+ const eq = piece.indexOf("=");
1235
+ if (eq < 0) continue;
1236
+ let key = piece.slice(0, eq).trim().toLowerCase();
1237
+ let raw = piece.slice(eq + 1).trim();
1238
+ if (raw.startsWith('"') && raw.endsWith('"') && raw.length >= 2) raw = raw.slice(1, -1).replace(/\\(.)/g, "$1");
1239
+ const segment = /^(.+?)\*(\d+)(\*)?$/.exec(key);
1240
+ if (segment) {
1241
+ const [, base, n, star] = segment;
1242
+ (continued[base] ??= [])[Number(n)] = star ? decode2231(raw, Number(n) === 0) : raw;
1243
+ continue;
1244
+ }
1245
+ if (key.endsWith("*")) {
1246
+ key = key.slice(0, -1);
1247
+ params[key] = decode2231(raw, true);
1248
+ continue;
1249
+ }
1250
+ params[key] ??= raw;
1251
+ }
1252
+ for (const [base, parts] of Object.entries(continued)) params[base] = parts.join("");
1253
+ return { main: (head ?? "").trim().toLowerCase(), params };
1254
+ }
1255
+ var pendingCharset2231 = "utf-8";
1256
+ function decode2231(raw, first) {
1257
+ let text = raw;
1258
+ if (first) {
1259
+ const m = /^([^']*)'[^']*'(.*)$/s.exec(raw);
1260
+ if (m) {
1261
+ pendingCharset2231 = m[1] || "utf-8";
1262
+ text = m[2];
1263
+ }
1264
+ }
1265
+ const bytes = bytesOfLatin1(text.replace(/%([0-9A-Fa-f]{2})/g, (_m, h) => String.fromCharCode(parseInt(h, 16))));
1266
+ return decodeCharset(bytes, pendingCharset2231) ?? text;
1267
+ }
1268
+ function base64Bytes(text) {
1269
+ return new Uint8Array(Buffer.from(text.replace(/[^A-Za-z0-9+/=]/g, ""), "base64"));
1270
+ }
1271
+ function quotedPrintableBytes(bytes) {
1272
+ const out = [];
1273
+ for (let i = 0; i < bytes.length; i++) {
1274
+ const b = bytes[i];
1275
+ if (b !== 61) {
1276
+ out.push(b);
1277
+ continue;
1278
+ }
1279
+ const a = bytes[i + 1];
1280
+ const c = bytes[i + 2];
1281
+ if (a === LF) {
1282
+ i += 1;
1283
+ continue;
1284
+ }
1285
+ if (a === CR && c === LF) {
1286
+ i += 2;
1287
+ continue;
1288
+ }
1289
+ if (a !== void 0 && c !== void 0 && isHex(a) && isHex(c)) {
1290
+ out.push(parseInt(String.fromCharCode(a, c), 16));
1291
+ i += 2;
1292
+ continue;
1293
+ }
1294
+ out.push(b);
1295
+ }
1296
+ return new Uint8Array(out);
1297
+ }
1298
+ function isHex(b) {
1299
+ return b >= 48 && b <= 57 || b >= 65 && b <= 70 || b >= 97 && b <= 102;
1300
+ }
1301
+ function transferDecode(body, encoding) {
1302
+ switch ((encoding ?? "7bit").trim().toLowerCase()) {
1303
+ case "base64":
1304
+ return base64Bytes(latin1(body));
1305
+ case "quoted-printable":
1306
+ return quotedPrintableBytes(body);
1307
+ default:
1308
+ return body;
1309
+ }
1310
+ }
1311
+ function decodeCharset(bytes, charset) {
1312
+ switch (charset.trim().toLowerCase()) {
1313
+ case "utf-8":
1314
+ case "utf8":
1315
+ return new TextDecoder("utf-8").decode(bytes);
1316
+ case "us-ascii":
1317
+ case "ascii":
1318
+ case "iso-8859-1":
1319
+ case "latin1":
1320
+ return Buffer.from(bytes).toString("latin1");
1321
+ case "windows-1252":
1322
+ case "cp1252":
1323
+ return new TextDecoder("windows-1252").decode(bytes);
1324
+ case "iso-8859-15":
1325
+ case "latin9":
1326
+ return new TextDecoder("iso-8859-15").decode(bytes);
1327
+ default:
1328
+ return null;
1329
+ }
1330
+ }
1331
+ function walk(bytes, entityHeaders, acc) {
1332
+ const [headerEnd, bodyStart] = splitEntity(bytes);
1333
+ const headers = entityHeaders ?? parseHeaders(latin1(bytes.subarray(0, headerEnd)));
1334
+ const body = bytes.subarray(entityHeaders ? 0 : bodyStart);
1335
+ const type = parseParams(header(headers, "content-type") ?? "text/plain");
1336
+ if (type.main.startsWith("multipart/") && type.params.boundary) {
1337
+ for (const child of splitMultipart(body, type.params.boundary)) walk(child, null, acc);
1338
+ return;
1339
+ }
1340
+ const disposition = parseParams(header(headers, "content-disposition") ?? "");
1341
+ const decoded = transferDecode(body, header(headers, "content-transfer-encoding"));
1342
+ const declaredDisposition = disposition.main === "attachment" || disposition.main === "inline" ? disposition.main : null;
1343
+ const filename = disposition.params.filename ?? type.params.name ?? null;
1344
+ const contentId = header(headers, "content-id")?.replace(/^<|>$/g, "") ?? null;
1345
+ const isText = type.main.startsWith("text/");
1346
+ const isAttachment = declaredDisposition === "attachment" || !isText;
1347
+ const part = {
1348
+ index: acc.parts.length,
1349
+ contentType: type.main,
1350
+ charset: type.params.charset?.toLowerCase() ?? null,
1351
+ disposition: declaredDisposition,
1352
+ filename,
1353
+ contentId,
1354
+ size: decoded.length,
1355
+ headers
1356
+ };
1357
+ acc.parts.push(part);
1358
+ if (isAttachment) {
1359
+ acc.attachments.push({ ...part, content: Buffer.from(decoded).toString("base64") });
1360
+ return;
1361
+ }
1362
+ const text = decodeCharset(decoded, type.params.charset ?? "utf-8");
1363
+ if (text === null) return;
1364
+ if (type.main === "text/plain" && acc.text === null) acc.text = text;
1365
+ if (type.main === "text/html" && acc.html === null) acc.html = text;
1366
+ }
1367
+ function splitMultipart(body, boundary) {
1368
+ const delimiter = bytesOfLatin1(`--${boundary}`);
1369
+ const children = [];
1370
+ let partStart = -1;
1371
+ for (let i = 0; i <= body.length - delimiter.length; i++) {
1372
+ if (i !== 0 && body[i - 1] !== LF) continue;
1373
+ if (!startsWith(body, i, delimiter)) continue;
1374
+ let partEnd = i;
1375
+ if (partEnd > 0 && body[partEnd - 1] === LF) partEnd -= 1;
1376
+ if (partEnd > 0 && body[partEnd - 1] === CR) partEnd -= 1;
1377
+ if (partStart >= 0) children.push(body.subarray(partStart, partEnd));
1378
+ let j = i + delimiter.length;
1379
+ const closing = body[j] === 45 && body[j + 1] === 45;
1380
+ if (closing) return children;
1381
+ while (j < body.length && body[j] !== LF) j++;
1382
+ partStart = j + 1;
1383
+ i = j;
1384
+ }
1385
+ if (partStart >= 0 && partStart <= body.length) children.push(body.subarray(partStart));
1386
+ return children;
1387
+ }
1388
+ function startsWith(haystack, at, needle) {
1389
+ for (let k = 0; k < needle.length; k++) if (haystack[at + k] !== needle[k]) return false;
1390
+ return true;
1391
+ }
1392
+ function parseMime(raw) {
1393
+ const bytes = typeof raw === "string" ? new Uint8Array(Buffer.from(raw, "utf8")) : raw;
1394
+ const [headerEnd, bodyStart] = splitEntity(bytes);
1395
+ const headers = parseHeaders(latin1(bytes.subarray(0, headerEnd)));
1396
+ const body = bytes.subarray(bodyStart);
1397
+ const acc = { parts: [], attachments: [], text: null, html: null };
1398
+ const type = parseParams(header(headers, "content-type") ?? "text/plain");
1399
+ if (type.main.startsWith("multipart/") && type.params.boundary) {
1400
+ for (const child of splitMultipart(body, type.params.boundary)) walk(child, null, acc);
1401
+ } else {
1402
+ walk(body, headers.filter((h) => h.name.startsWith("content-")), acc);
1403
+ }
1404
+ return {
1405
+ headers,
1406
+ subject: header(headers, "subject"),
1407
+ messageId: header(headers, "message-id"),
1408
+ text: acc.text,
1409
+ html: acc.html,
1410
+ parts: acc.parts,
1411
+ attachments: acc.attachments
1412
+ };
1413
+ }
1414
+
1118
1415
  // src/probe.ts
1119
1416
  async function probe(spec) {
1120
1417
  let response;
@@ -1374,7 +1671,7 @@ function constantTimeEquals(a, b) {
1374
1671
  for (let i = 0; i < a.length; i++) diff |= a.charCodeAt(i) ^ b.charCodeAt(i);
1375
1672
  return diff === 0;
1376
1673
  }
1377
- function header(headers, name) {
1674
+ function header2(headers, name) {
1378
1675
  const wanted = name.toLowerCase();
1379
1676
  for (const [key, value] of Object.entries(headers)) {
1380
1677
  if (key.toLowerCase() !== wanted) continue;
@@ -1392,9 +1689,9 @@ async function verifyDelivery(trigger, delivery, secret, now) {
1392
1689
  reason: `${trigger.service}.${trigger.operation} declares no signature scheme, so a delivery cannot be trusted.`
1393
1690
  };
1394
1691
  }
1395
- const rawHeader = header(delivery.headers, spec.signatureHeader);
1692
+ const rawHeader = header2(delivery.headers, spec.signatureHeader);
1396
1693
  const parsed = spec.parse && rawHeader ? spec.parse(rawHeader) : { signature: rawHeader };
1397
- const timestamp = parsed.timestamp ?? (spec.timestampHeader ? header(delivery.headers, spec.timestampHeader) : void 0);
1694
+ const timestamp = parsed.timestamp ?? (spec.timestampHeader ? header2(delivery.headers, spec.timestampHeader) : void 0);
1398
1695
  return verifyHmac({
1399
1696
  raw: delivery.raw,
1400
1697
  signature: parsed.signature,
@@ -1475,8 +1772,12 @@ function hasDelivery(value) {
1475
1772
  isEmptyPayload,
1476
1773
  isRemote,
1477
1774
  isUnconditionallyRetryable,
1775
+ leaseAction,
1776
+ leaseRenewAt,
1777
+ leaseState,
1478
1778
  linkRanges,
1479
1779
  measure,
1780
+ parseMime,
1480
1781
  payloadHash,
1481
1782
  postChain,
1482
1783
  probe,
@@ -1501,6 +1802,7 @@ function hasDelivery(value) {
1501
1802
  shouldRetry,
1502
1803
  sliceByteRange,
1503
1804
  splitToFit,
1805
+ subscriptionLease,
1504
1806
  triggerEvent,
1505
1807
  unchecked,
1506
1808
  utf8Length,