@amerged/ohmyhost-runtime 0.1.37 → 0.1.38
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 +1 -1
- package/dist/errors.d.ts +1 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js.map +1 -1
- package/dist/http.d.ts +8 -2
- package/dist/http.d.ts.map +1 -1
- package/dist/http.js +39 -11
- package/dist/http.js.map +1 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -1
- package/dist/index.js.map +1 -1
- package/dist/mail.d.ts +1 -0
- package/dist/mail.d.ts.map +1 -1
- package/dist/mail.js +11 -9
- package/dist/mail.js.map +1 -1
- package/dist/storage-batch-stream.d.ts.map +1 -1
- package/dist/storage-batch-stream.js +24 -27
- package/dist/storage-batch-stream.js.map +1 -1
- package/dist/storage.d.ts +2 -0
- package/dist/storage.d.ts.map +1 -1
- package/dist/storage.js +43 -51
- package/dist/storage.js.map +1 -1
- package/package.json +1 -1
- package/src/packages/customer-runtime/package.json +2 -2
- package/src/packages/customer-runtime/src/errors.ts +3 -0
- package/src/packages/customer-runtime/src/http.ts +63 -11
- package/src/packages/customer-runtime/src/index.ts +0 -1
- package/src/packages/customer-runtime/src/mail.ts +12 -6
- package/src/packages/customer-runtime/src/storage-batch-stream.ts +29 -25
- package/src/packages/customer-runtime/src/storage.ts +59 -48
- package/dist/mail-attachments.d.ts +0 -8
- package/dist/mail-attachments.d.ts.map +0 -1
- package/dist/mail-attachments.js +0 -32
- package/dist/mail-attachments.js.map +0 -1
- package/src/packages/customer-runtime/src/mail-attachments.ts +0 -53
|
@@ -28,6 +28,7 @@ const MAX_HEADER_BYTES = 2048;
|
|
|
28
28
|
async function timedRead(
|
|
29
29
|
reader: ReadableStreamDefaultReader<Uint8Array>,
|
|
30
30
|
timeoutMilliseconds: number,
|
|
31
|
+
status: number,
|
|
31
32
|
signal?: AbortSignal,
|
|
32
33
|
): Promise<ReadableStreamReadResult<Uint8Array>> {
|
|
33
34
|
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
@@ -35,13 +36,13 @@ async function timedRead(
|
|
|
35
36
|
try {
|
|
36
37
|
if (signal?.aborted) {
|
|
37
38
|
void reader.cancel().catch(() => undefined);
|
|
38
|
-
throw new CustomerRuntimeGatewayError("gateway_timeout",
|
|
39
|
+
throw new CustomerRuntimeGatewayError("gateway_timeout", status, true);
|
|
39
40
|
}
|
|
40
41
|
return await Promise.race([
|
|
41
42
|
reader.read(),
|
|
42
43
|
new Promise<never>((_resolve, reject) => {
|
|
43
44
|
const abort = () => {
|
|
44
|
-
reject(new CustomerRuntimeGatewayError("gateway_timeout",
|
|
45
|
+
reject(new CustomerRuntimeGatewayError("gateway_timeout", status, true));
|
|
45
46
|
void reader.cancel().catch(() => undefined);
|
|
46
47
|
};
|
|
47
48
|
timer = setTimeout(abort, timeoutMilliseconds);
|
|
@@ -53,8 +54,8 @@ async function timedRead(
|
|
|
53
54
|
]);
|
|
54
55
|
} catch (error) {
|
|
55
56
|
if (error instanceof CustomerRuntimeGatewayError) throw error;
|
|
56
|
-
if (signal?.aborted) throw new CustomerRuntimeGatewayError("gateway_timeout",
|
|
57
|
-
throw new CustomerRuntimeGatewayError("gateway_unavailable",
|
|
57
|
+
if (signal?.aborted) throw new CustomerRuntimeGatewayError("gateway_timeout", status, true);
|
|
58
|
+
throw new CustomerRuntimeGatewayError("gateway_unavailable", status, true);
|
|
58
59
|
} finally {
|
|
59
60
|
clearTimeout(timer);
|
|
60
61
|
if (abortListener !== undefined) signal?.removeEventListener("abort", abortListener);
|
|
@@ -72,7 +73,7 @@ export function decodeStorageBatch(
|
|
|
72
73
|
response.body === null
|
|
73
74
|
) {
|
|
74
75
|
void response.body?.cancel().catch(() => undefined);
|
|
75
|
-
invalidResponse();
|
|
76
|
+
invalidResponse(response.status);
|
|
76
77
|
}
|
|
77
78
|
const source = response.body.getReader();
|
|
78
79
|
const decoder = new TextDecoder("utf-8", { fatal: true });
|
|
@@ -101,9 +102,9 @@ export function decodeStorageBatch(
|
|
|
101
102
|
async function chunk(maximum: number): Promise<Uint8Array> {
|
|
102
103
|
if (cancelled) throw cancellationError();
|
|
103
104
|
while (offset === buffer.byteLength) {
|
|
104
|
-
const next = await timedRead(source, timeoutMilliseconds);
|
|
105
|
+
const next = await timedRead(source, timeoutMilliseconds, response.status);
|
|
105
106
|
if (cancelled) throw cancellationError();
|
|
106
|
-
if (next.done || !(next.value instanceof Uint8Array)) invalidResponse();
|
|
107
|
+
if (next.done || !(next.value instanceof Uint8Array)) invalidResponse(response.status);
|
|
107
108
|
buffer = next.value;
|
|
108
109
|
offset = 0;
|
|
109
110
|
}
|
|
@@ -126,7 +127,7 @@ export function decodeStorageBatch(
|
|
|
126
127
|
const length = new DataView((await exactBytes(4)).buffer).getUint32(0);
|
|
127
128
|
controlBytes += 4 + length;
|
|
128
129
|
if (length < 1 || length > MAX_HEADER_BYTES || controlBytes > MAX_CONTROL_BYTES)
|
|
129
|
-
invalidResponse();
|
|
130
|
+
invalidResponse(response.status);
|
|
130
131
|
const bytes = await exactBytes(length);
|
|
131
132
|
return parseHeader(bytes);
|
|
132
133
|
}
|
|
@@ -135,7 +136,7 @@ export function decodeStorageBatch(
|
|
|
135
136
|
if (buffer.byteLength - offset < 4) return undefined;
|
|
136
137
|
const length = new DataView(buffer.buffer, buffer.byteOffset + offset, 4).getUint32(0);
|
|
137
138
|
if (length < 1 || length > MAX_HEADER_BYTES || controlBytes + 4 + length > MAX_CONTROL_BYTES)
|
|
138
|
-
invalidResponse();
|
|
139
|
+
invalidResponse(response.status);
|
|
139
140
|
if (buffer.byteLength - offset < 4 + length) return undefined;
|
|
140
141
|
controlBytes += 4 + length;
|
|
141
142
|
const bytes = buffer.subarray(offset + 4, offset + 4 + length);
|
|
@@ -146,7 +147,7 @@ export function decodeStorageBatch(
|
|
|
146
147
|
try {
|
|
147
148
|
return JSON.parse(decoder.decode(bytes)) as unknown;
|
|
148
149
|
} catch {
|
|
149
|
-
return invalidResponse();
|
|
150
|
+
return invalidResponse(response.status);
|
|
150
151
|
}
|
|
151
152
|
}
|
|
152
153
|
const iterator = iterate();
|
|
@@ -173,7 +174,7 @@ export function decodeStorageBatch(
|
|
|
173
174
|
try {
|
|
174
175
|
for (let index = 0; index < keys.length; index++) {
|
|
175
176
|
const objectKey = keys[index];
|
|
176
|
-
if (objectKey === undefined) invalidResponse();
|
|
177
|
+
if (objectKey === undefined) invalidResponse(response.status);
|
|
177
178
|
if (!currentConsumed)
|
|
178
179
|
throw new CustomerRuntimeGatewayError(
|
|
179
180
|
"gateway_request_invalid",
|
|
@@ -190,8 +191,10 @@ export function decodeStorageBatch(
|
|
|
190
191
|
candidate?.["state"] === "found"
|
|
191
192
|
? ["index", "objectKey", "state", "contentType", "contentLength", "etag"]
|
|
192
193
|
: ["index", "objectKey", "state"],
|
|
194
|
+
response.status,
|
|
193
195
|
);
|
|
194
|
-
if (record["index"] !== index || record["objectKey"] !== keys[index])
|
|
196
|
+
if (record["index"] !== index || record["objectKey"] !== keys[index])
|
|
197
|
+
invalidResponse(response.status);
|
|
195
198
|
if (record["state"] === "not_found") {
|
|
196
199
|
yield Object.freeze({ state: "not_found", objectKey });
|
|
197
200
|
continue;
|
|
@@ -208,7 +211,7 @@ export function decodeStorageBatch(
|
|
|
208
211
|
typeof record["etag"] !== "string" ||
|
|
209
212
|
!/^[0-9a-f]{32}(?:-[1-9][0-9]{0,4})?$/u.test(record["etag"])
|
|
210
213
|
)
|
|
211
|
-
invalidResponse();
|
|
214
|
+
invalidResponse(response.status);
|
|
212
215
|
let remaining = Number(record["contentLength"]);
|
|
213
216
|
currentConsumed = remaining === 0;
|
|
214
217
|
const body = new ReadableStream<Uint8Array>(
|
|
@@ -268,18 +271,19 @@ export function decodeStorageBatch(
|
|
|
268
271
|
);
|
|
269
272
|
if (bodyFailure !== undefined) throw bodyFailure;
|
|
270
273
|
const buffered = bufferedHeader();
|
|
271
|
-
const terminal = exactRecord(
|
|
272
|
-
|
|
273
|
-
"count",
|
|
274
|
-
|
|
274
|
+
const terminal = exactRecord(
|
|
275
|
+
buffered === undefined ? await header() : buffered,
|
|
276
|
+
["state", "count"],
|
|
277
|
+
response.status,
|
|
278
|
+
);
|
|
275
279
|
if (
|
|
276
280
|
terminal["state"] !== "completed" ||
|
|
277
281
|
terminal["count"] !== keys.length ||
|
|
278
282
|
offset !== buffer.byteLength
|
|
279
283
|
)
|
|
280
|
-
invalidResponse();
|
|
281
|
-
const end = await timedRead(source, timeoutMilliseconds);
|
|
282
|
-
if (!end.done) invalidResponse();
|
|
284
|
+
invalidResponse(response.status);
|
|
285
|
+
const end = await timedRead(source, timeoutMilliseconds, response.status);
|
|
286
|
+
if (!end.done) invalidResponse(response.status);
|
|
283
287
|
if (cancelled) throw cancellationError();
|
|
284
288
|
finished = true;
|
|
285
289
|
} finally {
|
|
@@ -303,7 +307,7 @@ export async function readStorageBatchJson(
|
|
|
303
307
|
response.body === null
|
|
304
308
|
) {
|
|
305
309
|
void response.body?.cancel().catch(() => undefined);
|
|
306
|
-
invalidResponse();
|
|
310
|
+
invalidResponse(response.status);
|
|
307
311
|
}
|
|
308
312
|
const reader = response.body.getReader();
|
|
309
313
|
const chunks: Uint8Array[] = [];
|
|
@@ -311,14 +315,14 @@ export async function readStorageBatchJson(
|
|
|
311
315
|
let finished = false;
|
|
312
316
|
try {
|
|
313
317
|
for (;;) {
|
|
314
|
-
const next = await timedRead(reader, timeoutMilliseconds, signal);
|
|
318
|
+
const next = await timedRead(reader, timeoutMilliseconds, response.status, signal);
|
|
315
319
|
if (next.done) {
|
|
316
320
|
finished = true;
|
|
317
321
|
break;
|
|
318
322
|
}
|
|
319
|
-
if (!(next.value instanceof Uint8Array)) invalidResponse();
|
|
323
|
+
if (!(next.value instanceof Uint8Array)) invalidResponse(response.status);
|
|
320
324
|
length += next.value.byteLength;
|
|
321
|
-
if (length > MAX_CONTROL_BYTES) invalidResponse();
|
|
325
|
+
if (length > MAX_CONTROL_BYTES) invalidResponse(response.status);
|
|
322
326
|
chunks.push(next.value);
|
|
323
327
|
}
|
|
324
328
|
} finally {
|
|
@@ -334,6 +338,6 @@ export async function readStorageBatchJson(
|
|
|
334
338
|
try {
|
|
335
339
|
return JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes)) as unknown;
|
|
336
340
|
} catch {
|
|
337
|
-
return invalidResponse();
|
|
341
|
+
return invalidResponse(response.status);
|
|
338
342
|
}
|
|
339
343
|
}
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
postGatewayJson,
|
|
8
8
|
requireConfiguredUlid,
|
|
9
9
|
validateGatewayConnection,
|
|
10
|
+
throwGatewayRelayError,
|
|
10
11
|
type GatewayConnection,
|
|
11
12
|
} from "./http.js";
|
|
12
13
|
import {
|
|
@@ -33,10 +34,12 @@ export interface StorageUploadInput {
|
|
|
33
34
|
readonly bytes: Uint8Array;
|
|
34
35
|
}
|
|
35
36
|
|
|
37
|
+
/** Server-only private files; use global fetch for gateway and signed-object transports. */
|
|
36
38
|
export function createPrivateStorageClient(
|
|
37
39
|
configuration: GatewayConnection & {
|
|
38
40
|
readonly projectId: string;
|
|
39
41
|
readonly environmentId: string;
|
|
42
|
+
/** Global fetch for short-lived signed object URLs; explicit transports remain test seams. */
|
|
40
43
|
readonly capabilityFetch: (request: Request) => Promise<Response>;
|
|
41
44
|
},
|
|
42
45
|
) {
|
|
@@ -79,18 +82,22 @@ export function createPrivateStorageClient(
|
|
|
79
82
|
if (response.status !== 200) throwStorageError(response.status, response.body, environmentId);
|
|
80
83
|
const record = response.body as Record<string, unknown>;
|
|
81
84
|
if (record?.["state"] === "completed") {
|
|
82
|
-
const completed = exactRecord(response.body, ["state", "transfer_id"]);
|
|
85
|
+
const completed = exactRecord(response.body, ["state", "transfer_id"], response.status);
|
|
83
86
|
return Object.freeze({
|
|
84
87
|
state: "completed" as const,
|
|
85
|
-
transferId: requireUlid(completed["transfer_id"]),
|
|
88
|
+
transferId: requireUlid(completed["transfer_id"], response.status),
|
|
86
89
|
});
|
|
87
90
|
}
|
|
88
|
-
const ready = exactRecord(
|
|
89
|
-
|
|
91
|
+
const ready = exactRecord(
|
|
92
|
+
response.body,
|
|
93
|
+
["state", "transfer_id", "capability"],
|
|
94
|
+
response.status,
|
|
95
|
+
);
|
|
96
|
+
if (ready["state"] !== "ready") invalidResponse(response.status);
|
|
90
97
|
return Object.freeze({
|
|
91
98
|
state: "ready" as const,
|
|
92
|
-
transferId: requireUlid(ready["transfer_id"]),
|
|
93
|
-
capability: parseCapability(ready["capability"], "PUT", input.objectKey),
|
|
99
|
+
transferId: requireUlid(ready["transfer_id"], response.status),
|
|
100
|
+
capability: parseCapability(ready["capability"], "PUT", input.objectKey, response.status),
|
|
94
101
|
});
|
|
95
102
|
}
|
|
96
103
|
|
|
@@ -107,11 +114,12 @@ export function createPrivateStorageClient(
|
|
|
107
114
|
if (response.status !== 200 && response.status !== 202) {
|
|
108
115
|
throwStorageError(response.status, response.body, environmentId);
|
|
109
116
|
}
|
|
110
|
-
const record = exactRecord(response.body, ["state", "transfer_id"]);
|
|
111
|
-
if (record["state"] !== "completed" && record["state"] !== "pending")
|
|
117
|
+
const record = exactRecord(response.body, ["state", "transfer_id"], response.status);
|
|
118
|
+
if (record["state"] !== "completed" && record["state"] !== "pending")
|
|
119
|
+
invalidResponse(response.status);
|
|
112
120
|
return Object.freeze({
|
|
113
121
|
state: record["state"],
|
|
114
|
-
transferId: requireUlid(record["transfer_id"]),
|
|
122
|
+
transferId: requireUlid(record["transfer_id"], response.status),
|
|
115
123
|
});
|
|
116
124
|
}
|
|
117
125
|
|
|
@@ -127,9 +135,9 @@ export function createPrivateStorageClient(
|
|
|
127
135
|
8 * 1024,
|
|
128
136
|
);
|
|
129
137
|
if (response.status !== 200) throwStorageError(response.status, response.body, environmentId);
|
|
130
|
-
const record = exactRecord(response.body, ["state", "capability"]);
|
|
131
|
-
if (record["state"] !== "ready") invalidResponse();
|
|
132
|
-
return parseCapability(record["capability"], "GET", objectKey);
|
|
138
|
+
const record = exactRecord(response.body, ["state", "capability"], response.status);
|
|
139
|
+
if (record["state"] !== "ready") invalidResponse(response.status);
|
|
140
|
+
return parseCapability(record["capability"], "GET", objectKey, response.status);
|
|
133
141
|
}
|
|
134
142
|
|
|
135
143
|
async function deleteObject(input: { idempotencyKey: string; objectKey: string }) {
|
|
@@ -142,11 +150,11 @@ export function createPrivateStorageClient(
|
|
|
142
150
|
input.idempotencyKey,
|
|
143
151
|
);
|
|
144
152
|
if (response.status !== 200) throwStorageError(response.status, response.body, environmentId);
|
|
145
|
-
const record = exactRecord(response.body, ["state", "deletion_id"]);
|
|
146
|
-
if (record["state"] !== "completed") invalidResponse();
|
|
153
|
+
const record = exactRecord(response.body, ["state", "deletion_id"], response.status);
|
|
154
|
+
if (record["state"] !== "completed") invalidResponse(response.status);
|
|
147
155
|
return Object.freeze({
|
|
148
156
|
state: "completed" as const,
|
|
149
|
-
deletionId: requireUlid(record["deletion_id"]),
|
|
157
|
+
deletionId: requireUlid(record["deletion_id"], response.status),
|
|
150
158
|
});
|
|
151
159
|
}
|
|
152
160
|
|
|
@@ -257,9 +265,9 @@ export function createPrivateStorageClient(
|
|
|
257
265
|
controller.signal,
|
|
258
266
|
);
|
|
259
267
|
if (response.status !== 200) throwStorageError(response.status, value, environmentId);
|
|
260
|
-
const record = exactRecord(value, ["state", "results"]);
|
|
268
|
+
const record = exactRecord(value, ["state", "results"], response.status);
|
|
261
269
|
if (!Array.isArray(record["results"]) || record["results"].length !== objects.length)
|
|
262
|
-
invalidResponse();
|
|
270
|
+
invalidResponse(response.status);
|
|
263
271
|
const results = record["results"].map((value, index): StorageDeleteResult => {
|
|
264
272
|
const candidate = value as Record<string, unknown>;
|
|
265
273
|
const item = exactRecord(
|
|
@@ -269,22 +277,23 @@ export function createPrivateStorageClient(
|
|
|
269
277
|
: candidate?.["state"] === "pending"
|
|
270
278
|
? ["object_key", "state", "retry_at"]
|
|
271
279
|
: ["object_key", "state", "code", "retryable"],
|
|
280
|
+
response.status,
|
|
272
281
|
);
|
|
273
282
|
const requested = objects[index];
|
|
274
283
|
if (requested === undefined || item["object_key"] !== requested.objectKey)
|
|
275
|
-
invalidResponse();
|
|
284
|
+
invalidResponse(response.status);
|
|
276
285
|
const objectKey = requested.objectKey;
|
|
277
286
|
if (item["state"] === "completed")
|
|
278
287
|
return Object.freeze({
|
|
279
288
|
state: "completed",
|
|
280
289
|
objectKey,
|
|
281
|
-
deletionId: requireUlid(item["deletion_id"]),
|
|
290
|
+
deletionId: requireUlid(item["deletion_id"], response.status),
|
|
282
291
|
});
|
|
283
292
|
if (item["state"] === "pending") {
|
|
284
293
|
return Object.freeze({
|
|
285
294
|
state: "pending",
|
|
286
295
|
objectKey,
|
|
287
|
-
retryAt: requireRetryAt(item["retry_at"]),
|
|
296
|
+
retryAt: requireRetryAt(item["retry_at"], response.status),
|
|
288
297
|
});
|
|
289
298
|
}
|
|
290
299
|
if (
|
|
@@ -293,7 +302,7 @@ export function createPrivateStorageClient(
|
|
|
293
302
|
typeof item["retryable"] !== "boolean" ||
|
|
294
303
|
(item["code"] === "object_not_found" && item["retryable"] !== false)
|
|
295
304
|
)
|
|
296
|
-
invalidResponse();
|
|
305
|
+
invalidResponse(response.status);
|
|
297
306
|
return Object.freeze({
|
|
298
307
|
state: "failed",
|
|
299
308
|
objectKey,
|
|
@@ -305,10 +314,10 @@ export function createPrivateStorageClient(
|
|
|
305
314
|
record["state"] !==
|
|
306
315
|
(results.every((item) => item.state === "completed") ? "completed" : "partial")
|
|
307
316
|
)
|
|
308
|
-
invalidResponse();
|
|
317
|
+
invalidResponse(response.status);
|
|
309
318
|
if (controller.signal.aborted || Date.now() >= deadline) {
|
|
310
319
|
controller.abort();
|
|
311
|
-
throw new CustomerRuntimeGatewayError("gateway_timeout",
|
|
320
|
+
throw new CustomerRuntimeGatewayError("gateway_timeout", response.status, true);
|
|
312
321
|
}
|
|
313
322
|
return Object.freeze(results);
|
|
314
323
|
} finally {
|
|
@@ -341,16 +350,18 @@ function validateBatchKeys(value: readonly string[]): readonly string[] {
|
|
|
341
350
|
return Object.freeze(Array.from(value));
|
|
342
351
|
}
|
|
343
352
|
|
|
344
|
-
function parseCapability(
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
353
|
+
function parseCapability(
|
|
354
|
+
value: unknown,
|
|
355
|
+
operation: "GET" | "PUT",
|
|
356
|
+
objectKey: string,
|
|
357
|
+
status: number,
|
|
358
|
+
) {
|
|
359
|
+
const record = exactRecord(
|
|
360
|
+
value,
|
|
361
|
+
["operation", "objectKey", "url", "expiresAt", "expectedContentLength", "requiredHeaders"],
|
|
362
|
+
status,
|
|
363
|
+
);
|
|
364
|
+
const headers = exactHeaders(record["requiredHeaders"], status);
|
|
354
365
|
if (
|
|
355
366
|
record["operation"] !== operation ||
|
|
356
367
|
record["objectKey"] !== objectKey ||
|
|
@@ -363,7 +374,7 @@ function parseCapability(value: unknown, operation: "GET" | "PUT", objectKey: st
|
|
|
363
374
|
Number(record["expectedContentLength"]) < 0 ||
|
|
364
375
|
Number(record["expectedContentLength"]) > 1_073_741_824))
|
|
365
376
|
) {
|
|
366
|
-
invalidResponse();
|
|
377
|
+
invalidResponse(status);
|
|
367
378
|
}
|
|
368
379
|
return Object.freeze({
|
|
369
380
|
operation,
|
|
@@ -376,8 +387,8 @@ function parseCapability(value: unknown, operation: "GET" | "PUT", objectKey: st
|
|
|
376
387
|
});
|
|
377
388
|
}
|
|
378
389
|
|
|
379
|
-
function exactHeaders(value: unknown): Readonly<Record<string, string>> {
|
|
380
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) invalidResponse();
|
|
390
|
+
function exactHeaders(value: unknown, status: number): Readonly<Record<string, string>> {
|
|
391
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) invalidResponse(status);
|
|
381
392
|
const entries = Object.entries(value as Record<string, unknown>);
|
|
382
393
|
if (
|
|
383
394
|
entries.length > 16 ||
|
|
@@ -390,12 +401,13 @@ function exactHeaders(value: unknown): Readonly<Record<string, string>> {
|
|
|
390
401
|
/[\r\n]/u.test(header),
|
|
391
402
|
)
|
|
392
403
|
) {
|
|
393
|
-
invalidResponse();
|
|
404
|
+
invalidResponse(status);
|
|
394
405
|
}
|
|
395
406
|
return Object.freeze(Object.fromEntries(entries) as Record<string, string>);
|
|
396
407
|
}
|
|
397
408
|
|
|
398
409
|
function throwStorageError(status: number, body: unknown, environmentId: string): never {
|
|
410
|
+
throwGatewayRelayError(status, body);
|
|
399
411
|
const candidate = body as Record<string, unknown>;
|
|
400
412
|
const record = exactRecord(
|
|
401
413
|
body,
|
|
@@ -407,6 +419,7 @@ function throwStorageError(status: number, body: unknown, environmentId: string)
|
|
|
407
419
|
: typeof body === "object" && body !== null && "transfer_id" in body
|
|
408
420
|
? ["code", "transfer_id"]
|
|
409
421
|
: ["code"],
|
|
422
|
+
status,
|
|
410
423
|
);
|
|
411
424
|
const code = record["code"];
|
|
412
425
|
if (status === 401 && code === "unauthorized") {
|
|
@@ -416,7 +429,7 @@ function throwStorageError(status: number, body: unknown, environmentId: string)
|
|
|
416
429
|
throw new CustomerRuntimeGatewayError("storage_object_not_found", status, false);
|
|
417
430
|
}
|
|
418
431
|
if (status === 409 && code === "upload_expired") {
|
|
419
|
-
requireUlid(record["transfer_id"]);
|
|
432
|
+
requireUlid(record["transfer_id"], status);
|
|
420
433
|
throw new CustomerRuntimeGatewayError("storage_upload_expired", status, false);
|
|
421
434
|
}
|
|
422
435
|
if (status === 409 && code === "data_change_in_progress") {
|
|
@@ -432,11 +445,11 @@ function throwStorageError(status: number, body: unknown, environmentId: string)
|
|
|
432
445
|
"storage_binding_data_changed",
|
|
433
446
|
status,
|
|
434
447
|
false,
|
|
435
|
-
`environment
|
|
448
|
+
`the environment's data area changed while this request ran; read the current state for environment ${environmentId} and issue a new request`,
|
|
436
449
|
);
|
|
437
450
|
}
|
|
438
451
|
if (status === 409 && code === "storage_delete_pending") {
|
|
439
|
-
const retryAt = requireRetryAt(record["retry_at"]);
|
|
452
|
+
const retryAt = requireRetryAt(record["retry_at"], status);
|
|
440
453
|
throw new CustomerRuntimeGatewayError(
|
|
441
454
|
"storage_delete_pending",
|
|
442
455
|
status,
|
|
@@ -450,20 +463,17 @@ function throwStorageError(status: number, body: unknown, environmentId: string)
|
|
|
450
463
|
if (status === 413 && typeof code === "string") {
|
|
451
464
|
throw new CustomerRuntimeGatewayError("storage_quota_exceeded", status, false);
|
|
452
465
|
}
|
|
453
|
-
|
|
454
|
-
throw new CustomerRuntimeGatewayError("gateway_unavailable", status, true);
|
|
455
|
-
}
|
|
456
|
-
return invalidResponse();
|
|
466
|
+
return invalidResponse(status);
|
|
457
467
|
}
|
|
458
468
|
|
|
459
|
-
function requireRetryAt(value: unknown): string {
|
|
469
|
+
function requireRetryAt(value: unknown, status: number): string {
|
|
460
470
|
if (
|
|
461
471
|
typeof value !== "string" ||
|
|
462
472
|
!/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/u.test(value) ||
|
|
463
473
|
!Number.isFinite(Date.parse(value)) ||
|
|
464
474
|
new Date(value).toISOString() !== value
|
|
465
475
|
)
|
|
466
|
-
invalidResponse();
|
|
476
|
+
invalidResponse(status);
|
|
467
477
|
return value;
|
|
468
478
|
}
|
|
469
479
|
|
|
@@ -507,8 +517,9 @@ function validSignedUrl(value: string): boolean {
|
|
|
507
517
|
}
|
|
508
518
|
}
|
|
509
519
|
|
|
510
|
-
function requireUlid(value: unknown): string {
|
|
511
|
-
if (typeof value !== "string" || !/^[0-9A-HJKMNP-TV-Z]{26}$/u.test(value))
|
|
520
|
+
function requireUlid(value: unknown, status: number | null = null): string {
|
|
521
|
+
if (typeof value !== "string" || !/^[0-9A-HJKMNP-TV-Z]{26}$/u.test(value))
|
|
522
|
+
invalidResponse(status);
|
|
512
523
|
return value;
|
|
513
524
|
}
|
|
514
525
|
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import { type GatewayConnection } from "./http.js";
|
|
2
|
-
/** Stream owned attachments through the private mail binding using the environment's runtime key. */
|
|
3
|
-
export declare function createMailAttachmentClient(configuration: GatewayConnection & {
|
|
4
|
-
projectId: string;
|
|
5
|
-
}): {
|
|
6
|
-
download(downloadPath: string): Promise<Response>;
|
|
7
|
-
};
|
|
8
|
-
//# sourceMappingURL=mail-attachments.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"mail-attachments.d.ts","sourceRoot":"","sources":["../src/mail-attachments.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,iBAAiB,EACvB,MAAM,WAAW,CAAC;AAEnB,qGAAqG;AACrG,wBAAgB,0BAA0B,CACxC,aAAa,EAAE,iBAAiB,GAAG;IAAE,SAAS,EAAE,MAAM,CAAA;CAAE;2BAazB,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC;EA8B1D"}
|
package/dist/mail-attachments.js
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import { validateGatewayConnection, fetchWithTimeout, requireConfiguredUlid, } from "./http.js";
|
|
2
|
-
import { CustomerRuntimeGatewayError } from "./errors.js";
|
|
3
|
-
/** Stream owned attachments through the private mail binding using the environment's runtime key. */
|
|
4
|
-
export function createMailAttachmentClient(configuration) {
|
|
5
|
-
const connection = validateGatewayConnection(configuration);
|
|
6
|
-
const projectId = requireConfiguredUlid(configuration.projectId, "projectId", "OHMYHOST_PROJECT_ID");
|
|
7
|
-
const pattern = new RegExp(`^/v1/projects/${projectId}/environments/[0-7][0-9A-HJKMNP-TV-Z]{25}/mail/messages/[0-7][0-9A-HJKMNP-TV-Z]{25}/attachments/[A-Za-z0-9_-]{1,128}$`, "u");
|
|
8
|
-
return {
|
|
9
|
-
async download(downloadPath) {
|
|
10
|
-
if (!pattern.test(downloadPath))
|
|
11
|
-
throw new CustomerRuntimeGatewayError("gateway_request_invalid", null, false);
|
|
12
|
-
const response = await fetchWithTimeout(connection.fetch, new Request(new URL(downloadPath, connection.endpoint), {
|
|
13
|
-
redirect: "manual",
|
|
14
|
-
headers: { authorization: `Bearer ${connection.key}` },
|
|
15
|
-
}), connection.timeoutMilliseconds);
|
|
16
|
-
if (response.status !== 200) {
|
|
17
|
-
await response.body?.cancel();
|
|
18
|
-
throw new CustomerRuntimeGatewayError(response.status === 410
|
|
19
|
-
? "mail_content_expired"
|
|
20
|
-
: response.status === 413
|
|
21
|
-
? "mail_attachment_too_large"
|
|
22
|
-
: response.status === 404
|
|
23
|
-
? "mail_message_not_found"
|
|
24
|
-
: response.status === 401
|
|
25
|
-
? "unauthorized"
|
|
26
|
-
: "gateway_unavailable", response.status, response.status >= 500);
|
|
27
|
-
}
|
|
28
|
-
return response;
|
|
29
|
-
},
|
|
30
|
-
};
|
|
31
|
-
}
|
|
32
|
-
//# sourceMappingURL=mail-attachments.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"mail-attachments.js","sourceRoot":"","sources":["../src/mail-attachments.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,yBAAyB,EACzB,gBAAgB,EAChB,qBAAqB,GAEtB,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,2BAA2B,EAAE,MAAM,aAAa,CAAC;AAC1D,qGAAqG;AACrG,MAAM,UAAU,0BAA0B,CACxC,aAAwD;IAExD,MAAM,UAAU,GAAG,yBAAyB,CAAC,aAAa,CAAC,CAAC;IAC5D,MAAM,SAAS,GAAG,qBAAqB,CACrC,aAAa,CAAC,SAAS,EACvB,WAAW,EACX,qBAAqB,CACtB,CAAC;IACF,MAAM,OAAO,GAAG,IAAI,MAAM,CACxB,iBAAiB,SAAS,uHAAuH,EACjJ,GAAG,CACJ,CAAC;IACF,OAAO;QACL,KAAK,CAAC,QAAQ,CAAC,YAAoB;YACjC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,YAAY,CAAC;gBAC7B,MAAM,IAAI,2BAA2B,CAAC,yBAAyB,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC;YAChF,MAAM,QAAQ,GAAG,MAAM,gBAAgB,CACrC,UAAU,CAAC,KAAK,EAChB,IAAI,OAAO,CAAC,IAAI,GAAG,CAAC,YAAY,EAAE,UAAU,CAAC,QAAQ,CAAC,EAAE;gBACtD,QAAQ,EAAE,QAAQ;gBAClB,OAAO,EAAE,EAAE,aAAa,EAAE,UAAU,UAAU,CAAC,GAAG,EAAE,EAAE;aACvD,CAAC,EACF,UAAU,CAAC,mBAAmB,CAC/B,CAAC;YACF,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;gBAC5B,MAAM,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC;gBAC9B,MAAM,IAAI,2BAA2B,CACnC,QAAQ,CAAC,MAAM,KAAK,GAAG;oBACrB,CAAC,CAAC,sBAAsB;oBACxB,CAAC,CAAC,QAAQ,CAAC,MAAM,KAAK,GAAG;wBACvB,CAAC,CAAC,2BAA2B;wBAC7B,CAAC,CAAC,QAAQ,CAAC,MAAM,KAAK,GAAG;4BACvB,CAAC,CAAC,wBAAwB;4BAC1B,CAAC,CAAC,QAAQ,CAAC,MAAM,KAAK,GAAG;gCACvB,CAAC,CAAC,cAAc;gCAChB,CAAC,CAAC,qBAAqB,EAC/B,QAAQ,CAAC,MAAM,EACf,QAAQ,CAAC,MAAM,IAAI,GAAG,CACvB,CAAC;YACJ,CAAC;YACD,OAAO,QAAQ,CAAC;QAClB,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
validateGatewayConnection,
|
|
3
|
-
fetchWithTimeout,
|
|
4
|
-
requireConfiguredUlid,
|
|
5
|
-
type GatewayConnection,
|
|
6
|
-
} from "./http.js";
|
|
7
|
-
import { CustomerRuntimeGatewayError } from "./errors.js";
|
|
8
|
-
/** Stream owned attachments through the private mail binding using the environment's runtime key. */
|
|
9
|
-
export function createMailAttachmentClient(
|
|
10
|
-
configuration: GatewayConnection & { projectId: string },
|
|
11
|
-
) {
|
|
12
|
-
const connection = validateGatewayConnection(configuration);
|
|
13
|
-
const projectId = requireConfiguredUlid(
|
|
14
|
-
configuration.projectId,
|
|
15
|
-
"projectId",
|
|
16
|
-
"OHMYHOST_PROJECT_ID",
|
|
17
|
-
);
|
|
18
|
-
const pattern = new RegExp(
|
|
19
|
-
`^/v1/projects/${projectId}/environments/[0-7][0-9A-HJKMNP-TV-Z]{25}/mail/messages/[0-7][0-9A-HJKMNP-TV-Z]{25}/attachments/[A-Za-z0-9_-]{1,128}$`,
|
|
20
|
-
"u",
|
|
21
|
-
);
|
|
22
|
-
return {
|
|
23
|
-
async download(downloadPath: string): Promise<Response> {
|
|
24
|
-
if (!pattern.test(downloadPath))
|
|
25
|
-
throw new CustomerRuntimeGatewayError("gateway_request_invalid", null, false);
|
|
26
|
-
const response = await fetchWithTimeout(
|
|
27
|
-
connection.fetch,
|
|
28
|
-
new Request(new URL(downloadPath, connection.endpoint), {
|
|
29
|
-
redirect: "manual",
|
|
30
|
-
headers: { authorization: `Bearer ${connection.key}` },
|
|
31
|
-
}),
|
|
32
|
-
connection.timeoutMilliseconds,
|
|
33
|
-
);
|
|
34
|
-
if (response.status !== 200) {
|
|
35
|
-
await response.body?.cancel();
|
|
36
|
-
throw new CustomerRuntimeGatewayError(
|
|
37
|
-
response.status === 410
|
|
38
|
-
? "mail_content_expired"
|
|
39
|
-
: response.status === 413
|
|
40
|
-
? "mail_attachment_too_large"
|
|
41
|
-
: response.status === 404
|
|
42
|
-
? "mail_message_not_found"
|
|
43
|
-
: response.status === 401
|
|
44
|
-
? "unauthorized"
|
|
45
|
-
: "gateway_unavailable",
|
|
46
|
-
response.status,
|
|
47
|
-
response.status >= 500,
|
|
48
|
-
);
|
|
49
|
-
}
|
|
50
|
-
return response;
|
|
51
|
-
},
|
|
52
|
-
};
|
|
53
|
-
}
|