@primitivedotdev/sdk 1.26.1 → 1.27.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/README.md +42 -0
- package/dist/api/index.d.ts +2 -2
- package/dist/api/index.js +4 -4
- package/dist/{api-BEv_YML6.js → api-D4xf448r.js} +506 -3
- package/dist/contract/index.d.ts +1 -1
- package/dist/contract/index.js +2 -1
- package/dist/{index-JpXUV16f.d.ts → index-DCLGz6IH.d.ts} +4 -36
- package/dist/{index-B1a5L2zU.d.ts → index-DwP3ul-y.d.ts} +110 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +3 -3
- package/dist/openapi/index.js +1 -1
- package/dist/{operations.generated-DVHnicf5.js → operations.generated-CoVn-Rb6.js} +192 -3
- package/dist/parser/address.js +1 -1
- package/dist/parser/index.js +1 -1
- package/dist/payloads/index.js +1 -1
- package/dist/{webhook-CLVRgb-L.js → trust-DBOnyTHV.js} +671 -2147
- package/dist/webhook/index.d.ts +1 -1
- package/dist/webhook/index.js +2 -2
- package/dist/webhook-B72YFapb.js +2132 -0
- package/package.json +3 -3
- package/dist/errors-BVnAu5jX.js +0 -688
- /package/dist/{address-parser-C2JJzLdA.js → address-parser-BxXv4vx0.js} +0 -0
- /package/dist/{payloads-BfJAtqW9.js → payloads-BMngnJlE.js} +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { t as __exportAll } from "./chunk-pbuEa-1d.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { L as WebhookVerificationError, _ as parseWebhookEvent, m as formatAddress } from "./trust-DBOnyTHV.js";
|
|
3
|
+
import { a as pushFile, i as pushBytes, o as loadNodeFsPromises } from "./payloads-BMngnJlE.js";
|
|
4
4
|
//#region ../packages/api-core/src/api/core/bodySerializer.gen.ts
|
|
5
5
|
const jsonBodySerializer = { bodySerializer: (body) => JSON.stringify(body, (_key, value) => typeof value === "bigint" ? value.toString() : value) };
|
|
6
6
|
//#endregion
|
|
@@ -3393,6 +3393,507 @@ function isMemoryJsonValue(value, seen = /* @__PURE__ */ new Set()) {
|
|
|
3393
3393
|
}
|
|
3394
3394
|
}
|
|
3395
3395
|
//#endregion
|
|
3396
|
+
//#region src/api/event-transport.ts
|
|
3397
|
+
var EventReceiverError = class extends Error {
|
|
3398
|
+
constructor(message, code, status = 0, retryAfterMs = 0) {
|
|
3399
|
+
super(message);
|
|
3400
|
+
this.code = code;
|
|
3401
|
+
this.status = status;
|
|
3402
|
+
this.retryAfterMs = retryAfterMs;
|
|
3403
|
+
this.name = "EventReceiverError";
|
|
3404
|
+
}
|
|
3405
|
+
};
|
|
3406
|
+
function record(value) {
|
|
3407
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
3408
|
+
}
|
|
3409
|
+
function validateOffer(value) {
|
|
3410
|
+
if (!record(value) || !Number.isSafeInteger(value.backlog) || Number(value.backlog) < 0 || !Number.isSafeInteger(value.gap_count) || Number(value.gap_count) < 0 || value.last_gap_reason !== null && typeof value.last_gap_reason !== "string" || value.retention_seconds !== 86400 || value.handler_timeout_seconds !== 30) throw new EventReceiverError("Invalid event status response", "invalid_response");
|
|
3411
|
+
const d = value.delivery;
|
|
3412
|
+
if (d !== null && (!record(d) || ![
|
|
3413
|
+
"queue_id",
|
|
3414
|
+
"event_id",
|
|
3415
|
+
"delivery_id",
|
|
3416
|
+
"lease_token",
|
|
3417
|
+
"event_type",
|
|
3418
|
+
"body",
|
|
3419
|
+
"lease_expires_at"
|
|
3420
|
+
].every((key) => typeof d[key] === "string") || !Number.isFinite(Date.parse(String(d.lease_expires_at))) || !record(d.headers) || !Object.values(d.headers).every((header) => typeof header === "string"))) throw new EventReceiverError("Invalid event delivery response", "invalid_response");
|
|
3421
|
+
return value;
|
|
3422
|
+
}
|
|
3423
|
+
function retryAfter(value) {
|
|
3424
|
+
if (!value) return 0;
|
|
3425
|
+
const ms = /^\d+(\.\d+)?$/.test(value) ? Number(value) * 1e3 : Date.parse(value) - Date.now();
|
|
3426
|
+
return Number.isFinite(ms) ? Math.max(0, ms) : 0;
|
|
3427
|
+
}
|
|
3428
|
+
function unwrap(result) {
|
|
3429
|
+
if (result.data !== void 0 && !result.error) return result.data;
|
|
3430
|
+
const code = record(result.error) && record(result.error.error) && typeof result.error.error.code === "string" ? result.error.error.code : "request_failed";
|
|
3431
|
+
throw new EventReceiverError(`Event request failed (${result.response?.status ?? 0}, ${code})`, code, result.response?.status ?? 0, retryAfter(result.response?.headers.get("retry-after") ?? null));
|
|
3432
|
+
}
|
|
3433
|
+
function delay(ms, signal) {
|
|
3434
|
+
signal.throwIfAborted();
|
|
3435
|
+
return new Promise((resolve, reject) => {
|
|
3436
|
+
const abort = () => {
|
|
3437
|
+
clearTimeout(timer);
|
|
3438
|
+
reject(signal.reason);
|
|
3439
|
+
};
|
|
3440
|
+
const timer = setTimeout(() => {
|
|
3441
|
+
signal.removeEventListener("abort", abort);
|
|
3442
|
+
resolve();
|
|
3443
|
+
}, Math.min(ms, 2147483647));
|
|
3444
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
3445
|
+
});
|
|
3446
|
+
}
|
|
3447
|
+
async function eventRetry(operation, signal, onRetry) {
|
|
3448
|
+
for (let attempt = 0;; attempt++) {
|
|
3449
|
+
signal.throwIfAborted();
|
|
3450
|
+
try {
|
|
3451
|
+
return await operation();
|
|
3452
|
+
} catch (error) {
|
|
3453
|
+
signal.throwIfAborted();
|
|
3454
|
+
if (error instanceof EventReceiverError && (!([
|
|
3455
|
+
0,
|
|
3456
|
+
408,
|
|
3457
|
+
429
|
|
3458
|
+
].includes(error.status) || error.status >= 500) || [
|
|
3459
|
+
"invalid_response",
|
|
3460
|
+
"unsupported",
|
|
3461
|
+
"pull_unavailable",
|
|
3462
|
+
"subscription_unavailable"
|
|
3463
|
+
].includes(error.code))) throw error;
|
|
3464
|
+
if (!(error instanceof EventReceiverError)) throw error;
|
|
3465
|
+
onRetry?.();
|
|
3466
|
+
await delay(Math.max(error instanceof EventReceiverError ? error.retryAfterMs : 0, Math.min(1e4, 250 * 2 ** Math.min(attempt, 6)) * (.8 + Math.random() * .4)), signal);
|
|
3467
|
+
}
|
|
3468
|
+
}
|
|
3469
|
+
}
|
|
3470
|
+
/** Shared by the SDK and CLI. Completion retries preserve the original evidence. */
|
|
3471
|
+
var EventConnection = class {
|
|
3472
|
+
socket;
|
|
3473
|
+
pending;
|
|
3474
|
+
accountId;
|
|
3475
|
+
touch;
|
|
3476
|
+
constructor(client, endpointId, options = {}) {
|
|
3477
|
+
this.client = client;
|
|
3478
|
+
this.endpointId = endpointId;
|
|
3479
|
+
this.options = options;
|
|
3480
|
+
}
|
|
3481
|
+
close() {
|
|
3482
|
+
const socket = this.socket;
|
|
3483
|
+
this.socket = void 0;
|
|
3484
|
+
this.pending?.reject(new EventReceiverError("Event connection closed", "disconnected"));
|
|
3485
|
+
this.pending = void 0;
|
|
3486
|
+
socket?.close();
|
|
3487
|
+
}
|
|
3488
|
+
async open(signal) {
|
|
3489
|
+
if (this.options.transport === "poll" || this.socket?.readyState === 1) return;
|
|
3490
|
+
const account = unwrap(await getAccount({
|
|
3491
|
+
client: this.client,
|
|
3492
|
+
responseStyle: "fields",
|
|
3493
|
+
throwOnError: false,
|
|
3494
|
+
signal: AbortSignal.any([signal, AbortSignal.timeout(15e3)])
|
|
3495
|
+
}));
|
|
3496
|
+
if (!account.data?.id || this.accountId && account.data.id !== this.accountId) throw new EventReceiverError("The authenticated account changed; restart the receiver", "identity_changed", 403);
|
|
3497
|
+
this.accountId = account.data.id;
|
|
3498
|
+
const config = this.client.getConfig();
|
|
3499
|
+
const auth = typeof config.auth === "function" ? await config.auth({
|
|
3500
|
+
type: "http",
|
|
3501
|
+
scheme: "bearer"
|
|
3502
|
+
}) : config.auth;
|
|
3503
|
+
const authorization = new Headers(config.headers).get("authorization");
|
|
3504
|
+
const token = typeof auth === "string" ? auth.replace(/^Bearer /i, "") : authorization?.replace(/^Bearer /i, "");
|
|
3505
|
+
if (!token) throw new EventReceiverError("Event receiver requires bearer credentials", "unauthorized", 401);
|
|
3506
|
+
const url = new URL(`${config.baseUrl?.replace(/\/+$/, "")}/endpoints/${encodeURIComponent(this.endpointId)}/stream`);
|
|
3507
|
+
if (url.protocol !== "https:" && !(url.protocol === "http:" && [
|
|
3508
|
+
"localhost",
|
|
3509
|
+
"127.0.0.1",
|
|
3510
|
+
"[::1]"
|
|
3511
|
+
].includes(url.hostname))) throw new EventReceiverError("Event connections require HTTPS (except loopback development)", "unsupported");
|
|
3512
|
+
url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
|
|
3513
|
+
const socket = (this.options.webSocketFactory ?? ((address, protocol) => new WebSocket(address, protocol)))(url.href, "primitive.events.v1");
|
|
3514
|
+
this.socket = socket;
|
|
3515
|
+
socket.addEventListener("message", (message) => {
|
|
3516
|
+
if (this.socket !== socket) return;
|
|
3517
|
+
let frame;
|
|
3518
|
+
try {
|
|
3519
|
+
frame = JSON.parse(String(message.data));
|
|
3520
|
+
} catch {
|
|
3521
|
+
this.fail(new EventReceiverError("Invalid event frame", "invalid_response"));
|
|
3522
|
+
return;
|
|
3523
|
+
}
|
|
3524
|
+
this.touch?.();
|
|
3525
|
+
if (!record(frame)) {
|
|
3526
|
+
this.fail(new EventReceiverError("Invalid event frame", "invalid_response"));
|
|
3527
|
+
return;
|
|
3528
|
+
}
|
|
3529
|
+
if (frame.type === "ping") {
|
|
3530
|
+
socket.send(JSON.stringify({ type: "pong" }));
|
|
3531
|
+
return;
|
|
3532
|
+
}
|
|
3533
|
+
if (frame.type === "status") {
|
|
3534
|
+
try {
|
|
3535
|
+
this.options.onStatus?.(validateOffer(frame.data));
|
|
3536
|
+
} catch (error) {
|
|
3537
|
+
this.fail(error);
|
|
3538
|
+
}
|
|
3539
|
+
return;
|
|
3540
|
+
}
|
|
3541
|
+
if (frame.type === "error") {
|
|
3542
|
+
this.fail(new EventReceiverError("Event stream request failed", typeof frame.code === "string" ? frame.code : "request_failed", typeof frame.status === "number" ? frame.status : 0, retryAfter(typeof frame.retry_after === "string" ? frame.retry_after : null)));
|
|
3543
|
+
return;
|
|
3544
|
+
}
|
|
3545
|
+
this.pending?.resolve(frame);
|
|
3546
|
+
});
|
|
3547
|
+
socket.addEventListener("close", () => {
|
|
3548
|
+
if (this.socket === socket) this.fail(new EventReceiverError("Event stream disconnected", "disconnected"));
|
|
3549
|
+
});
|
|
3550
|
+
socket.addEventListener("error", () => {
|
|
3551
|
+
if (this.socket === socket) this.fail(new EventReceiverError("Event stream connection failed", "disconnected"));
|
|
3552
|
+
});
|
|
3553
|
+
const ready = await this.exchange(signal, void 0, () => {
|
|
3554
|
+
socket.addEventListener("open", () => socket.send(JSON.stringify({
|
|
3555
|
+
type: "authenticate",
|
|
3556
|
+
token
|
|
3557
|
+
})), { once: true });
|
|
3558
|
+
}, 15e3);
|
|
3559
|
+
if (!record(ready) || ready.type !== "ready" || ready.protocol !== "primitive.events.v1") {
|
|
3560
|
+
this.close();
|
|
3561
|
+
throw new EventReceiverError("Unsupported event stream protocol", "unsupported");
|
|
3562
|
+
}
|
|
3563
|
+
}
|
|
3564
|
+
fail(error) {
|
|
3565
|
+
this.pending?.reject(error);
|
|
3566
|
+
this.pending = void 0;
|
|
3567
|
+
this.close();
|
|
3568
|
+
}
|
|
3569
|
+
async exchange(signal, frame, start, timeoutMs = 6e4) {
|
|
3570
|
+
signal.throwIfAborted();
|
|
3571
|
+
if (this.pending) throw new EventReceiverError("Concurrent event operation", "busy", 409);
|
|
3572
|
+
let cleanup = () => {};
|
|
3573
|
+
try {
|
|
3574
|
+
return await new Promise((resolve, reject) => {
|
|
3575
|
+
const abort = () => {
|
|
3576
|
+
reject(signal.reason);
|
|
3577
|
+
this.close();
|
|
3578
|
+
};
|
|
3579
|
+
let timer;
|
|
3580
|
+
this.touch = () => {
|
|
3581
|
+
clearTimeout(timer);
|
|
3582
|
+
timer = setTimeout(() => this.fail(new EventReceiverError("Event stream timed out", "disconnected")), timeoutMs);
|
|
3583
|
+
};
|
|
3584
|
+
this.touch();
|
|
3585
|
+
cleanup = () => {
|
|
3586
|
+
this.touch = void 0;
|
|
3587
|
+
clearTimeout(timer);
|
|
3588
|
+
signal.removeEventListener("abort", abort);
|
|
3589
|
+
this.pending = void 0;
|
|
3590
|
+
};
|
|
3591
|
+
this.pending = {
|
|
3592
|
+
resolve,
|
|
3593
|
+
reject
|
|
3594
|
+
};
|
|
3595
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
3596
|
+
if (frame) this.socket?.send(JSON.stringify(frame));
|
|
3597
|
+
start?.();
|
|
3598
|
+
});
|
|
3599
|
+
} finally {
|
|
3600
|
+
cleanup();
|
|
3601
|
+
}
|
|
3602
|
+
}
|
|
3603
|
+
async receive(signal) {
|
|
3604
|
+
if (this.options.transport === "poll") return validateOffer(unwrap(await pullWebhookEvent({
|
|
3605
|
+
client: this.client,
|
|
3606
|
+
path: { id: this.endpointId },
|
|
3607
|
+
body: { wait_seconds: 25 },
|
|
3608
|
+
responseStyle: "fields",
|
|
3609
|
+
throwOnError: false,
|
|
3610
|
+
signal: AbortSignal.any([signal, AbortSignal.timeout(45e3)])
|
|
3611
|
+
})).data);
|
|
3612
|
+
await this.open(signal);
|
|
3613
|
+
const frame = await this.exchange(signal, { type: "receive" }, void 0, 9e4);
|
|
3614
|
+
if (!record(frame) || frame.type !== "event") throw new EventReceiverError("Invalid event frame", "invalid_response");
|
|
3615
|
+
return validateOffer(frame.data);
|
|
3616
|
+
}
|
|
3617
|
+
async complete(body, signal) {
|
|
3618
|
+
let receipt;
|
|
3619
|
+
if (this.options.transport === "poll") receipt = unwrap(await completeWebhookEvent({
|
|
3620
|
+
client: this.client,
|
|
3621
|
+
path: { id: this.endpointId },
|
|
3622
|
+
body,
|
|
3623
|
+
responseStyle: "fields",
|
|
3624
|
+
throwOnError: false,
|
|
3625
|
+
signal: AbortSignal.any([signal, AbortSignal.timeout(15e3)])
|
|
3626
|
+
})).data;
|
|
3627
|
+
else {
|
|
3628
|
+
await this.open(signal);
|
|
3629
|
+
const frame = await this.exchange(signal, {
|
|
3630
|
+
type: "complete",
|
|
3631
|
+
body
|
|
3632
|
+
}, void 0, 15e3);
|
|
3633
|
+
if (!record(frame) || frame.type !== "receipt") throw new EventReceiverError("Invalid event receipt", "invalid_response");
|
|
3634
|
+
receipt = frame.data;
|
|
3635
|
+
}
|
|
3636
|
+
if (!record(receipt) || !["completed", "already_completed"].includes(String(receipt.result))) throw new EventReceiverError("Invalid event receipt", "invalid_response");
|
|
3637
|
+
}
|
|
3638
|
+
};
|
|
3639
|
+
//#endregion
|
|
3640
|
+
//#region src/api/events.ts
|
|
3641
|
+
var DeliveryExpired = class extends EventReceiverError {
|
|
3642
|
+
constructor() {
|
|
3643
|
+
super("Event acceptance deadline expired; the event may be delivered again", "delivery_expired", 409);
|
|
3644
|
+
}
|
|
3645
|
+
};
|
|
3646
|
+
function validate(options) {
|
|
3647
|
+
if (options.transport !== void 0 && options.transport !== "websocket" && options.transport !== "poll") throw new TypeError("transport must be websocket or poll");
|
|
3648
|
+
if (!/^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$/.test(options.subscription)) throw new TypeError("subscription must be 1-64 letters, digits, underscores or hyphens, starting with a letter or digit");
|
|
3649
|
+
if (options.events !== void 0 && (options.events.length === 0 || options.events.length > 50 || options.events.some((event) => typeof event !== "string" || !event.trim()))) throw new TypeError("events must contain 1-50 nonempty event names");
|
|
3650
|
+
if (options.timeoutMs !== void 0 && (!Number.isFinite(options.timeoutMs) || options.timeoutMs <= 0 || options.timeoutMs > 2147483647)) throw new TypeError("timeoutMs must be positive and at most 2147483647");
|
|
3651
|
+
}
|
|
3652
|
+
var EventsResource = class {
|
|
3653
|
+
active = /* @__PURE__ */ new Set();
|
|
3654
|
+
constructor(client) {
|
|
3655
|
+
this.client = client;
|
|
3656
|
+
}
|
|
3657
|
+
reserve(options) {
|
|
3658
|
+
validate(options);
|
|
3659
|
+
options.signal?.throwIfAborted();
|
|
3660
|
+
if (this.active.has(options.subscription)) throw new EventReceiverError("A receiver or unsettled delivery already uses this subscription on this client", "busy", 409);
|
|
3661
|
+
this.active.add(options.subscription);
|
|
3662
|
+
return () => {
|
|
3663
|
+
this.active.delete(options.subscription);
|
|
3664
|
+
};
|
|
3665
|
+
}
|
|
3666
|
+
async connect(options, signal) {
|
|
3667
|
+
const config = this.client.getConfig();
|
|
3668
|
+
const client = new PrimitiveApiClient({
|
|
3669
|
+
...config,
|
|
3670
|
+
headers: new Headers(config.headers),
|
|
3671
|
+
apiBaseUrl: config.baseUrl
|
|
3672
|
+
}).client;
|
|
3673
|
+
let status = { type: "ready" };
|
|
3674
|
+
const emit = (next) => {
|
|
3675
|
+
status = {
|
|
3676
|
+
...status,
|
|
3677
|
+
...next
|
|
3678
|
+
};
|
|
3679
|
+
options.onStatus?.(status);
|
|
3680
|
+
};
|
|
3681
|
+
const retry = (operation, operationSignal = signal) => eventRetry(operation, operationSignal, () => emit({ type: "reconnecting" }));
|
|
3682
|
+
const endpoint = (await retry(async () => unwrap(await createEndpoint({
|
|
3683
|
+
client,
|
|
3684
|
+
responseStyle: "fields",
|
|
3685
|
+
throwOnError: false,
|
|
3686
|
+
signal: AbortSignal.any([signal, AbortSignal.timeout(15e3)]),
|
|
3687
|
+
body: {
|
|
3688
|
+
kind: "pull",
|
|
3689
|
+
name: options.subscription,
|
|
3690
|
+
...options.events === void 0 ? {} : { rules: { event_types: [...new Set(options.events)] } }
|
|
3691
|
+
}
|
|
3692
|
+
})))).data;
|
|
3693
|
+
if (!endpoint || endpoint.kind !== "pull" || endpoint.enabled === false) throw new EventReceiverError("Subscription is unavailable", "subscription_unavailable", 409);
|
|
3694
|
+
if (!endpoint.receiver_capabilities?.completion_modes.includes("sdk") || options.transport !== "poll" && !endpoint.receiver_capabilities.stream_protocols.includes("primitive.events.v1")) throw new EventReceiverError("This API does not support SDK event receiving with the selected transport", "unsupported");
|
|
3695
|
+
let gaps = -1;
|
|
3696
|
+
const update = (data) => {
|
|
3697
|
+
status = {
|
|
3698
|
+
type: "ready",
|
|
3699
|
+
backlog: data.backlog,
|
|
3700
|
+
gapCount: data.gap_count,
|
|
3701
|
+
lastGapReason: data.last_gap_reason
|
|
3702
|
+
};
|
|
3703
|
+
if (data.gap_count > 0 && data.gap_count !== gaps) {
|
|
3704
|
+
gaps = data.gap_count;
|
|
3705
|
+
emit({
|
|
3706
|
+
...status,
|
|
3707
|
+
type: "gap"
|
|
3708
|
+
});
|
|
3709
|
+
if (options.onGap === "error") throw new EventReceiverError("Subscription reports lost events", "event_gap", 409);
|
|
3710
|
+
}
|
|
3711
|
+
};
|
|
3712
|
+
const connection = new EventConnection(client, endpoint.id, {
|
|
3713
|
+
...options,
|
|
3714
|
+
onStatus: update
|
|
3715
|
+
});
|
|
3716
|
+
try {
|
|
3717
|
+
await retry(() => connection.open(signal));
|
|
3718
|
+
emit(status);
|
|
3719
|
+
} catch (error) {
|
|
3720
|
+
connection.close();
|
|
3721
|
+
throw error;
|
|
3722
|
+
}
|
|
3723
|
+
return {
|
|
3724
|
+
connection,
|
|
3725
|
+
emit,
|
|
3726
|
+
retry,
|
|
3727
|
+
update,
|
|
3728
|
+
get status() {
|
|
3729
|
+
return status;
|
|
3730
|
+
}
|
|
3731
|
+
};
|
|
3732
|
+
}
|
|
3733
|
+
async wait(options) {
|
|
3734
|
+
const release = this.reserve(options);
|
|
3735
|
+
const timeout = new AbortController();
|
|
3736
|
+
const timeoutReason = new DOMException("Event wait timed out", "TimeoutError");
|
|
3737
|
+
const timer = options.timeoutMs === void 0 ? void 0 : setTimeout(() => timeout.abort(timeoutReason), options.timeoutMs);
|
|
3738
|
+
const signal = AbortSignal.any([timeout.signal, ...options.signal ? [options.signal] : []]);
|
|
3739
|
+
let session;
|
|
3740
|
+
let handedOff = false;
|
|
3741
|
+
try {
|
|
3742
|
+
session = await this.connect(options, signal);
|
|
3743
|
+
const receiving = session;
|
|
3744
|
+
while (true) {
|
|
3745
|
+
const data = await receiving.retry(() => receiving.connection.receive(signal));
|
|
3746
|
+
signal.throwIfAborted();
|
|
3747
|
+
session.update(data);
|
|
3748
|
+
if (!data.delivery) {
|
|
3749
|
+
await delay(250, signal);
|
|
3750
|
+
continue;
|
|
3751
|
+
}
|
|
3752
|
+
const selected = session;
|
|
3753
|
+
const delivery = this.delivery(data.delivery, selected.connection, options.signal, () => {
|
|
3754
|
+
selected.connection.close();
|
|
3755
|
+
release();
|
|
3756
|
+
});
|
|
3757
|
+
handedOff = true;
|
|
3758
|
+
return delivery;
|
|
3759
|
+
}
|
|
3760
|
+
} catch (error) {
|
|
3761
|
+
if (timeout.signal.aborted && !options.signal?.aborted && error === timeoutReason) return null;
|
|
3762
|
+
throw error;
|
|
3763
|
+
} finally {
|
|
3764
|
+
clearTimeout(timer);
|
|
3765
|
+
if (!handedOff) {
|
|
3766
|
+
session?.connection.close();
|
|
3767
|
+
release();
|
|
3768
|
+
}
|
|
3769
|
+
}
|
|
3770
|
+
}
|
|
3771
|
+
async listen(handler, options) {
|
|
3772
|
+
if (typeof handler !== "function") throw new TypeError("handler must be a function");
|
|
3773
|
+
const release = this.reserve(options);
|
|
3774
|
+
const stopping = new AbortController();
|
|
3775
|
+
const signal = AbortSignal.any([stopping.signal, ...options.signal ? [options.signal] : []]);
|
|
3776
|
+
let session;
|
|
3777
|
+
try {
|
|
3778
|
+
session = await this.connect(options, signal);
|
|
3779
|
+
} catch (error) {
|
|
3780
|
+
release();
|
|
3781
|
+
throw error;
|
|
3782
|
+
}
|
|
3783
|
+
let ended = false;
|
|
3784
|
+
let disposeDelivery = () => {};
|
|
3785
|
+
const closed = (async () => {
|
|
3786
|
+
try {
|
|
3787
|
+
while (!signal.aborted) {
|
|
3788
|
+
const offer = await session.retry(() => session.connection.receive(signal));
|
|
3789
|
+
signal.throwIfAborted();
|
|
3790
|
+
session.update(offer);
|
|
3791
|
+
if (!offer.delivery) {
|
|
3792
|
+
await delay(250, signal);
|
|
3793
|
+
continue;
|
|
3794
|
+
}
|
|
3795
|
+
const delivery = this.delivery(offer.delivery, session.connection, options.signal, () => {});
|
|
3796
|
+
disposeDelivery = delivery.dispose;
|
|
3797
|
+
let removeAbort = () => {};
|
|
3798
|
+
try {
|
|
3799
|
+
await Promise.race([Promise.resolve().then(() => handler(delivery.event, { signal: delivery.signal })), new Promise((_, reject) => {
|
|
3800
|
+
const aborted = () => reject(delivery.signal.reason);
|
|
3801
|
+
delivery.signal.addEventListener("abort", aborted, { once: true });
|
|
3802
|
+
removeAbort = () => delivery.signal.removeEventListener("abort", aborted);
|
|
3803
|
+
if (delivery.signal.aborted) aborted();
|
|
3804
|
+
})]);
|
|
3805
|
+
} catch (error) {
|
|
3806
|
+
if (delivery.signal.aborted) throw error;
|
|
3807
|
+
session.emit({
|
|
3808
|
+
type: "handler_error",
|
|
3809
|
+
error
|
|
3810
|
+
});
|
|
3811
|
+
await delivery.retry();
|
|
3812
|
+
continue;
|
|
3813
|
+
} finally {
|
|
3814
|
+
removeAbort();
|
|
3815
|
+
}
|
|
3816
|
+
await delivery.ack();
|
|
3817
|
+
}
|
|
3818
|
+
} catch (error) {
|
|
3819
|
+
if (!signal.aborted) throw error;
|
|
3820
|
+
} finally {
|
|
3821
|
+
ended = true;
|
|
3822
|
+
disposeDelivery();
|
|
3823
|
+
session.connection.close();
|
|
3824
|
+
release();
|
|
3825
|
+
session.emit({ type: "closed" });
|
|
3826
|
+
}
|
|
3827
|
+
})();
|
|
3828
|
+
closed.catch(() => {});
|
|
3829
|
+
return {
|
|
3830
|
+
closed,
|
|
3831
|
+
get status() {
|
|
3832
|
+
return ended ? { type: "closed" } : session.status;
|
|
3833
|
+
},
|
|
3834
|
+
close: async () => {
|
|
3835
|
+
stopping.abort();
|
|
3836
|
+
await closed;
|
|
3837
|
+
}
|
|
3838
|
+
};
|
|
3839
|
+
}
|
|
3840
|
+
delivery(raw, connection, callerSignal, release) {
|
|
3841
|
+
callerSignal?.throwIfAborted();
|
|
3842
|
+
const event = {
|
|
3843
|
+
id: raw.event_id,
|
|
3844
|
+
type: raw.event_type,
|
|
3845
|
+
body: raw.body,
|
|
3846
|
+
headers: raw.headers,
|
|
3847
|
+
data: parseWebhookEvent(JSON.parse(raw.body), raw.event_type)
|
|
3848
|
+
};
|
|
3849
|
+
const started = performance.now();
|
|
3850
|
+
const lifetime = new AbortController();
|
|
3851
|
+
const signal = AbortSignal.any([lifetime.signal, ...callerSignal ? [callerSignal] : []]);
|
|
3852
|
+
const remaining = Math.min(3e4, Date.parse(raw.lease_expires_at) - Date.now() - 5e3);
|
|
3853
|
+
if (remaining <= 0) throw new DeliveryExpired();
|
|
3854
|
+
let released = false;
|
|
3855
|
+
let outcome;
|
|
3856
|
+
let settled;
|
|
3857
|
+
const cleanup = () => {
|
|
3858
|
+
if (!released) {
|
|
3859
|
+
released = true;
|
|
3860
|
+
clearTimeout(timer);
|
|
3861
|
+
signal.removeEventListener("abort", cleanup);
|
|
3862
|
+
release();
|
|
3863
|
+
}
|
|
3864
|
+
};
|
|
3865
|
+
const timer = setTimeout(() => lifetime.abort(new DeliveryExpired()), remaining);
|
|
3866
|
+
signal.addEventListener("abort", cleanup, { once: true });
|
|
3867
|
+
function complete(accepted) {
|
|
3868
|
+
if (settled !== void 0) return outcome === accepted ? settled : Promise.reject(new EventReceiverError("Delivery outcome has already been chosen", "completion_conflict", 409));
|
|
3869
|
+
if (signal.aborted) return Promise.reject(signal.reason);
|
|
3870
|
+
outcome = accepted;
|
|
3871
|
+
clearTimeout(timer);
|
|
3872
|
+
const body = {
|
|
3873
|
+
mode: "sdk",
|
|
3874
|
+
accepted,
|
|
3875
|
+
duration_ms: Math.min(3e4, Math.round(performance.now() - started)),
|
|
3876
|
+
queue_id: raw.queue_id,
|
|
3877
|
+
delivery_id: raw.delivery_id,
|
|
3878
|
+
lease_token: raw.lease_token
|
|
3879
|
+
};
|
|
3880
|
+
const receiptSignal = AbortSignal.any([signal, AbortSignal.timeout(Math.min(6e4, Math.max(1, Date.parse(raw.lease_expires_at) - Date.now())))]);
|
|
3881
|
+
settled = eventRetry(() => connection.complete(body, receiptSignal), receiptSignal).finally(cleanup);
|
|
3882
|
+
return settled;
|
|
3883
|
+
}
|
|
3884
|
+
return {
|
|
3885
|
+
event,
|
|
3886
|
+
signal,
|
|
3887
|
+
dispose: () => {
|
|
3888
|
+
lifetime.abort();
|
|
3889
|
+
cleanup();
|
|
3890
|
+
},
|
|
3891
|
+
ack: () => complete(true),
|
|
3892
|
+
retry: () => complete(false)
|
|
3893
|
+
};
|
|
3894
|
+
}
|
|
3895
|
+
};
|
|
3896
|
+
//#endregion
|
|
3396
3897
|
//#region src/api/verify-signature.ts
|
|
3397
3898
|
/**
|
|
3398
3899
|
* Workers-safe webhook signature verification.
|
|
@@ -3980,6 +4481,8 @@ var MemoriesResource = class {
|
|
|
3980
4481
|
}
|
|
3981
4482
|
};
|
|
3982
4483
|
var PrimitiveClient = class extends PrimitiveApiClient {
|
|
4484
|
+
/** Receive durable events in this process, without a public URL. */
|
|
4485
|
+
events = new EventsResource(this.client);
|
|
3983
4486
|
/** Agent-account lifecycle operations (create, claim/upgrade). */
|
|
3984
4487
|
agent = new AgentResource(this.client);
|
|
3985
4488
|
/** Inbound mail: long-poll stream + waitForNext over the forward tail. */
|
|
@@ -4303,4 +4806,4 @@ async function createAgent(options) {
|
|
|
4303
4806
|
};
|
|
4304
4807
|
}
|
|
4305
4808
|
//#endregion
|
|
4306
|
-
export {
|
|
4809
|
+
export { deleteWakeSchedule as $, registerPayoutAddress as $t, createEndpoint as A, updateAccount as An, listDeliveries as At, defineAgent as B, verifyAgentClaim as Bn, listRegistries as Bt, checkDomainDns as C, startCliLogin as Cn, getTemplate as Ct, createAgentClaimLink as D, testFunction as Dn, getWebhookSecret as Dt, createAgentAccount as E, testEndpointRules as En, getWakeSchedule as Et, createRegistry as F, updateRegistry as Fn, listFunctionLogs as Ft, deleteFunction as G, createConfig as Gn, listTemplates as Gt, deleteEmail as H, verifyCliSignup as Hn, listRegistryRequests as Ht, createRoute as I, updateRoute as In, listFunctionSecrets as It, deleteOrgSecret as J, listWakeSchedules as Jt, deleteFunctionSecret as K, listWakeAuthorizations as Kt, createWakeAuthorization as L, updateSpendPolicy as Ln, listFunctions as Lt, createFunction as M, updateEndpoint as Mn, listEmails as Mt, createFunctionSecret as N, updateFilter as Nn, listEndpoints as Nt, createChallenge as O, unpublishAgent as On, installTemplate as Ot, createOrgSecret as P, updateFunction as Pn, listFilters as Pt, deleteWakeAuthorization as Q, pullWebhookEvent as Qt, createWakeSchedule as R, updateWakeAuthorization as Rn, listOrgSecrets as Rt, cancelSentEmail as S, startAgentSignup as Sn, getStorageStats as St, completeWebhookEvent as T, testEndpoint as Tn, getThread as Tt, deleteEndpoint as U, verifyDomain as Un, listRoutes as Ut, deleteDomain as V, verifyAgentSignup as Vn, listRegistryAgents as Vt, deleteFilter as W, createClient as Wn, listSentEmails as Wt, deleteRoute as X, pollCliLogin as Xt, deleteRegistry as Y, payChallenge as Yt, deleteSentEmail as Z, publishAgent as Zt, PrimitiveApiClient as _, setFunctionSecret as _n, getOutboundStatus as _t, PrimitiveClient as a, rescheduleSentEmail as an, downloadSentAttachmentPart as at, addDomain as b, simulateRoute as bn, getSentEmail as bt, createPrimitiveClient as c, resolveRegistryHandle as cn, getChallenge as ct, DeliveryExpired as d, sdk_gen_exports as dn, getFunction as dt, removeAgentConnection as en, discardEmailContent as et, EventsResource as f, searchEmails as fn, getFunctionRouting as ft, DEFAULT_API_BASE_URL as g, setFunctionRoute as gn, getOrgRoutingTopology as gt, isMemoryJsonValue as h, sendEmail as hn, getMemory as ht, MemoriesResource as i, replyToEmail as in, downloadRawEmail as it, createFilter as j, updateDomain as jn, listDomains as jt, createEmailChallenge as k, unsetFunctionRoute as kn, listDeclinedPayments as kt, PRIMITIVE_SIGNATURE_HEADER as l, rotateWebhookSecret as ln, getConversation as lt, EventReceiverError as m, semanticSearch as mn, getInboxStatus as mt, AgentResource as n, replayDelivery as nn, downloadDomainZoneFile as nt, client as o, resendAgentSignupVerification as on, getAccount as ot, EventConnection as p, searchMemories as pn, getFunctionTestRunTrace as pt, deleteMemory as q, listWakeDispatches as qt, InboxResource as r, replayEmailWebhooks as rn, downloadEmailAttachmentPart as rt, createAgent as s, resendCliSignupVerification as sn, getAgent as st, AccountResource as t, reorderRoutes as tn, downloadAttachments as tt, verifyWebhookSignature as u, runWakeSchedule as un, getEmail as ut, PrimitiveApiError as v, setMemory as vn, getRegistry as vt, cliLogout as w, startCliSignup as wn, getTemplateInstall as wt, awaitReply as x, startAgentClaim as xn, getSpendPolicy as xt, createPrimitiveApiClient as y, setOrgSecret as yn, getSendPermissions as yt, decideRegistryRequest as z, updateWakeSchedule as zn, listPayoutAddresses as zt };
|
package/dist/contract/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { A as RawContentInline, E as ParsedError, I as WebhookAttachment, T as ParsedDataFailed, d as EmailAuth, f as EmailReceivedEvent, k as RawContentDownloadOnly, l as EmailAddress, u as EmailAnalysis, w as ParsedDataComplete } from "../types-DcXtFARL.js";
|
|
2
|
-
import { A as SignResult, M as signWebhookPayload, S as StandardWebhooksSignResult, _ as WEBHOOK_VERSION, w as signStandardWebhooksPayload } from "../index-
|
|
2
|
+
import { A as SignResult, M as signWebhookPayload, S as StandardWebhooksSignResult, _ as WEBHOOK_VERSION, w as signStandardWebhooksPayload } from "../index-DCLGz6IH.js";
|
|
3
3
|
|
|
4
4
|
//#region src/contract/contract.d.ts
|
|
5
5
|
/** Maximum raw email size for inline inclusion (256 KB). */
|
package/dist/contract/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { k as validateEmailReceivedEvent } from "../trust-DBOnyTHV.js";
|
|
2
|
+
import { C as signWebhookPayload, _ as signStandardWebhooksPayload, f as WEBHOOK_VERSION } from "../webhook-B72YFapb.js";
|
|
2
3
|
import { createHash } from "node:crypto";
|
|
3
4
|
//#region src/contract/contract.ts
|
|
4
5
|
/**
|
|
@@ -1395,42 +1395,10 @@ declare const emailReceivedEventJsonSchema: {
|
|
|
1395
1395
|
*/
|
|
1396
1396
|
declare const WEBHOOK_VERSION = "2025-12-14";
|
|
1397
1397
|
//#endregion
|
|
1398
|
-
//#region src/webhook/
|
|
1399
|
-
/**
|
|
1400
|
-
* Parse a webhook payload, returning typed events for known types
|
|
1401
|
-
* and UnknownEvent for future event types.
|
|
1402
|
-
*
|
|
1403
|
-
* This provides forward-compatibility: when Primitive adds new event types,
|
|
1404
|
-
* your code won't break - you'll receive an UnknownEvent that you can
|
|
1405
|
-
* handle or ignore.
|
|
1406
|
-
*
|
|
1407
|
-
* Known event types are validated against the canonical schema. Unknown
|
|
1408
|
-
* event types are returned as-is for forward compatibility.
|
|
1409
|
-
*
|
|
1410
|
-
* For most use cases, prefer `handleWebhook()` which also verifies the
|
|
1411
|
-
* signature before parsing the payload.
|
|
1412
|
-
*
|
|
1413
|
-
* @param input - The parsed JSON payload
|
|
1414
|
-
* @returns Typed event for known types, UnknownEvent for unknown types
|
|
1415
|
-
* @throws WebhookPayloadError if the input is not a valid webhook structure
|
|
1416
|
-
* @throws WebhookValidationError if a known event fails schema validation
|
|
1417
|
-
*
|
|
1418
|
-
* @example
|
|
1419
|
-
* ```typescript
|
|
1420
|
-
* import { parseWebhookEvent } from '@primitivedotdev/sdk';
|
|
1421
|
-
*
|
|
1422
|
-
* const event = parseWebhookEvent(JSON.parse(rawBody));
|
|
1423
|
-
*
|
|
1424
|
-
* if (event.event === "email.received") {
|
|
1425
|
-
* // TypeScript knows this is EmailReceivedEvent
|
|
1426
|
-
* console.log(event.email.headers.subject);
|
|
1427
|
-
* } else {
|
|
1428
|
-
* // Handle or log unknown event types
|
|
1429
|
-
* console.log("Unknown event:", event.event);
|
|
1430
|
-
* }
|
|
1431
|
-
* ```
|
|
1432
|
-
*/
|
|
1398
|
+
//#region src/webhook/parse-event.d.ts
|
|
1433
1399
|
declare function parseWebhookEvent(input: unknown, eventType?: string | null): WebhookEvent;
|
|
1400
|
+
//#endregion
|
|
1401
|
+
//#region src/webhook/index.d.ts
|
|
1434
1402
|
/**
|
|
1435
1403
|
* Request headers in any common format.
|
|
1436
1404
|
*
|
|
@@ -1664,4 +1632,4 @@ declare function decodeRawEmail(event: EmailReceivedEvent, options?: DecodeRawEm
|
|
|
1664
1632
|
*/
|
|
1665
1633
|
declare function verifyRawEmailDownload(downloaded: Buffer | ArrayBuffer | Uint8Array, event: EmailReceivedEvent): Buffer;
|
|
1666
1634
|
//#endregion
|
|
1667
|
-
export { SignResult as A, validateEmailReceivedEvent as B, StandardWebhooksVerifyOptions as C, LEGACY_SIGNATURE_HEADER as D, LEGACY_CONFIRMED_HEADER as E, VerifyDownloadTokenOptions as F, VerifyDownloadTokenResult as I, generateDownloadToken as L, signWebhookPayload as M, verifyWebhookSignature as N, PRIMITIVE_CONFIRMED_HEADER as O, GenerateDownloadTokenOptions as P, verifyDownloadToken as R, StandardWebhooksSignResult as S, verifyStandardWebhooksSignature as T, WEBHOOK_VERSION as _, WebhookHeaders as a, STANDARD_WEBHOOK_SIGNATURE_HEADER as b, getDownloadTimeRemaining as c, handleWebhookEvent as d, isDownloadExpired as f,
|
|
1635
|
+
export { SignResult as A, validateEmailReceivedEvent as B, StandardWebhooksVerifyOptions as C, LEGACY_SIGNATURE_HEADER as D, LEGACY_CONFIRMED_HEADER as E, VerifyDownloadTokenOptions as F, VerifyDownloadTokenResult as I, generateDownloadToken as L, signWebhookPayload as M, verifyWebhookSignature as N, PRIMITIVE_CONFIRMED_HEADER as O, GenerateDownloadTokenOptions as P, verifyDownloadToken as R, StandardWebhooksSignResult as S, verifyStandardWebhooksSignature as T, WEBHOOK_VERSION as _, WebhookHeaders as a, STANDARD_WEBHOOK_SIGNATURE_HEADER as b, getDownloadTimeRemaining as c, handleWebhookEvent as d, isDownloadExpired as f, parseWebhookEvent as g, verifyRawEmailDownload as h, WEBHOOK_EVENT_HEADER as i, VerifyOptions as j, PRIMITIVE_SIGNATURE_HEADER as k, getEventHeader as l, receive as m, HandleWebhookOptions as n, confirmedHeaders as o, isRawIncluded as p, ReceiveRequestOptions as r, decodeRawEmail as s, DecodeRawEmailOptions as t, handleWebhook as u, emailReceivedEventJsonSchema as v, signStandardWebhooksPayload as w, STANDARD_WEBHOOK_TIMESTAMP_HEADER as x, STANDARD_WEBHOOK_ID_HEADER as y, safeValidateEmailReceivedEvent as z };
|