@outfoxx/sunday 2.0.0-beta.2 → 2.0.0-beta.21

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.
Files changed (141) hide show
  1. package/README.md +346 -4
  2. package/build/authentication-adapter.d.ts +21 -0
  3. package/build/authentication-adapter.js +74 -0
  4. package/build/authentication-adapter.js.map +1 -0
  5. package/build/bearer-token-rejection-adapter.d.ts +6 -0
  6. package/build/bearer-token-rejection-adapter.js +25 -0
  7. package/build/bearer-token-rejection-adapter.js.map +1 -0
  8. package/build/client-settings.d.ts +22 -0
  9. package/build/client-settings.js +180 -0
  10. package/build/client-settings.js.map +1 -0
  11. package/build/credentials.d.ts +58 -0
  12. package/build/credentials.js +51 -0
  13. package/build/credentials.js.map +1 -0
  14. package/build/event-parser.d.ts +23 -8
  15. package/build/event-parser.js +93 -121
  16. package/build/event-parser.js.map +1 -1
  17. package/build/fetch-copy.d.ts +15 -0
  18. package/build/fetch-copy.js +71 -0
  19. package/build/fetch-copy.js.map +1 -0
  20. package/build/fetch-event-source.d.ts +18 -8
  21. package/build/fetch-event-source.js +241 -144
  22. package/build/fetch-event-source.js.map +1 -1
  23. package/build/fetch-oauth-token-provider.d.ts +28 -0
  24. package/build/fetch-oauth-token-provider.js +213 -0
  25. package/build/fetch-oauth-token-provider.js.map +1 -0
  26. package/build/fetch-transport.d.ts +15 -1
  27. package/build/fetch-transport.js +142 -132
  28. package/build/fetch-transport.js.map +1 -1
  29. package/build/fetch.js +24 -10
  30. package/build/fetch.js.map +1 -1
  31. package/build/header-token-adapters.d.ts +28 -0
  32. package/build/header-token-adapters.js +121 -0
  33. package/build/header-token-adapters.js.map +1 -0
  34. package/build/index.d.ts +23 -1
  35. package/build/index.js +22 -1
  36. package/build/index.js.map +1 -1
  37. package/build/media-type-codecs/binary-decoder.d.ts +1 -0
  38. package/build/media-type-codecs/binary-decoder.js +4 -11
  39. package/build/media-type-codecs/binary-decoder.js.map +1 -1
  40. package/build/media-type-codecs/cbor-encoder.d.ts +2 -2
  41. package/build/media-type-codecs/cbor-encoder.js +2 -2
  42. package/build/media-type-codecs/cbor-encoder.js.map +1 -1
  43. package/build/media-type-codecs/json-encoder.d.ts +3 -3
  44. package/build/media-type-codecs/json-encoder.js +4 -4
  45. package/build/media-type-codecs/json-encoder.js.map +1 -1
  46. package/build/media-type-codecs/media-type-encoder.d.ts +3 -3
  47. package/build/media-type-codecs/media-type-encoder.js.map +1 -1
  48. package/build/media-type-codecs/www-form-url-encoder.d.ts +2 -2
  49. package/build/media-type-codecs/www-form-url-encoder.js +2 -2
  50. package/build/media-type-codecs/www-form-url-encoder.js.map +1 -1
  51. package/build/model-construction.d.ts +5 -0
  52. package/build/model-construction.js +40 -0
  53. package/build/model-construction.js.map +1 -0
  54. package/build/model-schema.d.ts +9 -0
  55. package/build/model-schema.js +131 -0
  56. package/build/model-schema.js.map +1 -0
  57. package/build/oauth-wire.d.ts +33 -0
  58. package/build/oauth-wire.js +143 -0
  59. package/build/oauth-wire.js.map +1 -0
  60. package/build/operation.d.ts +3 -1
  61. package/build/problem.d.ts +39 -16
  62. package/build/problem.js +88 -32
  63. package/build/problem.js.map +1 -1
  64. package/build/request-adapter.d.ts +8 -0
  65. package/build/request-adapter.js +15 -0
  66. package/build/request-adapter.js.map +1 -0
  67. package/build/request-adapters-compat.d.ts +6 -0
  68. package/build/request-adapters-compat.js +19 -0
  69. package/build/request-adapters-compat.js.map +1 -0
  70. package/build/request-adapters.d.ts +13 -23
  71. package/build/request-adapters.js +40 -31
  72. package/build/request-adapters.js.map +1 -1
  73. package/build/request-body.d.ts +24 -0
  74. package/build/request-body.js +104 -0
  75. package/build/request-body.js.map +1 -0
  76. package/build/request-cancellation-error.d.ts +13 -0
  77. package/build/request-cancellation-error.js +43 -0
  78. package/build/request-cancellation-error.js.map +1 -0
  79. package/build/request-context.d.ts +4 -0
  80. package/build/request-context.js +29 -0
  81. package/build/request-context.js.map +1 -0
  82. package/build/request-execution.d.ts +13 -0
  83. package/build/request-execution.js +80 -0
  84. package/build/request-execution.js.map +1 -0
  85. package/build/request-security-bindings.d.ts +5 -0
  86. package/build/request-security-bindings.js +30 -0
  87. package/build/request-security-bindings.js.map +1 -0
  88. package/build/request-security.d.ts +20 -0
  89. package/build/request-security.js +223 -0
  90. package/build/request-security.js.map +1 -0
  91. package/build/request-validation-error.d.ts +4 -0
  92. package/build/request-validation-error.js +21 -0
  93. package/build/request-validation-error.js.map +1 -0
  94. package/build/response-adapter.d.ts +10 -0
  95. package/build/response-adapter.js +15 -0
  96. package/build/response-adapter.js.map +1 -0
  97. package/build/response-adapters.d.ts +13 -0
  98. package/build/response-adapters.js +75 -0
  99. package/build/response-adapters.js.map +1 -0
  100. package/build/schema-builtins.js +11 -21
  101. package/build/schema-builtins.js.map +1 -1
  102. package/build/schema-runtime.d.ts +8 -1
  103. package/build/schema-runtime.js +12 -3
  104. package/build/schema-runtime.js.map +1 -1
  105. package/build/security-binding.d.ts +22 -0
  106. package/build/security-binding.js +15 -0
  107. package/build/security-binding.js.map +1 -0
  108. package/build/standard-authentication-adapter.d.ts +18 -0
  109. package/build/standard-authentication-adapter.js +58 -0
  110. package/build/standard-authentication-adapter.js.map +1 -0
  111. package/build/token-manager.d.ts +42 -0
  112. package/build/token-manager.js +276 -0
  113. package/build/token-manager.js.map +1 -0
  114. package/build/token-provider.d.ts +52 -0
  115. package/build/token-provider.js +102 -0
  116. package/build/token-provider.js.map +1 -0
  117. package/build/transport.d.ts +10 -3
  118. package/build/unknown-variant.d.ts +12 -0
  119. package/build/unknown-variant.js +45 -0
  120. package/build/unknown-variant.js.map +1 -0
  121. package/build/url-template.d.ts +3 -0
  122. package/build/url-template.js +30 -2
  123. package/build/url-template.js.map +1 -1
  124. package/build/util/abort.d.ts +3 -0
  125. package/build/util/abort.js +42 -0
  126. package/build/util/abort.js.map +1 -0
  127. package/build/util/base64.d.ts +4 -0
  128. package/build/util/base64.js +46 -0
  129. package/build/util/base64.js.map +1 -0
  130. package/build/util/native-fetch.d.ts +2 -0
  131. package/build/util/native-fetch.js +39 -0
  132. package/build/util/native-fetch.js.map +1 -0
  133. package/build/util/nullify.js +12 -5
  134. package/build/util/nullify.js.map +1 -1
  135. package/build/util/owned-body.d.ts +12 -0
  136. package/build/util/owned-body.js +243 -0
  137. package/build/util/owned-body.js.map +1 -0
  138. package/build/util/status-title.d.ts +2 -0
  139. package/build/util/status-title.js +83 -0
  140. package/build/util/status-title.js.map +1 -0
  141. package/package.json +18 -6
package/README.md CHANGED
@@ -1,5 +1,4 @@
1
- Sunday 🙏 The framework of REST for TypeScript/JavaScript
2
- ===
1
+ # Sunday 🙏 The framework of REST for TypeScript/JavaScript
3
2
 
4
3
  ![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/outfoxx/sunday-js/ci.yml?branch=main)
5
4
  ![Coverage](https://sonarcloud.io/api/project_badges/measure?project=outfoxx_sunday-js&metric=coverage)
@@ -13,8 +12,7 @@ TypeScript/JavaScript framework for generated REST clients.
13
12
 
14
13
  ---
15
14
 
16
- NPM
17
- ---
15
+ ## NPM
18
16
 
19
17
  Sunday is delivered as a standard NPM package.
20
18
 
@@ -37,3 +35,347 @@ Package Name:
37
35
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
38
36
  See the License for the specific language governing permissions and
39
37
  limitations under the License.
38
+
39
+ ## Profiled credentials
40
+
41
+ Generated request specifications select `SecurityBinding` entries. Register application providers with
42
+ providers through `new FetchTransport(baseUrl, authenticationAdapters({external: provider}))`. Client secrets,
43
+ authorization codes, token storage, and interactive login remain application bindings.
44
+
45
+ `FetchOAuthTokenProvider` exchanges `clientCredentials` and application-authorized `authorizationCode`
46
+ grants and rotates refresh tokens. Configure `identity`, `clientId`, and explicit client authentication
47
+ (`client_secret_basic`, `client_secret_post`, or public-client `none`). An interactive application supplies
48
+ a fresh `grantIdentity` and an `authorization(request, signal)` callback returning
49
+ `{code, redirectUri, codeVerifier}` after its S256 PKCE browser flow has verified state, issuer, and redirect
50
+ URI. Codes are consumed once even when exchange fails or is canceled. Discovery requires a separately
51
+ configured `issuer`; acquisition endpoint overrides cannot change it. Public authorization-code/PKCE
52
+ clients using `authentication: 'none'` accept discovery that omits `none`, including an absent or empty
53
+ authentication-method list. Supplied lists must contain only strings. Confidential clients still
54
+ require their configured method; an absent list defaults to `client_secret_basic`. Discovery and
55
+ endpoint trust are checked again on refresh. Other authentication methods use
56
+ an application `TokenProvider`. Token endpoints require HTTPS except for loopback development URLs.
57
+
58
+ External and static providers implement `configure` and `acquire`, optionally `refresh`. A `TokenSet`
59
+ contains `accessToken`, optional `expiresAt` in Unix milliseconds, and optional `refreshToken`. A returned
60
+ refresh token replaces the previous one; omission retains it. Expired interactive sessions without refresh
61
+ capability and invalid interactive refresh grants require fresh application authorization.
62
+
63
+ Cache keys include provider/client identity, grant identity, profile, flow, resolved endpoints, scopes,
64
+ audience, and resource. Change provider identity with credential configuration and grant identity with
65
+ session/grant inputs. The manager defaults to 30 seconds of expiry skew, coalesces concurrent renewal,
66
+ and accepts application-owned `TokenStore` storage. Canceling one waiter preserves others; canceling the
67
+ last signals acquisition cancellation. Once persistence starts, saving a rotated token finishes even if
68
+ callers cancel. A provider result arriving after cancellation cannot start persistence; if an identity
69
+ server already consumed that refresh token despite cancellation, interactive clients must authorize again.
70
+
71
+ The transport attaches complete AND credential sets and checks them on every execution. Bodyless
72
+ GET/HEAD/OPTIONS requests may recover one explicit bearer `invalid_token` challenge; POST, streamed bodies,
73
+ and 403 responses are never automatically replayed. Managed credential requests use manual redirect
74
+ handling, and query credentials are redacted from response diagnostics. Providers never receive secrets
75
+ from generated metadata, and provider failures use safe runtime diagnostics.
76
+
77
+ Credentials are isolated by logical security scheme as well as provider and acquisition inputs.
78
+ Discovery metadata is fetched and verified on each acquisition or renewal. Temporary provider outages
79
+ allow event connections to reconnect; a rejected refresh grant triggers fresh client credentials only
80
+ for the client-credentials flow. Interactive sessions require fresh application authorization.
81
+ Built-in OAuth providers retain at most 1,024 consumed authorization-code hashes per provider instance.
82
+ After this limit, create a provider for a newly authorized application session; old hashes are never
83
+ evicted to allow code reuse. Refresh exchanges do not consume this history.
84
+
85
+ ### Typed request parameters
86
+
87
+ `parameterValidation` is an optional callback on the request specification. The transport invokes it
88
+ before encoding on every request build, including bodyless requests and event streams. Generated
89
+ callbacks validate captured typed parameters in request mode; reusing an operation checks mutable
90
+ values again. Custom transports must invoke the callback at the same boundary before transmission.
91
+
92
+ Parameter failures throw `RequestValidationError` with the native validation error as `cause`.
93
+ Event sources close on this error, and event stream iterators reject instead of reconnecting.
94
+
95
+ ## URI template variables
96
+
97
+ `URLTemplate.complete` preserves the distinction between an empty string and a missing,
98
+ `undefined`, or `null` value. For `/items{/id}`, `id: ''` produces `/items/`, while an
99
+ undefined `id` produces `/items`. An empty collection is undefined; a list containing
100
+ an empty string has a defined member. Per-call values override stored template parameters,
101
+ including explicit null or undefined values that remove a stored value for that expansion.
102
+
103
+ ## Explicit standard authentication
104
+
105
+ Choose standard authentication once in the transport factory supplied to generated clients.
106
+ `StandardAuthenticationAdapter` composes credential attachment, missing-provider checks, rejection
107
+ feedback, and terminal bearer-rejection cancellation. It delegates to the existing adapters, including
108
+ their body ownership rules, and ignores public/detached operation responses. Feedback runs before
109
+ cancellation. There is no hidden replay; a later explicit request can obtain renewed credentials.
110
+
111
+ The standard settings helper selects this policy on both sides. Lower-level `AuthenticationAdapter`
112
+ remains available when an application wants feedback without the standard stop policy. Ordinary
113
+ transport constructors perform no authentication and do not install this composition.
114
+
115
+ ```typescript
116
+ // Pass this factory to the generated configuration-based client factory.
117
+ const standardFactory = standardTransportFromSettings;
118
+ // Customize codecs or append application adapters through its second argument.
119
+
120
+ // Or construct the generated service with this caller-owned transport.
121
+ const customTransport = new FetchTransport(baseUrl, {
122
+ requestAdapter: auth0Link, responseAdapter: auth0Link,
123
+ });
124
+ ```
125
+
126
+ Caching is a separate explicit choice: construct `TokenManager` around a Sunday OAuth provider and
127
+ supply that manager through provider credentials to the generated factory. This retains expiry renewal,
128
+ refresh rotation, and application-selected storage. A provider returned by an OAuth credential factory
129
+ can likewise be an explicitly constructed manager. Supplied external providers are used unchanged;
130
+ standard composition never wraps them, creates a second cache, closes them, or clears their storage.
131
+ Keep managers at application/session scope, share them across clients using the same credential lifecycle,
132
+ and settle outstanding requests before closing managers or clearing credentials.
133
+
134
+ Static bearer, basic, and API-key credentials use their declared placement without an OAuth cache.
135
+ Only an explicit Bearer `invalid_token` challenge for a sent bearer credential invokes the bearer stop
136
+ policy; 403s and unrelated challenges retain ordinary response handling. Authentication selection and
137
+ credential validation in generated settings remain active for the standard path. The direct-transport
138
+ path lets a custom adapter own those choices using operation metadata.
139
+
140
+ ## Application-owned token lifecycle
141
+
142
+ Transport constructors perform no authentication or authorization by default. They accept request and response
143
+ adapters, with no token-provider option or authentication retry loop. External authentication frameworks can
144
+ implement these adapters directly, without using Sunday's token or security classes.
145
+
146
+ The optional `AuthenticationAdapter` acquires credentials for declared bindings, reports missing providers,
147
+ and sends matching rejection feedback to the credential owner. Install it explicitly in each desired slot;
148
+ installing a request adapter alone does not install response behavior. It adds no cache.
149
+
150
+ `BearerTokenRejectionAdapter` is a separate, opt-in response policy. It throws the cancellation marker when
151
+ an initiating request sent a bearer credential and its response is 401 with an explicit Bearer `invalid_token`
152
+ challenge. It leaves all other responses and all bodies untouched. Compose it after `AuthenticationAdapter`
153
+ when provider feedback should run before cancellation. It has no counter, renewal, or replay behavior.
154
+ The removed automatic recovery policy has no implicit replacement: applications choose their own
155
+ recovery policy, and a cancellation-marked error always stops the exchange, retries, and SSE reconnections.
156
+ An external framework (for example Auth0) remains responsible for storage, renewal and logout.
157
+ Return only its access token and optional expiry; keep its refresh token inside that framework.
158
+
159
+ For Sunday's caching and refresh lifecycle, explicitly supply a `TokenManager` as the provider:
160
+
161
+ ```typescript
162
+ const tokens = new TokenManager(oauthProvider, {
163
+ store: applicationStore,
164
+ expirySkewMs: 30_000,
165
+ now: applicationClock,
166
+ });
167
+ const settings = ClientSettings.resolve(baseUrl, alternatives, {
168
+ identity: { kind: 'provider', provider: tokens },
169
+ });
170
+ // Supply an external framework's provider directly instead to keep its lifecycle authoritative.
171
+ const transport = standardTransportFromSettings(settings);
172
+ ```
173
+
174
+ Managers retain expiry skew, coalesced renewal, refresh rotation and application-selected persistence.
175
+ Cancel and await outstanding requests before discarding a manager.
176
+ Transport closure does not erase the store or close a shared provider. Separate managers do not
177
+ coordinate refreshes merely because they share a store. Reuse the same manager across clients that
178
+ share a credential lifecycle, and distinguish users/environments with provider and grant identities.
179
+
180
+ Providers can implement `reject` to handle an invalid bearer token. It receives the original request
181
+ context and rejected access token, and returns whether recovery can proceed. Compare the rejected
182
+ credential with the current session before renewing: a late rejection must not invalidate a newer token
183
+ or a new login. Providers without this capability do not automatically retry. Acquisition and rejection
184
+ must not start interactive login automatically; return an authorization-required failure to the caller.
185
+ For an Auth0 adapter, retrieve through its credentials manager and force renewal through that same
186
+ manager on rejection, serializing concurrent rejection handling without maintaining a second token cache.
187
+
188
+ Request export materializes an optionally authorized native request once and hands its lifecycle to
189
+ the consumer. Exported requests retain no Sunday-managed recovery state. Executing an operation through
190
+ Sunday invokes the request adapter once per attempt. Authentication responses do not trigger an implicit retry;
191
+ an application may make another request after its credential owner handles the rejection.
192
+
193
+ Migration: replace transport `tokenManager`/`token_manager` and `tokenProviders`/`token_providers` arguments
194
+ with explicit request/response adapters. Replace `TokenManagerFactory` hooks with explicitly constructed
195
+ managers supplied through provider credentials. Direct built-in OAuth providers are not implicitly cached;
196
+ wrap them explicitly when Sunday should retain and refresh their tokens. For logout, stop outstanding
197
+ requests and settle refresh commits before clearing the owning framework's or application's storage.
198
+
199
+ ## Request and response adapters
200
+
201
+ `FetchTransport` accepts independent `requestAdapter` and `responseAdapter` options. Each is a
202
+ structural interface with an `adapt` method returning a native Fetch value or a promise:
203
+
204
+ ```typescript
205
+ const transport = new FetchTransport('https://api.example', {
206
+ requestAdapter: chainRequestAdapters(auth, {
207
+ adapt(request) {
208
+ const headers = new Headers(request.headers);
209
+ headers.set('X-Client', 'example');
210
+ return copyRequest(request, { headers });
211
+ },
212
+ }),
213
+ responseAdapter: chainResponseAdapters(auth, {
214
+ adapt(response, request) {
215
+ console.log(request.method, response.status);
216
+ return response;
217
+ },
218
+ }),
219
+ });
220
+ ```
221
+
222
+ Here `auth` explicitly implements both capabilities, for example a
223
+ `RefreshingHeaderTokenAuthorizingAdapter`. Installing only its request capability does not install
224
+ its response capability. `RequestAdapters` and `ResponseAdapters` also provide `chainTo`; composition
225
+ flattens existing chains. Plain objects need no inheritance or prototype extensions.
226
+
227
+ Adapters run once per Sunday-managed Fetch call, with response adaptation before validation or body
228
+ consumption. Transports do not recognize token types, classify bearer rejection, or retry authentication.
229
+ `AuthenticationAdapter` reports matching rejections to the credential owner; a provider declining recovery
230
+ raises `RequestCancellationError`. A later explicit request acquires credentials again. Generic SSE
231
+ reconnection and native Fetch behavior remain independent of token handling.
232
+
233
+ Fetch retains its native redirects and recovery. Adapters cannot observe hidden intermediate responses
234
+ or wire-level changes from service workers/custom Fetch implementations. The response adapter receives
235
+ the request submitted to Fetch. Changing response status or Location afterward does not initiate a new
236
+ native redirect. Credentialed calls use manual redirects and omit ambient credentials; browsers expose
237
+ manual redirect responses as `opaqueredirect`, without readable status or Location. An adapter may handle
238
+ that response, but Sunday does not emulate browser redirect internals.
239
+
240
+ Throw `RequestCancellationError`, subclass it, or brand an existing application Error with
241
+ `[requestCancellation]: true` to stop without retry or SSE reconnection. Sunday preserves that stop
242
+ instance, including when native Fetch carries it as an error cause. Sunday does not inspect causes
243
+ of errors thrown by adapters, decoders, or supplied Fetch callbacks. Native aborts also stop; `AbortSignal.any`
244
+ preserves cancellation from the invocation and adapter-provided signals, including the original abort
245
+ reason. Async adapters should observe `request.signal`. Sunday stops awaiting an aborted adapter and
246
+ cleans up late returned resources; JavaScript cannot forcibly terminate arbitrary application promises.
247
+ Cancellation also reaches owned upload and replacement-response streams while an adapter or native
248
+ body reader is consuming them. Cleanup releases readers without waiting for a producer's cleanup
249
+ promise. Late adapter results receive the original cancellation reason.
250
+ Event-source handler properties and EventTarget listeners are observers. Thrown exceptions and rejected
251
+ callback promises follow the runtime's native event-error reporting; they do not request retries or
252
+ cancellation, even when the error carries a stop marker. Use `close()` or an abort signal to stop a
253
+ subscription. Registered listeners run before the handler property and can suppress it with
254
+ `stopImmediatePropagation()`. `onmessage` continues to receive named SSE messages as well as default
255
+ messages.
256
+
257
+ A failed parallel credential acquisition cancels its siblings. `RefreshingHeaderTokenAuthorizingAdapter`
258
+ uses `TokenManager` to coalesce refreshes and prevent invalidated tokens from returning to the cache.
259
+
260
+ An adapter consuming a body must return a replacement. Use `copyRequest` / `copyResponse` to preserve
261
+ known body ownership through native constructors. Sunday does not call `clone()` or add a stream tee;
262
+ native Request copying may create branches, and Sunday immediately cancels the obsolete input branch
263
+ without waiting for its cleanup. The active body retains native streaming and replay behavior.
264
+ Prepare new upload streams with `prepareRequestBody` before constructing a Request.
265
+ These helpers share/transfer the existing body; do not subsequently consume it through the old value.
266
+ `copyResponse` preserves URL/type/redirect metadata for inspectable responses; opaque/error responses
267
+ cannot be reconstructed by Fetch's Response constructor. URL-changing request copies transfer the body
268
+ as a stream, so provide a fresh BodyInit if native replayability is needed. Same-URL copies retain
269
+ native body sources on all supported platforms.
270
+
271
+ No-op and header-only response adapters retain native Fetch body capabilities, including BYOB readers
272
+ where the platform provides them. Independent replacement streams share one source owner for
273
+ cancellation and source-error capture. Original errors are restored only around native body reads or
274
+ Fetch itself; errors deliberately thrown by adapters are never classified as substituted body errors.
275
+
276
+ `transportRequest()` exports an optionally authorized, adapted request exactly once. Its consumer owns
277
+ execution (`fetch(exportedRequest)`), response processing, and retry policy. Passing a native Request to
278
+ `transportResponse()` instead starts a new managed invocation. Migrate old `adapt(transport, request)`
279
+ implementations to `adapt(request)` and pass `requestAdapter`; `adapter` remains a deprecated constructor
280
+ option accepting the new signature.
281
+
282
+ Bun execution requires Bun 1.4 or newer for UTF-8 SSE reconnect IDs: older Bun releases
283
+ re-encode Fetch header bytes and corrupt `Last-Event-ID`. Development and CI use Bun 1.4.4.
284
+
285
+ The standard `bun run check` includes native-socket Node/Bun checks and Chromium, Firefox, and WebKit
286
+ adapter regressions. Install browsers with `bunx playwright install chromium firefox webkit` (CI uses
287
+ `--with-deps`). The HTTP/2 upload fixture also requires Node and OpenSSL; it creates a temporary,
288
+ self-signed loopback certificate and removes it after testing. Tests cover buffered and streaming
289
+ execution, cancellation, body ownership, exception identity, and native redirects.
290
+
291
+ Browser request streaming is a separate capability from response streaming. Verified with Playwright
292
+ 1.65.0 on macOS: Chromium 158.0.8127.18 and WebKit 27.2 upload copied request streams over HTTPS/HTTP/2,
293
+ including bytes produced before the stream closes. Firefox 159.0 still stringifies streams passed
294
+ directly to native Request. Sunday now buffers unsupported upload streams into a Blob **before**
295
+ constructing the request, preserving their bytes. Supported runtimes retain native streaming.
296
+
297
+ The fallback buffers at most **16 MiB** by default. Set `maxBufferedRequestBytes` on `FetchTransport`
298
+ to change this bound; it does not limit native streaming uploads. Buffering waits for the producer to
299
+ finish before transmission. An oversized body fails with RangeError and cancels its source. Aborts and
300
+ source errors retain their original identity, and cancellation does not wait for source cleanup.
301
+ Existing Blob/string/form bodies are unchanged, and buffering does not add Sunday retries or caches.
302
+
303
+ Adapters that introduce a stream use the same exported helper before constructing their replacement
304
+ request. For example, an adapter supplying the body of an initially bodyless POST can use:
305
+
306
+ ```typescript
307
+ const body = await prepareRequestBody(makeUploadStream(), {
308
+ signal: request.signal,
309
+ maxBytes: 16 * 1024 * 1024,
310
+ });
311
+ return copyRequest(request, { body, duplex: 'half' });
312
+ ```
313
+
314
+ `prepareRequestBody` passes through supported streams and other BodyInit values unchanged; its
315
+ `maxBytes` applies only to the fallback. `bufferToBlob(stream, options)` explicitly buffers on any
316
+ runtime with the same cancellation and limit behavior. These helpers consume the source when buffering
317
+ and must run before native Request construction: an already-stringified Firefox request cannot be
318
+ recovered. When changing a request URL with `copyRequest`, also normalize and pass its existing body
319
+ explicitly: rebuilding a native Request at a new URL exposes that body as a stream again. On engines
320
+ without `Request.body` (including Firefox 159), retrieve an existing body with `request.blob()` instead.
321
+ Sunday preserves the body through both paths when adding query credentials.
322
+ Adapters remain responsible for replacing/discarding resources they consume.
323
+
324
+ `supportsStreamingUploads()` caches a runtime capability probe without network requests. It does not
325
+ check the destination's HTTP version or server behavior, and Sunday never retries a failed transmission
326
+ to select the fallback. Bun supports uploads without reading `duplex`, which the probe accounts for.
327
+ The browser tests compare native and Sunday request copying explicitly and save the exact engine
328
+ versions and observed upload outcomes in `reports/browser-streaming-*.json`. Shared buffer contract
329
+ checks run in Chromium, Firefox, WebKit, Node, and Bun. The same async execution contracts run in all
330
+ five engines, covering replacement-body cleanup, original error identity, credential acquisition and
331
+ recovery, refresh coordination, and concurrent SSE iterator reads. The ownership matrix also checks
332
+ native byte-stream capabilities, adapter-selected errors, upload failures, and late resource cleanup.
333
+ The lifecycle matrix also checks atomic credential setup, unhandled rejections, nested application
334
+ errors, truncated SSE reconnections, and suppression of callbacks after termination.
335
+ CI runs WebKit on macOS to exercise Safari's network implementation; Chromium, Firefox, Node, and Bun
336
+ run on Linux. Playwright's Linux WebKit backend advertises streaming uploads but cannot send them,
337
+ and reports remote HTTP/2 resets as aborts. The constructor-only capability probe cannot detect those
338
+ backend limitations. Applications targeting that backend can explicitly use `bufferToBlob` for uploads.
339
+ `SUNDAY_TEST_BROWSERS=webkit` selects a browser suite locally (a comma-separated list is also accepted).
340
+ See the [ownership and failure audit](documentation/adapter-ownership-audit.md) for the recurring causes,
341
+ pre-existing defects versus refactor regressions, and the checks to carry into other runtimes.
342
+
343
+ ## Binary downloads
344
+
345
+ Generated operations set `RequestSpec.binaryResponse` for whole-body binary responses,
346
+ including unions whose branches are all binary. `FetchTransport` then preserves the
347
+ raw bytes regardless of `Content-Type`, while applying the response schema and retaining
348
+ normal HTTP problem handling. This also permits download media types without a registered
349
+ media decoder. Manually constructed binary request specs can set the same flag.
350
+
351
+ Mixed binary/non-binary unions and binary fields inside structured documents continue to
352
+ use media decoding. Successful 200 responses may contain an empty `ArrayBuffer`; typed
353
+ 204/205 responses retain the existing empty-response error behavior.
354
+
355
+ ### Application-owned authentication adapters
356
+
357
+ Transports delegate requests directly to `requestAdapter` and responses to `responseAdapter`.
358
+ Use `requestSecurityBindings(request)` to inspect the generated operation's audience, scopes, and credential
359
+ placement. Bindings are immutable metadata, preserved by `copyRequest` and adapter composition; they are not
360
+ sent as headers and contain no token receipts. Both adapters run for public and protected operations, so the
361
+ application must select the requests and responses it owns and return usable body streams.
362
+
363
+ `FetchTransport.fromSettings` reads only the endpoint; it does not install authentication.
364
+ `standardTransportFromSettings` explicitly selects the complete standard policy. Use
365
+ `authenticationAdapters(settings.tokenProviders, customRequestAdapter, customResponseAdapter)` to compose
366
+ provider integration explicitly before application adapters. The deprecated `adapter` option retains its
367
+ usual precedence when `requestAdapter` is absent.
368
+
369
+ Adapters may throw `RequestCancellationError` (or an application error carrying the cancellation marker)
370
+ to stop the exchange, or return a response for ordinary handling. `AuthorizationRequiredError` carries
371
+ that marker, so terminal credential failure cannot reconnect SSE. There is no implicit authentication replay.
372
+ Exported native requests retain their materialized headers but detach operation security bindings;
373
+ re-executing an export does not reuse its old provider rejection receipt.
374
+
375
+ The standard settings factory forwards `maxBufferedRequestBytes` to query-credential adaptation as
376
+ well as upload preparation. When installing `AuthenticationAdapter` directly, pass the same limit
377
+ in its optional second argument. The `authenticationAdapters` convenience accepts these options as
378
+ its fourth argument. This keeps buffering limits explicit without giving transports token responsibilities.
379
+
380
+ The original `@outfoxx/sunday/request-adapters` subpath retains its header-token adapter exports through
381
+ a compatibility entry point. Internal transports continue importing only the token-independent adapter chain.
@@ -0,0 +1,21 @@
1
+ import type { RequestAdapter } from './request-adapter.js';
2
+ import type { ResponseAdapter } from './response-adapter.js';
3
+ import type { TokenProvider } from './token-provider.js';
4
+ /** Explicit provider integration. Transports know only the request and response adapter contracts. */
5
+ export declare class AuthenticationAdapter implements RequestAdapter, ResponseAdapter {
6
+ private readonly providers;
7
+ private readonly options?;
8
+ private readonly receipt;
9
+ /** Uses caller-owned providers and the explicit buffer bound when query credentials require a body copy. */
10
+ constructor(providers: Readonly<Record<string, TokenProvider>>, options?: {
11
+ maxBufferedRequestBytes?: number;
12
+ } | undefined);
13
+ adapt(request: Request): Promise<Request>;
14
+ adapt(response: Response, request: Request): Promise<Response>;
15
+ private adaptResponse;
16
+ }
17
+ /** Compose provider integration explicitly, before optional application adapters. */
18
+ export declare function authenticationAdapters(providers: Readonly<Record<string, TokenProvider>>, requestAdapter?: RequestAdapter, responseAdapter?: ResponseAdapter, options?: ConstructorParameters<typeof AuthenticationAdapter>[1]): {
19
+ requestAdapter: RequestAdapter;
20
+ responseAdapter: ResponseAdapter;
21
+ };
@@ -0,0 +1,74 @@
1
+ // Copyright 2020 Outfox, Inc.
2
+ //
3
+ // Licensed under the Apache License, Version 2.0 (the "License");
4
+ // you may not use this file except in compliance with the License.
5
+ // You may obtain a copy of the License at
6
+ //
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ //
9
+ // Unless required by applicable law or agreed to in writing, software
10
+ // distributed under the License is distributed on an "AS IS" BASIS,
11
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ // See the License for the specific language governing permissions and
13
+ // limitations under the License.
14
+ import { RequestAdapters } from './request-adapters.js';
15
+ import { ResponseAdapters } from './response-adapters.js';
16
+ import { TokenProviderError } from './token-provider.js';
17
+ import { RequestCancellationError } from './request-cancellation-error.js';
18
+ import { requestContext } from './request-context.js';
19
+ import { requestSecurityBindings } from './request-security-bindings.js';
20
+ import { authorizeRequest, rejectCredentials, redactSecurityResponse, } from './request-security.js';
21
+ /** Explicit provider integration. Transports know only the request and response adapter contracts. */
22
+ export class AuthenticationAdapter {
23
+ providers;
24
+ options;
25
+ receipt = Symbol('authenticationReceipt');
26
+ /** Uses caller-owned providers and the explicit buffer bound when query credentials require a body copy. */
27
+ constructor(providers, options) {
28
+ this.providers = providers;
29
+ this.options = options;
30
+ }
31
+ async adapt(value, request) {
32
+ if (request)
33
+ return this.adaptResponse(value, request);
34
+ const original = value;
35
+ const bindings = requestSecurityBindings(original);
36
+ if (!bindings.length)
37
+ return original;
38
+ try {
39
+ const authorized = await authorizeRequest(original, bindings, this.providers, this.options?.maxBufferedRequestBytes);
40
+ requestContext(authorized.request).set(this.receipt, authorized);
41
+ return authorized.request;
42
+ }
43
+ catch (error) {
44
+ if (error instanceof TokenProviderError && error.reason !== 'temporary')
45
+ throw new RequestCancellationError(error.message, { cause: error });
46
+ throw error;
47
+ }
48
+ }
49
+ async adaptResponse(response, request) {
50
+ if (!requestSecurityBindings(request).length)
51
+ return response;
52
+ const authorized = requestContext(request).get(this.receipt);
53
+ if (!authorized)
54
+ return response;
55
+ try {
56
+ await rejectCredentials({ ...authorized, request }, response);
57
+ }
58
+ catch (error) {
59
+ if (error instanceof TokenProviderError && error.reason !== 'temporary')
60
+ throw new RequestCancellationError(error.message, { cause: error });
61
+ throw error;
62
+ }
63
+ return redactSecurityResponse(response, authorized.bindings);
64
+ }
65
+ }
66
+ /** Compose provider integration explicitly, before optional application adapters. */
67
+ export function authenticationAdapters(providers, requestAdapter, responseAdapter, options) {
68
+ const authentication = new AuthenticationAdapter(providers, options);
69
+ return {
70
+ requestAdapter: new RequestAdapters(authentication, ...(requestAdapter ? [requestAdapter] : [])),
71
+ responseAdapter: new ResponseAdapters(authentication, ...(responseAdapter ? [responseAdapter] : [])),
72
+ };
73
+ }
74
+ //# sourceMappingURL=authentication-adapter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"authentication-adapter.js","sourceRoot":"","sources":["../src/authentication-adapter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ import type { ResponseAdapter } from './response-adapter.js';
2
+ /** Opt-in stop policy for explicit bearer rejection. Does not acquire, invalidate, refresh, or replay. */
3
+ export declare class BearerTokenRejectionAdapter implements ResponseAdapter {
4
+ /** Leaves unrelated responses and all bodies untouched. Compose after provider feedback when used. */
5
+ adapt(response: Response, request: Request): Promise<Response>;
6
+ }
@@ -0,0 +1,25 @@
1
+ // Copyright 2020 Outfox, Inc.
2
+ //
3
+ // Licensed under the Apache License, Version 2.0 (the "License");
4
+ // you may not use this file except in compliance with the License.
5
+ // You may obtain a copy of the License at
6
+ //
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ //
9
+ // Unless required by applicable law or agreed to in writing, software
10
+ // distributed under the License is distributed on an "AS IS" BASIS,
11
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ // See the License for the specific language governing permissions and
13
+ // limitations under the License.
14
+ import { RequestCancellationError } from './request-cancellation-error.js';
15
+ import { isBearerTokenRejection } from './request-security.js';
16
+ /** Opt-in stop policy for explicit bearer rejection. Does not acquire, invalidate, refresh, or replay. */
17
+ export class BearerTokenRejectionAdapter {
18
+ /** Leaves unrelated responses and all bodies untouched. Compose after provider feedback when used. */
19
+ adapt(response, request) {
20
+ if (isBearerTokenRejection(request, response))
21
+ return Promise.reject(new RequestCancellationError('Access token rejected'));
22
+ return Promise.resolve(response);
23
+ }
24
+ }
25
+ //# sourceMappingURL=bearer-token-rejection-adapter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bearer-token-rejection-adapter.js","sourceRoot":"","sources":["../src/bearer-token-rejection-adapter.ts"],"names":[],"mappings":"AAAA,8BAA8B;AAC9B,EAAE;AACF,kEAAkE;AAClE,mEAAmE;AACnE,0CAA0C;AAC1C,EAAE;AACF,gDAAgD;AAChD,EAAE;AACF,sEAAsE;AACtE,oEAAoE;AACpE,2EAA2E;AAC3E,sEAAsE;AACtE,iCAAiC;AAGjC,OAAO,EAAE,wBAAwB,EAAE,MAAM,iCAAiC,CAAC;AAC3E,OAAO,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAE/D,0GAA0G;AAC1G,MAAM,OAAO,2BAA2B;IACtC,sGAAsG;IACtG,KAAK,CAAC,QAAkB,EAAE,OAAgB;QACxC,IAAI,sBAAsB,CAAC,OAAO,EAAE,QAAQ,CAAC;YAC3C,OAAO,OAAO,CAAC,MAAM,CACnB,IAAI,wBAAwB,CAAC,uBAAuB,CAAC,CACtD,CAAC;QACJ,OAAO,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IACnC,CAAC;CACF"}
@@ -0,0 +1,22 @@
1
+ import { Credentials } from './credentials.js';
2
+ import { SecurityBinding } from './security-binding.js';
3
+ import { TokenProvider } from './token-provider.js';
4
+ /** Resolved, transport-independent inputs supplied to an application's transport factory. */
5
+ export declare class ClientSettings {
6
+ #private;
7
+ readonly baseUrl: string;
8
+ /** Providers shared by this configuration's operations; caching requires an explicit TokenManager. */
9
+ readonly tokenProviders: Readonly<Record<string, TokenProvider>>;
10
+ readonly bindings: Readonly<Record<string, readonly SecurityBinding[]>>;
11
+ /** Snapshots operation bindings and credentials without acquiring tokens or making requests. */
12
+ constructor(baseUrl: string, bindings?: Readonly<Record<string, readonly SecurityBinding[]>>, credentials?: Readonly<Record<string, Credentials>>);
13
+ /** Expands server variables once and resolves relative servers against their document location. */
14
+ static serverUrl(template: string, variables: Readonly<Record<string, string>>, documentBaseUrl?: string): string;
15
+ /**
16
+ * Chooses one complete alternative per operation using credentials and optional explicit scheme sets.
17
+ * `alternativeSelection` selects a zero-based candidate index, including its scopes and endpoint metadata.
18
+ */
19
+ static resolve(baseUrl: string, alternatives: Readonly<Record<string, readonly (readonly SecurityBinding[])[]>>, credentials: Readonly<Record<string, Credentials>>, selection?: Readonly<Record<string, readonly string[]>>, alternativeSelection?: Readonly<Record<string, number>>): ClientSettings;
20
+ private prepareTokenProviders;
21
+ private prepareProvider;
22
+ }