@spacefast/common 0.0.13 → 0.0.17
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/dist/agents/client-registry.d.ts +22 -0
- package/dist/agents/client-registry.js +41 -0
- package/dist/agents/connect-targets.d.ts +51 -6
- package/dist/agents/connect-targets.js +93 -28
- package/dist/brand.d.ts +0 -11
- package/dist/brand.js +0 -20
- package/dist/config/domains.d.ts +15 -6
- package/dist/config/domains.js +24 -10
- package/dist/config/postgres-budget.d.ts +3 -0
- package/dist/config/postgres-budget.js +6 -1
- package/dist/config/postgres-budget.json +2 -1
- package/dist/contracts/abuse.js +1 -1
- package/dist/contracts/access.d.ts +149 -44
- package/dist/contracts/access.js +94 -8
- package/dist/contracts/activity.js +5 -1
- package/dist/contracts/api-keys.js +9 -9
- package/dist/contracts/archives.js +11 -11
- package/dist/contracts/beta.js +1 -1
- package/dist/contracts/builds.js +14 -14
- package/dist/contracts/channels.js +2 -2
- package/dist/contracts/comments.d.ts +86 -15
- package/dist/contracts/comments.js +116 -20
- package/dist/contracts/common.d.ts +12 -12
- package/dist/contracts/common.js +33 -30
- package/dist/contracts/continuation.js +5 -5
- package/dist/contracts/data-source-routes.js +1 -1
- package/dist/contracts/device-auth.d.ts +6 -6
- package/dist/contracts/device-auth.js +12 -12
- package/dist/contracts/docs.d.ts +10 -10
- package/dist/contracts/domains.js +39 -33
- package/dist/contracts/enums.d.ts +2 -10
- package/dist/contracts/enums.js +0 -2
- package/dist/contracts/error-code-meta.d.ts +116 -47
- package/dist/contracts/error-code-meta.js +37 -16
- package/dist/contracts/error-codes.d.ts +7 -4
- package/dist/contracts/error-codes.js +46 -21
- package/dist/contracts/events.d.ts +10 -3
- package/dist/contracts/events.js +33 -5
- package/dist/contracts/execution.d.ts +26 -0
- package/dist/contracts/execution.js +21 -0
- package/dist/contracts/feature-lifecycle.d.ts +21 -24
- package/dist/contracts/feature-lifecycle.js +28 -26
- package/dist/contracts/features.js +4 -4
- package/dist/contracts/functions.d.ts +8 -140
- package/dist/contracts/functions.js +9 -19
- package/dist/contracts/git.d.ts +1 -1
- package/dist/contracts/grants.d.ts +96 -79
- package/dist/contracts/grants.js +130 -43
- package/dist/contracts/ids.d.ts +2 -2
- package/dist/contracts/ids.js +2 -4
- package/dist/contracts/internal.js +2 -2
- package/dist/contracts/mcp.d.ts +17 -556
- package/dist/contracts/mcp.js +9 -103
- package/dist/contracts/me.js +3 -4
- package/dist/contracts/notifications.d.ts +108 -0
- package/dist/contracts/notifications.js +54 -0
- package/dist/contracts/oauth-resources.d.ts +18 -1
- package/dist/contracts/oauth-resources.js +22 -2
- package/dist/contracts/operations.d.ts +2 -50
- package/dist/contracts/operations.js +0 -19
- package/dist/contracts/pages.d.ts +8 -4
- package/dist/contracts/pages.js +16 -3
- package/dist/contracts/platform.js +10 -8
- package/dist/contracts/problem-document.d.ts +14 -0
- package/dist/contracts/problem-document.js +35 -0
- package/dist/contracts/push-new.d.ts +21 -6
- package/dist/contracts/push-new.js +3 -7
- package/dist/contracts/resources.d.ts +8 -7
- package/dist/contracts/resources.js +9 -6
- package/dist/contracts/runtime-api.d.ts +162 -92
- package/dist/contracts/runtime-api.js +99 -27
- package/dist/contracts/runtime-app.d.ts +41 -11
- package/dist/contracts/runtime-app.js +22 -1
- package/dist/contracts/runtime-db.d.ts +424 -0
- package/dist/contracts/runtime-db.js +108 -0
- package/dist/contracts/runtime-storage.d.ts +120 -0
- package/dist/contracts/runtime-storage.js +40 -0
- package/dist/contracts/sf-config-v1.d.ts +49 -1
- package/dist/contracts/sf-config-v1.js +2 -2
- package/dist/contracts/sites.d.ts +1 -0
- package/dist/contracts/sites.js +20 -12
- package/dist/contracts/space-config.d.ts +62 -22
- package/dist/contracts/space-config.js +173 -47
- package/dist/contracts/spaces.d.ts +79 -45
- package/dist/contracts/spaces.js +66 -50
- package/dist/contracts/superadmin-spaces.d.ts +13 -2
- package/dist/contracts/superadmin-spaces.js +15 -0
- package/dist/contracts/superadmin-tenants.js +4 -4
- package/dist/contracts/superadmin.d.ts +4 -0
- package/dist/contracts/superadmin.js +14 -1
- package/dist/contracts/tags.d.ts +17 -17
- package/dist/contracts/tags.js +1 -1
- package/dist/contracts/teams.js +25 -16
- package/dist/contracts/theme-json.js +1 -1
- package/dist/contracts/transfers.js +4 -4
- package/dist/contracts/variables.js +3 -3
- package/dist/contracts/zero.d.ts +204 -338
- package/dist/contracts/zero.js +94 -98
- package/dist/dashboard-paths/index.d.ts +16 -1
- package/dist/dashboard-paths/index.js +18 -0
- package/dist/docs/agent-handoff-document.d.ts +2 -2
- package/dist/docs/agent-handoff-document.js +8 -7
- package/dist/docs/agent-prose.d.ts +22 -3
- package/dist/docs/agent-prose.js +221 -51
- package/dist/docs/agent-setup.d.ts +13 -10
- package/dist/docs/agent-setup.js +279 -53
- package/dist/docs/agent-solutions.d.ts +6 -6
- package/dist/docs/agent-solutions.js +23 -21
- package/dist/docs/catalog.d.ts +29 -74
- package/dist/docs/catalog.js +65 -92
- package/dist/docs/error-docs.js +293 -221
- package/dist/docs/skill-distribution.d.ts +2 -0
- package/dist/docs/skill-distribution.js +7 -4
- package/dist/docs/skills.d.ts +546 -0
- package/dist/docs/skills.js +340 -0
- package/dist/utils/browser-credential.d.ts +19 -0
- package/dist/utils/browser-credential.js +62 -0
- package/dist/utils/comment-avatars.d.ts +7 -0
- package/dist/utils/comment-avatars.js +42 -0
- package/dist/utils/content-type.d.ts +3 -3
- package/dist/utils/content-type.js +38 -61
- package/dist/utils/domain-diagnostics.d.ts +5 -5
- package/dist/utils/domain-diagnostics.js +21 -10
- package/dist/utils/local-space-state.d.ts +78 -4
- package/dist/utils/local-space-state.js +383 -82
- package/dist/utils/one-shot-replay.d.ts +14 -0
- package/dist/utils/one-shot-replay.js +21 -0
- package/dist/utils/page-preview.d.ts +7 -0
- package/dist/utils/page-preview.js +9 -0
- package/dist/utils/pages.d.ts +2 -11
- package/dist/utils/pages.js +58 -14
- package/dist/utils/publish-form-data.d.ts +22 -0
- package/dist/utils/publish-form-data.js +26 -0
- package/dist/utils/publish-policy.js +20 -4
- package/dist/utils/query-keys.d.ts +7 -4
- package/dist/utils/query-keys.js +13 -4
- package/dist/utils/runtime-paths.d.ts +3 -0
- package/dist/utils/runtime-paths.js +7 -0
- package/dist/utils/secure-local-file.d.ts +73 -0
- package/dist/utils/secure-local-file.js +1373 -0
- package/dist/utils/space-config.d.ts +13 -4
- package/dist/utils/space-config.js +123 -15
- package/dist/utils/storage-policy.d.ts +20 -0
- package/dist/utils/storage-policy.fixtures.json +174 -0
- package/dist/utils/storage-policy.js +193 -0
- package/dist/utils/upload-session.d.ts +12 -1
- package/dist/utils/upload-session.js +37 -2
- package/dist/utils/version-file-browser.d.ts +13 -0
- package/dist/utils/version-file-browser.js +59 -0
- package/package.json +19 -1
- package/dist/contracts/mounts.d.ts +0 -142
- package/dist/contracts/mounts.js +0 -56
- package/dist/utils/asset-fingerprint.d.ts +0 -64
- package/dist/utils/asset-fingerprint.js +0 -620
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type Grant } from "../contracts/grants.js";
|
|
2
2
|
import { type SpaceConfig, type SpaceConfigFile } from "../contracts/space-config.js";
|
|
3
|
-
export type SpaceConfigBlockingIssueCode = "config_file_too_large" | "config_templates_over_limit" | "config_meta_too_long" | "config_invalid" | "inject_snippet_too_large" | "inject_invalid";
|
|
3
|
+
export type SpaceConfigBlockingIssueCode = "config_file_too_large" | "config_templates_over_limit" | "config_meta_too_long" | "config_name_too_long" | "config_functions_key_removed" | "config_invalid" | "config_runtime_invalid_kind" | "config_runtime_entry_missing" | "inject_snippet_too_large" | "inject_invalid";
|
|
4
4
|
export type SpaceConfigIssue = {
|
|
5
5
|
severity: "info" | "warning" | "error";
|
|
6
6
|
code: SpaceConfigBlockingIssueCode | "single_html_index" | "files_mode_inferred";
|
|
@@ -15,12 +15,21 @@ export declare function compileSpaceConfigPublicGrant(fileConfig: Pick<SpaceConf
|
|
|
15
15
|
type SpaceConfigBlockingIssue = SpaceConfigIssue & {
|
|
16
16
|
code: SpaceConfigBlockingIssueCode;
|
|
17
17
|
};
|
|
18
|
+
/**
|
|
19
|
+
* Keys a committed `sf.jsonc` owns outright: they are facts about the content
|
|
20
|
+
* of a version (who may read it, what it is called, what runs it), so they roll
|
|
21
|
+
* forward and back with the version instead of living in the space's mutable
|
|
22
|
+
* overlay. API intake refuses them rather than storing or dropping them.
|
|
23
|
+
*/
|
|
24
|
+
export declare const SPACE_CONFIG_FILE_ONLY_KEYS: readonly ["access", "name", "runtime"];
|
|
18
25
|
/**
|
|
19
26
|
* Validates a space `config` overlay at API intake (PATCH /spaces, publish
|
|
20
|
-
* `config`): the 16 KB compiled-overlay cap plus the shared
|
|
21
|
-
* Returns the first blocking issue, or null when the overlay is
|
|
27
|
+
* `config`): file-only keys, the 16 KB compiled-overlay cap, plus the shared
|
|
28
|
+
* shape caps. Returns the first blocking issue, or null when the overlay is
|
|
29
|
+
* acceptable. Takes the raw request value, because the overlay schema strips
|
|
30
|
+
* file-only keys and this is what has to see them.
|
|
22
31
|
*/
|
|
23
|
-
export declare function spaceConfigOverlayIssue(config:
|
|
32
|
+
export declare function spaceConfigOverlayIssue(config: unknown): SpaceConfigBlockingIssue | null;
|
|
24
33
|
export declare function stripJsonComments(raw: string): string;
|
|
25
34
|
export type ParsedSpaceConfigFile = {
|
|
26
35
|
/** Parsed config (cap- and shape-valid keys only); null when unusable. */
|
|
@@ -123,19 +123,103 @@ function capIssues(candidate) {
|
|
|
123
123
|
}
|
|
124
124
|
return issues;
|
|
125
125
|
}
|
|
126
|
+
// File-only keys (`name`, `runtime`) never reach the API overlay, so their
|
|
127
|
+
// checks live beside the shared caps rather than inside them. Both run BEFORE
|
|
128
|
+
// zod so a bad declaration reports its stable code instead of a shape error.
|
|
129
|
+
const RUNTIME_KINDS = new Set(["zero", "functions"]);
|
|
130
|
+
function fileOnlyIssues(candidate) {
|
|
131
|
+
const issues = [];
|
|
132
|
+
const name = candidate.name;
|
|
133
|
+
if (typeof name === "string" && name.trim().length > SPACE_CONFIG_CAPS.nameChars) {
|
|
134
|
+
issues.push({
|
|
135
|
+
severity: "error",
|
|
136
|
+
code: "config_name_too_long",
|
|
137
|
+
message: `name supports up to ${SPACE_CONFIG_CAPS.nameChars} characters.`,
|
|
138
|
+
path: "name",
|
|
139
|
+
details: { length: name.trim().length, limit: SPACE_CONFIG_CAPS.nameChars },
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
const runtime = candidate.runtime;
|
|
143
|
+
if (runtime === undefined) {
|
|
144
|
+
return issues;
|
|
145
|
+
}
|
|
146
|
+
if (typeof runtime !== "object" || runtime === null || Array.isArray(runtime)) {
|
|
147
|
+
issues.push({
|
|
148
|
+
severity: "error",
|
|
149
|
+
code: "config_runtime_invalid_kind",
|
|
150
|
+
message: 'runtime must be an object declaring a kind of "zero" or "functions".',
|
|
151
|
+
path: "runtime",
|
|
152
|
+
});
|
|
153
|
+
return issues;
|
|
154
|
+
}
|
|
155
|
+
const declaration = runtime;
|
|
156
|
+
const kind = declaration.kind;
|
|
157
|
+
if (typeof kind !== "string" || !RUNTIME_KINDS.has(kind)) {
|
|
158
|
+
issues.push({
|
|
159
|
+
severity: "error",
|
|
160
|
+
code: "config_runtime_invalid_kind",
|
|
161
|
+
message: 'runtime.kind must be "zero" or "functions".',
|
|
162
|
+
path: "runtime.kind",
|
|
163
|
+
...(typeof kind === "string" ? { details: { kind } } : {}),
|
|
164
|
+
});
|
|
165
|
+
return issues;
|
|
166
|
+
}
|
|
167
|
+
if (kind === "zero") {
|
|
168
|
+
// Zero is never inferred, so a declaration that names neither entry has
|
|
169
|
+
// nothing to compile — failing here beats publishing static files under a
|
|
170
|
+
// config that promised a database app.
|
|
171
|
+
for (const key of ["server", "client"]) {
|
|
172
|
+
const value = declaration[key];
|
|
173
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
174
|
+
issues.push({
|
|
175
|
+
severity: "error",
|
|
176
|
+
code: "config_runtime_entry_missing",
|
|
177
|
+
message: `runtime.${key} is required and must name a module when runtime.kind is "zero".`,
|
|
178
|
+
path: `runtime.${key}`,
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return issues;
|
|
183
|
+
}
|
|
184
|
+
const entry = declaration.entry;
|
|
185
|
+
if (entry !== undefined && (typeof entry !== "string" || entry.trim().length === 0)) {
|
|
186
|
+
issues.push({
|
|
187
|
+
severity: "error",
|
|
188
|
+
code: "config_runtime_entry_missing",
|
|
189
|
+
message: "runtime.entry must name a module when present; omit it to detect the worker instead.",
|
|
190
|
+
path: "runtime.entry",
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
return issues;
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Keys a committed `sf.jsonc` owns outright: they are facts about the content
|
|
197
|
+
* of a version (who may read it, what it is called, what runs it), so they roll
|
|
198
|
+
* forward and back with the version instead of living in the space's mutable
|
|
199
|
+
* overlay. API intake refuses them rather than storing or dropping them.
|
|
200
|
+
*/
|
|
201
|
+
export const SPACE_CONFIG_FILE_ONLY_KEYS = ["access", "name", "runtime"];
|
|
126
202
|
/**
|
|
127
203
|
* Validates a space `config` overlay at API intake (PATCH /spaces, publish
|
|
128
|
-
* `config`): the 16 KB compiled-overlay cap plus the shared
|
|
129
|
-
* Returns the first blocking issue, or null when the overlay is
|
|
204
|
+
* `config`): file-only keys, the 16 KB compiled-overlay cap, plus the shared
|
|
205
|
+
* shape caps. Returns the first blocking issue, or null when the overlay is
|
|
206
|
+
* acceptable. Takes the raw request value, because the overlay schema strips
|
|
207
|
+
* file-only keys and this is what has to see them.
|
|
130
208
|
*/
|
|
131
209
|
export function spaceConfigOverlayIssue(config) {
|
|
132
|
-
if (
|
|
133
|
-
return
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
210
|
+
if (config === null || config === undefined || typeof config !== "object") {
|
|
211
|
+
return null;
|
|
212
|
+
}
|
|
213
|
+
for (const key of SPACE_CONFIG_FILE_ONLY_KEYS) {
|
|
214
|
+
if (Object.prototype.hasOwnProperty.call(config, key)) {
|
|
215
|
+
return {
|
|
216
|
+
severity: "error",
|
|
217
|
+
code: "config_invalid",
|
|
218
|
+
message: `${key} is only valid in the committed ${SPACE_CONFIG_CANONICAL_FILE} file.`,
|
|
219
|
+
path: key,
|
|
220
|
+
details: { file: SPACE_CONFIG_CANONICAL_FILE },
|
|
221
|
+
};
|
|
222
|
+
}
|
|
139
223
|
}
|
|
140
224
|
const encoded = JSON.stringify(config);
|
|
141
225
|
if (byteLength(encoded) > SPACE_CONFIG_CAPS.overlayBytes) {
|
|
@@ -149,9 +233,18 @@ export function spaceConfigOverlayIssue(config) {
|
|
|
149
233
|
}
|
|
150
234
|
return capIssues(config)[0] ?? null;
|
|
151
235
|
}
|
|
152
|
-
//
|
|
153
|
-
//
|
|
154
|
-
const ROUTING_KEYS = new Set(["
|
|
236
|
+
// Other platforms' routing vocabulary. We have one matcher grammar; naming it
|
|
237
|
+
// four more ways would be four more things to keep in sync.
|
|
238
|
+
const ROUTING_KEYS = new Set(["proxies", "proxy", "routes"]);
|
|
239
|
+
// OUR routing vocabulary. These keys are real sf.jsonc keys, owned end to end
|
|
240
|
+
// by the routing compiler (crates/stattic-runtime-core): the strict v1 compiler
|
|
241
|
+
// validates them and the merge folds them in behind `_redirects`/`_headers`.
|
|
242
|
+
// This lane resolves the serving config, so it neither validates nor carries
|
|
243
|
+
// them — it just leaves them alone.
|
|
244
|
+
const ROUTING_RULE_KEYS = new Set(["redirects", "rewrites", "headers"]);
|
|
245
|
+
// Replaced by the `runtime` declaration; never silently ignored like other
|
|
246
|
+
// unknown keys, because it is the one key whose loss changes what gets served.
|
|
247
|
+
const REMOVED_FUNCTIONS_KEY = "functions";
|
|
155
248
|
const KNOWN_FILE_KEYS = new Set(Object.keys(spaceConfigFileSchema.shape));
|
|
156
249
|
// Strips JSONC comments and trailing commas (string-aware single pass).
|
|
157
250
|
export function stripJsonComments(raw) {
|
|
@@ -290,7 +383,23 @@ export function parseSpaceConfigFile(raw, options = {}) {
|
|
|
290
383
|
if (KNOWN_FILE_KEYS.has(key)) {
|
|
291
384
|
continue;
|
|
292
385
|
}
|
|
293
|
-
if (
|
|
386
|
+
if (ROUTING_RULE_KEYS.has(key)) {
|
|
387
|
+
delete record[key];
|
|
388
|
+
continue;
|
|
389
|
+
}
|
|
390
|
+
if (key === REMOVED_FUNCTIONS_KEY) {
|
|
391
|
+
// The runtime declaration replaced this key outright (spec "The old
|
|
392
|
+
// top-level functions.* key is removed in the same cutover"): warning-and-
|
|
393
|
+
// strip would publish static files under a config that asked for a
|
|
394
|
+
// worker, so it blocks with its own code instead.
|
|
395
|
+
issues.push({
|
|
396
|
+
severity: "error",
|
|
397
|
+
code: "config_functions_key_removed",
|
|
398
|
+
message: `The "${REMOVED_FUNCTIONS_KEY}" key was removed from ${configPath}; declare runtime.kind "functions" instead.`,
|
|
399
|
+
path: key,
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
else if (ROUTING_KEYS.has(key)) {
|
|
294
403
|
issues.push({
|
|
295
404
|
severity: "error",
|
|
296
405
|
code: "config_invalid",
|
|
@@ -308,8 +417,7 @@ export function parseSpaceConfigFile(raw, options = {}) {
|
|
|
308
417
|
}
|
|
309
418
|
delete record[key];
|
|
310
419
|
}
|
|
311
|
-
|
|
312
|
-
issues.push(...caps);
|
|
420
|
+
issues.push(...capIssues(record), ...fileOnlyIssues(record));
|
|
313
421
|
if (issues.some((issue) => issue.severity === "error")) {
|
|
314
422
|
return { config: null, issues };
|
|
315
423
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export declare const STORAGE_OBJECT_MAX_FILE_BYTES: number;
|
|
2
|
+
export declare const STORAGE_OBJECT_MAX_OWNER_BYTES: number;
|
|
3
|
+
export declare const STORAGE_CONTENT_SNIFF_BYTES = 512;
|
|
4
|
+
export declare const STORAGE_BLOCKED_CONTENT_TYPES: string[];
|
|
5
|
+
export declare const STORAGE_BLOCKED_CONTENT_TYPE_FRAGMENTS: string[];
|
|
6
|
+
export declare const STORAGE_BLOCKED_MAGIC_HEX: string[];
|
|
7
|
+
export declare const STORAGE_BLOCKED_TEXT_PREFIXES: string[];
|
|
8
|
+
export declare function storageDeclaredTypeBlocked(contentType: string): boolean;
|
|
9
|
+
export declare function storageContentBlocked(contentType: string, input: Uint8Array): boolean;
|
|
10
|
+
export type StorageByteRange = {
|
|
11
|
+
unsatisfiable: true;
|
|
12
|
+
} | {
|
|
13
|
+
start: number;
|
|
14
|
+
end: number;
|
|
15
|
+
header: string;
|
|
16
|
+
};
|
|
17
|
+
export declare function storageParseSingleRange(rangeHeader: string | null | undefined, size: number): StorageByteRange | null;
|
|
18
|
+
export declare function storageIfNoneMatchMatches(header: string | null | undefined, etag: string): boolean;
|
|
19
|
+
export declare function storageParseHttpDate(value: string): number | null;
|
|
20
|
+
export declare function storageIfRangeMatches(ifRange: string, etag: string, lastModified: string): boolean;
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
{
|
|
2
|
+
"content": [
|
|
3
|
+
{
|
|
4
|
+
"name": "declared html",
|
|
5
|
+
"contentType": "text/html; charset=utf-8",
|
|
6
|
+
"hex": "706c61696e",
|
|
7
|
+
"expected": true
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"name": "declared javascript",
|
|
11
|
+
"contentType": "application/javascript",
|
|
12
|
+
"hex": "706c61696e",
|
|
13
|
+
"expected": true
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"name": "windows executable magic",
|
|
17
|
+
"contentType": "application/octet-stream",
|
|
18
|
+
"hex": "4d5a0000",
|
|
19
|
+
"expected": true
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"name": "mach executable magic",
|
|
23
|
+
"contentType": "application/octet-stream",
|
|
24
|
+
"hex": "feedfacf0000",
|
|
25
|
+
"expected": true
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"name": "trimmed active html",
|
|
29
|
+
"contentType": "text/plain",
|
|
30
|
+
"hex": "20090a3c7363726970743e",
|
|
31
|
+
"expected": true
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"name": "svg remains allowed",
|
|
35
|
+
"contentType": "image/svg+xml",
|
|
36
|
+
"hex": "3c73766720786d6c6e733d22687474703a2f2f7777772e77332e6f72672f323030302f737667223e",
|
|
37
|
+
"expected": false
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"name": "plain bytes remain allowed",
|
|
41
|
+
"contentType": "application/octet-stream",
|
|
42
|
+
"hex": "68656c6c6f2073746f72616765",
|
|
43
|
+
"expected": false
|
|
44
|
+
}
|
|
45
|
+
],
|
|
46
|
+
"ranges": [
|
|
47
|
+
{ "name": "absent", "header": null, "size": 14, "expected": null },
|
|
48
|
+
{
|
|
49
|
+
"name": "full tail",
|
|
50
|
+
"header": "bytes=7-",
|
|
51
|
+
"size": 14,
|
|
52
|
+
"expected": { "start": 7, "end": 13, "header": "bytes=7-13" }
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"name": "bounded",
|
|
56
|
+
"header": "bytes=0-6",
|
|
57
|
+
"size": 14,
|
|
58
|
+
"expected": { "start": 0, "end": 6, "header": "bytes=0-6" }
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
"name": "suffix",
|
|
62
|
+
"header": "bytes=-4",
|
|
63
|
+
"size": 14,
|
|
64
|
+
"expected": { "start": 10, "end": 13, "header": "bytes=10-13" }
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
"name": "clamped",
|
|
68
|
+
"header": "bytes=10-99",
|
|
69
|
+
"size": 14,
|
|
70
|
+
"expected": { "start": 10, "end": 13, "header": "bytes=10-13" }
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
"name": "unsatisfiable",
|
|
74
|
+
"header": "bytes=20-30",
|
|
75
|
+
"size": 14,
|
|
76
|
+
"expected": { "unsatisfiable": true }
|
|
77
|
+
},
|
|
78
|
+
{ "name": "multiple ignored", "header": "bytes=0-1,4-5", "size": 14, "expected": null },
|
|
79
|
+
{ "name": "malformed ignored", "header": "items=0-1", "size": 14, "expected": null }
|
|
80
|
+
],
|
|
81
|
+
"ifNoneMatch": [
|
|
82
|
+
{ "name": "exact", "header": "\"abc\"", "etag": "\"abc\"", "expected": true },
|
|
83
|
+
{ "name": "weak", "header": "W/\"abc\"", "etag": "\"abc\"", "expected": true },
|
|
84
|
+
{ "name": "list", "header": "\"no\", W/\"abc\"", "etag": "\"abc\"", "expected": true },
|
|
85
|
+
{ "name": "wildcard", "header": "*", "etag": "\"abc\"", "expected": true },
|
|
86
|
+
{ "name": "miss", "header": "\"no\"", "etag": "\"abc\"", "expected": false }
|
|
87
|
+
],
|
|
88
|
+
"ifRange": [
|
|
89
|
+
{
|
|
90
|
+
"name": "exact etag",
|
|
91
|
+
"header": "\"abc\"",
|
|
92
|
+
"etag": "\"abc\"",
|
|
93
|
+
"lastModified": "Wed, 21 Oct 2015 07:28:00 GMT",
|
|
94
|
+
"expected": true
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
"name": "weak etag rejected",
|
|
98
|
+
"header": "W/\"abc\"",
|
|
99
|
+
"etag": "\"abc\"",
|
|
100
|
+
"lastModified": "Wed, 21 Oct 2015 07:28:00 GMT",
|
|
101
|
+
"expected": false
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
"name": "newer date",
|
|
105
|
+
"header": "Wed, 21 Oct 2015 07:29:00 GMT",
|
|
106
|
+
"etag": "\"abc\"",
|
|
107
|
+
"lastModified": "Wed, 21 Oct 2015 07:28:00 GMT",
|
|
108
|
+
"expected": true
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
"name": "older date",
|
|
112
|
+
"header": "Wed, 21 Oct 2015 07:27:00 GMT",
|
|
113
|
+
"etag": "\"abc\"",
|
|
114
|
+
"lastModified": "Wed, 21 Oct 2015 07:28:00 GMT",
|
|
115
|
+
"expected": false
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
"name": "leap second",
|
|
119
|
+
"header": "Wed, 21 Oct 2015 07:28:60 GMT",
|
|
120
|
+
"etag": "\"abc\"",
|
|
121
|
+
"lastModified": "Wed, 21 Oct 2015 07:28:59 GMT",
|
|
122
|
+
"expected": true
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
"name": "obsolete RFC 850 date",
|
|
126
|
+
"header": "Sunday, 06-Nov-94 08:49:37 GMT",
|
|
127
|
+
"etag": "\"abc\"",
|
|
128
|
+
"lastModified": "Sun, 06 Nov 1994 08:49:37 GMT",
|
|
129
|
+
"expected": true
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
"name": "obsolete asctime date",
|
|
133
|
+
"header": "Sun Nov 6 08:49:37 1994",
|
|
134
|
+
"etag": "\"abc\"",
|
|
135
|
+
"lastModified": "Sun, 06 Nov 1994 08:49:37 GMT",
|
|
136
|
+
"expected": true
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
"name": "relative date rejected",
|
|
140
|
+
"header": "tomorrow",
|
|
141
|
+
"etag": "\"abc\"",
|
|
142
|
+
"lastModified": "Wed, 21 Oct 2015 07:28:00 GMT",
|
|
143
|
+
"expected": false
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
"name": "wrong weekday rejected",
|
|
147
|
+
"header": "Thu, 21 Oct 2015 07:29:00 GMT",
|
|
148
|
+
"etag": "\"abc\"",
|
|
149
|
+
"lastModified": "Wed, 21 Oct 2015 07:28:00 GMT",
|
|
150
|
+
"expected": false
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
"name": "invalid calendar date rejected",
|
|
154
|
+
"header": "Fri, 31 Feb 2023 07:29:00 GMT",
|
|
155
|
+
"etag": "\"abc\"",
|
|
156
|
+
"lastModified": "Wed, 21 Oct 2015 07:28:00 GMT",
|
|
157
|
+
"expected": false
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
"name": "out-of-range time rejected",
|
|
161
|
+
"header": "Wed, 21 Oct 2015 07:28:61 GMT",
|
|
162
|
+
"etag": "\"abc\"",
|
|
163
|
+
"lastModified": "Wed, 21 Oct 2015 07:28:00 GMT",
|
|
164
|
+
"expected": false
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
"name": "trailing garbage rejected",
|
|
168
|
+
"header": "Wed, 21 Oct 2015 07:29:00 GMT later",
|
|
169
|
+
"etag": "\"abc\"",
|
|
170
|
+
"lastModified": "Wed, 21 Oct 2015 07:28:00 GMT",
|
|
171
|
+
"expected": false
|
|
172
|
+
}
|
|
173
|
+
]
|
|
174
|
+
}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
// Single source of truth for storage HTTP and object-intake policy.
|
|
2
|
+
//
|
|
3
|
+
// TypeScript adapters import this module directly. The PHP runtime binding is
|
|
4
|
+
// generated into runtime/engine/shared/storage-policy.generated.php by
|
|
5
|
+
// `bun --filter @spacefast/control-plane runtime:codegen-policy`. Both execute
|
|
6
|
+
// the same storage-policy.fixtures.json corpus.
|
|
7
|
+
export const STORAGE_OBJECT_MAX_FILE_BYTES = 5 * 1024 * 1024;
|
|
8
|
+
export const STORAGE_OBJECT_MAX_OWNER_BYTES = 100 * 1024 * 1024;
|
|
9
|
+
export const STORAGE_CONTENT_SNIFF_BYTES = 512;
|
|
10
|
+
export const STORAGE_BLOCKED_CONTENT_TYPES = [
|
|
11
|
+
"application/java-archive",
|
|
12
|
+
"application/java-vm",
|
|
13
|
+
"application/vnd.microsoft.portable-executable",
|
|
14
|
+
"application/x-dosexec",
|
|
15
|
+
"application/x-httpd-php",
|
|
16
|
+
"application/x-java-archive",
|
|
17
|
+
"application/x-mach-binary",
|
|
18
|
+
"application/x-msdownload",
|
|
19
|
+
"application/xhtml+xml",
|
|
20
|
+
"text/html",
|
|
21
|
+
];
|
|
22
|
+
export const STORAGE_BLOCKED_CONTENT_TYPE_FRAGMENTS = ["javascript", "ecmascript"];
|
|
23
|
+
export const STORAGE_BLOCKED_MAGIC_HEX = [
|
|
24
|
+
"4d5a",
|
|
25
|
+
"7f454c46",
|
|
26
|
+
"feedface",
|
|
27
|
+
"cefaedfe",
|
|
28
|
+
"feedfacf",
|
|
29
|
+
"cffaedfe",
|
|
30
|
+
"cafebabe",
|
|
31
|
+
];
|
|
32
|
+
export const STORAGE_BLOCKED_TEXT_PREFIXES = ["#!", "<!doctype html", "<html", "<script", "<?php"];
|
|
33
|
+
const BLOCKED_CONTENT_TYPES = new Set(STORAGE_BLOCKED_CONTENT_TYPES);
|
|
34
|
+
export function storageDeclaredTypeBlocked(contentType) {
|
|
35
|
+
const declared = contentType.split(";", 1)[0]?.trim().toLowerCase() ?? "";
|
|
36
|
+
return (BLOCKED_CONTENT_TYPES.has(declared) ||
|
|
37
|
+
STORAGE_BLOCKED_CONTENT_TYPE_FRAGMENTS.some((fragment) => declared.includes(fragment)));
|
|
38
|
+
}
|
|
39
|
+
function bytesStartWithHex(bytes, hex) {
|
|
40
|
+
if (bytes.byteLength < hex.length / 2)
|
|
41
|
+
return false;
|
|
42
|
+
for (let offset = 0; offset < hex.length; offset += 2) {
|
|
43
|
+
if (bytes[offset / 2] !== Number.parseInt(hex.slice(offset, offset + 2), 16)) {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
export function storageContentBlocked(contentType, input) {
|
|
50
|
+
if (storageDeclaredTypeBlocked(contentType))
|
|
51
|
+
return true;
|
|
52
|
+
const prefix = input.subarray(0, STORAGE_CONTENT_SNIFF_BYTES);
|
|
53
|
+
if (STORAGE_BLOCKED_MAGIC_HEX.some((magic) => bytesStartWithHex(prefix, magic))) {
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
const decoded = new TextDecoder().decode(prefix);
|
|
57
|
+
let contentStart = 0;
|
|
58
|
+
while (contentStart < decoded.length) {
|
|
59
|
+
const code = decoded.charCodeAt(contentStart);
|
|
60
|
+
if (code !== 0 && code !== 9 && code !== 10 && code !== 11 && code !== 13 && code !== 32) {
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
contentStart += 1;
|
|
64
|
+
}
|
|
65
|
+
const text = decoded.slice(contentStart).toLowerCase();
|
|
66
|
+
return STORAGE_BLOCKED_TEXT_PREFIXES.some((blocked) => text.startsWith(blocked));
|
|
67
|
+
}
|
|
68
|
+
export function storageParseSingleRange(rangeHeader, size) {
|
|
69
|
+
if (!rangeHeader || rangeHeader.trim() === "" || rangeHeader.includes(","))
|
|
70
|
+
return null;
|
|
71
|
+
const match = /^bytes=(\d*)-(\d*)$/.exec(rangeHeader.trim());
|
|
72
|
+
if (!match || (!match[1] && !match[2]))
|
|
73
|
+
return null;
|
|
74
|
+
if (!match[1]) {
|
|
75
|
+
const suffix = Number(match[2]);
|
|
76
|
+
if (!Number.isSafeInteger(suffix) || suffix <= 0 || size <= 0) {
|
|
77
|
+
return { unsatisfiable: true };
|
|
78
|
+
}
|
|
79
|
+
const start = Math.max(0, size - suffix);
|
|
80
|
+
const end = size - 1;
|
|
81
|
+
return { start, end, header: `bytes=${start}-${end}` };
|
|
82
|
+
}
|
|
83
|
+
const start = Number(match[1]);
|
|
84
|
+
const requestedEnd = match[2] ? Number(match[2]) : size - 1;
|
|
85
|
+
const end = Math.min(requestedEnd, size - 1);
|
|
86
|
+
if (!Number.isSafeInteger(start) ||
|
|
87
|
+
!Number.isSafeInteger(requestedEnd) ||
|
|
88
|
+
start >= size ||
|
|
89
|
+
start > end ||
|
|
90
|
+
size < 0) {
|
|
91
|
+
return { unsatisfiable: true };
|
|
92
|
+
}
|
|
93
|
+
return { start, end, header: `bytes=${start}-${end}` };
|
|
94
|
+
}
|
|
95
|
+
function storageEtagOpaqueTag(etag) {
|
|
96
|
+
const trimmed = etag.trim();
|
|
97
|
+
return /^W\//i.test(trimmed) ? trimmed.slice(2).trim() : trimmed;
|
|
98
|
+
}
|
|
99
|
+
export function storageIfNoneMatchMatches(header, etag) {
|
|
100
|
+
const value = header?.trim() ?? "";
|
|
101
|
+
if (value === "")
|
|
102
|
+
return false;
|
|
103
|
+
if (value === "*")
|
|
104
|
+
return true;
|
|
105
|
+
const target = storageEtagOpaqueTag(etag);
|
|
106
|
+
if (target === "")
|
|
107
|
+
return false;
|
|
108
|
+
return value
|
|
109
|
+
.split(",")
|
|
110
|
+
.map((candidate) => candidate.trim())
|
|
111
|
+
.filter(Boolean)
|
|
112
|
+
.some((candidate) => storageEtagOpaqueTag(candidate) === target);
|
|
113
|
+
}
|
|
114
|
+
const HTTP_MONTHS = [
|
|
115
|
+
"Jan",
|
|
116
|
+
"Feb",
|
|
117
|
+
"Mar",
|
|
118
|
+
"Apr",
|
|
119
|
+
"May",
|
|
120
|
+
"Jun",
|
|
121
|
+
"Jul",
|
|
122
|
+
"Aug",
|
|
123
|
+
"Sep",
|
|
124
|
+
"Oct",
|
|
125
|
+
"Nov",
|
|
126
|
+
"Dec",
|
|
127
|
+
];
|
|
128
|
+
const HTTP_WEEKDAYS = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
|
|
129
|
+
const HTTP_WEEKDAYS_LONG = [
|
|
130
|
+
"Sunday",
|
|
131
|
+
"Monday",
|
|
132
|
+
"Tuesday",
|
|
133
|
+
"Wednesday",
|
|
134
|
+
"Thursday",
|
|
135
|
+
"Friday",
|
|
136
|
+
"Saturday",
|
|
137
|
+
];
|
|
138
|
+
function storageHttpDateTimestamp(weekday, day, month, year, hour, minute, second) {
|
|
139
|
+
if (hour > 23 || minute > 59 || second > 60)
|
|
140
|
+
return null;
|
|
141
|
+
const normalizedSecond = Math.min(second, 59);
|
|
142
|
+
const date = new Date(0);
|
|
143
|
+
date.setUTCFullYear(year, month, day);
|
|
144
|
+
date.setUTCHours(hour, minute, normalizedSecond, 0);
|
|
145
|
+
if (date.getUTCFullYear() !== year ||
|
|
146
|
+
date.getUTCMonth() !== month ||
|
|
147
|
+
date.getUTCDate() !== day ||
|
|
148
|
+
date.getUTCHours() !== hour ||
|
|
149
|
+
date.getUTCMinutes() !== minute ||
|
|
150
|
+
date.getUTCSeconds() !== normalizedSecond ||
|
|
151
|
+
(weekday !== null && date.getUTCDay() !== weekday)) {
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
return date.getTime() + (second === 60 ? 1_000 : 0);
|
|
155
|
+
}
|
|
156
|
+
// HTTP-date has exactly three accepted wire forms (RFC 9110 §5.6.7). General
|
|
157
|
+
// date parsers also accept relative/free-form input such as `tomorrow`, so
|
|
158
|
+
// validators must parse the grammar and validate its calendar components.
|
|
159
|
+
export function storageParseHttpDate(value) {
|
|
160
|
+
const input = value.trim();
|
|
161
|
+
let match = /^(Sun|Mon|Tue|Wed|Thu|Fri|Sat), (\d{2}) (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) (\d{4}) (\d{2}):(\d{2}):(\d{2}) GMT$/.exec(input);
|
|
162
|
+
if (match) {
|
|
163
|
+
return storageHttpDateTimestamp(HTTP_WEEKDAYS.indexOf(match[1] ?? ""), Number(match[2]), HTTP_MONTHS.indexOf(match[3] ?? ""), Number(match[4]), Number(match[5]), Number(match[6]), Number(match[7]));
|
|
164
|
+
}
|
|
165
|
+
match =
|
|
166
|
+
/^(Sunday|Monday|Tuesday|Wednesday|Thursday|Friday|Saturday), (\d{2})-(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)-(\d{2}) (\d{2}):(\d{2}):(\d{2}) GMT$/.exec(input);
|
|
167
|
+
if (match) {
|
|
168
|
+
const now = new Date();
|
|
169
|
+
let year = Math.floor(now.getUTCFullYear() / 100) * 100 + Number(match[4]);
|
|
170
|
+
const candidate = storageHttpDateTimestamp(null, Number(match[2]), HTTP_MONTHS.indexOf(match[3] ?? ""), year, Number(match[5]), Number(match[6]), Number(match[7]));
|
|
171
|
+
const fiftyYearsFromNow = new Date(now);
|
|
172
|
+
fiftyYearsFromNow.setUTCFullYear(now.getUTCFullYear() + 50);
|
|
173
|
+
if (candidate !== null && candidate > fiftyYearsFromNow.getTime())
|
|
174
|
+
year -= 100;
|
|
175
|
+
return storageHttpDateTimestamp(HTTP_WEEKDAYS_LONG.indexOf(match[1] ?? ""), Number(match[2]), HTTP_MONTHS.indexOf(match[3] ?? ""), year, Number(match[5]), Number(match[6]), Number(match[7]));
|
|
176
|
+
}
|
|
177
|
+
match =
|
|
178
|
+
/^(Sun|Mon|Tue|Wed|Thu|Fri|Sat) (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) (?:([0-9]{2})| ([0-9])) (\d{2}):(\d{2}):(\d{2}) (\d{4})$/.exec(input);
|
|
179
|
+
if (!match)
|
|
180
|
+
return null;
|
|
181
|
+
return storageHttpDateTimestamp(HTTP_WEEKDAYS.indexOf(match[1] ?? ""), Number(match[3] ?? match[4]), HTTP_MONTHS.indexOf(match[2] ?? ""), Number(match[8]), Number(match[5]), Number(match[6]), Number(match[7]));
|
|
182
|
+
}
|
|
183
|
+
export function storageIfRangeMatches(ifRange, etag, lastModified) {
|
|
184
|
+
const value = ifRange.trim();
|
|
185
|
+
if (value === "" || /^W\//i.test(value))
|
|
186
|
+
return false;
|
|
187
|
+
if (value.startsWith('"')) {
|
|
188
|
+
return !/^W\//i.test(etag.trim()) && etag.trim() === value;
|
|
189
|
+
}
|
|
190
|
+
const requestTime = storageParseHttpDate(value);
|
|
191
|
+
const modifiedTime = storageParseHttpDate(lastModified);
|
|
192
|
+
return requestTime !== null && modifiedTime !== null && requestTime >= modifiedTime;
|
|
193
|
+
}
|
|
@@ -19,10 +19,21 @@ export type UploadSessionEvent = {
|
|
|
19
19
|
target: UploadTarget;
|
|
20
20
|
error: unknown;
|
|
21
21
|
};
|
|
22
|
+
/** The RFC 9457 problem document a runtime upload target returns on failure. */
|
|
23
|
+
export type UploadTargetServerError = {
|
|
24
|
+
code: string;
|
|
25
|
+
message: string;
|
|
26
|
+
pointer?: string;
|
|
27
|
+
type?: string;
|
|
28
|
+
details?: unknown;
|
|
29
|
+
suggestions?: readonly string[];
|
|
30
|
+
requestId?: string;
|
|
31
|
+
};
|
|
22
32
|
export declare class UploadTargetError extends Error {
|
|
23
33
|
readonly target: UploadTarget;
|
|
24
34
|
readonly status: number;
|
|
25
|
-
|
|
35
|
+
readonly serverError?: UploadTargetServerError | undefined;
|
|
36
|
+
constructor(target: UploadTarget, status: number, message: string, serverError?: UploadTargetServerError | undefined);
|
|
26
37
|
}
|
|
27
38
|
export declare function uploadPageHasRemainder(page: UploadSession): boolean;
|
|
28
39
|
export declare const UPLOAD_STALLED_RESUME_MAX_ATTEMPTS = 3;
|
|
@@ -1,13 +1,47 @@
|
|
|
1
1
|
export class UploadTargetError extends Error {
|
|
2
2
|
target;
|
|
3
3
|
status;
|
|
4
|
-
|
|
4
|
+
serverError;
|
|
5
|
+
constructor(target, status, message, serverError) {
|
|
5
6
|
super(message);
|
|
6
7
|
this.target = target;
|
|
7
8
|
this.status = status;
|
|
9
|
+
this.serverError = serverError;
|
|
8
10
|
this.name = "UploadTargetError";
|
|
9
11
|
}
|
|
10
12
|
}
|
|
13
|
+
function parseUploadTargetServerError(raw) {
|
|
14
|
+
let parsed;
|
|
15
|
+
try {
|
|
16
|
+
parsed = JSON.parse(raw);
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return undefined;
|
|
20
|
+
}
|
|
21
|
+
if (!parsed || typeof parsed !== "object") {
|
|
22
|
+
return undefined;
|
|
23
|
+
}
|
|
24
|
+
const record = parsed;
|
|
25
|
+
if (typeof record.code !== "string" || typeof record.detail !== "string") {
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
const stringField = (name) => typeof record[name] === "string" ? record[name] : undefined;
|
|
29
|
+
const suggestions = Array.isArray(record.suggestions)
|
|
30
|
+
? record.suggestions.filter((entry) => typeof entry === "string")
|
|
31
|
+
: [];
|
|
32
|
+
const pointer = stringField("pointer");
|
|
33
|
+
const type = stringField("type");
|
|
34
|
+
const requestId = stringField("requestId");
|
|
35
|
+
return {
|
|
36
|
+
code: record.code,
|
|
37
|
+
message: record.detail,
|
|
38
|
+
...(pointer ? { pointer } : {}),
|
|
39
|
+
...(type ? { type } : {}),
|
|
40
|
+
...(record.details === undefined ? {} : { details: record.details }),
|
|
41
|
+
...(suggestions.length > 0 ? { suggestions } : {}),
|
|
42
|
+
...(requestId ? { requestId } : {}),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
11
45
|
// Manifest paging policy — ONE definition, shared by every uploader (the CLI
|
|
12
46
|
// and the control plane's inline uploader).
|
|
13
47
|
//
|
|
@@ -78,8 +112,9 @@ export async function executeUploadSession(input) {
|
|
|
78
112
|
});
|
|
79
113
|
if (!response.ok) {
|
|
80
114
|
const details = await response.text().catch(() => "");
|
|
115
|
+
const serverError = parseUploadTargetServerError(details);
|
|
81
116
|
const suffix = details.trim() ? `: ${details.trim().slice(0, 1_000)}` : "";
|
|
82
|
-
throw new UploadTargetError(target, response.status, `Upload failed for ${target.path} with ${response.status} ${response.statusText}${suffix}
|
|
117
|
+
throw new UploadTargetError(target, response.status, `Upload failed for ${target.path} with ${response.status} ${response.statusText}${suffix}.`, serverError);
|
|
83
118
|
}
|
|
84
119
|
input.onEvent?.({ type: "target-done", target });
|
|
85
120
|
}
|