primitive 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 +17 -0
- package/dist/oclif/index.js +1456 -370
- package/package.json +1 -1
package/dist/oclif/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as saveActiveChatState, C as resolveCliAuth, D as deleteChatState, E as chatStatePath, M as createClient, N as createConfig, O as loadActiveChatState, S as normalizeApiBaseUrl, T as saveSignupCredentials, _ as deleteCliCredentials, a as normalizeCliEnvironmentName, b as hasStoredCliLogin, c as resolveConfigEnvironment, d as validateCliHeaderName, f as validateCliHeaderValue, g as credentialsPath, h as credentialsLockPath, i as loadCliConfig, j as PrimitiveApiClient, k as loadChatConversationByLocalId, l as saveCliConfig, m as cliAccessTokenExpiresAt, n as deleteCliConfig, o as redactCliEnvironment, p as acquireCliCredentialsLock, r as emptyCliConfig, s as removeCliEnvironment, u as upsertCliEnvironment, v as deleteCliCredentialsLock, w as saveCliCredentials, x as loadCliCredentials, y as detectPrimitiveKeyEnvMisname } from "../cli-config-BKso8oJu.js";
|
|
1
|
+
import { A as saveActiveChatState, C as resolveCliAuth, D as deleteChatState, E as chatStatePath, M as createClient$1, N as createConfig$1, O as loadActiveChatState, S as normalizeApiBaseUrl, T as saveSignupCredentials, _ as deleteCliCredentials, a as normalizeCliEnvironmentName, b as hasStoredCliLogin, c as resolveConfigEnvironment, d as validateCliHeaderName, f as validateCliHeaderValue, g as credentialsPath, h as credentialsLockPath, i as loadCliConfig, j as PrimitiveApiClient, k as loadChatConversationByLocalId, l as saveCliConfig, m as cliAccessTokenExpiresAt, n as deleteCliConfig, o as redactCliEnvironment, p as acquireCliCredentialsLock, r as emptyCliConfig, s as removeCliEnvironment, u as upsertCliEnvironment, v as deleteCliCredentialsLock, w as saveCliCredentials, x as loadCliCredentials, y as detectPrimitiveKeyEnvMisname } from "../cli-config-BKso8oJu.js";
|
|
2
2
|
import { Args, Command, Errors, Flags, ux } from "@oclif/core";
|
|
3
3
|
import { chmodSync, closeSync, constants, existsSync, fstatSync, fsyncSync, linkSync, lstatSync, mkdirSync, mkdtempSync, openSync, readFileSync, readdirSync, renameSync, rmSync, rmdirSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
4
4
|
import { createHash, randomUUID, scryptSync } from "node:crypto";
|
|
@@ -27,7 +27,7 @@ var __exportAll = (all, no_symbols) => {
|
|
|
27
27
|
};
|
|
28
28
|
//#endregion
|
|
29
29
|
//#region ../packages/api-core/src/api/client.gen.ts
|
|
30
|
-
const client = createClient(createConfig({ baseUrl: "https://api.primitive.dev/v1" }));
|
|
30
|
+
const client = createClient$1(createConfig$1({ baseUrl: "https://api.primitive.dev/v1" }));
|
|
31
31
|
//#endregion
|
|
32
32
|
//#region ../packages/api-core/src/api/sdk.gen.ts
|
|
33
33
|
var sdk_gen_exports = /* @__PURE__ */ __exportAll({
|
|
@@ -36,7 +36,7 @@ var sdk_gen_exports = /* @__PURE__ */ __exportAll({
|
|
|
36
36
|
cancelSentEmail: () => cancelSentEmail,
|
|
37
37
|
checkDomainDns: () => checkDomainDns,
|
|
38
38
|
cliLogout: () => cliLogout,
|
|
39
|
-
completeWebhookEvent: () => completeWebhookEvent,
|
|
39
|
+
completeWebhookEvent: () => completeWebhookEvent$1,
|
|
40
40
|
createAgentAccount: () => createAgentAccount,
|
|
41
41
|
createAgentClaimLink: () => createAgentClaimLink,
|
|
42
42
|
createChallenge: () => createChallenge,
|
|
@@ -71,7 +71,7 @@ var sdk_gen_exports = /* @__PURE__ */ __exportAll({
|
|
|
71
71
|
downloadEmailAttachmentPart: () => downloadEmailAttachmentPart,
|
|
72
72
|
downloadRawEmail: () => downloadRawEmail,
|
|
73
73
|
downloadSentAttachmentPart: () => downloadSentAttachmentPart,
|
|
74
|
-
getAccount: () => getAccount,
|
|
74
|
+
getAccount: () => getAccount$1,
|
|
75
75
|
getAgent: () => getAgent,
|
|
76
76
|
getChallenge: () => getChallenge,
|
|
77
77
|
getConversation: () => getConversation,
|
|
@@ -117,7 +117,7 @@ var sdk_gen_exports = /* @__PURE__ */ __exportAll({
|
|
|
117
117
|
payChallenge: () => payChallenge,
|
|
118
118
|
pollCliLogin: () => pollCliLogin,
|
|
119
119
|
publishAgent: () => publishAgent,
|
|
120
|
-
pullWebhookEvent: () => pullWebhookEvent,
|
|
120
|
+
pullWebhookEvent: () => pullWebhookEvent$1,
|
|
121
121
|
registerPayoutAddress: () => registerPayoutAddress,
|
|
122
122
|
removeAgentConnection: () => removeAgentConnection,
|
|
123
123
|
reorderRoutes: () => reorderRoutes,
|
|
@@ -411,7 +411,7 @@ const cliLogout = (options) => (options?.client ?? client).post({
|
|
|
411
411
|
/**
|
|
412
412
|
* Get account info
|
|
413
413
|
*/
|
|
414
|
-
const getAccount = (options) => (options?.client ?? client).get({
|
|
414
|
+
const getAccount$1 = (options) => (options?.client ?? client).get({
|
|
415
415
|
security: [{
|
|
416
416
|
scheme: "bearer",
|
|
417
417
|
type: "http"
|
|
@@ -1025,7 +1025,7 @@ const updateEndpoint = (options) => (options.client ?? client).patch({
|
|
|
1025
1025
|
*
|
|
1026
1026
|
* Wait up to 30 seconds for one existing event. The body string preserves the exact webhook serialization; headers and canonical occurrence/type metadata travel alongside it. A fixed lease protects the attempt while a short handler accepts input. Retry may redeliver an occurrence: deduplicate event_id. Empty delivery means no offer now, not proof all input was delivered; inspect backlog and persistent gap_count/last_gap_reason. Queue retention is 24 hours and does not extend source-content retention. Disconnecting preserves pending work. Same named destination shares consumption; different destinations get independent copies.
|
|
1027
1027
|
*/
|
|
1028
|
-
const pullWebhookEvent = (options) => (options.client ?? client).post({
|
|
1028
|
+
const pullWebhookEvent$1 = (options) => (options.client ?? client).post({
|
|
1029
1029
|
security: [{
|
|
1030
1030
|
scheme: "bearer",
|
|
1031
1031
|
type: "http"
|
|
@@ -1042,7 +1042,7 @@ const pullWebhookEvent = (options) => (options.client ?? client).post({
|
|
|
1042
1042
|
*
|
|
1043
1043
|
* Report normalized handling evidence for the queue, attempt and lease. Required JSON fields: queue_id, delivery_id, lease_token, mode, duration_ms. For mode=exec include exit_code; stdout includes write_succeeded; http includes status_code and optional transport_error, error_code, confirmed. Supply this discriminated request with --raw-body in the generated CLI. The server classifies success and retry. Duplicate identical completion is idempotent; a stale lease cannot finish a newer attempt. Exec success means durable input acceptance, not completion of agent reasoning. Only successful HTTP handling with recognized confirmation evidence can confirm content discard. Never send arbitrary handler output or HTTP response bodies.
|
|
1044
1044
|
*/
|
|
1045
|
-
const completeWebhookEvent = (options) => (options.client ?? client).post({
|
|
1045
|
+
const completeWebhookEvent$1 = (options) => (options.client ?? client).post({
|
|
1046
1046
|
security: [{
|
|
1047
1047
|
scheme: "bearer",
|
|
1048
1048
|
type: "http"
|
|
@@ -11405,14 +11405,16 @@ const openapiDocument = {
|
|
|
11405
11405
|
"oneOf": [
|
|
11406
11406
|
{ "$ref": "#/components/schemas/CompleteWebhookHttpInput" },
|
|
11407
11407
|
{ "$ref": "#/components/schemas/CompleteWebhookExecInput" },
|
|
11408
|
-
{ "$ref": "#/components/schemas/CompleteWebhookStdoutInput" }
|
|
11408
|
+
{ "$ref": "#/components/schemas/CompleteWebhookStdoutInput" },
|
|
11409
|
+
{ "$ref": "#/components/schemas/CompleteWebhookSdkInput" }
|
|
11409
11410
|
],
|
|
11410
11411
|
"discriminator": {
|
|
11411
11412
|
"propertyName": "mode",
|
|
11412
11413
|
"mapping": {
|
|
11413
11414
|
"http": "#/components/schemas/CompleteWebhookHttpInput",
|
|
11414
11415
|
"exec": "#/components/schemas/CompleteWebhookExecInput",
|
|
11415
|
-
"stdout": "#/components/schemas/CompleteWebhookStdoutInput"
|
|
11416
|
+
"stdout": "#/components/schemas/CompleteWebhookStdoutInput",
|
|
11417
|
+
"sdk": "#/components/schemas/CompleteWebhookSdkInput"
|
|
11416
11418
|
}
|
|
11417
11419
|
}
|
|
11418
11420
|
},
|
|
@@ -11566,6 +11568,49 @@ const openapiDocument = {
|
|
|
11566
11568
|
],
|
|
11567
11569
|
"additionalProperties": false
|
|
11568
11570
|
},
|
|
11571
|
+
"CompleteWebhookSdkInput": {
|
|
11572
|
+
"type": "object",
|
|
11573
|
+
"properties": {
|
|
11574
|
+
"queue_id": {
|
|
11575
|
+
"type": "string",
|
|
11576
|
+
"format": "uuid",
|
|
11577
|
+
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
|
|
11578
|
+
},
|
|
11579
|
+
"delivery_id": {
|
|
11580
|
+
"type": "string",
|
|
11581
|
+
"format": "uuid",
|
|
11582
|
+
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
|
|
11583
|
+
},
|
|
11584
|
+
"lease_token": {
|
|
11585
|
+
"type": "string",
|
|
11586
|
+
"format": "uuid",
|
|
11587
|
+
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
|
|
11588
|
+
},
|
|
11589
|
+
"duration_ms": {
|
|
11590
|
+
"type": "integer",
|
|
11591
|
+
"minimum": 0,
|
|
11592
|
+
"maximum": 3e4
|
|
11593
|
+
},
|
|
11594
|
+
"mode": {
|
|
11595
|
+
"type": "string",
|
|
11596
|
+
"enum": ["sdk"]
|
|
11597
|
+
},
|
|
11598
|
+
"accepted": { "type": "boolean" },
|
|
11599
|
+
"transport_error": {
|
|
11600
|
+
"type": "string",
|
|
11601
|
+
"enum": ["timeout", "io"]
|
|
11602
|
+
}
|
|
11603
|
+
},
|
|
11604
|
+
"required": [
|
|
11605
|
+
"queue_id",
|
|
11606
|
+
"delivery_id",
|
|
11607
|
+
"lease_token",
|
|
11608
|
+
"duration_ms",
|
|
11609
|
+
"mode",
|
|
11610
|
+
"accepted"
|
|
11611
|
+
],
|
|
11612
|
+
"additionalProperties": false
|
|
11613
|
+
},
|
|
11569
11614
|
"CompleteWebhookResponse": {
|
|
11570
11615
|
"type": "object",
|
|
11571
11616
|
"properties": {
|
|
@@ -11599,6 +11644,31 @@ const openapiDocument = {
|
|
|
11599
11644
|
"Endpoint": {
|
|
11600
11645
|
"type": "object",
|
|
11601
11646
|
"properties": {
|
|
11647
|
+
"receiver_capabilities": {
|
|
11648
|
+
"type": "object",
|
|
11649
|
+
"properties": {
|
|
11650
|
+
"completion_modes": {
|
|
11651
|
+
"type": "array",
|
|
11652
|
+
"items": {
|
|
11653
|
+
"type": "string",
|
|
11654
|
+
"enum": [
|
|
11655
|
+
"http",
|
|
11656
|
+
"exec",
|
|
11657
|
+
"stdout",
|
|
11658
|
+
"sdk"
|
|
11659
|
+
]
|
|
11660
|
+
}
|
|
11661
|
+
},
|
|
11662
|
+
"stream_protocols": {
|
|
11663
|
+
"type": "array",
|
|
11664
|
+
"items": {
|
|
11665
|
+
"type": "string",
|
|
11666
|
+
"enum": ["primitive.events.v1"]
|
|
11667
|
+
}
|
|
11668
|
+
}
|
|
11669
|
+
},
|
|
11670
|
+
"required": ["completion_modes", "stream_protocols"]
|
|
11671
|
+
},
|
|
11602
11672
|
"id": {
|
|
11603
11673
|
"type": "string",
|
|
11604
11674
|
"format": "uuid"
|
|
@@ -17908,6 +17978,49 @@ const operationManifest = [
|
|
|
17908
17978
|
"write_succeeded"
|
|
17909
17979
|
],
|
|
17910
17980
|
"additionalProperties": false
|
|
17981
|
+
},
|
|
17982
|
+
{
|
|
17983
|
+
"type": "object",
|
|
17984
|
+
"properties": {
|
|
17985
|
+
"queue_id": {
|
|
17986
|
+
"type": "string",
|
|
17987
|
+
"format": "uuid",
|
|
17988
|
+
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
|
|
17989
|
+
},
|
|
17990
|
+
"delivery_id": {
|
|
17991
|
+
"type": "string",
|
|
17992
|
+
"format": "uuid",
|
|
17993
|
+
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
|
|
17994
|
+
},
|
|
17995
|
+
"lease_token": {
|
|
17996
|
+
"type": "string",
|
|
17997
|
+
"format": "uuid",
|
|
17998
|
+
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
|
|
17999
|
+
},
|
|
18000
|
+
"duration_ms": {
|
|
18001
|
+
"type": "integer",
|
|
18002
|
+
"minimum": 0,
|
|
18003
|
+
"maximum": 3e4
|
|
18004
|
+
},
|
|
18005
|
+
"mode": {
|
|
18006
|
+
"type": "string",
|
|
18007
|
+
"enum": ["sdk"]
|
|
18008
|
+
},
|
|
18009
|
+
"accepted": { "type": "boolean" },
|
|
18010
|
+
"transport_error": {
|
|
18011
|
+
"type": "string",
|
|
18012
|
+
"enum": ["timeout", "io"]
|
|
18013
|
+
}
|
|
18014
|
+
},
|
|
18015
|
+
"required": [
|
|
18016
|
+
"queue_id",
|
|
18017
|
+
"delivery_id",
|
|
18018
|
+
"lease_token",
|
|
18019
|
+
"duration_ms",
|
|
18020
|
+
"mode",
|
|
18021
|
+
"accepted"
|
|
18022
|
+
],
|
|
18023
|
+
"additionalProperties": false
|
|
17911
18024
|
}
|
|
17912
18025
|
],
|
|
17913
18026
|
"discriminator": {
|
|
@@ -17915,7 +18028,8 @@ const operationManifest = [
|
|
|
17915
18028
|
"mapping": {
|
|
17916
18029
|
"http": "#/components/schemas/CompleteWebhookHttpInput",
|
|
17917
18030
|
"exec": "#/components/schemas/CompleteWebhookExecInput",
|
|
17918
|
-
"stdout": "#/components/schemas/CompleteWebhookStdoutInput"
|
|
18031
|
+
"stdout": "#/components/schemas/CompleteWebhookStdoutInput",
|
|
18032
|
+
"sdk": "#/components/schemas/CompleteWebhookSdkInput"
|
|
17919
18033
|
}
|
|
17920
18034
|
}
|
|
17921
18035
|
},
|
|
@@ -18026,6 +18140,31 @@ const operationManifest = [
|
|
|
18026
18140
|
"responseSchema": {
|
|
18027
18141
|
"type": "object",
|
|
18028
18142
|
"properties": {
|
|
18143
|
+
"receiver_capabilities": {
|
|
18144
|
+
"type": "object",
|
|
18145
|
+
"properties": {
|
|
18146
|
+
"completion_modes": {
|
|
18147
|
+
"type": "array",
|
|
18148
|
+
"items": {
|
|
18149
|
+
"type": "string",
|
|
18150
|
+
"enum": [
|
|
18151
|
+
"http",
|
|
18152
|
+
"exec",
|
|
18153
|
+
"stdout",
|
|
18154
|
+
"sdk"
|
|
18155
|
+
]
|
|
18156
|
+
}
|
|
18157
|
+
},
|
|
18158
|
+
"stream_protocols": {
|
|
18159
|
+
"type": "array",
|
|
18160
|
+
"items": {
|
|
18161
|
+
"type": "string",
|
|
18162
|
+
"enum": ["primitive.events.v1"]
|
|
18163
|
+
}
|
|
18164
|
+
}
|
|
18165
|
+
},
|
|
18166
|
+
"required": ["completion_modes", "stream_protocols"]
|
|
18167
|
+
},
|
|
18029
18168
|
"id": {
|
|
18030
18169
|
"type": "string",
|
|
18031
18170
|
"format": "uuid"
|
|
@@ -18177,6 +18316,31 @@ const operationManifest = [
|
|
|
18177
18316
|
"items": {
|
|
18178
18317
|
"type": "object",
|
|
18179
18318
|
"properties": {
|
|
18319
|
+
"receiver_capabilities": {
|
|
18320
|
+
"type": "object",
|
|
18321
|
+
"properties": {
|
|
18322
|
+
"completion_modes": {
|
|
18323
|
+
"type": "array",
|
|
18324
|
+
"items": {
|
|
18325
|
+
"type": "string",
|
|
18326
|
+
"enum": [
|
|
18327
|
+
"http",
|
|
18328
|
+
"exec",
|
|
18329
|
+
"stdout",
|
|
18330
|
+
"sdk"
|
|
18331
|
+
]
|
|
18332
|
+
}
|
|
18333
|
+
},
|
|
18334
|
+
"stream_protocols": {
|
|
18335
|
+
"type": "array",
|
|
18336
|
+
"items": {
|
|
18337
|
+
"type": "string",
|
|
18338
|
+
"enum": ["primitive.events.v1"]
|
|
18339
|
+
}
|
|
18340
|
+
}
|
|
18341
|
+
},
|
|
18342
|
+
"required": ["completion_modes", "stream_protocols"]
|
|
18343
|
+
},
|
|
18180
18344
|
"id": {
|
|
18181
18345
|
"type": "string",
|
|
18182
18346
|
"format": "uuid"
|
|
@@ -18618,6 +18782,31 @@ const operationManifest = [
|
|
|
18618
18782
|
"responseSchema": {
|
|
18619
18783
|
"type": "object",
|
|
18620
18784
|
"properties": {
|
|
18785
|
+
"receiver_capabilities": {
|
|
18786
|
+
"type": "object",
|
|
18787
|
+
"properties": {
|
|
18788
|
+
"completion_modes": {
|
|
18789
|
+
"type": "array",
|
|
18790
|
+
"items": {
|
|
18791
|
+
"type": "string",
|
|
18792
|
+
"enum": [
|
|
18793
|
+
"http",
|
|
18794
|
+
"exec",
|
|
18795
|
+
"stdout",
|
|
18796
|
+
"sdk"
|
|
18797
|
+
]
|
|
18798
|
+
}
|
|
18799
|
+
},
|
|
18800
|
+
"stream_protocols": {
|
|
18801
|
+
"type": "array",
|
|
18802
|
+
"items": {
|
|
18803
|
+
"type": "string",
|
|
18804
|
+
"enum": ["primitive.events.v1"]
|
|
18805
|
+
}
|
|
18806
|
+
}
|
|
18807
|
+
},
|
|
18808
|
+
"required": ["completion_modes", "stream_protocols"]
|
|
18809
|
+
},
|
|
18621
18810
|
"id": {
|
|
18622
18811
|
"type": "string",
|
|
18623
18812
|
"format": "uuid"
|
|
@@ -27644,7 +27833,7 @@ function isMemoryJsonValue(value, seen = /* @__PURE__ */ new Set()) {
|
|
|
27644
27833
|
const API_HEADERS_ENV = "PRIMITIVE_API_HEADERS";
|
|
27645
27834
|
const OAUTH_REFRESH_SKEW_MS = 60 * 1e3;
|
|
27646
27835
|
const SAVED_CLI_OAUTH_SESSION_EXPIRED_MESSAGE = "Saved Primitive CLI OAuth session expired or was revoked. Run `primitive signin` to authenticate again.";
|
|
27647
|
-
function mergeHeaders(...headers) {
|
|
27836
|
+
function mergeHeaders$1(...headers) {
|
|
27648
27837
|
const merged = {};
|
|
27649
27838
|
for (const headerSet of headers) {
|
|
27650
27839
|
if (!headerSet) continue;
|
|
@@ -27682,7 +27871,7 @@ function resolveCliApiRequestConfig(params) {
|
|
|
27682
27871
|
apiBaseUrl,
|
|
27683
27872
|
baseUrlOverridden: apiBaseUrl !== void 0,
|
|
27684
27873
|
environmentName: currentEnvironment?.name ?? null,
|
|
27685
|
-
headers: mergeHeaders(currentEnvironment?.config.headers, cliApiHeadersFromEnv(params.env)),
|
|
27874
|
+
headers: mergeHeaders$1(currentEnvironment?.config.headers, cliApiHeadersFromEnv(params.env)),
|
|
27686
27875
|
resolvedApiBaseUrl: normalizeApiBaseUrl(apiBaseUrl)
|
|
27687
27876
|
};
|
|
27688
27877
|
}
|
|
@@ -30335,7 +30524,7 @@ function checkApiKey(opts) {
|
|
|
30335
30524
|
}
|
|
30336
30525
|
async function checkAccount(client) {
|
|
30337
30526
|
try {
|
|
30338
|
-
const result = await getAccount({
|
|
30527
|
+
const result = await getAccount$1({
|
|
30339
30528
|
client: client.client,
|
|
30340
30529
|
responseStyle: "fields"
|
|
30341
30530
|
});
|
|
@@ -32114,8 +32303,8 @@ const PRIMITIVE_TEAM_AUTHOR = {
|
|
|
32114
32303
|
name: "Primitive Team",
|
|
32115
32304
|
url: "https://primitive.dev"
|
|
32116
32305
|
};
|
|
32117
|
-
const SDK_VERSION_RANGE = "^1.
|
|
32118
|
-
const CLI_VERSION_RANGE = "^1.
|
|
32306
|
+
const SDK_VERSION_RANGE = "^1.27.0";
|
|
32307
|
+
const CLI_VERSION_RANGE = "^1.27.0";
|
|
32119
32308
|
const ESBUILD_VERSION_RANGE = "^0.27.0";
|
|
32120
32309
|
function renderHandler() {
|
|
32121
32310
|
return `// env.PRIMITIVE_API_KEY, env.PRIMITIVE_WEBHOOK_SECRET, and
|
|
@@ -34149,6 +34338,11 @@ const FORWARDED_HEADERS = new Set([
|
|
|
34149
34338
|
"x-primitive-email-id",
|
|
34150
34339
|
"x-primitive-endpoint-id"
|
|
34151
34340
|
]);
|
|
34341
|
+
function normalizeForwardTarget(target) {
|
|
34342
|
+
if (/^\d+$/.test(target)) return `http://localhost:${target}`;
|
|
34343
|
+
if (/^(localhost|127\.0\.0\.1|\[::1\])(?::|\/|$)/.test(target)) return `http://${target}`;
|
|
34344
|
+
return target;
|
|
34345
|
+
}
|
|
34152
34346
|
function validateListenHandlerOptions(options) {
|
|
34153
34347
|
if (options.exec !== void 0 && process.platform === "win32") throw new Error("--exec requires a POSIX shell and process groups. On Windows, use --forward-to with a local HTTP handler.");
|
|
34154
34348
|
if (options.exec !== void 0 && options.forwardTo !== void 0) throw new Error("Choose either --exec or --forward-to.");
|
|
@@ -34156,7 +34350,7 @@ function validateListenHandlerOptions(options) {
|
|
|
34156
34350
|
if (options.forwardTo !== void 0) {
|
|
34157
34351
|
let url;
|
|
34158
34352
|
try {
|
|
34159
|
-
url = new URL(options.forwardTo);
|
|
34353
|
+
url = new URL(normalizeForwardTarget(options.forwardTo));
|
|
34160
34354
|
} catch {
|
|
34161
34355
|
throw new Error("--forward-to requires an HTTP or HTTPS URL.");
|
|
34162
34356
|
}
|
|
@@ -34301,7 +34495,7 @@ var ForwardFailure = class extends Error {
|
|
|
34301
34495
|
};
|
|
34302
34496
|
async function forward(target, delivery, signal) {
|
|
34303
34497
|
signal.throwIfAborted();
|
|
34304
|
-
const url = new URL(target);
|
|
34498
|
+
const url = new URL(normalizeForwardTarget(target));
|
|
34305
34499
|
const body = Buffer.from(delivery.body, "utf8");
|
|
34306
34500
|
const headers = {};
|
|
34307
34501
|
for (const [name, value] of Object.entries(delivery.headers)) if (FORWARDED_HEADERS.has(name.toLowerCase())) headers[name.toLowerCase()] = value;
|
|
@@ -34441,6 +34635,1169 @@ function createListenHandler(options = {}) {
|
|
|
34441
34635
|
};
|
|
34442
34636
|
}
|
|
34443
34637
|
//#endregion
|
|
34638
|
+
//#region ../sdk-node/dist/payloads-BMngnJlE.js
|
|
34639
|
+
/**
|
|
34640
|
+
* Load a Node builtin at call time. Exported for direct testing of the
|
|
34641
|
+
* unavailable-module error path; production code goes through the typed
|
|
34642
|
+
* wrappers below.
|
|
34643
|
+
*/
|
|
34644
|
+
async function loadBuiltin(specifier, operation) {
|
|
34645
|
+
let builtin;
|
|
34646
|
+
if (typeof process !== "undefined" && typeof process.getBuiltinModule === "function") try {
|
|
34647
|
+
builtin = process.getBuiltinModule(specifier);
|
|
34648
|
+
} catch {
|
|
34649
|
+
builtin = void 0;
|
|
34650
|
+
}
|
|
34651
|
+
if (builtin !== void 0 && builtin !== null) return builtin;
|
|
34652
|
+
try {
|
|
34653
|
+
return await import(specifier);
|
|
34654
|
+
} catch (cause) {
|
|
34655
|
+
throw new Error(`${operation} requires file system access, which this runtime does not provide. Pass in-memory bytes instead of a file path when running outside Node.js.`, { cause });
|
|
34656
|
+
}
|
|
34657
|
+
}
|
|
34658
|
+
/** `node:fs`, loaded lazily so Workers-style bundles never see the import. */
|
|
34659
|
+
function loadNodeFs(operation) {
|
|
34660
|
+
return loadBuiltin("node:fs", operation);
|
|
34661
|
+
}
|
|
34662
|
+
/** `node:fs/promises`, loaded lazily for the same reason as {@link loadNodeFs}. */
|
|
34663
|
+
function loadNodeFsPromises(operation) {
|
|
34664
|
+
return loadBuiltin("node:fs/promises", operation);
|
|
34665
|
+
}
|
|
34666
|
+
const CHUNK_SIZE = 64 * 1024 * 1024;
|
|
34667
|
+
const MANIFEST_VERSION = 1;
|
|
34668
|
+
const CHUNK_KDF_INFO = "payloads-chunk";
|
|
34669
|
+
const OBJECT_ID_BYTES = 16;
|
|
34670
|
+
const CEK_BYTES = 32;
|
|
34671
|
+
function subtle() {
|
|
34672
|
+
return globalThis.crypto.subtle;
|
|
34673
|
+
}
|
|
34674
|
+
const textEncoder = new TextEncoder();
|
|
34675
|
+
function randomBytes(length) {
|
|
34676
|
+
const out = new Uint8Array(length);
|
|
34677
|
+
globalThis.crypto.getRandomValues(out);
|
|
34678
|
+
return out;
|
|
34679
|
+
}
|
|
34680
|
+
function toHex(bytes) {
|
|
34681
|
+
let out = "";
|
|
34682
|
+
for (const b of bytes) out += b.toString(16).padStart(2, "0");
|
|
34683
|
+
return out;
|
|
34684
|
+
}
|
|
34685
|
+
function fromHex(hex) {
|
|
34686
|
+
if (hex.length % 2 !== 0) throw new Error("invalid hex: odd length");
|
|
34687
|
+
if (hex.length > 0 && !/^[0-9a-fA-F]+$/.test(hex)) throw new Error("invalid hex: non-hex character");
|
|
34688
|
+
const out = new Uint8Array(hex.length / 2);
|
|
34689
|
+
for (let i = 0; i < out.length; i++) out[i] = Number.parseInt(hex.slice(i * 2, i * 2 + 2), 16);
|
|
34690
|
+
return out;
|
|
34691
|
+
}
|
|
34692
|
+
async function contentHash(bytes) {
|
|
34693
|
+
return new Uint8Array(await subtle().digest("SHA-256", bytes));
|
|
34694
|
+
}
|
|
34695
|
+
async function contentHashHex(bytes) {
|
|
34696
|
+
return toHex(await contentHash(bytes));
|
|
34697
|
+
}
|
|
34698
|
+
/** Binary Merkle root over ordered hex leaf hashes (odd node promoted). */
|
|
34699
|
+
async function merkleRoot(leafHashesHex) {
|
|
34700
|
+
if (leafHashesHex.length === 0) return contentHashHex(new Uint8Array(0));
|
|
34701
|
+
let level = leafHashesHex.map(fromHex);
|
|
34702
|
+
while (level.length > 1) {
|
|
34703
|
+
const next = [];
|
|
34704
|
+
for (let i = 0; i < level.length; i += 2) {
|
|
34705
|
+
const left = level[i];
|
|
34706
|
+
const right = i + 1 < level.length ? level[i + 1] : level[i];
|
|
34707
|
+
const combined = new Uint8Array(left.length + right.length);
|
|
34708
|
+
combined.set(left, 0);
|
|
34709
|
+
combined.set(right, left.length);
|
|
34710
|
+
next.push(await contentHash(combined));
|
|
34711
|
+
}
|
|
34712
|
+
level = next;
|
|
34713
|
+
}
|
|
34714
|
+
return toHex(level[0]);
|
|
34715
|
+
}
|
|
34716
|
+
async function deriveChunkKey(cek, index) {
|
|
34717
|
+
const baseKey = await subtle().importKey("raw", cek, "HKDF", false, ["deriveKey"]);
|
|
34718
|
+
const info = textEncoder.encode(`${CHUNK_KDF_INFO}:${index}`);
|
|
34719
|
+
return subtle().deriveKey({
|
|
34720
|
+
name: "HKDF",
|
|
34721
|
+
hash: "SHA-256",
|
|
34722
|
+
salt: new Uint8Array(32),
|
|
34723
|
+
info
|
|
34724
|
+
}, baseKey, {
|
|
34725
|
+
name: "AES-GCM",
|
|
34726
|
+
length: 256
|
|
34727
|
+
}, false, ["encrypt", "decrypt"]);
|
|
34728
|
+
}
|
|
34729
|
+
function chunkNonce(index) {
|
|
34730
|
+
const nonce = new Uint8Array(12);
|
|
34731
|
+
new DataView(nonce.buffer).setUint32(8, index >>> 0, false);
|
|
34732
|
+
return nonce;
|
|
34733
|
+
}
|
|
34734
|
+
function chunkAad(objectId, index) {
|
|
34735
|
+
const aad = new Uint8Array(objectId.length + 4);
|
|
34736
|
+
aad.set(objectId, 0);
|
|
34737
|
+
new DataView(aad.buffer).setUint32(objectId.length, index >>> 0, false);
|
|
34738
|
+
return aad;
|
|
34739
|
+
}
|
|
34740
|
+
async function encryptChunk(cek, objectId, index, plaintext) {
|
|
34741
|
+
const key = await deriveChunkKey(cek, index);
|
|
34742
|
+
const ct = await subtle().encrypt({
|
|
34743
|
+
name: "AES-GCM",
|
|
34744
|
+
iv: chunkNonce(index),
|
|
34745
|
+
additionalData: chunkAad(objectId, index)
|
|
34746
|
+
}, key, plaintext);
|
|
34747
|
+
return new Uint8Array(ct);
|
|
34748
|
+
}
|
|
34749
|
+
async function decryptChunk(cek, objectId, index, ciphertext) {
|
|
34750
|
+
const key = await deriveChunkKey(cek, index);
|
|
34751
|
+
const pt = await subtle().decrypt({
|
|
34752
|
+
name: "AES-GCM",
|
|
34753
|
+
iv: chunkNonce(index),
|
|
34754
|
+
additionalData: chunkAad(objectId, index)
|
|
34755
|
+
}, key, ciphertext);
|
|
34756
|
+
return new Uint8Array(pt);
|
|
34757
|
+
}
|
|
34758
|
+
function authHeaders(apiKey) {
|
|
34759
|
+
return { authorization: `Bearer ${apiKey}` };
|
|
34760
|
+
}
|
|
34761
|
+
function apiRoot(baseUrl) {
|
|
34762
|
+
let end = baseUrl.length;
|
|
34763
|
+
while (end > 0 && baseUrl.charCodeAt(end - 1) === 47) end--;
|
|
34764
|
+
return `${baseUrl.slice(0, end)}/v1/payloads`;
|
|
34765
|
+
}
|
|
34766
|
+
const DEFAULT_MAX_RETRIES = 6;
|
|
34767
|
+
const RETRYABLE_STATUS = new Set([
|
|
34768
|
+
429,
|
|
34769
|
+
500,
|
|
34770
|
+
502,
|
|
34771
|
+
503,
|
|
34772
|
+
504
|
|
34773
|
+
]);
|
|
34774
|
+
function sleep$1(ms) {
|
|
34775
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
34776
|
+
}
|
|
34777
|
+
/**
|
|
34778
|
+
* fetch with exponential backoff on transient failures. Request bodies here are
|
|
34779
|
+
* in-memory buffers/strings (never streams), so a retry can safely resend them.
|
|
34780
|
+
*/
|
|
34781
|
+
async function retryingFetch(url, init, label, maxRetries = DEFAULT_MAX_RETRIES) {
|
|
34782
|
+
for (let attempt = 0;; attempt++) {
|
|
34783
|
+
let res;
|
|
34784
|
+
let networkError;
|
|
34785
|
+
try {
|
|
34786
|
+
res = await fetch(url, init);
|
|
34787
|
+
} catch (err) {
|
|
34788
|
+
networkError = err;
|
|
34789
|
+
}
|
|
34790
|
+
if (res && !RETRYABLE_STATUS.has(res.status)) return res;
|
|
34791
|
+
if (attempt >= maxRetries) {
|
|
34792
|
+
if (res) return res;
|
|
34793
|
+
throw new Error(`${label}: network error after ${attempt} retries: ${networkError?.message ?? networkError}`);
|
|
34794
|
+
}
|
|
34795
|
+
if (res) await res.text().catch(() => {});
|
|
34796
|
+
await sleep$1(Math.min(1e3 * 2 ** attempt, 15e3) + Math.floor(Math.random() * 250));
|
|
34797
|
+
}
|
|
34798
|
+
}
|
|
34799
|
+
async function initiate(opts, manifest) {
|
|
34800
|
+
const res = await retryingFetch(apiRoot(opts.baseUrl), {
|
|
34801
|
+
method: "POST",
|
|
34802
|
+
headers: {
|
|
34803
|
+
...authHeaders(opts.apiKey),
|
|
34804
|
+
"content-type": "application/json"
|
|
34805
|
+
},
|
|
34806
|
+
body: JSON.stringify({ manifest })
|
|
34807
|
+
}, "initiate");
|
|
34808
|
+
if (!res.ok) throw new Error(`initiate failed: HTTP ${res.status} ${await res.text()}`);
|
|
34809
|
+
await res.text();
|
|
34810
|
+
}
|
|
34811
|
+
async function putChunk(opts, root, d, bytes) {
|
|
34812
|
+
const res = await retryingFetch(`${apiRoot(opts.baseUrl)}/${root}/chunks/${d.ciphertextHash}`, {
|
|
34813
|
+
method: "PUT",
|
|
34814
|
+
headers: {
|
|
34815
|
+
...authHeaders(opts.apiKey),
|
|
34816
|
+
"content-type": "application/octet-stream"
|
|
34817
|
+
},
|
|
34818
|
+
body: bytes
|
|
34819
|
+
}, `chunk ${d.index} upload`);
|
|
34820
|
+
if (!res.ok) throw new Error(`chunk ${d.index} upload failed: HTTP ${res.status} ${await res.text()}`);
|
|
34821
|
+
await res.text();
|
|
34822
|
+
}
|
|
34823
|
+
async function finalize(opts, root) {
|
|
34824
|
+
const res = await retryingFetch(`${apiRoot(opts.baseUrl)}/${root}/finalize`, {
|
|
34825
|
+
method: "POST",
|
|
34826
|
+
headers: authHeaders(opts.apiKey)
|
|
34827
|
+
}, "finalize");
|
|
34828
|
+
if (!res.ok) throw new Error(`finalize failed: HTTP ${res.status} ${await res.text()}`);
|
|
34829
|
+
await res.text();
|
|
34830
|
+
}
|
|
34831
|
+
async function fetchManifest(opts, root) {
|
|
34832
|
+
const res = await retryingFetch(`${apiRoot(opts.baseUrl)}/${root}/manifest`, { headers: authHeaders(opts.apiKey) }, "get manifest");
|
|
34833
|
+
if (!res.ok) throw new Error(`get manifest failed: HTTP ${res.status} ${await res.text()}`);
|
|
34834
|
+
return (await res.json()).data.manifest;
|
|
34835
|
+
}
|
|
34836
|
+
async function getChunkBytes(opts, root, d) {
|
|
34837
|
+
const res = await retryingFetch(`${apiRoot(opts.baseUrl)}/${root}/chunks/${d.ciphertextHash}`, { headers: authHeaders(opts.apiKey) }, `get chunk ${d.index}`);
|
|
34838
|
+
if (!res.ok) throw new Error(`get chunk ${d.index} failed: HTTP ${res.status}`);
|
|
34839
|
+
return new Uint8Array(await res.arrayBuffer());
|
|
34840
|
+
}
|
|
34841
|
+
async function readWindow(fh, position, length) {
|
|
34842
|
+
const buf = Buffer.allocUnsafe(length);
|
|
34843
|
+
let read = 0;
|
|
34844
|
+
while (read < length) {
|
|
34845
|
+
const { bytesRead } = await fh.read(buf, read, length - read, position + read);
|
|
34846
|
+
if (bytesRead === 0) break;
|
|
34847
|
+
read += bytesRead;
|
|
34848
|
+
}
|
|
34849
|
+
return read === length ? buf : buf.subarray(0, read);
|
|
34850
|
+
}
|
|
34851
|
+
async function mapLimit(items, limit, fn) {
|
|
34852
|
+
let next = 0;
|
|
34853
|
+
let firstError;
|
|
34854
|
+
const worker = async () => {
|
|
34855
|
+
while (next < items.length && firstError === void 0) {
|
|
34856
|
+
const i = next++;
|
|
34857
|
+
try {
|
|
34858
|
+
await fn(items[i], i);
|
|
34859
|
+
} catch (err) {
|
|
34860
|
+
if (firstError === void 0) firstError = err;
|
|
34861
|
+
return;
|
|
34862
|
+
}
|
|
34863
|
+
}
|
|
34864
|
+
};
|
|
34865
|
+
await Promise.all(Array.from({ length: Math.min(limit, items.length || 1) }, () => worker()));
|
|
34866
|
+
if (firstError !== void 0) throw firstError;
|
|
34867
|
+
}
|
|
34868
|
+
/**
|
|
34869
|
+
* Stream a file up as a Primitive Payload. Two passes over the file (encrypt to
|
|
34870
|
+
* build the manifest, then encrypt again to upload) keep memory bounded to a few
|
|
34871
|
+
* chunks — the whole object is never resident. Returns the content address
|
|
34872
|
+
* (merkleRoot) and the hex CEK needed to download it.
|
|
34873
|
+
*/
|
|
34874
|
+
async function pushFile(filePath, opts) {
|
|
34875
|
+
const { open, stat } = await loadNodeFsPromises("pushFile");
|
|
34876
|
+
const chunkSize = opts.chunkSize ?? CHUNK_SIZE;
|
|
34877
|
+
const concurrency = opts.concurrency ?? 3;
|
|
34878
|
+
const { size } = await stat(filePath);
|
|
34879
|
+
const cek = randomBytes(CEK_BYTES);
|
|
34880
|
+
const objectId = randomBytes(OBJECT_ID_BYTES);
|
|
34881
|
+
const chunkCount = size === 0 ? 0 : Math.ceil(size / chunkSize);
|
|
34882
|
+
const fh = await open(filePath, "r");
|
|
34883
|
+
try {
|
|
34884
|
+
const descriptors = [];
|
|
34885
|
+
for (let index = 0; index < chunkCount; index++) {
|
|
34886
|
+
const position = index * chunkSize;
|
|
34887
|
+
const length = Math.min(chunkSize, size - position);
|
|
34888
|
+
const plaintext = await readWindow(fh, position, length);
|
|
34889
|
+
const ciphertext = await encryptChunk(cek, objectId, index, plaintext);
|
|
34890
|
+
descriptors.push({
|
|
34891
|
+
index,
|
|
34892
|
+
ciphertextHash: await contentHashHex(ciphertext),
|
|
34893
|
+
plaintextSize: length,
|
|
34894
|
+
ciphertextSize: ciphertext.length
|
|
34895
|
+
});
|
|
34896
|
+
opts.onProgress?.("encrypt", index + 1, chunkCount);
|
|
34897
|
+
}
|
|
34898
|
+
const root = await merkleRoot(descriptors.map((d) => d.ciphertextHash));
|
|
34899
|
+
await initiate(opts, {
|
|
34900
|
+
version: MANIFEST_VERSION,
|
|
34901
|
+
objectId: toHex(objectId),
|
|
34902
|
+
chunkSize,
|
|
34903
|
+
totalPlaintextSize: size,
|
|
34904
|
+
chunkCount,
|
|
34905
|
+
chunks: descriptors,
|
|
34906
|
+
merkleRoot: root
|
|
34907
|
+
});
|
|
34908
|
+
let uploaded = 0;
|
|
34909
|
+
await mapLimit(descriptors, concurrency, async (d) => {
|
|
34910
|
+
const plaintext = await readWindow(fh, d.index * chunkSize, d.plaintextSize);
|
|
34911
|
+
await putChunk(opts, root, d, await encryptChunk(cek, objectId, d.index, plaintext));
|
|
34912
|
+
opts.onProgress?.("upload", ++uploaded, chunkCount);
|
|
34913
|
+
});
|
|
34914
|
+
await finalize(opts, root);
|
|
34915
|
+
return {
|
|
34916
|
+
merkleRoot: root,
|
|
34917
|
+
cek: toHex(cek),
|
|
34918
|
+
chunkCount,
|
|
34919
|
+
totalBytes: size
|
|
34920
|
+
};
|
|
34921
|
+
} finally {
|
|
34922
|
+
await fh.close();
|
|
34923
|
+
}
|
|
34924
|
+
}
|
|
34925
|
+
/**
|
|
34926
|
+
* Stream a Primitive Payload down to a file, verifying and decrypting one chunk
|
|
34927
|
+
* at a time (bounded memory). Every chunk is content-address-checked before
|
|
34928
|
+
* decryption, so a corrupt or substituted chunk throws.
|
|
34929
|
+
*/
|
|
34930
|
+
async function pullFile(root, outPath, opts) {
|
|
34931
|
+
const { createWriteStream } = await loadNodeFs("pullFile");
|
|
34932
|
+
const { rm } = await loadNodeFsPromises("pullFile");
|
|
34933
|
+
const manifest = await fetchManifest(opts, root);
|
|
34934
|
+
const computedRoot = await merkleRoot(manifest.chunks.map((c) => c.ciphertextHash));
|
|
34935
|
+
if (computedRoot !== root) throw new Error(`manifest does not match the requested content address (got ${computedRoot})`);
|
|
34936
|
+
const cek = fromHex(opts.cek);
|
|
34937
|
+
const objectId = fromHex(manifest.objectId);
|
|
34938
|
+
const out = createWriteStream(outPath);
|
|
34939
|
+
let onWriteError = () => {};
|
|
34940
|
+
const writeErrored = new Promise((_, reject) => {
|
|
34941
|
+
onWriteError = reject;
|
|
34942
|
+
});
|
|
34943
|
+
writeErrored.catch(() => {});
|
|
34944
|
+
out.on("error", (err) => onWriteError(err));
|
|
34945
|
+
try {
|
|
34946
|
+
let done = 0;
|
|
34947
|
+
for (const d of manifest.chunks) {
|
|
34948
|
+
const ciphertext = await getChunkBytes(opts, root, d);
|
|
34949
|
+
if (await contentHashHex(ciphertext) !== d.ciphertextHash) throw new Error(`chunk ${d.index} failed integrity check`);
|
|
34950
|
+
const plaintext = await decryptChunk(cek, objectId, d.index, ciphertext);
|
|
34951
|
+
if (!out.write(plaintext)) await Promise.race([new Promise((resolve) => out.once("drain", resolve)), writeErrored]);
|
|
34952
|
+
opts.onProgress?.("download", ++done, manifest.chunkCount);
|
|
34953
|
+
}
|
|
34954
|
+
await Promise.race([new Promise((resolve) => out.end(resolve)), writeErrored]);
|
|
34955
|
+
} catch (err) {
|
|
34956
|
+
out.destroy();
|
|
34957
|
+
await rm(outPath, { force: true });
|
|
34958
|
+
throw err;
|
|
34959
|
+
}
|
|
34960
|
+
return manifest;
|
|
34961
|
+
}
|
|
34962
|
+
//#endregion
|
|
34963
|
+
//#region ../sdk-node/dist/api-D4xf448r.js
|
|
34964
|
+
const jsonBodySerializer = { bodySerializer: (body) => JSON.stringify(body, (_key, value) => typeof value === "bigint" ? value.toString() : value) };
|
|
34965
|
+
function createSseClient({ onRequest, onSseError, onSseEvent, responseTransformer, responseValidator, sseDefaultRetryDelay, sseMaxRetryAttempts, sseMaxRetryDelay, sseSleepFn, url, ...options }) {
|
|
34966
|
+
let lastEventId;
|
|
34967
|
+
const sleep = sseSleepFn ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
34968
|
+
const createStream = async function* () {
|
|
34969
|
+
let retryDelay = sseDefaultRetryDelay ?? 3e3;
|
|
34970
|
+
let attempt = 0;
|
|
34971
|
+
const signal = options.signal ?? new AbortController().signal;
|
|
34972
|
+
while (true) {
|
|
34973
|
+
if (signal.aborted) break;
|
|
34974
|
+
attempt++;
|
|
34975
|
+
const headers = options.headers instanceof Headers ? options.headers : new Headers(options.headers);
|
|
34976
|
+
if (lastEventId !== void 0) headers.set("Last-Event-ID", lastEventId);
|
|
34977
|
+
try {
|
|
34978
|
+
const requestInit = {
|
|
34979
|
+
redirect: "follow",
|
|
34980
|
+
...options,
|
|
34981
|
+
body: options.serializedBody,
|
|
34982
|
+
headers,
|
|
34983
|
+
signal
|
|
34984
|
+
};
|
|
34985
|
+
let request = new Request(url, requestInit);
|
|
34986
|
+
if (onRequest) request = await onRequest(url, requestInit);
|
|
34987
|
+
const response = await (options.fetch ?? globalThis.fetch)(request);
|
|
34988
|
+
if (!response.ok) throw new Error(`SSE failed: ${response.status} ${response.statusText}`);
|
|
34989
|
+
if (!response.body) throw new Error("No body in SSE response");
|
|
34990
|
+
const reader = response.body.pipeThrough(new TextDecoderStream()).getReader();
|
|
34991
|
+
let buffer = "";
|
|
34992
|
+
const abortHandler = () => {
|
|
34993
|
+
try {
|
|
34994
|
+
reader.cancel();
|
|
34995
|
+
} catch {}
|
|
34996
|
+
};
|
|
34997
|
+
signal.addEventListener("abort", abortHandler);
|
|
34998
|
+
try {
|
|
34999
|
+
while (true) {
|
|
35000
|
+
const { done, value } = await reader.read();
|
|
35001
|
+
if (done) break;
|
|
35002
|
+
buffer += value;
|
|
35003
|
+
buffer = buffer.replace(/\r\n?/g, "\n");
|
|
35004
|
+
const chunks = buffer.split("\n\n");
|
|
35005
|
+
buffer = chunks.pop() ?? "";
|
|
35006
|
+
for (const chunk of chunks) {
|
|
35007
|
+
const lines = chunk.split("\n");
|
|
35008
|
+
const dataLines = [];
|
|
35009
|
+
let eventName;
|
|
35010
|
+
for (const line of lines) if (line.startsWith("data:")) dataLines.push(line.replace(/^data:\s*/, ""));
|
|
35011
|
+
else if (line.startsWith("event:")) eventName = line.replace(/^event:\s*/, "");
|
|
35012
|
+
else if (line.startsWith("id:")) lastEventId = line.replace(/^id:\s*/, "");
|
|
35013
|
+
else if (line.startsWith("retry:")) {
|
|
35014
|
+
const parsed = Number.parseInt(line.replace(/^retry:\s*/, ""), 10);
|
|
35015
|
+
if (!Number.isNaN(parsed)) retryDelay = parsed;
|
|
35016
|
+
}
|
|
35017
|
+
let data;
|
|
35018
|
+
let parsedJson = false;
|
|
35019
|
+
if (dataLines.length) {
|
|
35020
|
+
const rawData = dataLines.join("\n");
|
|
35021
|
+
try {
|
|
35022
|
+
data = JSON.parse(rawData);
|
|
35023
|
+
parsedJson = true;
|
|
35024
|
+
} catch {
|
|
35025
|
+
data = rawData;
|
|
35026
|
+
}
|
|
35027
|
+
}
|
|
35028
|
+
if (parsedJson) {
|
|
35029
|
+
if (responseValidator) await responseValidator(data);
|
|
35030
|
+
if (responseTransformer) data = await responseTransformer(data);
|
|
35031
|
+
}
|
|
35032
|
+
onSseEvent?.({
|
|
35033
|
+
data,
|
|
35034
|
+
event: eventName,
|
|
35035
|
+
id: lastEventId,
|
|
35036
|
+
retry: retryDelay
|
|
35037
|
+
});
|
|
35038
|
+
if (dataLines.length) yield data;
|
|
35039
|
+
}
|
|
35040
|
+
}
|
|
35041
|
+
} finally {
|
|
35042
|
+
signal.removeEventListener("abort", abortHandler);
|
|
35043
|
+
reader.releaseLock();
|
|
35044
|
+
}
|
|
35045
|
+
break;
|
|
35046
|
+
} catch (error) {
|
|
35047
|
+
onSseError?.(error);
|
|
35048
|
+
if (sseMaxRetryAttempts !== void 0 && attempt >= sseMaxRetryAttempts) break;
|
|
35049
|
+
await sleep(Math.min(retryDelay * 2 ** (attempt - 1), sseMaxRetryDelay ?? 3e4));
|
|
35050
|
+
}
|
|
35051
|
+
}
|
|
35052
|
+
};
|
|
35053
|
+
return { stream: createStream() };
|
|
35054
|
+
}
|
|
35055
|
+
const separatorArrayExplode = (style) => {
|
|
35056
|
+
switch (style) {
|
|
35057
|
+
case "label": return ".";
|
|
35058
|
+
case "matrix": return ";";
|
|
35059
|
+
case "simple": return ",";
|
|
35060
|
+
default: return "&";
|
|
35061
|
+
}
|
|
35062
|
+
};
|
|
35063
|
+
const separatorArrayNoExplode = (style) => {
|
|
35064
|
+
switch (style) {
|
|
35065
|
+
case "form": return ",";
|
|
35066
|
+
case "pipeDelimited": return "|";
|
|
35067
|
+
case "spaceDelimited": return "%20";
|
|
35068
|
+
default: return ",";
|
|
35069
|
+
}
|
|
35070
|
+
};
|
|
35071
|
+
const separatorObjectExplode = (style) => {
|
|
35072
|
+
switch (style) {
|
|
35073
|
+
case "label": return ".";
|
|
35074
|
+
case "matrix": return ";";
|
|
35075
|
+
case "simple": return ",";
|
|
35076
|
+
default: return "&";
|
|
35077
|
+
}
|
|
35078
|
+
};
|
|
35079
|
+
const serializeArrayParam = ({ allowReserved, explode, name, style, value }) => {
|
|
35080
|
+
if (!explode) {
|
|
35081
|
+
const joinedValues = (allowReserved ? value : value.map((v) => encodeURIComponent(v))).join(separatorArrayNoExplode(style));
|
|
35082
|
+
switch (style) {
|
|
35083
|
+
case "label": return `.${joinedValues}`;
|
|
35084
|
+
case "matrix": return `;${name}=${joinedValues}`;
|
|
35085
|
+
case "simple": return joinedValues;
|
|
35086
|
+
default: return `${name}=${joinedValues}`;
|
|
35087
|
+
}
|
|
35088
|
+
}
|
|
35089
|
+
const separator = separatorArrayExplode(style);
|
|
35090
|
+
const joinedValues = value.map((v) => {
|
|
35091
|
+
if (style === "label" || style === "simple") return allowReserved ? v : encodeURIComponent(v);
|
|
35092
|
+
return serializePrimitiveParam({
|
|
35093
|
+
allowReserved,
|
|
35094
|
+
name,
|
|
35095
|
+
value: v
|
|
35096
|
+
});
|
|
35097
|
+
}).join(separator);
|
|
35098
|
+
return style === "label" || style === "matrix" ? separator + joinedValues : joinedValues;
|
|
35099
|
+
};
|
|
35100
|
+
const serializePrimitiveParam = ({ allowReserved, name, value }) => {
|
|
35101
|
+
if (value === void 0 || value === null) return "";
|
|
35102
|
+
if (typeof value === "object") throw new Error("Deeply-nested arrays/objects aren’t supported. Provide your own `querySerializer()` to handle these.");
|
|
35103
|
+
return `${name}=${allowReserved ? value : encodeURIComponent(value)}`;
|
|
35104
|
+
};
|
|
35105
|
+
const serializeObjectParam = ({ allowReserved, explode, name, style, value, valueOnly }) => {
|
|
35106
|
+
if (value instanceof Date) return valueOnly ? value.toISOString() : `${name}=${value.toISOString()}`;
|
|
35107
|
+
if (style !== "deepObject" && !explode) {
|
|
35108
|
+
let values = [];
|
|
35109
|
+
Object.entries(value).forEach(([key, v]) => {
|
|
35110
|
+
values = [
|
|
35111
|
+
...values,
|
|
35112
|
+
key,
|
|
35113
|
+
allowReserved ? v : encodeURIComponent(v)
|
|
35114
|
+
];
|
|
35115
|
+
});
|
|
35116
|
+
const joinedValues = values.join(",");
|
|
35117
|
+
switch (style) {
|
|
35118
|
+
case "form": return `${name}=${joinedValues}`;
|
|
35119
|
+
case "label": return `.${joinedValues}`;
|
|
35120
|
+
case "matrix": return `;${name}=${joinedValues}`;
|
|
35121
|
+
default: return joinedValues;
|
|
35122
|
+
}
|
|
35123
|
+
}
|
|
35124
|
+
const separator = separatorObjectExplode(style);
|
|
35125
|
+
const joinedValues = Object.entries(value).map(([key, v]) => serializePrimitiveParam({
|
|
35126
|
+
allowReserved,
|
|
35127
|
+
name: style === "deepObject" ? `${name}[${key}]` : key,
|
|
35128
|
+
value: v
|
|
35129
|
+
})).join(separator);
|
|
35130
|
+
return style === "label" || style === "matrix" ? separator + joinedValues : joinedValues;
|
|
35131
|
+
};
|
|
35132
|
+
const PATH_PARAM_RE = /\{[^{}]+\}/g;
|
|
35133
|
+
const defaultPathSerializer = ({ path, url: _url }) => {
|
|
35134
|
+
let url = _url;
|
|
35135
|
+
const matches = _url.match(PATH_PARAM_RE);
|
|
35136
|
+
if (matches) for (const match of matches) {
|
|
35137
|
+
let explode = false;
|
|
35138
|
+
let name = match.substring(1, match.length - 1);
|
|
35139
|
+
let style = "simple";
|
|
35140
|
+
if (name.endsWith("*")) {
|
|
35141
|
+
explode = true;
|
|
35142
|
+
name = name.substring(0, name.length - 1);
|
|
35143
|
+
}
|
|
35144
|
+
if (name.startsWith(".")) {
|
|
35145
|
+
name = name.substring(1);
|
|
35146
|
+
style = "label";
|
|
35147
|
+
} else if (name.startsWith(";")) {
|
|
35148
|
+
name = name.substring(1);
|
|
35149
|
+
style = "matrix";
|
|
35150
|
+
}
|
|
35151
|
+
const value = path[name];
|
|
35152
|
+
if (value === void 0 || value === null) continue;
|
|
35153
|
+
if (Array.isArray(value)) {
|
|
35154
|
+
url = url.replace(match, serializeArrayParam({
|
|
35155
|
+
explode,
|
|
35156
|
+
name,
|
|
35157
|
+
style,
|
|
35158
|
+
value
|
|
35159
|
+
}));
|
|
35160
|
+
continue;
|
|
35161
|
+
}
|
|
35162
|
+
if (typeof value === "object") {
|
|
35163
|
+
url = url.replace(match, serializeObjectParam({
|
|
35164
|
+
explode,
|
|
35165
|
+
name,
|
|
35166
|
+
style,
|
|
35167
|
+
value,
|
|
35168
|
+
valueOnly: true
|
|
35169
|
+
}));
|
|
35170
|
+
continue;
|
|
35171
|
+
}
|
|
35172
|
+
if (style === "matrix") {
|
|
35173
|
+
url = url.replace(match, `;${serializePrimitiveParam({
|
|
35174
|
+
name,
|
|
35175
|
+
value
|
|
35176
|
+
})}`);
|
|
35177
|
+
continue;
|
|
35178
|
+
}
|
|
35179
|
+
const replaceValue = encodeURIComponent(style === "label" ? `.${value}` : value);
|
|
35180
|
+
url = url.replace(match, replaceValue);
|
|
35181
|
+
}
|
|
35182
|
+
return url;
|
|
35183
|
+
};
|
|
35184
|
+
const getUrl = ({ baseUrl, path, query, querySerializer, url: _url }) => {
|
|
35185
|
+
const pathUrl = _url.startsWith("/") ? _url : `/${_url}`;
|
|
35186
|
+
let url = (baseUrl ?? "") + pathUrl;
|
|
35187
|
+
if (path) url = defaultPathSerializer({
|
|
35188
|
+
path,
|
|
35189
|
+
url
|
|
35190
|
+
});
|
|
35191
|
+
let search = query ? querySerializer(query) : "";
|
|
35192
|
+
if (search.startsWith("?")) search = search.substring(1);
|
|
35193
|
+
if (search) url += `?${search}`;
|
|
35194
|
+
return url;
|
|
35195
|
+
};
|
|
35196
|
+
function getValidRequestBody(options) {
|
|
35197
|
+
const hasBody = options.body !== void 0;
|
|
35198
|
+
if (hasBody && options.bodySerializer) {
|
|
35199
|
+
if ("serializedBody" in options) return options.serializedBody !== void 0 && options.serializedBody !== "" ? options.serializedBody : null;
|
|
35200
|
+
return options.body !== "" ? options.body : null;
|
|
35201
|
+
}
|
|
35202
|
+
if (hasBody) return options.body;
|
|
35203
|
+
}
|
|
35204
|
+
const getAuthToken = async (auth, callback) => {
|
|
35205
|
+
const token = typeof callback === "function" ? await callback(auth) : callback;
|
|
35206
|
+
if (!token) return;
|
|
35207
|
+
if (auth.scheme === "bearer") return `Bearer ${token}`;
|
|
35208
|
+
if (auth.scheme === "basic") return `Basic ${btoa(token)}`;
|
|
35209
|
+
return token;
|
|
35210
|
+
};
|
|
35211
|
+
const createQuerySerializer = ({ parameters = {}, ...args } = {}) => {
|
|
35212
|
+
const querySerializer = (queryParams) => {
|
|
35213
|
+
const search = [];
|
|
35214
|
+
if (queryParams && typeof queryParams === "object") for (const name in queryParams) {
|
|
35215
|
+
const value = queryParams[name];
|
|
35216
|
+
if (value === void 0 || value === null) continue;
|
|
35217
|
+
const options = parameters[name] || args;
|
|
35218
|
+
if (Array.isArray(value)) {
|
|
35219
|
+
const serializedArray = serializeArrayParam({
|
|
35220
|
+
allowReserved: options.allowReserved,
|
|
35221
|
+
explode: true,
|
|
35222
|
+
name,
|
|
35223
|
+
style: "form",
|
|
35224
|
+
value,
|
|
35225
|
+
...options.array
|
|
35226
|
+
});
|
|
35227
|
+
if (serializedArray) search.push(serializedArray);
|
|
35228
|
+
} else if (typeof value === "object") {
|
|
35229
|
+
const serializedObject = serializeObjectParam({
|
|
35230
|
+
allowReserved: options.allowReserved,
|
|
35231
|
+
explode: true,
|
|
35232
|
+
name,
|
|
35233
|
+
style: "deepObject",
|
|
35234
|
+
value,
|
|
35235
|
+
...options.object
|
|
35236
|
+
});
|
|
35237
|
+
if (serializedObject) search.push(serializedObject);
|
|
35238
|
+
} else {
|
|
35239
|
+
const serializedPrimitive = serializePrimitiveParam({
|
|
35240
|
+
allowReserved: options.allowReserved,
|
|
35241
|
+
name,
|
|
35242
|
+
value
|
|
35243
|
+
});
|
|
35244
|
+
if (serializedPrimitive) search.push(serializedPrimitive);
|
|
35245
|
+
}
|
|
35246
|
+
}
|
|
35247
|
+
return search.join("&");
|
|
35248
|
+
};
|
|
35249
|
+
return querySerializer;
|
|
35250
|
+
};
|
|
35251
|
+
/**
|
|
35252
|
+
* Infers parseAs value from provided Content-Type header.
|
|
35253
|
+
*/
|
|
35254
|
+
const getParseAs = (contentType) => {
|
|
35255
|
+
if (!contentType) return "stream";
|
|
35256
|
+
const cleanContent = contentType.split(";")[0]?.trim();
|
|
35257
|
+
if (!cleanContent) return;
|
|
35258
|
+
if (cleanContent.startsWith("application/json") || cleanContent.endsWith("+json")) return "json";
|
|
35259
|
+
if (cleanContent === "multipart/form-data") return "formData";
|
|
35260
|
+
if ([
|
|
35261
|
+
"application/",
|
|
35262
|
+
"audio/",
|
|
35263
|
+
"image/",
|
|
35264
|
+
"video/"
|
|
35265
|
+
].some((type) => cleanContent.startsWith(type))) return "blob";
|
|
35266
|
+
if (cleanContent.startsWith("text/")) return "text";
|
|
35267
|
+
};
|
|
35268
|
+
const checkForExistence = (options, name) => {
|
|
35269
|
+
if (!name) return false;
|
|
35270
|
+
if (options.headers.has(name) || options.query?.[name] || options.headers.get("Cookie")?.includes(`${name}=`)) return true;
|
|
35271
|
+
return false;
|
|
35272
|
+
};
|
|
35273
|
+
async function setAuthParams(options) {
|
|
35274
|
+
for (const auth of options.security ?? []) {
|
|
35275
|
+
if (checkForExistence(options, auth.name)) continue;
|
|
35276
|
+
const token = await getAuthToken(auth, options.auth);
|
|
35277
|
+
if (!token) continue;
|
|
35278
|
+
const name = auth.name ?? "Authorization";
|
|
35279
|
+
switch (auth.in) {
|
|
35280
|
+
case "query":
|
|
35281
|
+
if (!options.query) options.query = {};
|
|
35282
|
+
options.query[name] = token;
|
|
35283
|
+
break;
|
|
35284
|
+
case "cookie":
|
|
35285
|
+
options.headers.append("Cookie", `${name}=${token}`);
|
|
35286
|
+
break;
|
|
35287
|
+
default:
|
|
35288
|
+
options.headers.set(name, token);
|
|
35289
|
+
break;
|
|
35290
|
+
}
|
|
35291
|
+
}
|
|
35292
|
+
}
|
|
35293
|
+
const buildUrl = (options) => getUrl({
|
|
35294
|
+
baseUrl: options.baseUrl,
|
|
35295
|
+
path: options.path,
|
|
35296
|
+
query: options.query,
|
|
35297
|
+
querySerializer: typeof options.querySerializer === "function" ? options.querySerializer : createQuerySerializer(options.querySerializer),
|
|
35298
|
+
url: options.url
|
|
35299
|
+
});
|
|
35300
|
+
const mergeConfigs = (a, b) => {
|
|
35301
|
+
const config = {
|
|
35302
|
+
...a,
|
|
35303
|
+
...b
|
|
35304
|
+
};
|
|
35305
|
+
if (config.baseUrl?.endsWith("/")) config.baseUrl = config.baseUrl.substring(0, config.baseUrl.length - 1);
|
|
35306
|
+
config.headers = mergeHeaders(a.headers, b.headers);
|
|
35307
|
+
return config;
|
|
35308
|
+
};
|
|
35309
|
+
const headersEntries = (headers) => {
|
|
35310
|
+
const entries = [];
|
|
35311
|
+
headers.forEach((value, key) => {
|
|
35312
|
+
entries.push([key, value]);
|
|
35313
|
+
});
|
|
35314
|
+
return entries;
|
|
35315
|
+
};
|
|
35316
|
+
const mergeHeaders = (...headers) => {
|
|
35317
|
+
const mergedHeaders = new Headers();
|
|
35318
|
+
for (const header of headers) {
|
|
35319
|
+
if (!header) continue;
|
|
35320
|
+
const iterator = header instanceof Headers ? headersEntries(header) : Object.entries(header);
|
|
35321
|
+
for (const [key, value] of iterator) if (value === null) mergedHeaders.delete(key);
|
|
35322
|
+
else if (Array.isArray(value)) for (const v of value) mergedHeaders.append(key, v);
|
|
35323
|
+
else if (value !== void 0) mergedHeaders.set(key, typeof value === "object" ? JSON.stringify(value) : value);
|
|
35324
|
+
}
|
|
35325
|
+
return mergedHeaders;
|
|
35326
|
+
};
|
|
35327
|
+
var Interceptors = class {
|
|
35328
|
+
fns = [];
|
|
35329
|
+
clear() {
|
|
35330
|
+
this.fns = [];
|
|
35331
|
+
}
|
|
35332
|
+
eject(id) {
|
|
35333
|
+
const index = this.getInterceptorIndex(id);
|
|
35334
|
+
if (this.fns[index]) this.fns[index] = null;
|
|
35335
|
+
}
|
|
35336
|
+
exists(id) {
|
|
35337
|
+
const index = this.getInterceptorIndex(id);
|
|
35338
|
+
return Boolean(this.fns[index]);
|
|
35339
|
+
}
|
|
35340
|
+
getInterceptorIndex(id) {
|
|
35341
|
+
if (typeof id === "number") return this.fns[id] ? id : -1;
|
|
35342
|
+
return this.fns.indexOf(id);
|
|
35343
|
+
}
|
|
35344
|
+
update(id, fn) {
|
|
35345
|
+
const index = this.getInterceptorIndex(id);
|
|
35346
|
+
if (this.fns[index]) {
|
|
35347
|
+
this.fns[index] = fn;
|
|
35348
|
+
return id;
|
|
35349
|
+
}
|
|
35350
|
+
return false;
|
|
35351
|
+
}
|
|
35352
|
+
use(fn) {
|
|
35353
|
+
this.fns.push(fn);
|
|
35354
|
+
return this.fns.length - 1;
|
|
35355
|
+
}
|
|
35356
|
+
};
|
|
35357
|
+
const createInterceptors = () => ({
|
|
35358
|
+
error: new Interceptors(),
|
|
35359
|
+
request: new Interceptors(),
|
|
35360
|
+
response: new Interceptors()
|
|
35361
|
+
});
|
|
35362
|
+
const defaultQuerySerializer = createQuerySerializer({
|
|
35363
|
+
allowReserved: false,
|
|
35364
|
+
array: {
|
|
35365
|
+
explode: true,
|
|
35366
|
+
style: "form"
|
|
35367
|
+
},
|
|
35368
|
+
object: {
|
|
35369
|
+
explode: true,
|
|
35370
|
+
style: "deepObject"
|
|
35371
|
+
}
|
|
35372
|
+
});
|
|
35373
|
+
const defaultHeaders = { "Content-Type": "application/json" };
|
|
35374
|
+
const createConfig = (override = {}) => ({
|
|
35375
|
+
...jsonBodySerializer,
|
|
35376
|
+
headers: defaultHeaders,
|
|
35377
|
+
parseAs: "auto",
|
|
35378
|
+
querySerializer: defaultQuerySerializer,
|
|
35379
|
+
...override
|
|
35380
|
+
});
|
|
35381
|
+
const createClient = (config = {}) => {
|
|
35382
|
+
let _config = mergeConfigs(createConfig(), config);
|
|
35383
|
+
const getConfig = () => ({ ..._config });
|
|
35384
|
+
const setConfig = (config) => {
|
|
35385
|
+
_config = mergeConfigs(_config, config);
|
|
35386
|
+
return getConfig();
|
|
35387
|
+
};
|
|
35388
|
+
const interceptors = createInterceptors();
|
|
35389
|
+
const beforeRequest = async (options) => {
|
|
35390
|
+
const opts = {
|
|
35391
|
+
..._config,
|
|
35392
|
+
...options,
|
|
35393
|
+
fetch: options.fetch ?? _config.fetch ?? globalThis.fetch,
|
|
35394
|
+
headers: mergeHeaders(_config.headers, options.headers),
|
|
35395
|
+
serializedBody: void 0
|
|
35396
|
+
};
|
|
35397
|
+
if (opts.security) await setAuthParams(opts);
|
|
35398
|
+
if (opts.requestValidator) await opts.requestValidator(opts);
|
|
35399
|
+
if (opts.body !== void 0 && opts.bodySerializer) opts.serializedBody = opts.bodySerializer(opts.body);
|
|
35400
|
+
if (opts.body === void 0 || opts.serializedBody === "") opts.headers.delete("Content-Type");
|
|
35401
|
+
const resolvedOpts = opts;
|
|
35402
|
+
return {
|
|
35403
|
+
opts: resolvedOpts,
|
|
35404
|
+
url: buildUrl(resolvedOpts)
|
|
35405
|
+
};
|
|
35406
|
+
};
|
|
35407
|
+
const request = async (options) => {
|
|
35408
|
+
const throwOnError = options.throwOnError ?? _config.throwOnError;
|
|
35409
|
+
const responseStyle = options.responseStyle ?? _config.responseStyle;
|
|
35410
|
+
let request;
|
|
35411
|
+
let response;
|
|
35412
|
+
try {
|
|
35413
|
+
const { opts, url } = await beforeRequest(options);
|
|
35414
|
+
const requestInit = {
|
|
35415
|
+
redirect: "follow",
|
|
35416
|
+
...opts,
|
|
35417
|
+
body: getValidRequestBody(opts)
|
|
35418
|
+
};
|
|
35419
|
+
request = new Request(url, requestInit);
|
|
35420
|
+
for (const fn of interceptors.request.fns) if (fn) request = await fn(request, opts);
|
|
35421
|
+
const _fetch = opts.fetch;
|
|
35422
|
+
response = await _fetch(request);
|
|
35423
|
+
for (const fn of interceptors.response.fns) if (fn) response = await fn(response, request, opts);
|
|
35424
|
+
const result = {
|
|
35425
|
+
request,
|
|
35426
|
+
response
|
|
35427
|
+
};
|
|
35428
|
+
if (response.ok) {
|
|
35429
|
+
const parseAs = (opts.parseAs === "auto" ? getParseAs(response.headers.get("Content-Type")) : opts.parseAs) ?? "json";
|
|
35430
|
+
if (response.status === 204 || response.headers.get("Content-Length") === "0") {
|
|
35431
|
+
let emptyData;
|
|
35432
|
+
switch (parseAs) {
|
|
35433
|
+
case "arrayBuffer":
|
|
35434
|
+
case "blob":
|
|
35435
|
+
case "text":
|
|
35436
|
+
emptyData = await response[parseAs]();
|
|
35437
|
+
break;
|
|
35438
|
+
case "formData":
|
|
35439
|
+
emptyData = new FormData();
|
|
35440
|
+
break;
|
|
35441
|
+
case "stream":
|
|
35442
|
+
emptyData = response.body;
|
|
35443
|
+
break;
|
|
35444
|
+
default:
|
|
35445
|
+
emptyData = {};
|
|
35446
|
+
break;
|
|
35447
|
+
}
|
|
35448
|
+
return opts.responseStyle === "data" ? emptyData : {
|
|
35449
|
+
data: emptyData,
|
|
35450
|
+
...result
|
|
35451
|
+
};
|
|
35452
|
+
}
|
|
35453
|
+
let data;
|
|
35454
|
+
switch (parseAs) {
|
|
35455
|
+
case "arrayBuffer":
|
|
35456
|
+
case "blob":
|
|
35457
|
+
case "formData":
|
|
35458
|
+
case "text":
|
|
35459
|
+
data = await response[parseAs]();
|
|
35460
|
+
break;
|
|
35461
|
+
case "json": {
|
|
35462
|
+
const text = await response.text();
|
|
35463
|
+
data = text ? JSON.parse(text) : {};
|
|
35464
|
+
break;
|
|
35465
|
+
}
|
|
35466
|
+
case "stream": return opts.responseStyle === "data" ? response.body : {
|
|
35467
|
+
data: response.body,
|
|
35468
|
+
...result
|
|
35469
|
+
};
|
|
35470
|
+
}
|
|
35471
|
+
if (parseAs === "json") {
|
|
35472
|
+
if (opts.responseValidator) await opts.responseValidator(data);
|
|
35473
|
+
if (opts.responseTransformer) data = await opts.responseTransformer(data);
|
|
35474
|
+
}
|
|
35475
|
+
return opts.responseStyle === "data" ? data : {
|
|
35476
|
+
data,
|
|
35477
|
+
...result
|
|
35478
|
+
};
|
|
35479
|
+
}
|
|
35480
|
+
const textError = await response.text();
|
|
35481
|
+
let jsonError;
|
|
35482
|
+
try {
|
|
35483
|
+
jsonError = JSON.parse(textError);
|
|
35484
|
+
} catch {}
|
|
35485
|
+
throw jsonError ?? textError;
|
|
35486
|
+
} catch (error) {
|
|
35487
|
+
let finalError = error;
|
|
35488
|
+
for (const fn of interceptors.error.fns) if (fn) finalError = await fn(finalError, response, request, options);
|
|
35489
|
+
finalError = finalError || {};
|
|
35490
|
+
if (throwOnError) throw finalError;
|
|
35491
|
+
return responseStyle === "data" ? void 0 : {
|
|
35492
|
+
error: finalError,
|
|
35493
|
+
request,
|
|
35494
|
+
response
|
|
35495
|
+
};
|
|
35496
|
+
}
|
|
35497
|
+
};
|
|
35498
|
+
const makeMethodFn = (method) => (options) => request({
|
|
35499
|
+
...options,
|
|
35500
|
+
method
|
|
35501
|
+
});
|
|
35502
|
+
const makeSseFn = (method) => async (options) => {
|
|
35503
|
+
const { opts, url } = await beforeRequest(options);
|
|
35504
|
+
return createSseClient({
|
|
35505
|
+
...opts,
|
|
35506
|
+
body: opts.body,
|
|
35507
|
+
method,
|
|
35508
|
+
onRequest: async (url, init) => {
|
|
35509
|
+
let request = new Request(url, init);
|
|
35510
|
+
for (const fn of interceptors.request.fns) if (fn) request = await fn(request, opts);
|
|
35511
|
+
return request;
|
|
35512
|
+
},
|
|
35513
|
+
serializedBody: getValidRequestBody(opts),
|
|
35514
|
+
url
|
|
35515
|
+
});
|
|
35516
|
+
};
|
|
35517
|
+
const _buildUrl = (options) => buildUrl({
|
|
35518
|
+
..._config,
|
|
35519
|
+
...options
|
|
35520
|
+
});
|
|
35521
|
+
return {
|
|
35522
|
+
buildUrl: _buildUrl,
|
|
35523
|
+
connect: makeMethodFn("CONNECT"),
|
|
35524
|
+
delete: makeMethodFn("DELETE"),
|
|
35525
|
+
get: makeMethodFn("GET"),
|
|
35526
|
+
getConfig,
|
|
35527
|
+
head: makeMethodFn("HEAD"),
|
|
35528
|
+
interceptors,
|
|
35529
|
+
options: makeMethodFn("OPTIONS"),
|
|
35530
|
+
patch: makeMethodFn("PATCH"),
|
|
35531
|
+
post: makeMethodFn("POST"),
|
|
35532
|
+
put: makeMethodFn("PUT"),
|
|
35533
|
+
request,
|
|
35534
|
+
setConfig,
|
|
35535
|
+
sse: {
|
|
35536
|
+
connect: makeSseFn("CONNECT"),
|
|
35537
|
+
delete: makeSseFn("DELETE"),
|
|
35538
|
+
get: makeSseFn("GET"),
|
|
35539
|
+
head: makeSseFn("HEAD"),
|
|
35540
|
+
options: makeSseFn("OPTIONS"),
|
|
35541
|
+
patch: makeSseFn("PATCH"),
|
|
35542
|
+
post: makeSseFn("POST"),
|
|
35543
|
+
put: makeSseFn("PUT"),
|
|
35544
|
+
trace: makeSseFn("TRACE")
|
|
35545
|
+
},
|
|
35546
|
+
trace: makeMethodFn("TRACE")
|
|
35547
|
+
};
|
|
35548
|
+
};
|
|
35549
|
+
const client$1 = createClient(createConfig({ baseUrl: "https://api.primitive.dev/v1" }));
|
|
35550
|
+
/**
|
|
35551
|
+
* Get account info
|
|
35552
|
+
*/
|
|
35553
|
+
const getAccount = (options) => (options?.client ?? client$1).get({
|
|
35554
|
+
security: [{
|
|
35555
|
+
scheme: "bearer",
|
|
35556
|
+
type: "http"
|
|
35557
|
+
}],
|
|
35558
|
+
url: "/account",
|
|
35559
|
+
...options
|
|
35560
|
+
});
|
|
35561
|
+
/**
|
|
35562
|
+
* Receive a pending webhook event without a public endpoint
|
|
35563
|
+
*
|
|
35564
|
+
* Wait up to 30 seconds for one existing event. The body string preserves the exact webhook serialization; headers and canonical occurrence/type metadata travel alongside it. A fixed lease protects the attempt while a short handler accepts input. Retry may redeliver an occurrence: deduplicate event_id. Empty delivery means no offer now, not proof all input was delivered; inspect backlog and persistent gap_count/last_gap_reason. Queue retention is 24 hours and does not extend source-content retention. Disconnecting preserves pending work. Same named destination shares consumption; different destinations get independent copies.
|
|
35565
|
+
*/
|
|
35566
|
+
const pullWebhookEvent = (options) => (options.client ?? client$1).post({
|
|
35567
|
+
security: [{
|
|
35568
|
+
scheme: "bearer",
|
|
35569
|
+
type: "http"
|
|
35570
|
+
}],
|
|
35571
|
+
url: "/endpoints/{id}/pull",
|
|
35572
|
+
...options,
|
|
35573
|
+
headers: {
|
|
35574
|
+
...options.body !== void 0 && { "Content-Type": "application/json" },
|
|
35575
|
+
...options.headers
|
|
35576
|
+
}
|
|
35577
|
+
});
|
|
35578
|
+
/**
|
|
35579
|
+
* Report the outcome of a local webhook delivery attempt
|
|
35580
|
+
*
|
|
35581
|
+
* Report normalized handling evidence for the queue, attempt and lease. Required JSON fields: queue_id, delivery_id, lease_token, mode, duration_ms. For mode=exec include exit_code; stdout includes write_succeeded; http includes status_code and optional transport_error, error_code, confirmed. Supply this discriminated request with --raw-body in the generated CLI. The server classifies success and retry. Duplicate identical completion is idempotent; a stale lease cannot finish a newer attempt. Exec success means durable input acceptance, not completion of agent reasoning. Only successful HTTP handling with recognized confirmation evidence can confirm content discard. Never send arbitrary handler output or HTTP response bodies.
|
|
35582
|
+
*/
|
|
35583
|
+
const completeWebhookEvent = (options) => (options.client ?? client$1).post({
|
|
35584
|
+
security: [{
|
|
35585
|
+
scheme: "bearer",
|
|
35586
|
+
type: "http"
|
|
35587
|
+
}],
|
|
35588
|
+
url: "/endpoints/{id}/complete",
|
|
35589
|
+
...options,
|
|
35590
|
+
headers: {
|
|
35591
|
+
...options.body !== void 0 && { "Content-Type": "application/json" },
|
|
35592
|
+
...options.headers
|
|
35593
|
+
}
|
|
35594
|
+
});
|
|
35595
|
+
var EventReceiverError = class extends Error {
|
|
35596
|
+
constructor(message, code, status = 0, retryAfterMs = 0) {
|
|
35597
|
+
super(message);
|
|
35598
|
+
this.code = code;
|
|
35599
|
+
this.status = status;
|
|
35600
|
+
this.retryAfterMs = retryAfterMs;
|
|
35601
|
+
this.name = "EventReceiverError";
|
|
35602
|
+
}
|
|
35603
|
+
};
|
|
35604
|
+
function record(value) {
|
|
35605
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
35606
|
+
}
|
|
35607
|
+
function validateOffer(value) {
|
|
35608
|
+
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");
|
|
35609
|
+
const d = value.delivery;
|
|
35610
|
+
if (d !== null && (!record(d) || ![
|
|
35611
|
+
"queue_id",
|
|
35612
|
+
"event_id",
|
|
35613
|
+
"delivery_id",
|
|
35614
|
+
"lease_token",
|
|
35615
|
+
"event_type",
|
|
35616
|
+
"body",
|
|
35617
|
+
"lease_expires_at"
|
|
35618
|
+
].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");
|
|
35619
|
+
return value;
|
|
35620
|
+
}
|
|
35621
|
+
function retryAfter(value) {
|
|
35622
|
+
if (!value) return 0;
|
|
35623
|
+
const ms = /^\d+(\.\d+)?$/.test(value) ? Number(value) * 1e3 : Date.parse(value) - Date.now();
|
|
35624
|
+
return Number.isFinite(ms) ? Math.max(0, ms) : 0;
|
|
35625
|
+
}
|
|
35626
|
+
function unwrap(result) {
|
|
35627
|
+
if (result.data !== void 0 && !result.error) return result.data;
|
|
35628
|
+
const code = record(result.error) && record(result.error.error) && typeof result.error.error.code === "string" ? result.error.error.code : "request_failed";
|
|
35629
|
+
throw new EventReceiverError(`Event request failed (${result.response?.status ?? 0}, ${code})`, code, result.response?.status ?? 0, retryAfter(result.response?.headers.get("retry-after") ?? null));
|
|
35630
|
+
}
|
|
35631
|
+
/** Shared by the SDK and CLI. Completion retries preserve the original evidence. */
|
|
35632
|
+
var EventConnection = class {
|
|
35633
|
+
socket;
|
|
35634
|
+
pending;
|
|
35635
|
+
accountId;
|
|
35636
|
+
touch;
|
|
35637
|
+
constructor(client, endpointId, options = {}) {
|
|
35638
|
+
this.client = client;
|
|
35639
|
+
this.endpointId = endpointId;
|
|
35640
|
+
this.options = options;
|
|
35641
|
+
}
|
|
35642
|
+
close() {
|
|
35643
|
+
const socket = this.socket;
|
|
35644
|
+
this.socket = void 0;
|
|
35645
|
+
this.pending?.reject(new EventReceiverError("Event connection closed", "disconnected"));
|
|
35646
|
+
this.pending = void 0;
|
|
35647
|
+
socket?.close();
|
|
35648
|
+
}
|
|
35649
|
+
async open(signal) {
|
|
35650
|
+
if (this.options.transport === "poll" || this.socket?.readyState === 1) return;
|
|
35651
|
+
const account = unwrap(await getAccount({
|
|
35652
|
+
client: this.client,
|
|
35653
|
+
responseStyle: "fields",
|
|
35654
|
+
throwOnError: false,
|
|
35655
|
+
signal: AbortSignal.any([signal, AbortSignal.timeout(15e3)])
|
|
35656
|
+
}));
|
|
35657
|
+
if (!account.data?.id || this.accountId && account.data.id !== this.accountId) throw new EventReceiverError("The authenticated account changed; restart the receiver", "identity_changed", 403);
|
|
35658
|
+
this.accountId = account.data.id;
|
|
35659
|
+
const config = this.client.getConfig();
|
|
35660
|
+
const auth = typeof config.auth === "function" ? await config.auth({
|
|
35661
|
+
type: "http",
|
|
35662
|
+
scheme: "bearer"
|
|
35663
|
+
}) : config.auth;
|
|
35664
|
+
const authorization = new Headers(config.headers).get("authorization");
|
|
35665
|
+
const token = typeof auth === "string" ? auth.replace(/^Bearer /i, "") : authorization?.replace(/^Bearer /i, "");
|
|
35666
|
+
if (!token) throw new EventReceiverError("Event receiver requires bearer credentials", "unauthorized", 401);
|
|
35667
|
+
const url = new URL(`${config.baseUrl?.replace(/\/+$/, "")}/endpoints/${encodeURIComponent(this.endpointId)}/stream`);
|
|
35668
|
+
if (url.protocol !== "https:" && !(url.protocol === "http:" && [
|
|
35669
|
+
"localhost",
|
|
35670
|
+
"127.0.0.1",
|
|
35671
|
+
"[::1]"
|
|
35672
|
+
].includes(url.hostname))) throw new EventReceiverError("Event connections require HTTPS (except loopback development)", "unsupported");
|
|
35673
|
+
url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
|
|
35674
|
+
const socket = (this.options.webSocketFactory ?? ((address, protocol) => new WebSocket(address, protocol)))(url.href, "primitive.events.v1");
|
|
35675
|
+
this.socket = socket;
|
|
35676
|
+
socket.addEventListener("message", (message) => {
|
|
35677
|
+
if (this.socket !== socket) return;
|
|
35678
|
+
let frame;
|
|
35679
|
+
try {
|
|
35680
|
+
frame = JSON.parse(String(message.data));
|
|
35681
|
+
} catch {
|
|
35682
|
+
this.fail(new EventReceiverError("Invalid event frame", "invalid_response"));
|
|
35683
|
+
return;
|
|
35684
|
+
}
|
|
35685
|
+
this.touch?.();
|
|
35686
|
+
if (!record(frame)) {
|
|
35687
|
+
this.fail(new EventReceiverError("Invalid event frame", "invalid_response"));
|
|
35688
|
+
return;
|
|
35689
|
+
}
|
|
35690
|
+
if (frame.type === "ping") {
|
|
35691
|
+
socket.send(JSON.stringify({ type: "pong" }));
|
|
35692
|
+
return;
|
|
35693
|
+
}
|
|
35694
|
+
if (frame.type === "status") {
|
|
35695
|
+
try {
|
|
35696
|
+
this.options.onStatus?.(validateOffer(frame.data));
|
|
35697
|
+
} catch (error) {
|
|
35698
|
+
this.fail(error);
|
|
35699
|
+
}
|
|
35700
|
+
return;
|
|
35701
|
+
}
|
|
35702
|
+
if (frame.type === "error") {
|
|
35703
|
+
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)));
|
|
35704
|
+
return;
|
|
35705
|
+
}
|
|
35706
|
+
this.pending?.resolve(frame);
|
|
35707
|
+
});
|
|
35708
|
+
socket.addEventListener("close", () => {
|
|
35709
|
+
if (this.socket === socket) this.fail(new EventReceiverError("Event stream disconnected", "disconnected"));
|
|
35710
|
+
});
|
|
35711
|
+
socket.addEventListener("error", () => {
|
|
35712
|
+
if (this.socket === socket) this.fail(new EventReceiverError("Event stream connection failed", "disconnected"));
|
|
35713
|
+
});
|
|
35714
|
+
const ready = await this.exchange(signal, void 0, () => {
|
|
35715
|
+
socket.addEventListener("open", () => socket.send(JSON.stringify({
|
|
35716
|
+
type: "authenticate",
|
|
35717
|
+
token
|
|
35718
|
+
})), { once: true });
|
|
35719
|
+
}, 15e3);
|
|
35720
|
+
if (!record(ready) || ready.type !== "ready" || ready.protocol !== "primitive.events.v1") {
|
|
35721
|
+
this.close();
|
|
35722
|
+
throw new EventReceiverError("Unsupported event stream protocol", "unsupported");
|
|
35723
|
+
}
|
|
35724
|
+
}
|
|
35725
|
+
fail(error) {
|
|
35726
|
+
this.pending?.reject(error);
|
|
35727
|
+
this.pending = void 0;
|
|
35728
|
+
this.close();
|
|
35729
|
+
}
|
|
35730
|
+
async exchange(signal, frame, start, timeoutMs = 6e4) {
|
|
35731
|
+
signal.throwIfAborted();
|
|
35732
|
+
if (this.pending) throw new EventReceiverError("Concurrent event operation", "busy", 409);
|
|
35733
|
+
let cleanup = () => {};
|
|
35734
|
+
try {
|
|
35735
|
+
return await new Promise((resolve, reject) => {
|
|
35736
|
+
const abort = () => {
|
|
35737
|
+
reject(signal.reason);
|
|
35738
|
+
this.close();
|
|
35739
|
+
};
|
|
35740
|
+
let timer;
|
|
35741
|
+
this.touch = () => {
|
|
35742
|
+
clearTimeout(timer);
|
|
35743
|
+
timer = setTimeout(() => this.fail(new EventReceiverError("Event stream timed out", "disconnected")), timeoutMs);
|
|
35744
|
+
};
|
|
35745
|
+
this.touch();
|
|
35746
|
+
cleanup = () => {
|
|
35747
|
+
this.touch = void 0;
|
|
35748
|
+
clearTimeout(timer);
|
|
35749
|
+
signal.removeEventListener("abort", abort);
|
|
35750
|
+
this.pending = void 0;
|
|
35751
|
+
};
|
|
35752
|
+
this.pending = {
|
|
35753
|
+
resolve,
|
|
35754
|
+
reject
|
|
35755
|
+
};
|
|
35756
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
35757
|
+
if (frame) this.socket?.send(JSON.stringify(frame));
|
|
35758
|
+
start?.();
|
|
35759
|
+
});
|
|
35760
|
+
} finally {
|
|
35761
|
+
cleanup();
|
|
35762
|
+
}
|
|
35763
|
+
}
|
|
35764
|
+
async receive(signal) {
|
|
35765
|
+
if (this.options.transport === "poll") return validateOffer(unwrap(await pullWebhookEvent({
|
|
35766
|
+
client: this.client,
|
|
35767
|
+
path: { id: this.endpointId },
|
|
35768
|
+
body: { wait_seconds: 25 },
|
|
35769
|
+
responseStyle: "fields",
|
|
35770
|
+
throwOnError: false,
|
|
35771
|
+
signal: AbortSignal.any([signal, AbortSignal.timeout(45e3)])
|
|
35772
|
+
})).data);
|
|
35773
|
+
await this.open(signal);
|
|
35774
|
+
const frame = await this.exchange(signal, { type: "receive" }, void 0, 9e4);
|
|
35775
|
+
if (!record(frame) || frame.type !== "event") throw new EventReceiverError("Invalid event frame", "invalid_response");
|
|
35776
|
+
return validateOffer(frame.data);
|
|
35777
|
+
}
|
|
35778
|
+
async complete(body, signal) {
|
|
35779
|
+
let receipt;
|
|
35780
|
+
if (this.options.transport === "poll") receipt = unwrap(await completeWebhookEvent({
|
|
35781
|
+
client: this.client,
|
|
35782
|
+
path: { id: this.endpointId },
|
|
35783
|
+
body,
|
|
35784
|
+
responseStyle: "fields",
|
|
35785
|
+
throwOnError: false,
|
|
35786
|
+
signal: AbortSignal.any([signal, AbortSignal.timeout(15e3)])
|
|
35787
|
+
})).data;
|
|
35788
|
+
else {
|
|
35789
|
+
await this.open(signal);
|
|
35790
|
+
const frame = await this.exchange(signal, {
|
|
35791
|
+
type: "complete",
|
|
35792
|
+
body
|
|
35793
|
+
}, void 0, 15e3);
|
|
35794
|
+
if (!record(frame) || frame.type !== "receipt") throw new EventReceiverError("Invalid event receipt", "invalid_response");
|
|
35795
|
+
receipt = frame.data;
|
|
35796
|
+
}
|
|
35797
|
+
if (!record(receipt) || !["completed", "already_completed"].includes(String(receipt.result))) throw new EventReceiverError("Invalid event receipt", "invalid_response");
|
|
35798
|
+
}
|
|
35799
|
+
};
|
|
35800
|
+
//#endregion
|
|
34444
35801
|
//#region src/oclif/listen-state.ts
|
|
34445
35802
|
var ListenStateError = class extends Error {};
|
|
34446
35803
|
function normalizeListenOrigin(value) {
|
|
@@ -34726,6 +36083,7 @@ async function runListen(options) {
|
|
|
34726
36083
|
let verified = false;
|
|
34727
36084
|
let confirmed = 0;
|
|
34728
36085
|
let release;
|
|
36086
|
+
let stream;
|
|
34729
36087
|
async function retry(operation) {
|
|
34730
36088
|
for (let attempt = 0;; attempt++) {
|
|
34731
36089
|
signal.throwIfAborted();
|
|
@@ -34736,9 +36094,14 @@ async function runListen(options) {
|
|
|
34736
36094
|
error = failure(result, now());
|
|
34737
36095
|
} catch (caught) {
|
|
34738
36096
|
if (signal.aborted) throw signal.reason;
|
|
34739
|
-
if (caught instanceof
|
|
34740
|
-
|
|
34741
|
-
|
|
36097
|
+
if (caught instanceof EventReceiverError) {
|
|
36098
|
+
if (["invalid_response", "unsupported"].includes(caught.code)) throw new ListenError(caught.message);
|
|
36099
|
+
error = new RequestFailure(caught.status, caught.code, caught.retryAfterMs);
|
|
36100
|
+
} else {
|
|
36101
|
+
if (caught instanceof ListenError) throw caught;
|
|
36102
|
+
if (caught instanceof SyntaxError) throw new ListenError("The API returned malformed JSON.");
|
|
36103
|
+
error = new RequestFailure(0, "transport", 0);
|
|
36104
|
+
}
|
|
34742
36105
|
}
|
|
34743
36106
|
if (![
|
|
34744
36107
|
0,
|
|
@@ -34762,7 +36125,7 @@ async function runListen(options) {
|
|
|
34762
36125
|
signal: AbortSignal.any([
|
|
34763
36126
|
signal,
|
|
34764
36127
|
...init?.signal ? [init.signal] : [],
|
|
34765
|
-
AbortSignal.timeout(
|
|
36128
|
+
AbortSignal.timeout(45e3)
|
|
34766
36129
|
])
|
|
34767
36130
|
})
|
|
34768
36131
|
});
|
|
@@ -34773,7 +36136,7 @@ async function runListen(options) {
|
|
|
34773
36136
|
if (origin !== void 0 && origin !== base) throw new ListenError("The API environment changed while listening. Restart the listener.");
|
|
34774
36137
|
origin = base;
|
|
34775
36138
|
if (!verified || auth.auth.apiKey !== verifiedKey) {
|
|
34776
|
-
const account = await retry(() => getAccount({
|
|
36139
|
+
const account = await retry(() => getAccount$1({
|
|
34777
36140
|
client: auth.apiClient.client,
|
|
34778
36141
|
responseStyle: "fields",
|
|
34779
36142
|
signal: AbortSignal.any([signal, AbortSignal.timeout(3e4)])
|
|
@@ -34809,6 +36172,12 @@ async function runListen(options) {
|
|
|
34809
36172
|
});
|
|
34810
36173
|
const endpoint = registered.data;
|
|
34811
36174
|
if (registered.success !== true || !endpoint || typeof endpoint.id !== "string" || endpoint.kind !== "pull" || endpoint.enabled === false) throw new ListenError("The API did not return an enabled pull subscription.");
|
|
36175
|
+
const streamClient = await freshClient();
|
|
36176
|
+
if (options.transport !== "poll") {
|
|
36177
|
+
if (!endpoint.receiver_capabilities?.stream_protocols.includes("primitive.events.v1")) throw new ListenError("This API does not support WebSocket events. Use --transport poll explicitly for an older API.");
|
|
36178
|
+
if (!endpoint.receiver_capabilities.completion_modes.includes(options.mode ?? "stdout")) throw new ListenError("This API does not support the selected listener handler mode.");
|
|
36179
|
+
stream = new EventConnection(streamClient, endpoint.id);
|
|
36180
|
+
}
|
|
34812
36181
|
const selected = endpoint.rules?.event_types;
|
|
34813
36182
|
const selection = Array.isArray(selected) && selected.every((value) => typeof value === "string" && /^[a-zA-Z0-9_.-]+$/.test(value)) ? selected.join(", ") : "all events";
|
|
34814
36183
|
stderr.write(`Listening on subscription ${subscription.name} (${endpoint.id}); ${resumed ? "resumed" : "created"}, mode ${options.mode ?? "stdout"}, selection: ${selection}. Retention: 24 hours; handler limit: 30 seconds. Pending count follows the first poll. Ctrl-C disconnects; delete with primitive endpoints delete --id ${endpoint.id}.\n`);
|
|
@@ -34816,13 +36185,22 @@ async function runListen(options) {
|
|
|
34816
36185
|
let lastBacklog = -1;
|
|
34817
36186
|
while (!signal.aborted && (options.number === void 0 || confirmed < options.number)) {
|
|
34818
36187
|
const pollStarted = now();
|
|
34819
|
-
const result = await retry(async () =>
|
|
34820
|
-
|
|
34821
|
-
|
|
34822
|
-
|
|
34823
|
-
|
|
34824
|
-
|
|
34825
|
-
|
|
36188
|
+
const result = await retry(async () => {
|
|
36189
|
+
if (stream) {
|
|
36190
|
+
streamClient.setConfig((await freshClient()).getConfig());
|
|
36191
|
+
return { data: {
|
|
36192
|
+
success: true,
|
|
36193
|
+
data: await stream.receive(signal)
|
|
36194
|
+
} };
|
|
36195
|
+
}
|
|
36196
|
+
return pullWebhookEvent$1({
|
|
36197
|
+
client: await freshClient(),
|
|
36198
|
+
path: { id: endpoint.id },
|
|
36199
|
+
body: { wait_seconds: 25 },
|
|
36200
|
+
responseStyle: "fields",
|
|
36201
|
+
signal: AbortSignal.any([signal, AbortSignal.timeout(45e3)])
|
|
36202
|
+
});
|
|
36203
|
+
});
|
|
34826
36204
|
if (result.success !== true || !result.data || !Number.isSafeInteger(result.data.gap_count) || result.data.gap_count < 0 || result.data.last_gap_reason !== null && typeof result.data.last_gap_reason !== "string" || !Number.isSafeInteger(result.data.backlog) || result.data.backlog < 0 || result.data.handler_timeout_seconds !== 30 || result.data.retention_seconds !== 86400) throw new ListenError("The API returned an invalid pull response.");
|
|
34827
36205
|
const data = result.data;
|
|
34828
36206
|
const gapReasons = {
|
|
@@ -34865,13 +36243,23 @@ async function runListen(options) {
|
|
|
34865
36243
|
lease_token: delivery.lease_token
|
|
34866
36244
|
};
|
|
34867
36245
|
try {
|
|
34868
|
-
const receipt = await retry(async () =>
|
|
34869
|
-
|
|
34870
|
-
|
|
34871
|
-
|
|
34872
|
-
|
|
34873
|
-
|
|
34874
|
-
|
|
36246
|
+
const receipt = await retry(async () => {
|
|
36247
|
+
if (stream) {
|
|
36248
|
+
streamClient.setConfig((await freshClient()).getConfig());
|
|
36249
|
+
await stream.complete(completion, signal);
|
|
36250
|
+
return { data: {
|
|
36251
|
+
success: true,
|
|
36252
|
+
data: { result: "completed" }
|
|
36253
|
+
} };
|
|
36254
|
+
}
|
|
36255
|
+
return completeWebhookEvent$1({
|
|
36256
|
+
client: await freshClient(),
|
|
36257
|
+
path: { id: endpoint.id },
|
|
36258
|
+
body: completion,
|
|
36259
|
+
responseStyle: "fields",
|
|
36260
|
+
signal: AbortSignal.any([signal, AbortSignal.timeout(3e4)])
|
|
36261
|
+
});
|
|
36262
|
+
});
|
|
34875
36263
|
if (receipt.success !== true || !["completed", "already_completed"].includes(receipt.data?.result)) throw new ListenError("The API returned an invalid completion receipt.");
|
|
34876
36264
|
if (handled.succeeded) confirmed++;
|
|
34877
36265
|
else stderr.write("Handler reported failure; the server will apply its retry policy.\n");
|
|
@@ -34883,6 +36271,7 @@ async function runListen(options) {
|
|
|
34883
36271
|
} catch (error) {
|
|
34884
36272
|
if (!signal.aborted) throw error;
|
|
34885
36273
|
} finally {
|
|
36274
|
+
stream?.close();
|
|
34886
36275
|
release?.();
|
|
34887
36276
|
}
|
|
34888
36277
|
return confirmed;
|
|
@@ -34895,9 +36284,24 @@ var ListenCommand = class ListenCommand extends Command {
|
|
|
34895
36284
|
static examples = [
|
|
34896
36285
|
"<%= config.bin %> listen",
|
|
34897
36286
|
"<%= config.bin %> listen --subscription my-agent --exec \"python3 accept.py\"",
|
|
34898
|
-
"<%= config.bin %> listen --forward-to
|
|
36287
|
+
"<%= config.bin %> listen --forward-to localhost:3000",
|
|
36288
|
+
"<%= config.bin %> listen --once --timeout 60"
|
|
34899
36289
|
];
|
|
34900
36290
|
static flags = {
|
|
36291
|
+
transport: Flags.string({
|
|
36292
|
+
description: "Event transport; WebSocket is the default.",
|
|
36293
|
+
options: ["websocket", "poll"],
|
|
36294
|
+
default: "websocket"
|
|
36295
|
+
}),
|
|
36296
|
+
once: Flags.boolean({
|
|
36297
|
+
description: "Exit after one successfully handled and confirmed event.",
|
|
36298
|
+
exclusive: ["number"]
|
|
36299
|
+
}),
|
|
36300
|
+
timeout: Flags.integer({
|
|
36301
|
+
description: "Stop after this many seconds; exit 2 if the requested count was not reached.",
|
|
36302
|
+
min: 1,
|
|
36303
|
+
max: 2147483
|
|
36304
|
+
}),
|
|
34901
36305
|
subscription: Flags.string({ description: "Stable subscription name. Defaults to a saved name for this account and API environment." }),
|
|
34902
36306
|
exec: Flags.string({
|
|
34903
36307
|
description: "Short POSIX shell command accepting each complete event on stdin.",
|
|
@@ -34931,17 +36335,23 @@ var ListenCommand = class ListenCommand extends Command {
|
|
|
34931
36335
|
forwardTo: flags["forward-to"]
|
|
34932
36336
|
});
|
|
34933
36337
|
const controller = new AbortController();
|
|
36338
|
+
let timedOut = false;
|
|
36339
|
+
const timer = flags.timeout === void 0 ? void 0 : setTimeout(() => {
|
|
36340
|
+
timedOut = true;
|
|
36341
|
+
controller.abort();
|
|
36342
|
+
}, flags.timeout * 1e3);
|
|
34934
36343
|
const cancel = () => controller.abort();
|
|
34935
36344
|
process.on("SIGINT", cancel);
|
|
34936
36345
|
process.on("SIGTERM", cancel);
|
|
34937
36346
|
try {
|
|
34938
36347
|
await runListen({
|
|
36348
|
+
transport: flags.transport === "poll" ? "poll" : "websocket",
|
|
34939
36349
|
configDir: this.config.configDir,
|
|
34940
36350
|
apiKey: flags["api-key"],
|
|
34941
36351
|
apiBaseUrl: flags["api-base-url"],
|
|
34942
36352
|
subscription: flags.subscription,
|
|
34943
36353
|
events: events === void 0 ? void 0 : [...new Set(events)],
|
|
34944
|
-
number: flags.number,
|
|
36354
|
+
number: flags.once ? 1 : flags.number,
|
|
34945
36355
|
mode: flags.exec ? "exec" : flags["forward-to"] ? "http" : "stdout",
|
|
34946
36356
|
handler,
|
|
34947
36357
|
signal: controller.signal
|
|
@@ -34949,10 +36359,11 @@ var ListenCommand = class ListenCommand extends Command {
|
|
|
34949
36359
|
} catch (error) {
|
|
34950
36360
|
throw new Errors.CLIError(error instanceof ListenError || error instanceof ListenStateError ? error.message : "The listener stopped because of a local state or connection error.");
|
|
34951
36361
|
} finally {
|
|
36362
|
+
clearTimeout(timer);
|
|
34952
36363
|
process.removeListener("SIGINT", cancel);
|
|
34953
36364
|
process.removeListener("SIGTERM", cancel);
|
|
34954
36365
|
}
|
|
34955
|
-
if (controller.signal.aborted) process.exitCode = 130;
|
|
36366
|
+
if (controller.signal.aborted) process.exitCode = timedOut ? 2 : 130;
|
|
34956
36367
|
}
|
|
34957
36368
|
};
|
|
34958
36369
|
//#endregion
|
|
@@ -35215,7 +36626,7 @@ const MAX_CLI_LOGIN_POLL_INTERVAL_SECONDS = 60;
|
|
|
35215
36626
|
function cliError$3(message) {
|
|
35216
36627
|
return new Errors.CLIError(message, { exit: 1 });
|
|
35217
36628
|
}
|
|
35218
|
-
function sleep
|
|
36629
|
+
function sleep(ms) {
|
|
35219
36630
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
35220
36631
|
}
|
|
35221
36632
|
function openBrowser(url) {
|
|
@@ -35268,7 +36679,7 @@ async function checkExistingLogin(params) {
|
|
|
35268
36679
|
apiBaseUrl: probeApiBaseUrl,
|
|
35269
36680
|
headers: requestConfig.headers
|
|
35270
36681
|
});
|
|
35271
|
-
const result = await (params.checkAccount ?? ((client) => getAccount({
|
|
36682
|
+
const result = await (params.checkAccount ?? ((client) => getAccount$1({
|
|
35272
36683
|
client: client.client,
|
|
35273
36684
|
responseStyle: "fields"
|
|
35274
36685
|
})))(apiClient);
|
|
@@ -35380,7 +36791,7 @@ var LoginCommand$1 = class extends Command {
|
|
|
35380
36791
|
let interval = Math.min(Math.max(1, start.interval), MAX_CLI_LOGIN_POLL_INTERVAL_SECONDS);
|
|
35381
36792
|
let nextPollDelay = 1;
|
|
35382
36793
|
while (Date.now() < deadline) {
|
|
35383
|
-
await sleep
|
|
36794
|
+
await sleep(nextPollDelay * 1e3);
|
|
35384
36795
|
nextPollDelay = interval;
|
|
35385
36796
|
const polled = await pollCliLogin({
|
|
35386
36797
|
body: { device_code: start.device_code },
|
|
@@ -37010,331 +38421,6 @@ var OrgSecretsSetCommand = class OrgSecretsSetCommand extends Command {
|
|
|
37010
38421
|
}
|
|
37011
38422
|
};
|
|
37012
38423
|
//#endregion
|
|
37013
|
-
//#region ../sdk-node/dist/payloads-BfJAtqW9.js
|
|
37014
|
-
/**
|
|
37015
|
-
* Load a Node builtin at call time. Exported for direct testing of the
|
|
37016
|
-
* unavailable-module error path; production code goes through the typed
|
|
37017
|
-
* wrappers below.
|
|
37018
|
-
*/
|
|
37019
|
-
async function loadBuiltin(specifier, operation) {
|
|
37020
|
-
let builtin;
|
|
37021
|
-
if (typeof process !== "undefined" && typeof process.getBuiltinModule === "function") try {
|
|
37022
|
-
builtin = process.getBuiltinModule(specifier);
|
|
37023
|
-
} catch {
|
|
37024
|
-
builtin = void 0;
|
|
37025
|
-
}
|
|
37026
|
-
if (builtin !== void 0 && builtin !== null) return builtin;
|
|
37027
|
-
try {
|
|
37028
|
-
return await import(specifier);
|
|
37029
|
-
} catch (cause) {
|
|
37030
|
-
throw new Error(`${operation} requires file system access, which this runtime does not provide. Pass in-memory bytes instead of a file path when running outside Node.js.`, { cause });
|
|
37031
|
-
}
|
|
37032
|
-
}
|
|
37033
|
-
/** `node:fs`, loaded lazily so Workers-style bundles never see the import. */
|
|
37034
|
-
function loadNodeFs(operation) {
|
|
37035
|
-
return loadBuiltin("node:fs", operation);
|
|
37036
|
-
}
|
|
37037
|
-
/** `node:fs/promises`, loaded lazily for the same reason as {@link loadNodeFs}. */
|
|
37038
|
-
function loadNodeFsPromises(operation) {
|
|
37039
|
-
return loadBuiltin("node:fs/promises", operation);
|
|
37040
|
-
}
|
|
37041
|
-
const CHUNK_SIZE = 64 * 1024 * 1024;
|
|
37042
|
-
const MANIFEST_VERSION = 1;
|
|
37043
|
-
const CHUNK_KDF_INFO = "payloads-chunk";
|
|
37044
|
-
const OBJECT_ID_BYTES = 16;
|
|
37045
|
-
const CEK_BYTES = 32;
|
|
37046
|
-
function subtle() {
|
|
37047
|
-
return globalThis.crypto.subtle;
|
|
37048
|
-
}
|
|
37049
|
-
const textEncoder = new TextEncoder();
|
|
37050
|
-
function randomBytes(length) {
|
|
37051
|
-
const out = new Uint8Array(length);
|
|
37052
|
-
globalThis.crypto.getRandomValues(out);
|
|
37053
|
-
return out;
|
|
37054
|
-
}
|
|
37055
|
-
function toHex(bytes) {
|
|
37056
|
-
let out = "";
|
|
37057
|
-
for (const b of bytes) out += b.toString(16).padStart(2, "0");
|
|
37058
|
-
return out;
|
|
37059
|
-
}
|
|
37060
|
-
function fromHex(hex) {
|
|
37061
|
-
if (hex.length % 2 !== 0) throw new Error("invalid hex: odd length");
|
|
37062
|
-
if (hex.length > 0 && !/^[0-9a-fA-F]+$/.test(hex)) throw new Error("invalid hex: non-hex character");
|
|
37063
|
-
const out = new Uint8Array(hex.length / 2);
|
|
37064
|
-
for (let i = 0; i < out.length; i++) out[i] = Number.parseInt(hex.slice(i * 2, i * 2 + 2), 16);
|
|
37065
|
-
return out;
|
|
37066
|
-
}
|
|
37067
|
-
async function contentHash(bytes) {
|
|
37068
|
-
return new Uint8Array(await subtle().digest("SHA-256", bytes));
|
|
37069
|
-
}
|
|
37070
|
-
async function contentHashHex(bytes) {
|
|
37071
|
-
return toHex(await contentHash(bytes));
|
|
37072
|
-
}
|
|
37073
|
-
/** Binary Merkle root over ordered hex leaf hashes (odd node promoted). */
|
|
37074
|
-
async function merkleRoot(leafHashesHex) {
|
|
37075
|
-
if (leafHashesHex.length === 0) return contentHashHex(new Uint8Array(0));
|
|
37076
|
-
let level = leafHashesHex.map(fromHex);
|
|
37077
|
-
while (level.length > 1) {
|
|
37078
|
-
const next = [];
|
|
37079
|
-
for (let i = 0; i < level.length; i += 2) {
|
|
37080
|
-
const left = level[i];
|
|
37081
|
-
const right = i + 1 < level.length ? level[i + 1] : level[i];
|
|
37082
|
-
const combined = new Uint8Array(left.length + right.length);
|
|
37083
|
-
combined.set(left, 0);
|
|
37084
|
-
combined.set(right, left.length);
|
|
37085
|
-
next.push(await contentHash(combined));
|
|
37086
|
-
}
|
|
37087
|
-
level = next;
|
|
37088
|
-
}
|
|
37089
|
-
return toHex(level[0]);
|
|
37090
|
-
}
|
|
37091
|
-
async function deriveChunkKey(cek, index) {
|
|
37092
|
-
const baseKey = await subtle().importKey("raw", cek, "HKDF", false, ["deriveKey"]);
|
|
37093
|
-
const info = textEncoder.encode(`${CHUNK_KDF_INFO}:${index}`);
|
|
37094
|
-
return subtle().deriveKey({
|
|
37095
|
-
name: "HKDF",
|
|
37096
|
-
hash: "SHA-256",
|
|
37097
|
-
salt: new Uint8Array(32),
|
|
37098
|
-
info
|
|
37099
|
-
}, baseKey, {
|
|
37100
|
-
name: "AES-GCM",
|
|
37101
|
-
length: 256
|
|
37102
|
-
}, false, ["encrypt", "decrypt"]);
|
|
37103
|
-
}
|
|
37104
|
-
function chunkNonce(index) {
|
|
37105
|
-
const nonce = new Uint8Array(12);
|
|
37106
|
-
new DataView(nonce.buffer).setUint32(8, index >>> 0, false);
|
|
37107
|
-
return nonce;
|
|
37108
|
-
}
|
|
37109
|
-
function chunkAad(objectId, index) {
|
|
37110
|
-
const aad = new Uint8Array(objectId.length + 4);
|
|
37111
|
-
aad.set(objectId, 0);
|
|
37112
|
-
new DataView(aad.buffer).setUint32(objectId.length, index >>> 0, false);
|
|
37113
|
-
return aad;
|
|
37114
|
-
}
|
|
37115
|
-
async function encryptChunk(cek, objectId, index, plaintext) {
|
|
37116
|
-
const key = await deriveChunkKey(cek, index);
|
|
37117
|
-
const ct = await subtle().encrypt({
|
|
37118
|
-
name: "AES-GCM",
|
|
37119
|
-
iv: chunkNonce(index),
|
|
37120
|
-
additionalData: chunkAad(objectId, index)
|
|
37121
|
-
}, key, plaintext);
|
|
37122
|
-
return new Uint8Array(ct);
|
|
37123
|
-
}
|
|
37124
|
-
async function decryptChunk(cek, objectId, index, ciphertext) {
|
|
37125
|
-
const key = await deriveChunkKey(cek, index);
|
|
37126
|
-
const pt = await subtle().decrypt({
|
|
37127
|
-
name: "AES-GCM",
|
|
37128
|
-
iv: chunkNonce(index),
|
|
37129
|
-
additionalData: chunkAad(objectId, index)
|
|
37130
|
-
}, key, ciphertext);
|
|
37131
|
-
return new Uint8Array(pt);
|
|
37132
|
-
}
|
|
37133
|
-
function authHeaders(apiKey) {
|
|
37134
|
-
return { authorization: `Bearer ${apiKey}` };
|
|
37135
|
-
}
|
|
37136
|
-
function apiRoot(baseUrl) {
|
|
37137
|
-
let end = baseUrl.length;
|
|
37138
|
-
while (end > 0 && baseUrl.charCodeAt(end - 1) === 47) end--;
|
|
37139
|
-
return `${baseUrl.slice(0, end)}/v1/payloads`;
|
|
37140
|
-
}
|
|
37141
|
-
const DEFAULT_MAX_RETRIES = 6;
|
|
37142
|
-
const RETRYABLE_STATUS = new Set([
|
|
37143
|
-
429,
|
|
37144
|
-
500,
|
|
37145
|
-
502,
|
|
37146
|
-
503,
|
|
37147
|
-
504
|
|
37148
|
-
]);
|
|
37149
|
-
function sleep(ms) {
|
|
37150
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
37151
|
-
}
|
|
37152
|
-
/**
|
|
37153
|
-
* fetch with exponential backoff on transient failures. Request bodies here are
|
|
37154
|
-
* in-memory buffers/strings (never streams), so a retry can safely resend them.
|
|
37155
|
-
*/
|
|
37156
|
-
async function retryingFetch(url, init, label, maxRetries = DEFAULT_MAX_RETRIES) {
|
|
37157
|
-
for (let attempt = 0;; attempt++) {
|
|
37158
|
-
let res;
|
|
37159
|
-
let networkError;
|
|
37160
|
-
try {
|
|
37161
|
-
res = await fetch(url, init);
|
|
37162
|
-
} catch (err) {
|
|
37163
|
-
networkError = err;
|
|
37164
|
-
}
|
|
37165
|
-
if (res && !RETRYABLE_STATUS.has(res.status)) return res;
|
|
37166
|
-
if (attempt >= maxRetries) {
|
|
37167
|
-
if (res) return res;
|
|
37168
|
-
throw new Error(`${label}: network error after ${attempt} retries: ${networkError?.message ?? networkError}`);
|
|
37169
|
-
}
|
|
37170
|
-
if (res) await res.text().catch(() => {});
|
|
37171
|
-
await sleep(Math.min(1e3 * 2 ** attempt, 15e3) + Math.floor(Math.random() * 250));
|
|
37172
|
-
}
|
|
37173
|
-
}
|
|
37174
|
-
async function initiate(opts, manifest) {
|
|
37175
|
-
const res = await retryingFetch(apiRoot(opts.baseUrl), {
|
|
37176
|
-
method: "POST",
|
|
37177
|
-
headers: {
|
|
37178
|
-
...authHeaders(opts.apiKey),
|
|
37179
|
-
"content-type": "application/json"
|
|
37180
|
-
},
|
|
37181
|
-
body: JSON.stringify({ manifest })
|
|
37182
|
-
}, "initiate");
|
|
37183
|
-
if (!res.ok) throw new Error(`initiate failed: HTTP ${res.status} ${await res.text()}`);
|
|
37184
|
-
await res.text();
|
|
37185
|
-
}
|
|
37186
|
-
async function putChunk(opts, root, d, bytes) {
|
|
37187
|
-
const res = await retryingFetch(`${apiRoot(opts.baseUrl)}/${root}/chunks/${d.ciphertextHash}`, {
|
|
37188
|
-
method: "PUT",
|
|
37189
|
-
headers: {
|
|
37190
|
-
...authHeaders(opts.apiKey),
|
|
37191
|
-
"content-type": "application/octet-stream"
|
|
37192
|
-
},
|
|
37193
|
-
body: bytes
|
|
37194
|
-
}, `chunk ${d.index} upload`);
|
|
37195
|
-
if (!res.ok) throw new Error(`chunk ${d.index} upload failed: HTTP ${res.status} ${await res.text()}`);
|
|
37196
|
-
await res.text();
|
|
37197
|
-
}
|
|
37198
|
-
async function finalize(opts, root) {
|
|
37199
|
-
const res = await retryingFetch(`${apiRoot(opts.baseUrl)}/${root}/finalize`, {
|
|
37200
|
-
method: "POST",
|
|
37201
|
-
headers: authHeaders(opts.apiKey)
|
|
37202
|
-
}, "finalize");
|
|
37203
|
-
if (!res.ok) throw new Error(`finalize failed: HTTP ${res.status} ${await res.text()}`);
|
|
37204
|
-
await res.text();
|
|
37205
|
-
}
|
|
37206
|
-
async function fetchManifest(opts, root) {
|
|
37207
|
-
const res = await retryingFetch(`${apiRoot(opts.baseUrl)}/${root}/manifest`, { headers: authHeaders(opts.apiKey) }, "get manifest");
|
|
37208
|
-
if (!res.ok) throw new Error(`get manifest failed: HTTP ${res.status} ${await res.text()}`);
|
|
37209
|
-
return (await res.json()).data.manifest;
|
|
37210
|
-
}
|
|
37211
|
-
async function getChunkBytes(opts, root, d) {
|
|
37212
|
-
const res = await retryingFetch(`${apiRoot(opts.baseUrl)}/${root}/chunks/${d.ciphertextHash}`, { headers: authHeaders(opts.apiKey) }, `get chunk ${d.index}`);
|
|
37213
|
-
if (!res.ok) throw new Error(`get chunk ${d.index} failed: HTTP ${res.status}`);
|
|
37214
|
-
return new Uint8Array(await res.arrayBuffer());
|
|
37215
|
-
}
|
|
37216
|
-
async function readWindow(fh, position, length) {
|
|
37217
|
-
const buf = Buffer.allocUnsafe(length);
|
|
37218
|
-
let read = 0;
|
|
37219
|
-
while (read < length) {
|
|
37220
|
-
const { bytesRead } = await fh.read(buf, read, length - read, position + read);
|
|
37221
|
-
if (bytesRead === 0) break;
|
|
37222
|
-
read += bytesRead;
|
|
37223
|
-
}
|
|
37224
|
-
return read === length ? buf : buf.subarray(0, read);
|
|
37225
|
-
}
|
|
37226
|
-
async function mapLimit(items, limit, fn) {
|
|
37227
|
-
let next = 0;
|
|
37228
|
-
let firstError;
|
|
37229
|
-
const worker = async () => {
|
|
37230
|
-
while (next < items.length && firstError === void 0) {
|
|
37231
|
-
const i = next++;
|
|
37232
|
-
try {
|
|
37233
|
-
await fn(items[i], i);
|
|
37234
|
-
} catch (err) {
|
|
37235
|
-
if (firstError === void 0) firstError = err;
|
|
37236
|
-
return;
|
|
37237
|
-
}
|
|
37238
|
-
}
|
|
37239
|
-
};
|
|
37240
|
-
await Promise.all(Array.from({ length: Math.min(limit, items.length || 1) }, () => worker()));
|
|
37241
|
-
if (firstError !== void 0) throw firstError;
|
|
37242
|
-
}
|
|
37243
|
-
/**
|
|
37244
|
-
* Stream a file up as a Primitive Payload. Two passes over the file (encrypt to
|
|
37245
|
-
* build the manifest, then encrypt again to upload) keep memory bounded to a few
|
|
37246
|
-
* chunks — the whole object is never resident. Returns the content address
|
|
37247
|
-
* (merkleRoot) and the hex CEK needed to download it.
|
|
37248
|
-
*/
|
|
37249
|
-
async function pushFile(filePath, opts) {
|
|
37250
|
-
const { open, stat } = await loadNodeFsPromises("pushFile");
|
|
37251
|
-
const chunkSize = opts.chunkSize ?? CHUNK_SIZE;
|
|
37252
|
-
const concurrency = opts.concurrency ?? 3;
|
|
37253
|
-
const { size } = await stat(filePath);
|
|
37254
|
-
const cek = randomBytes(CEK_BYTES);
|
|
37255
|
-
const objectId = randomBytes(OBJECT_ID_BYTES);
|
|
37256
|
-
const chunkCount = size === 0 ? 0 : Math.ceil(size / chunkSize);
|
|
37257
|
-
const fh = await open(filePath, "r");
|
|
37258
|
-
try {
|
|
37259
|
-
const descriptors = [];
|
|
37260
|
-
for (let index = 0; index < chunkCount; index++) {
|
|
37261
|
-
const position = index * chunkSize;
|
|
37262
|
-
const length = Math.min(chunkSize, size - position);
|
|
37263
|
-
const plaintext = await readWindow(fh, position, length);
|
|
37264
|
-
const ciphertext = await encryptChunk(cek, objectId, index, plaintext);
|
|
37265
|
-
descriptors.push({
|
|
37266
|
-
index,
|
|
37267
|
-
ciphertextHash: await contentHashHex(ciphertext),
|
|
37268
|
-
plaintextSize: length,
|
|
37269
|
-
ciphertextSize: ciphertext.length
|
|
37270
|
-
});
|
|
37271
|
-
opts.onProgress?.("encrypt", index + 1, chunkCount);
|
|
37272
|
-
}
|
|
37273
|
-
const root = await merkleRoot(descriptors.map((d) => d.ciphertextHash));
|
|
37274
|
-
await initiate(opts, {
|
|
37275
|
-
version: MANIFEST_VERSION,
|
|
37276
|
-
objectId: toHex(objectId),
|
|
37277
|
-
chunkSize,
|
|
37278
|
-
totalPlaintextSize: size,
|
|
37279
|
-
chunkCount,
|
|
37280
|
-
chunks: descriptors,
|
|
37281
|
-
merkleRoot: root
|
|
37282
|
-
});
|
|
37283
|
-
let uploaded = 0;
|
|
37284
|
-
await mapLimit(descriptors, concurrency, async (d) => {
|
|
37285
|
-
const plaintext = await readWindow(fh, d.index * chunkSize, d.plaintextSize);
|
|
37286
|
-
await putChunk(opts, root, d, await encryptChunk(cek, objectId, d.index, plaintext));
|
|
37287
|
-
opts.onProgress?.("upload", ++uploaded, chunkCount);
|
|
37288
|
-
});
|
|
37289
|
-
await finalize(opts, root);
|
|
37290
|
-
return {
|
|
37291
|
-
merkleRoot: root,
|
|
37292
|
-
cek: toHex(cek),
|
|
37293
|
-
chunkCount,
|
|
37294
|
-
totalBytes: size
|
|
37295
|
-
};
|
|
37296
|
-
} finally {
|
|
37297
|
-
await fh.close();
|
|
37298
|
-
}
|
|
37299
|
-
}
|
|
37300
|
-
/**
|
|
37301
|
-
* Stream a Primitive Payload down to a file, verifying and decrypting one chunk
|
|
37302
|
-
* at a time (bounded memory). Every chunk is content-address-checked before
|
|
37303
|
-
* decryption, so a corrupt or substituted chunk throws.
|
|
37304
|
-
*/
|
|
37305
|
-
async function pullFile(root, outPath, opts) {
|
|
37306
|
-
const { createWriteStream } = await loadNodeFs("pullFile");
|
|
37307
|
-
const { rm } = await loadNodeFsPromises("pullFile");
|
|
37308
|
-
const manifest = await fetchManifest(opts, root);
|
|
37309
|
-
const computedRoot = await merkleRoot(manifest.chunks.map((c) => c.ciphertextHash));
|
|
37310
|
-
if (computedRoot !== root) throw new Error(`manifest does not match the requested content address (got ${computedRoot})`);
|
|
37311
|
-
const cek = fromHex(opts.cek);
|
|
37312
|
-
const objectId = fromHex(manifest.objectId);
|
|
37313
|
-
const out = createWriteStream(outPath);
|
|
37314
|
-
let onWriteError = () => {};
|
|
37315
|
-
const writeErrored = new Promise((_, reject) => {
|
|
37316
|
-
onWriteError = reject;
|
|
37317
|
-
});
|
|
37318
|
-
writeErrored.catch(() => {});
|
|
37319
|
-
out.on("error", (err) => onWriteError(err));
|
|
37320
|
-
try {
|
|
37321
|
-
let done = 0;
|
|
37322
|
-
for (const d of manifest.chunks) {
|
|
37323
|
-
const ciphertext = await getChunkBytes(opts, root, d);
|
|
37324
|
-
if (await contentHashHex(ciphertext) !== d.ciphertextHash) throw new Error(`chunk ${d.index} failed integrity check`);
|
|
37325
|
-
const plaintext = await decryptChunk(cek, objectId, d.index, ciphertext);
|
|
37326
|
-
if (!out.write(plaintext)) await Promise.race([new Promise((resolve) => out.once("drain", resolve)), writeErrored]);
|
|
37327
|
-
opts.onProgress?.("download", ++done, manifest.chunkCount);
|
|
37328
|
-
}
|
|
37329
|
-
await Promise.race([new Promise((resolve) => out.end(resolve)), writeErrored]);
|
|
37330
|
-
} catch (err) {
|
|
37331
|
-
out.destroy();
|
|
37332
|
-
await rm(outPath, { force: true });
|
|
37333
|
-
throw err;
|
|
37334
|
-
}
|
|
37335
|
-
return manifest;
|
|
37336
|
-
}
|
|
37337
|
-
//#endregion
|
|
37338
38424
|
//#region src/oclif/commands/payloads.ts
|
|
37339
38425
|
const DEFAULT_API_BASE_URL = "https://api.primitive.dev";
|
|
37340
38426
|
const authFlags = {
|
|
@@ -39232,7 +40318,7 @@ var PaymentsRegisterPayoutAddressCommand = class PaymentsRegisterPayoutAddressCo
|
|
|
39232
40318
|
configDir: this.config.configDir
|
|
39233
40319
|
});
|
|
39234
40320
|
await runWithTiming(flags.time, async () => {
|
|
39235
|
-
const account = await getAccount({
|
|
40321
|
+
const account = await getAccount$1({
|
|
39236
40322
|
client: apiClient.client,
|
|
39237
40323
|
responseStyle: "fields"
|
|
39238
40324
|
});
|
|
@@ -41425,7 +42511,7 @@ var WhoamiCommand = class WhoamiCommand extends Command {
|
|
|
41425
42511
|
apiBaseUrl: flags["api-base-url"],
|
|
41426
42512
|
configDir: this.config.configDir
|
|
41427
42513
|
});
|
|
41428
|
-
const result = await getAccount({
|
|
42514
|
+
const result = await getAccount$1({
|
|
41429
42515
|
client: apiClient.client,
|
|
41430
42516
|
responseStyle: "fields"
|
|
41431
42517
|
});
|