@pulse-compute/s3#
S3 provides bounded exact-key head, getText and putText effects, plus Node-only opaque getBody reads. It is a supported extension in the synchronized 1.0.0-beta.6 candidate. Candidate preparation does not assert that the package has been published to npm. Consume the exact compatible S3, Pulse, CLI and provider tarballs from one release pack.
Authoring and lowering#
import { s3 } from '@pulse-compute/s3'
const written = await s3.putText(ctx, 'objects', key, text, {
contentType: 'application/json',
})
const inspected = await ctx.parallel({
metadata: s3.head(ctx, 'objects', key),
object: s3.getText(ctx, 'objects', key),
})
Use these expressions inside a Pulse handler. Await into a local variable or use keyed ctx.parallel. The logical binding must be a string literal. Key and text may be runtime strings; content type is optional literal metadata and does not serialize the text. Strict JSON responses require a response schema.
S3 owns its facade, operation contract, compiler recognition, Native lowering, key encoding, SigV4 protocol and result interpretation. Native targets compose the packaged AssemblyScript implementation with Crypto's SHA-256 and HMAC-SHA256 primitives. Node JavaScript uses the package protocol and Crypto's bounded Web Crypto realization. Providers own fixed authority, credential resolution, deadlines, raw metadata and transport. Application imports use only the package root; /provider, /signing, manifest, compiler and Native exports are first-party host/toolchain integration. The typed /signing bridge takes resolved credentials and caller-selected SHA-256/HMAC primitives; it never resolves secrets or sends requests. Assets uses it to retain its public signing helpers while sharing S3 protocol implementation. The provider signer uses the same canonicalization and signing-key derivation. Native AssemblyScript protocol code remains S3-owned.
Binding map#
Declare crypto: ['SHA-256', 'HMAC-SHA256'] in Pulse configuration. Map each logical name under <profile>.node.bindings.s3 or <profile>.fastly.bindings.s3.
| Field | Contract |
|---|---|
endpoint | Fixed HTTPS origin, without a path, query or credentials |
bucket, region | Explicit bucket and signing region |
accessKeyIdSecret, secretAccessKeySecret | Names of provider secrets |
sessionTokenSecret | Optional provider secret name |
maxTextBytes | 1–2097152; default 32768 |
timeoutMs | 1–30000; default 10000; includes credentials, signing and body completion |
backend | Fastly requires a named static backend; configure its secret store in fastly.bindings.secretStore |
Runtime keys cannot select an origin, bucket, region, backend or credentials. Keys are 1–1024 UTF-8 bytes, with no controls or ./.. path segments. Slashes, repeated slashes and Unicode normalization are preserved; encoding occurs once.
Results and bounds#
| Operation | Successful result | Other results |
|---|---|---|
head | found: byte length, optional opaque ETag and content type | not-found or failed with bounded reason and observed HTTP status |
getText | found: exact UTF-8 text, raw byte length and SHA-256, optional metadata | not-found or failed; malformed UTF-8 is rejected |
putText | stored: sent byte length and SHA-256, optional opaque ETag | not-stored before dispatch or for complete explicit 4xx rejection except 408; unknown when dispatch may have stored bytes |
GET and PUT admit up to 2097152 UTF-8 bytes with an explicit maxTextBytes binding. The default remains 32768; lower per-binding limits are enforced. Empty text is allowed and a BOM is preserved. HEAD requires a safe Content-Length and can describe an object larger than the text limit. Metadata is bounded to 16384 aggregate bytes and 1024 bytes per selected value; duplicate selected headers, malformed lengths, truncated bodies and compression fail closed.
PUT succeeds only on a complete 200 acknowledgement with an empty body. Its digest identifies sent bytes, not origin durability. Cancellation terminates the request lifecycle without a typed S3 result or a rollback promise. There are no retries, redirects, decompression or caches. The S3 result and text-effect envelope is 12,648,448 bytes: six times the 2 MiB text ceiling plus 64 KiB metadata allowance. Escaping consumes envelope capacity independently of object capacity.
For a 2 MiB encoded JSON object, explicitly configure the S3 binding's maxTextBytes: 2097152 and schemas.maxBytes: 2097152. When a request carries text inside JSON, control escapes and wrapper overhead count against the separate structured-body ceiling; the encoded request must fit that bound. dev.maxBodyBytes independently caps local HTTP ingress (default 65536), and custom hosts must pass their intended request/structured-body limits. Raising S3 capacity does not silently raise any of these limits.
Primary-memory Native modules using S3/digest enforce a 256 MiB Wasm maximum. This bounds the module, including staging, schema work and live values. The fixed-memory ES256 linked-guest ABI is unchanged and cannot establish this large-text profile. Conditional KV remains at 65536 bytes; HMAC/JWT limits are independent. No chunking, retries or hidden fallback is added.
Bounded binary response reads (AST-02A/C)#
const image = await s3.getBody(ctx, 'objects', key)
return image
Node Native, Node JavaScript and Fastly Native support getBody through the ordinary package facade and provider. It returns an opaque response: return it without inspecting or decoding its body. Native keeps bytes in provider-owned transport buffers; no application binary value ABI, text conversion, whole-object buffering, or JavaScript fallback is introduced. Fastly JavaScript remains ineligible because its SDK cannot expose the required raw header multiplicity.
The existing binding maxTextBytes also caps a returned body (default 32768, maximum 2097152). HEAD can describe a larger object. The deadline includes credentials, signing and transfer; the shorter request or binding deadline wins. Node body reads are demand-driven with no wrapper prefetch. Closing, cancelling, abandoning or failing a response releases its origin. Failures after headers error the body rather than returning successful truncated bytes.
Literal options select method: 'GET' | 'HEAD' (default GET), one closed range: 'bytes=0-1023' (GET only), and/or ifNoneMatch: '"etag"' (one tag or *). Runtime key strings remain supported. Open/suffix/multiple ranges, validator lists and date conditionals are outside this increment. Providers sign the selected headers. A 206 must match the requested interval, including end clipping at object length, and its declared length; an origin may ignore a range and return a bounded full 200. A 304 requires a conditional request. A 416 requires an unsatisfied requested start and a valid total length.
Only bounded Content-Length, Content-Type, ETag and Content-Range are projected; GET/206 require a safe length, duplicate selected headers and encoded bodies are rejected, and streamed bytes must exactly match the declared length. 200, 206, 304, 404, 412 and 416 are retained; non-body statuses release their upstream body. Pre-transfer failures return a bodyless 502 (504 for timeout) with a bounded x-pulse-s3-error reason. Request cancellation still terminates execution.
No whole-object digest or integrity assertion is made for this API. In particular, a partial response is not proof of the whole object, and an opaque ETag is not interpreted as a checksum. Use getText when bounded exact UTF-8 text and its computed digest are required.
Target support and evidence#
| Target | S3 support | Local acceptance |
|---|---|---|
| Node Native | Supported | Compiled Wasm with the Node provider |
| Node JavaScript | Supported | Package runtime with bounded Web Crypto |
| Fastly Native | Supported | Compiled provider Wasm with host ABI fixtures |
| Fastly JavaScript | Ineligible | SDK header projection loses required raw metadata |
The read and write corpus is repeated from isolated exact-version tarball installs, using installed compilers, package lowering and provider realizations. It covers signatures, key encoding, malformed and truncated data, limits, credentials, redaction, deadlines and ambiguous writes. Fastly JavaScript's exclusion does not gate these supported targets.
Local fixtures do not establish live Fastly Object Storage semantics. Live origin acceptance follows infrastructure setup (T2) and records endpoint, region, target artifact, exact observed bytes and acknowledgement behavior. There is no listing, multipart upload, copy, presigning, arbitrary bucket selection or conditional creation. Assets retains lookup and HTTP serving; its legacy signing/encoding helpers delegate to S3 with compatibility coverage. Bounded S3 effect admission and result rules remain independent from those direct JavaScript helpers.
Related material#
Fastly Native transfers only the selected response through a 16 KiB byte buffer. It waits for origin and downstream readiness under the copied deadline, handles short writes, and closes the origin on completion or failure. Discarded responses are closed when the invocation ends, without reading their bodies. A successful stream is explicitly finished; a failed stream remains unfinished so Compute aborts it at invocation exit. It never sends a second response after streaming headers. Native ABI fixtures prove generated Wasm behavior; this change does not claim Viceroy or deployed Fastly qualification.