@frontera-sdk/core 1.50.37 → 1.50.38

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@frontera-sdk/core",
3
- "version": "1.50.37",
3
+ "version": "1.50.38",
4
4
  "description": "Frontera app runtime: the platform bridge client, app bootstrap and typed platform client.",
5
5
  "keywords": [
6
6
  "frontera",
package/src/client.ts CHANGED
@@ -1,5 +1,10 @@
1
1
  import { resolveConfig, type FronteraConfig, type FronteraCredential } from './config'
2
- import { httpRequest, type RequestOptions } from './transport'
2
+ import {
3
+ httpRequest,
4
+ httpRequestEnvelope,
5
+ httpRequestRaw,
6
+ type RequestOptions,
7
+ } from './transport'
3
8
 
4
9
  /**
5
10
  * The transport-level entry point. Domain clients (`@frontera-sdk/blueprint`, and
@@ -23,6 +28,24 @@ export class FronteraClient {
23
28
  return httpRequest<T>(this.config, path, options, this.fetchImpl)
24
29
  }
25
30
 
31
+ /**
32
+ * Same auth and error mapping as `request`, but the 2xx `Response` comes back
33
+ * with its body unread — for SSE and other non-JSON payloads. Domain clients
34
+ * stream through this rather than rebuilding the Authorization header.
35
+ */
36
+ requestRaw(path: string, options: RequestOptions = {}): Promise<Response> {
37
+ return httpRequestRaw(this.config, path, options, this.fetchImpl)
38
+ }
39
+
40
+ /**
41
+ * Same as `request` but skips the envelope unwrap, so paginated routes keep
42
+ * `nextCursor` (which sits BESIDE `data` in the envelope and is otherwise
43
+ * dropped).
44
+ */
45
+ requestEnvelope<T>(path: string, options: RequestOptions = {}): Promise<T> {
46
+ return httpRequestEnvelope<T>(this.config, path, options, this.fetchImpl)
47
+ }
48
+
26
49
  withCredential(credential: FronteraCredential): FronteraClient {
27
50
  return new FronteraClient({ ...this.config, credential }, this.fetchImpl)
28
51
  }
package/src/transport.ts CHANGED
@@ -77,6 +77,80 @@ function buildUrl(config: FronteraConfig, path: string, query?: RequestOptions['
77
77
  return url.toString()
78
78
  }
79
79
 
80
+ /**
81
+ * Like `httpRequest`, but the 2xx `Response` is returned with its body
82
+ * untouched. This is what streaming endpoints need: an SSE body can only be
83
+ * read once, so the transport must not consume it. Non-2xx responses are still
84
+ * read and mapped to `FronteraError` — an error body is JSON, never a stream.
85
+ */
86
+ export async function httpRequestRaw(
87
+ config: FronteraConfig,
88
+ path: string,
89
+ options: RequestOptions = {},
90
+ fetchImpl: typeof fetch = fetch,
91
+ ): Promise<Response> {
92
+ const headers: Record<string, string> = {
93
+ ...lowerCasedKeys(options.headers),
94
+ ...authHeaders(config),
95
+ }
96
+ // FormData carries its own multipart content type WITH a boundary, which
97
+ // only it can generate — setting one here, or stringifying it, produces a
98
+ // body the server cannot parse.
99
+ const isFormData = typeof FormData !== 'undefined' && options.body instanceof FormData
100
+ if (options.body !== undefined && !isFormData) headers['content-type'] = 'application/json'
101
+
102
+ const response = await fetchImpl(buildUrl(config, path, options.query), {
103
+ method: options.method ?? 'GET',
104
+ headers,
105
+ body:
106
+ options.body === undefined
107
+ ? undefined
108
+ : isFormData
109
+ ? // `FormData`, not `BodyInit`: the guard above narrowed it to exactly
110
+ // that, and `BodyInit` is a DOM-lib global the CLI's tsconfig does
111
+ // not load (`lib: ["ESNext"]`, `types: ["bun"]`) — naming it here
112
+ // failed that package's typecheck on a file it only borrows.
113
+ (options.body as FormData)
114
+ : JSON.stringify(options.body),
115
+ signal: options.signal,
116
+ })
117
+
118
+ if (!response.ok) {
119
+ const text = await response.text()
120
+ let parsed: unknown = null
121
+ if (text.length > 0) {
122
+ try {
123
+ parsed = JSON.parse(text)
124
+ } catch {
125
+ parsed = text
126
+ }
127
+ }
128
+ throw errorFromResponse(response.status, parsed)
129
+ }
130
+ return response
131
+ }
132
+
133
+ /**
134
+ * Like `httpRequest`, but WITHOUT the envelope unwrap. Paginated routes put
135
+ * `nextCursor` beside `data` in the envelope, so unwrapping to `data` silently
136
+ * drops the cursor — callers that page need the whole body.
137
+ */
138
+ export async function httpRequestEnvelope<T>(
139
+ config: FronteraConfig,
140
+ path: string,
141
+ options: RequestOptions = {},
142
+ fetchImpl: typeof fetch = fetch,
143
+ ): Promise<T> {
144
+ const response = await httpRequestRaw(config, path, options, fetchImpl)
145
+ const text = await response.text()
146
+ if (text.length === 0) return null as T
147
+ try {
148
+ return JSON.parse(text) as T
149
+ } catch {
150
+ return text as T
151
+ }
152
+ }
153
+
80
154
  /**
81
155
  * `fetchImpl` is injectable so tests never touch the network. Production
82
156
  * callers omit it and get the platform `fetch`.