@zackbart/connecta 0.24.3 → 0.24.4
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/AGENTS.md +18 -20
- package/CHANGELOG.md +64 -1
- package/README.md +5 -6
- package/dist/branding.d.ts +31 -2
- package/dist/branding.js +116 -8
- package/dist/connectors/api.d.ts +1 -1
- package/dist/connectors/api.js +10 -2
- package/dist/connectors/guarded-fetch.d.ts +5 -1
- package/dist/connectors/guarded-fetch.js +34 -4
- package/dist/connectors/remote-mcp.js +8 -4
- package/dist/errors.d.ts +11 -3
- package/dist/errors.js +2 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +12 -1
- package/dist/meta-tools.js +105 -29
- package/dist/operator-ui/generated.js +2 -2
- package/dist/operator-ui/view.d.ts +38 -1
- package/dist/operator-ui/view.js +71 -0
- package/dist/providers/cloudflare.d.ts +14 -2
- package/dist/providers/cloudflare.js +107 -16
- package/dist/providers/linear.d.ts +26 -4
- package/dist/providers/linear.js +19 -4
- package/dist/providers/mixpanel.d.ts +16 -3
- package/dist/providers/mixpanel.js +13 -2
- package/dist/providers/notion.d.ts +8 -1
- package/dist/providers/notion.js +83 -10
- package/dist/providers/revenuecat.d.ts +30 -4
- package/dist/providers/revenuecat.js +42 -4
- package/dist/providers/stripe.d.ts +7 -1
- package/dist/providers/stripe.js +30 -4
- package/dist/providers/vercel.js +11 -1
- package/dist/registry.d.ts +12 -4
- package/dist/registry.js +22 -8
- package/dist/types.d.ts +37 -0
- package/dist/ui.js +18 -10
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/documentation/architecture.md +193 -181
- package/documentation/auth.md +197 -176
- package/documentation/code-mode.md +426 -321
- package/documentation/meta-tools.md +356 -416
- package/examples/worker/AGENTS.md +2 -1
- package/examples/worker/README.md +12 -10
- package/examples/worker/src/index.ts +12 -15
- package/package.json +1 -2
- package/templates/node/.env.example +3 -3
- package/templates/node/AGENTS.md +5 -4
- package/templates/node/README.md +2 -1
- package/templates/node/package.json +1 -1
- package/templates/node/src/index.ts +23 -22
- package/documentation/call-admission.md +0 -158
- package/documentation/cloudflare.md +0 -471
- package/documentation/connector-guides.md +0 -176
- package/documentation/connectors.md +0 -431
- package/documentation/linear.md +0 -193
- package/documentation/mixpanel.md +0 -160
- package/documentation/notion.md +0 -308
- package/documentation/operations.md +0 -359
- package/documentation/operator-ui.md +0 -135
- package/documentation/optional-modules-upgrade.md +0 -243
- package/documentation/provider-conventions.md +0 -729
- package/documentation/request-admission.md +0 -204
- package/documentation/revenuecat.md +0 -305
- package/documentation/storage-and-credentials.md +0 -254
- package/documentation/stripe.md +0 -262
- package/documentation/upgrading.md +0 -768
- package/documentation/vercel.md +0 -241
package/dist/meta-tools.js
CHANGED
|
@@ -99,19 +99,60 @@ export function alignEndToCharBoundary(bytes, offset, end, total) {
|
|
|
99
99
|
}
|
|
100
100
|
return e;
|
|
101
101
|
}
|
|
102
|
+
/** Prefix of the chunked paging envelope; `v1`'s single inline key still reads. */
|
|
103
|
+
const RESULT_ENVELOPE_V2 = "connecta-result-v2:";
|
|
104
|
+
/** `<total bytes>:<bytes per chunk>:` follow the prefix, then chunk 0's base64. */
|
|
105
|
+
const RESULT_ENVELOPE_V2_HEADER = new RegExp(`^${RESULT_ENVELOPE_V2}(\\d+):(\\d+):`);
|
|
106
|
+
/**
|
|
107
|
+
* Smallest chunk of result text stored under one key. A multiple of three so
|
|
108
|
+
* every chunk's base64 stands alone and a byte offset inside it lands on a
|
|
109
|
+
* whole quad, and a little under the 50,000-byte default page so a default page
|
|
110
|
+
* reads two or three chunks rather than dozens.
|
|
111
|
+
*/
|
|
112
|
+
const RESULT_CHUNK_BYTES = 49_152;
|
|
113
|
+
/**
|
|
114
|
+
* Keys one stashed result may occupy. Chunking trades write count for read
|
|
115
|
+
* count, and both are real: every chunk is a storage write at stash time, and
|
|
116
|
+
* `fileStorage` rewrites its whole file per write. Above roughly 1.5 MB the
|
|
117
|
+
* chunks widen instead of multiplying, so a result costs a bounded number of
|
|
118
|
+
* writes and a page still reads a small fraction of it.
|
|
119
|
+
*/
|
|
120
|
+
const RESULT_MAX_CHUNKS = 32;
|
|
121
|
+
/** Chunk width for a result of `totalBytes`, always a multiple of three. */
|
|
122
|
+
function resultChunkBytes(totalBytes) {
|
|
123
|
+
return Math.max(RESULT_CHUNK_BYTES, Math.ceil(totalBytes / RESULT_MAX_CHUNKS / 3) * 3);
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Base64 of `bytes`, in three-byte-aligned batches so the argument list of one
|
|
127
|
+
* spread never grows with the result. Alignment matters: an unaligned batch
|
|
128
|
+
* would pad mid-stream and the concatenation would no longer decode.
|
|
129
|
+
*/
|
|
130
|
+
function base64Of(bytes) {
|
|
131
|
+
let out = "";
|
|
132
|
+
for (let offset = 0; offset < bytes.length; offset += 12_288) {
|
|
133
|
+
out += btoa(String.fromCharCode(...bytes.subarray(offset, offset + 12_288)));
|
|
134
|
+
}
|
|
135
|
+
return out;
|
|
136
|
+
}
|
|
102
137
|
/** Stash a completed result, or return a notice without a paging route. */
|
|
103
138
|
async function stashResult(bytes, results) {
|
|
104
139
|
const totalBytes = bytes.length;
|
|
105
140
|
const id = crypto.randomUUID();
|
|
106
141
|
try {
|
|
107
|
-
// Base64 permits byte-range decoding
|
|
108
|
-
//
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
142
|
+
// Base64 permits byte-range decoding, and splitting the envelope across
|
|
143
|
+
// keys keeps a page's storage read proportional to the page instead of to
|
|
144
|
+
// the whole result (issue #540). Chunk 0 carries the header; the get_result
|
|
145
|
+
// reader below maps a byte offset back to chunk index and base64 quad.
|
|
146
|
+
const chunkBytes = resultChunkBytes(totalBytes);
|
|
147
|
+
const chunks = [`${RESULT_ENVELOPE_V2}${totalBytes}:${chunkBytes}:`];
|
|
148
|
+
for (let offset = 0; offset < bytes.length; offset += chunkBytes) {
|
|
149
|
+
const chunk = base64Of(bytes.subarray(offset, offset + chunkBytes));
|
|
150
|
+
if (offset === 0)
|
|
151
|
+
chunks[0] += chunk;
|
|
152
|
+
else
|
|
153
|
+
chunks.push(chunk);
|
|
112
154
|
}
|
|
113
|
-
|
|
114
|
-
if (!await results.set(`result:${id}`, stored, RESULT_TTL_SECONDS)) {
|
|
155
|
+
if (!await results.set(`result:${id}`, chunks, RESULT_TTL_SECONDS)) {
|
|
115
156
|
throw new Error("Result stash capacity exhausted");
|
|
116
157
|
}
|
|
117
158
|
}
|
|
@@ -449,39 +490,74 @@ export function createMetaTools(registry, baseUrl, opts = {}) {
|
|
|
449
490
|
`>= ${MIN_RESULT_OFFSET}. Omit it to start at the beginning.`);
|
|
450
491
|
}
|
|
451
492
|
const results = registry.resultsStorage();
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
493
|
+
const unavailableResult = () => ({
|
|
494
|
+
...jsonResult({
|
|
495
|
+
error: {
|
|
496
|
+
code: "unavailable",
|
|
497
|
+
message: "Result paging storage is unavailable.",
|
|
498
|
+
retryable: true,
|
|
499
|
+
},
|
|
500
|
+
}),
|
|
501
|
+
isError: true,
|
|
502
|
+
});
|
|
503
|
+
// `false` is a storage failure — retryable, and distinct from an id that
|
|
504
|
+
// is simply gone. Every key a page touches answers the same way, so a
|
|
505
|
+
// backend that dies halfway through a multi-chunk page says so.
|
|
506
|
+
const read = async (key) => {
|
|
507
|
+
try {
|
|
508
|
+
return await results.get(key) ?? null;
|
|
509
|
+
}
|
|
510
|
+
catch {
|
|
511
|
+
return false;
|
|
512
|
+
}
|
|
513
|
+
};
|
|
514
|
+
const stored = await read(`result:${args.id}`);
|
|
515
|
+
if (stored === false)
|
|
516
|
+
return unavailableResult();
|
|
517
|
+
if (stored === null) {
|
|
469
518
|
return errorResult(`Unknown or expired result id "${boundedEchoText(args.id)}"`);
|
|
470
519
|
}
|
|
471
520
|
const requestedOffset = args.offset ?? 0;
|
|
472
521
|
const maxBytes = args.maxBytes ?? globalCap;
|
|
473
|
-
//
|
|
474
|
-
// entries
|
|
475
|
-
|
|
522
|
+
// Read and decode only the chunks this page covers, plus a few bytes of
|
|
523
|
+
// UTF-8 boundary lookaround. Pre-upgrade entries stay readable for their
|
|
524
|
+
// short TTL: v1 inlined the whole envelope under one key, which is this
|
|
525
|
+
// format with a single chunk as wide as the result, and raw text before
|
|
526
|
+
// that still pays one full encode per page.
|
|
527
|
+
const chunked = RESULT_ENVELOPE_V2_HEADER.exec(stored.slice(0, 80));
|
|
528
|
+
const inline = chunked ? null : /^connecta-result-v1:(\d+):/.exec(stored.slice(0, 64));
|
|
529
|
+
const header = chunked ?? inline;
|
|
476
530
|
let bytes;
|
|
477
531
|
let total;
|
|
478
532
|
let start = 0;
|
|
479
533
|
if (header) {
|
|
480
534
|
total = Number(header[1]);
|
|
535
|
+
const chunkBytes = chunked ? Number(chunked[2]) : Math.max(total, 1);
|
|
481
536
|
start = Math.floor(Math.max(0, Math.min(requestedOffset, total) - 3) / 3) * 3;
|
|
482
537
|
const end = Math.min(total, requestedOffset + maxBytes + 4);
|
|
483
|
-
|
|
484
|
-
|
|
538
|
+
bytes = new Uint8Array(Math.max(0, end - start));
|
|
539
|
+
const lastChunk = Math.floor(Math.max(end - 1, start) / chunkBytes);
|
|
540
|
+
for (let index = Math.floor(start / chunkBytes); index <= lastChunk; index++) {
|
|
541
|
+
const encoded = index === 0
|
|
542
|
+
? stored.slice(header[0].length)
|
|
543
|
+
: await read(`result:${args.id}#${index}`);
|
|
544
|
+
if (encoded === false)
|
|
545
|
+
return unavailableResult();
|
|
546
|
+
if (encoded === null) {
|
|
547
|
+
// A chunk expired or was evicted under its own header; the id can no
|
|
548
|
+
// longer serve this range, and inventing U+0000 filler would be worse.
|
|
549
|
+
return errorResult(`Unknown or expired result id "${boundedEchoText(args.id)}"`);
|
|
550
|
+
}
|
|
551
|
+
const chunkStart = index * chunkBytes;
|
|
552
|
+
// Both bounds are chunk-local. `from` inherits `start`'s three-byte
|
|
553
|
+
// alignment because every chunk boundary is a multiple of three.
|
|
554
|
+
const from = Math.max(start, chunkStart) - chunkStart;
|
|
555
|
+
const to = Math.min(end, chunkStart + chunkBytes, total) - chunkStart;
|
|
556
|
+
const binary = atob(encoded.slice(from / 3 * 4, Math.ceil(to / 3) * 4));
|
|
557
|
+
for (let at = from; at < to; at++) {
|
|
558
|
+
bytes[chunkStart + at - start] = binary.charCodeAt(at - from);
|
|
559
|
+
}
|
|
560
|
+
}
|
|
485
561
|
}
|
|
486
562
|
else {
|
|
487
563
|
bytes = enc.encode(stored);
|