@deployfoundation/foundation-deploy 0.2.50 → 0.2.52
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 +4 -1
- package/agent-image/Dockerfile +36 -1
- package/dist/bin/app.js +6 -3
- package/dist/bin/foundation-deploy.js +139 -305
- package/dist/bin/release-account.js +1 -0
- package/dist/chunk-3614v9rr.js +907 -0
- package/dist/{chunk-efqdhd2e.js → chunk-59dpqyap.js} +254 -90
- package/dist/chunk-5wgwscd0.js +4 -0
- package/dist/{chunk-gnz747z2.js → chunk-96fjaysn.js} +204 -884
- package/dist/chunk-9zx95xs6.js +259 -0
- package/dist/chunk-jf5rw6h4.js +1117 -0
- package/dist/chunk-m76jt5rc.js +376 -0
- package/dist/chunk-mh1206ne.js +1764 -0
- package/dist/chunk-p7zkm4pp.js +151 -0
- package/dist/chunk-pxhftba1.js +336 -0
- package/dist/chunk-rxetj2p9.js +466 -0
- package/dist/chunk-sgdcmme0.js +301 -0
- package/dist/chunk-tey0gh86.js +38 -0
- package/dist/src/index.js +17 -13
- package/package.json +3 -1
- package/src/deploy/cli.ts +71 -0
- package/src/deploy/crm.ts +491 -0
- package/src/stacks/agent-stack.ts +34 -11
- package/src/stacks/api-stack.ts +340 -87
- package/src/stacks/data-stack.ts +27 -1
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CRM_MAX_OUTREACH_CANDIDATES_PAGE,
|
|
3
|
+
CRM_MAX_REQUEST_BYTES,
|
|
4
|
+
CRM_MAX_RESPONSE_BYTES,
|
|
5
|
+
CRM_REFUSAL_REASONS,
|
|
6
|
+
CrmInputError,
|
|
7
|
+
isCrmIdempotencyKey,
|
|
8
|
+
parseCrmGetActivityResponse,
|
|
9
|
+
parseCrmGetRecordResponse,
|
|
10
|
+
parseCrmOperationAck,
|
|
11
|
+
parseCrmOutreachCandidatesResponse,
|
|
12
|
+
parseCrmOutreachState,
|
|
13
|
+
parseCrmProviderMeta,
|
|
14
|
+
parseCrmSearchResponse
|
|
15
|
+
} from "./chunk-3614v9rr.js";
|
|
16
|
+
|
|
17
|
+
// ../connectors/src/crm-http.ts
|
|
18
|
+
import { randomUUID } from "node:crypto";
|
|
19
|
+
var CRM_HTTP_CONTRACT = "crm/1";
|
|
20
|
+
var CRM_HTTP_UPSERT_TIMEOUT_MS = 25000;
|
|
21
|
+
var CRM_HTTP_TIMEOUT_MS = 1e4;
|
|
22
|
+
var MAX_ERROR_BODY_BYTES = 4096;
|
|
23
|
+
var MAX_URL_CHARS = 2048;
|
|
24
|
+
var MAX_KEY_CHARS = 1024;
|
|
25
|
+
var CRM_HTTP_HEADERS = {
|
|
26
|
+
contract: "foundation-contract",
|
|
27
|
+
requestId: "foundation-request-id",
|
|
28
|
+
actor: "foundation-actor",
|
|
29
|
+
idempotencyKey: "idempotency-key",
|
|
30
|
+
tenant: "foundation-tenant",
|
|
31
|
+
replayed: "idempotent-replayed"
|
|
32
|
+
};
|
|
33
|
+
var CRM_HTTP_ROUTES = {
|
|
34
|
+
meta: { method: "GET", path: "/meta" },
|
|
35
|
+
search: { method: "POST", path: "/records/search" },
|
|
36
|
+
get_record: { method: "POST", path: "/records/get" },
|
|
37
|
+
get_activity: { method: "POST", path: "/activities/get" },
|
|
38
|
+
upsert_records: { method: "POST", path: "/records/upsert" },
|
|
39
|
+
update_status: { method: "POST", path: "/records/status" },
|
|
40
|
+
log_activity: { method: "POST", path: "/activities" },
|
|
41
|
+
update_note: { method: "POST", path: "/activities/update" },
|
|
42
|
+
outreach_state: { method: "POST", path: "/outreach/state" },
|
|
43
|
+
outreach_candidates: { method: "POST", path: "/outreach/candidates" },
|
|
44
|
+
delete_record: { method: "POST", path: "/records/delete" }
|
|
45
|
+
};
|
|
46
|
+
var CRM_HTTP_DETAIL_CODES = [
|
|
47
|
+
"contract",
|
|
48
|
+
"unknown_field",
|
|
49
|
+
"field_invalid",
|
|
50
|
+
"batch_too_large",
|
|
51
|
+
"idempotency_key_mismatch",
|
|
52
|
+
"contact_company_unknown",
|
|
53
|
+
"status_unknown",
|
|
54
|
+
"activity_type_unknown",
|
|
55
|
+
"cursor_invalid",
|
|
56
|
+
"outreach_invalid"
|
|
57
|
+
];
|
|
58
|
+
|
|
59
|
+
class CrmHttpError extends Error {
|
|
60
|
+
code;
|
|
61
|
+
status;
|
|
62
|
+
reason;
|
|
63
|
+
detailCode;
|
|
64
|
+
definitive;
|
|
65
|
+
sent;
|
|
66
|
+
constructor(code, details = {}) {
|
|
67
|
+
super(`CRM provider call failed: ${code}`);
|
|
68
|
+
this.code = code;
|
|
69
|
+
this.name = "CrmHttpError";
|
|
70
|
+
this.status = details.status;
|
|
71
|
+
this.reason = details.reason;
|
|
72
|
+
this.detailCode = details.detailCode;
|
|
73
|
+
this.definitive = details.definitive === true;
|
|
74
|
+
this.sent = details.sent !== false;
|
|
75
|
+
}
|
|
76
|
+
get statusClass() {
|
|
77
|
+
return this.status === undefined ? "none" : `${Math.floor(this.status / 100)}xx`;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
function encodeCrmActor(actor) {
|
|
81
|
+
return Buffer.from(JSON.stringify({
|
|
82
|
+
agent: actor.agent,
|
|
83
|
+
slackTeamId: actor.slackTeamId,
|
|
84
|
+
slackUserId: actor.slackUserId,
|
|
85
|
+
channelId: actor.channelId,
|
|
86
|
+
...actor.jobKind === undefined ? {} : { jobKind: actor.jobKind },
|
|
87
|
+
sessionHash: actor.sessionHash
|
|
88
|
+
})).toString("base64url");
|
|
89
|
+
}
|
|
90
|
+
function crmHttpBaseUrl(value) {
|
|
91
|
+
let url;
|
|
92
|
+
try {
|
|
93
|
+
url = new URL(value);
|
|
94
|
+
} catch {
|
|
95
|
+
throw new Error("CRM provider base URL must be an HTTPS URL");
|
|
96
|
+
}
|
|
97
|
+
if (value.length > MAX_URL_CHARS || url.protocol !== "https:" || url.username !== "" || url.password !== "" || url.search !== "" || url.hash !== "" || value.includes("?") || value.includes("#")) {
|
|
98
|
+
throw new Error("CRM provider base URL must be an HTTPS URL");
|
|
99
|
+
}
|
|
100
|
+
return `${url.origin}${url.pathname.replace(/\/+$/, "")}`;
|
|
101
|
+
}
|
|
102
|
+
function isCrmHttpKey(value) {
|
|
103
|
+
return typeof value === "string" && value.length >= 16 && value.length <= MAX_KEY_CHARS && /^[\x21-\x7e]+$/.test(value);
|
|
104
|
+
}
|
|
105
|
+
function createCrmHttpClient(options) {
|
|
106
|
+
const baseUrl = crmHttpBaseUrl(options.baseUrl);
|
|
107
|
+
if (!/^[\x21-\x7e][\x20-\x7e]{0,127}$/.test(options.userAgent)) {
|
|
108
|
+
throw new Error("CRM provider user agent is invalid");
|
|
109
|
+
}
|
|
110
|
+
if (!headerSafe(options.expectedTenant, 256)) {
|
|
111
|
+
throw new Error("CRM provider tenant is invalid");
|
|
112
|
+
}
|
|
113
|
+
if (options.actor !== undefined && !/^[A-Za-z0-9_-]{1,2048}$/.test(options.actor)) {
|
|
114
|
+
throw new Error("CRM provider actor is invalid");
|
|
115
|
+
}
|
|
116
|
+
const fetchImpl = options.fetchImpl ?? fetch;
|
|
117
|
+
async function call(route, body, timeoutMs) {
|
|
118
|
+
const serialized = body === undefined ? undefined : JSON.stringify(body);
|
|
119
|
+
if (serialized !== undefined && Buffer.byteLength(serialized, "utf8") > CRM_MAX_REQUEST_BYTES) {
|
|
120
|
+
throw new CrmInputError(`CRM request may not exceed ${CRM_MAX_REQUEST_BYTES} bytes`);
|
|
121
|
+
}
|
|
122
|
+
const idempotencyKey = typeof body?.idempotencyKey === "string" ? body.idempotencyKey : undefined;
|
|
123
|
+
if (idempotencyKey !== undefined && !isCrmIdempotencyKey(idempotencyKey)) {
|
|
124
|
+
throw new CrmInputError("idempotencyKey must be printable ASCII for this CRM");
|
|
125
|
+
}
|
|
126
|
+
let key = await options.credentials.get();
|
|
127
|
+
try {
|
|
128
|
+
return await send(route, serialized, idempotencyKey, key, timeoutMs);
|
|
129
|
+
} catch (error) {
|
|
130
|
+
if (!(error instanceof CrmHttpError) || error.code !== "unauthorized")
|
|
131
|
+
throw error;
|
|
132
|
+
const refreshed = await options.credentials.refresh();
|
|
133
|
+
if (refreshed === key)
|
|
134
|
+
throw error;
|
|
135
|
+
key = refreshed;
|
|
136
|
+
return send(route, serialized, idempotencyKey, key, timeoutMs);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
async function send(route, serialized, idempotencyKey, key, timeoutMs) {
|
|
140
|
+
if (!isCrmHttpKey(key))
|
|
141
|
+
throw new CrmHttpError("unauthorized", { sent: false });
|
|
142
|
+
const { method, path } = CRM_HTTP_ROUTES[route];
|
|
143
|
+
const signal = options.deadline === undefined ? AbortSignal.timeout(timeoutMs) : AbortSignal.any([AbortSignal.timeout(timeoutMs), options.deadline]);
|
|
144
|
+
if (signal.aborted)
|
|
145
|
+
throw new CrmHttpError("timeout", { sent: false });
|
|
146
|
+
let response;
|
|
147
|
+
try {
|
|
148
|
+
response = await fetchImpl(`${baseUrl}${path}`, {
|
|
149
|
+
method,
|
|
150
|
+
redirect: "error",
|
|
151
|
+
headers: {
|
|
152
|
+
authorization: `Bearer ${key}`,
|
|
153
|
+
accept: "application/json",
|
|
154
|
+
"user-agent": options.userAgent,
|
|
155
|
+
[CRM_HTTP_HEADERS.contract]: CRM_HTTP_CONTRACT,
|
|
156
|
+
[CRM_HTTP_HEADERS.requestId]: randomUUID(),
|
|
157
|
+
...options.actor === undefined ? {} : { [CRM_HTTP_HEADERS.actor]: options.actor },
|
|
158
|
+
...idempotencyKey === undefined ? {} : { [CRM_HTTP_HEADERS.idempotencyKey]: idempotencyKey },
|
|
159
|
+
...serialized === undefined ? {} : { "content-type": "application/json" }
|
|
160
|
+
},
|
|
161
|
+
...serialized === undefined ? {} : { body: serialized },
|
|
162
|
+
signal
|
|
163
|
+
});
|
|
164
|
+
} catch {
|
|
165
|
+
throw new CrmHttpError(signal.aborted ? "timeout" : "unavailable");
|
|
166
|
+
}
|
|
167
|
+
return readReply(response, signal, options.expectedTenant);
|
|
168
|
+
}
|
|
169
|
+
function ack(value, expected, matches) {
|
|
170
|
+
const parsed = parseReply(value, parseCrmOperationAck);
|
|
171
|
+
if (parsed.operation !== expected || !matches(parsed))
|
|
172
|
+
throw new CrmHttpError("protocol");
|
|
173
|
+
return parsed;
|
|
174
|
+
}
|
|
175
|
+
const sameRecord = (args) => (reply) => ("recordId" in reply) && reply.recordType === args.recordType && reply.recordId === args.recordId;
|
|
176
|
+
return {
|
|
177
|
+
meta: async () => parseReply(await call("meta", undefined, CRM_HTTP_TIMEOUT_MS), parseCrmProviderMeta),
|
|
178
|
+
search: async (args) => parseReply(await call("search", { ...args }, CRM_HTTP_TIMEOUT_MS), parseCrmSearchResponse),
|
|
179
|
+
getRecord: async (args) => parseReply(await call("get_record", { ...args }, CRM_HTTP_TIMEOUT_MS), parseCrmGetRecordResponse),
|
|
180
|
+
getActivity: async (args) => {
|
|
181
|
+
const activity = parseReply(await call("get_activity", { recordType: args.recordType, recordId: args.recordId, activityId: args.activityId }, CRM_HTTP_TIMEOUT_MS), parseCrmGetActivityResponse);
|
|
182
|
+
if (activity.id !== args.activityId)
|
|
183
|
+
throw new CrmHttpError("protocol");
|
|
184
|
+
return activity;
|
|
185
|
+
},
|
|
186
|
+
upsertRecords: async (args) => {
|
|
187
|
+
const records = (args.companies?.length ?? 0) + (args.contacts?.length ?? 0);
|
|
188
|
+
return ack(await call("upsert_records", { ...args }, CRM_HTTP_UPSERT_TIMEOUT_MS), "upsert_records", (reply) => ("records" in reply) && reply.records === records);
|
|
189
|
+
},
|
|
190
|
+
updateStatus: async (args, expect) => ack(await call("update_status", {
|
|
191
|
+
recordType: args.recordType,
|
|
192
|
+
recordId: args.recordId,
|
|
193
|
+
status: args.status,
|
|
194
|
+
idempotencyKey: args.idempotencyKey,
|
|
195
|
+
expect: { status: expect.status, statusVersion: expect.statusVersion }
|
|
196
|
+
}, CRM_HTTP_TIMEOUT_MS), "update_status", (reply) => sameRecord(args)(reply) && ("status" in reply) && reply.status === args.status),
|
|
197
|
+
logActivity: async (args, outreach) => ack(await call("log_activity", { ...args, ...outreach === undefined ? {} : { outreach: { ...outreach } } }, CRM_HTTP_TIMEOUT_MS), "log_activity", (reply) => sameRecord(args)(reply) && ("activityType" in reply) && reply.activityType === args.activityType),
|
|
198
|
+
updateNote: async (args) => ack(await call("update_note", {
|
|
199
|
+
recordType: args.recordType,
|
|
200
|
+
recordId: args.recordId,
|
|
201
|
+
activityId: args.activityId,
|
|
202
|
+
summary: args.summary,
|
|
203
|
+
idempotencyKey: args.idempotencyKey,
|
|
204
|
+
expect: { version: args.version }
|
|
205
|
+
}, CRM_HTTP_TIMEOUT_MS), "update_note", (reply) => sameRecord(args)(reply) && ("activityId" in reply) && reply.activityId === args.activityId && reply.version > args.version),
|
|
206
|
+
outreachState: async (args) => {
|
|
207
|
+
const state = parseReply(await call("outreach_state", { contactId: args.contactId }, CRM_HTTP_TIMEOUT_MS), parseCrmOutreachState);
|
|
208
|
+
if (state.contactId !== args.contactId)
|
|
209
|
+
throw new CrmHttpError("protocol");
|
|
210
|
+
return state;
|
|
211
|
+
},
|
|
212
|
+
outreachCandidates: async (args) => {
|
|
213
|
+
if (!Number.isInteger(args.limit) || args.limit < 1 || args.limit > CRM_MAX_OUTREACH_CANDIDATES_PAGE) {
|
|
214
|
+
throw new CrmInputError(`limit must be an integer between 1 and ${CRM_MAX_OUTREACH_CANDIDATES_PAGE}`);
|
|
215
|
+
}
|
|
216
|
+
const page = parseReply(await call("outreach_candidates", {
|
|
217
|
+
rankAttribute: args.rankAttribute,
|
|
218
|
+
statuses: { contact: [...args.statuses.contact] },
|
|
219
|
+
limit: args.limit,
|
|
220
|
+
...args.cursor === undefined ? {} : { cursor: args.cursor }
|
|
221
|
+
}, CRM_HTTP_TIMEOUT_MS), parseCrmOutreachCandidatesResponse);
|
|
222
|
+
if (page.candidates.length > args.limit)
|
|
223
|
+
throw new CrmHttpError("protocol");
|
|
224
|
+
return page;
|
|
225
|
+
},
|
|
226
|
+
deleteRecord: async (args) => ack(await call("delete_record", { ...args }, CRM_HTTP_TIMEOUT_MS), "delete_record", sameRecord(args))
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
function parseReply(value, parser) {
|
|
230
|
+
try {
|
|
231
|
+
return parser(value);
|
|
232
|
+
} catch {
|
|
233
|
+
throw new CrmHttpError("protocol");
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
async function readReply(response, signal, expectedTenant) {
|
|
237
|
+
const { status } = response;
|
|
238
|
+
const contract = response.headers.get(CRM_HTTP_HEADERS.contract);
|
|
239
|
+
if (contract === null) {
|
|
240
|
+
await discard(response);
|
|
241
|
+
if (status === 401)
|
|
242
|
+
throw new CrmHttpError("unauthorized", { status });
|
|
243
|
+
if (status === 403)
|
|
244
|
+
throw new CrmHttpError("forbidden", { status });
|
|
245
|
+
if (status === 429)
|
|
246
|
+
throw new CrmHttpError("rate_limited", { status });
|
|
247
|
+
if (status >= 500)
|
|
248
|
+
throw new CrmHttpError("server_error", { status });
|
|
249
|
+
throw new CrmHttpError("protocol", { status });
|
|
250
|
+
}
|
|
251
|
+
if (contractMajor(contract) !== contractMajor(CRM_HTTP_CONTRACT)) {
|
|
252
|
+
await discard(response);
|
|
253
|
+
throw new CrmHttpError("contract_mismatch", { status });
|
|
254
|
+
}
|
|
255
|
+
const tenantMatches = response.headers.get(CRM_HTTP_HEADERS.tenant) === expectedTenant;
|
|
256
|
+
if (status === 403) {
|
|
257
|
+
const error = contractError(status, parseJson(await readBounded(response, MAX_ERROR_BODY_BYTES, signal), status));
|
|
258
|
+
if (error.code === "forbidden" || tenantMatches)
|
|
259
|
+
throw error;
|
|
260
|
+
throw new CrmHttpError("tenant_mismatch", { status });
|
|
261
|
+
}
|
|
262
|
+
if (status !== 401 && !tenantMatches) {
|
|
263
|
+
await discard(response);
|
|
264
|
+
throw new CrmHttpError("tenant_mismatch", { status });
|
|
265
|
+
}
|
|
266
|
+
if (status === 200 || status === 201) {
|
|
267
|
+
return parseJson(await readBounded(response, CRM_MAX_RESPONSE_BYTES, signal), status);
|
|
268
|
+
}
|
|
269
|
+
if (status >= 500 && status !== 501) {
|
|
270
|
+
await discard(response);
|
|
271
|
+
throw new CrmHttpError("server_error", { status });
|
|
272
|
+
}
|
|
273
|
+
if (status === 429) {
|
|
274
|
+
await discard(response);
|
|
275
|
+
throw new CrmHttpError("rate_limited", { status });
|
|
276
|
+
}
|
|
277
|
+
if (status < 400) {
|
|
278
|
+
await discard(response);
|
|
279
|
+
throw new CrmHttpError("protocol", { status });
|
|
280
|
+
}
|
|
281
|
+
const body = parseJson(await readBounded(response, MAX_ERROR_BODY_BYTES, signal), status);
|
|
282
|
+
throw contractError(status, body);
|
|
283
|
+
}
|
|
284
|
+
function contractError(status, body) {
|
|
285
|
+
if (typeof body !== "object" || body === null || Array.isArray(body)) {
|
|
286
|
+
return new CrmHttpError("protocol", { status });
|
|
287
|
+
}
|
|
288
|
+
const fields = Object.fromEntries(Object.entries(body));
|
|
289
|
+
if (Object.keys(fields).some((key) => !["error", "reason", "detail_code"].includes(key))) {
|
|
290
|
+
return new CrmHttpError("protocol", { status });
|
|
291
|
+
}
|
|
292
|
+
const { error, reason } = fields;
|
|
293
|
+
const detailCode = CRM_HTTP_DETAIL_CODES.find((code2) => code2 === fields.detail_code);
|
|
294
|
+
const definitive = { status, definitive: true };
|
|
295
|
+
if (error === "invalid_input" && (status === 400 || status === 422)) {
|
|
296
|
+
return new CrmHttpError("invalid_input", {
|
|
297
|
+
...definitive,
|
|
298
|
+
...detailCode === undefined ? {} : { detailCode }
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
if (error === "refused" && status === 422) {
|
|
302
|
+
const known = CRM_REFUSAL_REASONS.find((candidate) => candidate === reason);
|
|
303
|
+
return known === undefined ? new CrmHttpError("protocol", { status }) : new CrmHttpError("refused", { ...definitive, reason: known });
|
|
304
|
+
}
|
|
305
|
+
const byStatus = {
|
|
306
|
+
401: ["unauthorized"],
|
|
307
|
+
403: ["forbidden", "not_editable"],
|
|
308
|
+
404: ["not_found"],
|
|
309
|
+
409: ["conflict"],
|
|
310
|
+
412: ["stale_status", "stale_version"],
|
|
311
|
+
501: ["unsupported"]
|
|
312
|
+
};
|
|
313
|
+
const allowed = byStatus[status] ?? [];
|
|
314
|
+
const code = allowed.find((candidate) => candidate === error);
|
|
315
|
+
return code === undefined ? new CrmHttpError("protocol", { status }) : new CrmHttpError(code, definitive);
|
|
316
|
+
}
|
|
317
|
+
function contractMajor(value) {
|
|
318
|
+
return /^crm\/(\d{1,4})(?:\.\d{1,4})?$/.exec(value.trim())?.[1];
|
|
319
|
+
}
|
|
320
|
+
function parseJson(text, status) {
|
|
321
|
+
try {
|
|
322
|
+
return JSON.parse(text);
|
|
323
|
+
} catch {
|
|
324
|
+
throw new CrmHttpError("protocol", { status });
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
async function readCrmHttpBody(response, maxBytes, signal) {
|
|
328
|
+
return readBounded(response, maxBytes, signal);
|
|
329
|
+
}
|
|
330
|
+
async function readBounded(response, maxBytes, signal) {
|
|
331
|
+
const declared = Number(response.headers.get("content-length") ?? "");
|
|
332
|
+
if (Number.isFinite(declared) && declared > maxBytes) {
|
|
333
|
+
await discard(response);
|
|
334
|
+
throw new CrmHttpError("protocol", { status: response.status });
|
|
335
|
+
}
|
|
336
|
+
const reader = response.body?.getReader();
|
|
337
|
+
if (reader === undefined)
|
|
338
|
+
return "";
|
|
339
|
+
const chunks = [];
|
|
340
|
+
let total = 0;
|
|
341
|
+
try {
|
|
342
|
+
while (true) {
|
|
343
|
+
let next;
|
|
344
|
+
try {
|
|
345
|
+
next = await reader.read();
|
|
346
|
+
} catch {
|
|
347
|
+
throw new CrmHttpError(signal?.aborted === true ? "timeout" : "unavailable", {
|
|
348
|
+
status: response.status
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
if (next.done)
|
|
352
|
+
break;
|
|
353
|
+
total += next.value.byteLength;
|
|
354
|
+
if (total > maxBytes) {
|
|
355
|
+
await reader.cancel().catch(() => {
|
|
356
|
+
return;
|
|
357
|
+
});
|
|
358
|
+
throw new CrmHttpError("protocol", { status: response.status });
|
|
359
|
+
}
|
|
360
|
+
chunks.push(next.value);
|
|
361
|
+
}
|
|
362
|
+
} finally {
|
|
363
|
+
reader.releaseLock();
|
|
364
|
+
}
|
|
365
|
+
return new TextDecoder().decode(Buffer.concat(chunks));
|
|
366
|
+
}
|
|
367
|
+
async function discard(response) {
|
|
368
|
+
await response.body?.cancel().catch(() => {
|
|
369
|
+
return;
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
function headerSafe(value, max) {
|
|
373
|
+
return value.length > 0 && value.length <= max && /^[\x21-\x7e](?:[\x20-\x7e]*[\x21-\x7e])?$/.test(value);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
export { CRM_HTTP_CONTRACT, CRM_HTTP_HEADERS, CRM_HTTP_ROUTES, CrmHttpError, encodeCrmActor, crmHttpBaseUrl, isCrmHttpKey, createCrmHttpClient, readCrmHttpBody };
|