Summary
createContentApiS3Signer silently falls back to the legacy generateUploadUrl mutation shape (top-level contentType/expiration/prefix/acl arguments) whenever the upload parameters carry none of suffix/fileName/extension — which is exactly what a default-configured S3UploadClient produces. Current Contember engine only accepts the input: S3GenerateSignedUploadInput envelope, so every default-configured upload fails.
Environment
Reproduction
const sign = createContentApiS3Signer(client)
// Default S3UploadClient parameters — nothing but the content type
// (S3UploadClient.upload builds { contentType: file.type, ...getUploadOptions?.(file) }):
await sign({ contentType: 'text/plain' })
The signer emits:
mutation($contentType_0: String, $expiration_0: Int, $prefix_0: String, $acl_0: S3Acl) {
url_0: generateUploadUrl(contentType: $contentType_0, expiration: $expiration_0, prefix: $prefix_0, acl: $acl_0) { ... }
}
which engine 2.1.0-alpha.39 rejects — verified directly against the Content API:
- envelope form
generateUploadUrl(input: $i) → 200, signed URL returned
- legacy form above → error (
"ACL is not supported" UserInputError; environments without the S3Acl scalar fail earlier with "Unknown type S3Acl" / "Cannot query field generateUploadUrl")
Expected behavior
The signer always requests the URL via generateUploadUrl(input: S3GenerateSignedUploadInput) — the only shape the current engine supports, and the same shape the signer already uses when fileName/suffix/extension happen to be present.
Actual behavior
packages/bindx-uploader/src/utils/urlSigner.ts branches on
const hasNewFormat = params.suffix || params.fileName || params.extension
and builds the legacy top-level-arguments mutation when none of those optional fields is set. A default-configured S3UploadClient (no getUploadOptions) therefore never uploads: the engine rejects the whole mutation. In batched mode a single legacy alias poisons the entire batch even when other aliases use the envelope.
Suspected root cause
packages/bindx-uploader/src/utils/urlSigner.ts, buildGenerateUploadUrlMutation — the hasNewFormat heuristic conflates "caller provided new-format-only fields" with "server supports the new format". The legacy branch predates the engine's switch to the input envelope and is dead weight against current engines.
Suggested fix
Always build the envelope form and drop the legacy branch (the envelope carries all legacy fields too — contentType/prefix/expiration/acl are members of S3GenerateSignedUploadInput). If compatibility with pre-envelope engines is still required, gate on an explicit option rather than on the presence of unrelated per-file fields.
Workaround shipped downstream
We applied a temporary workaround in our project, marked TODO [BindX] (<this-issue-url>): …. The workaround passes getUploadOptions: file => ({ fileName: file.name }) to S3UploadClient so hasNewFormat is always true; we will remove it once this issue is resolved.
Summary
createContentApiS3Signersilently falls back to the legacygenerateUploadUrlmutation shape (top-levelcontentType/expiration/prefix/aclarguments) whenever the upload parameters carry none ofsuffix/fileName/extension— which is exactly what a default-configuredS3UploadClientproduces. Current Contember engine only accepts theinput: S3GenerateSignedUploadInputenvelope, so every default-configured upload fails.Environment
@contember/bindx@0.1.38(version installed in the reporting project;@contember/bindx-uploader0.1.38–0.1.41 all carry the same branch)contember/bindx@mainas of94e9c2dcontember/engine:2.1.0-alpha.39tests/unit/uploader/urlSignerInputEnvelope.test.tsbug/uploader-legacy-upload-url-mutationReproduction
The signer emits:
which engine 2.1.0-alpha.39 rejects — verified directly against the Content API:
generateUploadUrl(input: $i)→ 200, signed URL returned"ACL is not supported"UserInputError; environments without theS3Aclscalar fail earlier with"Unknown type S3Acl"/"Cannot query field generateUploadUrl")Expected behavior
The signer always requests the URL via
generateUploadUrl(input: S3GenerateSignedUploadInput)— the only shape the current engine supports, and the same shape the signer already uses whenfileName/suffix/extensionhappen to be present.Actual behavior
packages/bindx-uploader/src/utils/urlSigner.tsbranches onand builds the legacy top-level-arguments mutation when none of those optional fields is set. A default-configured
S3UploadClient(nogetUploadOptions) therefore never uploads: the engine rejects the whole mutation. In batched mode a single legacy alias poisons the entire batch even when other aliases use the envelope.Suspected root cause
packages/bindx-uploader/src/utils/urlSigner.ts,buildGenerateUploadUrlMutation— thehasNewFormatheuristic conflates "caller provided new-format-only fields" with "server supports the new format". The legacy branch predates the engine's switch to the input envelope and is dead weight against current engines.Suggested fix
Always build the envelope form and drop the legacy branch (the envelope carries all legacy fields too —
contentType/prefix/expiration/aclare members ofS3GenerateSignedUploadInput). If compatibility with pre-envelope engines is still required, gate on an explicit option rather than on the presence of unrelated per-file fields.Workaround shipped downstream
We applied a temporary workaround in our project, marked
TODO [BindX] (<this-issue-url>): …. The workaround passesgetUploadOptions: file => ({ fileName: file.name })toS3UploadClientsohasNewFormatis always true; we will remove it once this issue is resolved.