@saeeol/sdk 7.3.3 → 7.3.4

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 (39) hide show
  1. package/package.json +2 -2
  2. package/src/client.ts +64 -0
  3. package/src/gen/client/client.gen.ts +215 -0
  4. package/src/gen/client/index.ts +25 -0
  5. package/src/gen/client/types.gen.ts +222 -0
  6. package/src/gen/client/utils.gen.ts +287 -0
  7. package/src/gen/client.gen.ts +22 -0
  8. package/src/gen/core/auth.gen.ts +41 -0
  9. package/src/gen/core/bodySerializer.gen.ts +74 -0
  10. package/src/gen/core/params.gen.ts +144 -0
  11. package/src/gen/core/pathSerializer.gen.ts +167 -0
  12. package/src/gen/core/queryKeySerializer.gen.ts +111 -0
  13. package/src/gen/core/serverSentEvents.gen.ts +210 -0
  14. package/src/gen/core/types.gen.ts +91 -0
  15. package/src/gen/core/utils.gen.ts +109 -0
  16. package/src/gen/sdk.gen.ts +1197 -0
  17. package/src/gen/types.gen.ts +3905 -0
  18. package/src/index.ts +21 -0
  19. package/src/process.ts +31 -0
  20. package/src/server.ts +165 -0
  21. package/src/v2/client.ts +97 -0
  22. package/src/v2/data.ts +32 -0
  23. package/src/v2/gen/client/client.gen.ts +285 -0
  24. package/src/v2/gen/client/index.ts +25 -0
  25. package/src/v2/gen/client/types.gen.ts +202 -0
  26. package/src/v2/gen/client/utils.gen.ts +289 -0
  27. package/src/v2/gen/client.gen.ts +18 -0
  28. package/src/v2/gen/core/auth.gen.ts +41 -0
  29. package/src/v2/gen/core/bodySerializer.gen.ts +82 -0
  30. package/src/v2/gen/core/params.gen.ts +169 -0
  31. package/src/v2/gen/core/pathSerializer.gen.ts +167 -0
  32. package/src/v2/gen/core/queryKeySerializer.gen.ts +111 -0
  33. package/src/v2/gen/core/serverSentEvents.gen.ts +239 -0
  34. package/src/v2/gen/core/types.gen.ts +86 -0
  35. package/src/v2/gen/core/utils.gen.ts +137 -0
  36. package/src/v2/gen/sdk.gen.ts +6316 -0
  37. package/src/v2/gen/types.gen.ts +7495 -0
  38. package/src/v2/index.ts +23 -0
  39. package/src/v2/server.ts +163 -0
@@ -0,0 +1,169 @@
1
+ // This file is auto-generated by @hey-api/openapi-ts
2
+
3
+ type Slot = "body" | "headers" | "path" | "query"
4
+
5
+ export type Field =
6
+ | {
7
+ in: Exclude<Slot, "body">
8
+ /**
9
+ * Field name. This is the name we want the user to see and use.
10
+ */
11
+ key: string
12
+ /**
13
+ * Field mapped name. This is the name we want to use in the request.
14
+ * If omitted, we use the same value as `key`.
15
+ */
16
+ map?: string
17
+ }
18
+ | {
19
+ in: Extract<Slot, "body">
20
+ /**
21
+ * Key isn't required for bodies.
22
+ */
23
+ key?: string
24
+ map?: string
25
+ }
26
+ | {
27
+ /**
28
+ * Field name. This is the name we want the user to see and use.
29
+ */
30
+ key: string
31
+ /**
32
+ * Field mapped name. This is the name we want to use in the request.
33
+ * If `in` is omitted, `map` aliases `key` to the transport layer.
34
+ */
35
+ map: Slot
36
+ }
37
+
38
+ export interface Fields {
39
+ allowExtra?: Partial<Record<Slot, boolean>>
40
+ args?: ReadonlyArray<Field>
41
+ }
42
+
43
+ export type FieldsConfig = ReadonlyArray<Field | Fields>
44
+
45
+ const extraPrefixesMap: Record<string, Slot> = {
46
+ $body_: "body",
47
+ $headers_: "headers",
48
+ $path_: "path",
49
+ $query_: "query",
50
+ }
51
+ const extraPrefixes = Object.entries(extraPrefixesMap)
52
+
53
+ type KeyMap = Map<
54
+ string,
55
+ | {
56
+ in: Slot
57
+ map?: string
58
+ }
59
+ | {
60
+ in?: never
61
+ map: Slot
62
+ }
63
+ >
64
+
65
+ const buildKeyMap = (fields: FieldsConfig, map?: KeyMap): KeyMap => {
66
+ if (!map) {
67
+ map = new Map()
68
+ }
69
+
70
+ for (const config of fields) {
71
+ if ("in" in config) {
72
+ if (config.key) {
73
+ map.set(config.key, {
74
+ in: config.in,
75
+ map: config.map,
76
+ })
77
+ }
78
+ } else if ("key" in config) {
79
+ map.set(config.key, {
80
+ map: config.map,
81
+ })
82
+ } else if (config.args) {
83
+ buildKeyMap(config.args, map)
84
+ }
85
+ }
86
+
87
+ return map
88
+ }
89
+
90
+ interface Params {
91
+ body: unknown
92
+ headers: Record<string, unknown>
93
+ path: Record<string, unknown>
94
+ query: Record<string, unknown>
95
+ }
96
+
97
+ const stripEmptySlots = (params: Params) => {
98
+ for (const [slot, value] of Object.entries(params)) {
99
+ if (value && typeof value === "object" && !Object.keys(value).length) {
100
+ delete params[slot as Slot]
101
+ }
102
+ }
103
+ }
104
+
105
+ export const buildClientParams = (args: ReadonlyArray<unknown>, fields: FieldsConfig) => {
106
+ const params: Params = {
107
+ body: {},
108
+ headers: {},
109
+ path: {},
110
+ query: {},
111
+ }
112
+
113
+ const map = buildKeyMap(fields)
114
+
115
+ let config: FieldsConfig[number] | undefined
116
+
117
+ for (const [index, arg] of args.entries()) {
118
+ if (fields[index]) {
119
+ config = fields[index]
120
+ }
121
+
122
+ if (!config) {
123
+ continue
124
+ }
125
+
126
+ if ("in" in config) {
127
+ if (config.key) {
128
+ const field = map.get(config.key)!
129
+ const name = field.map || config.key
130
+ if (field.in) {
131
+ ;(params[field.in] as Record<string, unknown>)[name] = arg
132
+ }
133
+ } else {
134
+ params.body = arg
135
+ }
136
+ } else {
137
+ for (const [key, value] of Object.entries(arg ?? {})) {
138
+ const field = map.get(key)
139
+
140
+ if (field) {
141
+ if (field.in) {
142
+ const name = field.map || key
143
+ ;(params[field.in] as Record<string, unknown>)[name] = value
144
+ } else {
145
+ params[field.map] = value
146
+ }
147
+ } else {
148
+ const extra = extraPrefixes.find(([prefix]) => key.startsWith(prefix))
149
+
150
+ if (extra) {
151
+ const [prefix, slot] = extra
152
+ ;(params[slot] as Record<string, unknown>)[key.slice(prefix.length)] = value
153
+ } else if ("allowExtra" in config && config.allowExtra) {
154
+ for (const [slot, allowed] of Object.entries(config.allowExtra)) {
155
+ if (allowed) {
156
+ ;(params[slot as Slot] as Record<string, unknown>)[key] = value
157
+ break
158
+ }
159
+ }
160
+ }
161
+ }
162
+ }
163
+ }
164
+ }
165
+
166
+ stripEmptySlots(params)
167
+
168
+ return params
169
+ }
@@ -0,0 +1,167 @@
1
+ // This file is auto-generated by @hey-api/openapi-ts
2
+
3
+ interface SerializeOptions<T> extends SerializePrimitiveOptions, SerializerOptions<T> {}
4
+
5
+ interface SerializePrimitiveOptions {
6
+ allowReserved?: boolean
7
+ name: string
8
+ }
9
+
10
+ export interface SerializerOptions<T> {
11
+ /**
12
+ * @default true
13
+ */
14
+ explode: boolean
15
+ style: T
16
+ }
17
+
18
+ export type ArrayStyle = "form" | "spaceDelimited" | "pipeDelimited"
19
+ export type ArraySeparatorStyle = ArrayStyle | MatrixStyle
20
+ type MatrixStyle = "label" | "matrix" | "simple"
21
+ export type ObjectStyle = "form" | "deepObject"
22
+ type ObjectSeparatorStyle = ObjectStyle | MatrixStyle
23
+
24
+ interface SerializePrimitiveParam extends SerializePrimitiveOptions {
25
+ value: string
26
+ }
27
+
28
+ export const separatorArrayExplode = (style: ArraySeparatorStyle) => {
29
+ switch (style) {
30
+ case "label":
31
+ return "."
32
+ case "matrix":
33
+ return ";"
34
+ case "simple":
35
+ return ","
36
+ default:
37
+ return "&"
38
+ }
39
+ }
40
+
41
+ export const separatorArrayNoExplode = (style: ArraySeparatorStyle) => {
42
+ switch (style) {
43
+ case "form":
44
+ return ","
45
+ case "pipeDelimited":
46
+ return "|"
47
+ case "spaceDelimited":
48
+ return "%20"
49
+ default:
50
+ return ","
51
+ }
52
+ }
53
+
54
+ export const separatorObjectExplode = (style: ObjectSeparatorStyle) => {
55
+ switch (style) {
56
+ case "label":
57
+ return "."
58
+ case "matrix":
59
+ return ";"
60
+ case "simple":
61
+ return ","
62
+ default:
63
+ return "&"
64
+ }
65
+ }
66
+
67
+ export const serializeArrayParam = ({
68
+ allowReserved,
69
+ explode,
70
+ name,
71
+ style,
72
+ value,
73
+ }: SerializeOptions<ArraySeparatorStyle> & {
74
+ value: unknown[]
75
+ }) => {
76
+ if (!explode) {
77
+ const joinedValues = (allowReserved ? value : value.map((v) => encodeURIComponent(v as string))).join(
78
+ separatorArrayNoExplode(style),
79
+ )
80
+ switch (style) {
81
+ case "label":
82
+ return `.${joinedValues}`
83
+ case "matrix":
84
+ return `;${name}=${joinedValues}`
85
+ case "simple":
86
+ return joinedValues
87
+ default:
88
+ return `${name}=${joinedValues}`
89
+ }
90
+ }
91
+
92
+ const separator = separatorArrayExplode(style)
93
+ const joinedValues = value
94
+ .map((v) => {
95
+ if (style === "label" || style === "simple") {
96
+ return allowReserved ? v : encodeURIComponent(v as string)
97
+ }
98
+
99
+ return serializePrimitiveParam({
100
+ allowReserved,
101
+ name,
102
+ value: v as string,
103
+ })
104
+ })
105
+ .join(separator)
106
+ return style === "label" || style === "matrix" ? separator + joinedValues : joinedValues
107
+ }
108
+
109
+ export const serializePrimitiveParam = ({ allowReserved, name, value }: SerializePrimitiveParam) => {
110
+ if (value === undefined || value === null) {
111
+ return ""
112
+ }
113
+
114
+ if (typeof value === "object") {
115
+ throw new Error(
116
+ "Deeply-nested arrays/objects aren’t supported. Provide your own `querySerializer()` to handle these.",
117
+ )
118
+ }
119
+
120
+ return `${name}=${allowReserved ? value : encodeURIComponent(value)}`
121
+ }
122
+
123
+ export const serializeObjectParam = ({
124
+ allowReserved,
125
+ explode,
126
+ name,
127
+ style,
128
+ value,
129
+ valueOnly,
130
+ }: SerializeOptions<ObjectSeparatorStyle> & {
131
+ value: Record<string, unknown> | Date
132
+ valueOnly?: boolean
133
+ }) => {
134
+ if (value instanceof Date) {
135
+ return valueOnly ? value.toISOString() : `${name}=${value.toISOString()}`
136
+ }
137
+
138
+ if (style !== "deepObject" && !explode) {
139
+ let values: string[] = []
140
+ Object.entries(value).forEach(([key, v]) => {
141
+ values = [...values, key, allowReserved ? (v as string) : encodeURIComponent(v as string)]
142
+ })
143
+ const joinedValues = values.join(",")
144
+ switch (style) {
145
+ case "form":
146
+ return `${name}=${joinedValues}`
147
+ case "label":
148
+ return `.${joinedValues}`
149
+ case "matrix":
150
+ return `;${name}=${joinedValues}`
151
+ default:
152
+ return joinedValues
153
+ }
154
+ }
155
+
156
+ const separator = separatorObjectExplode(style)
157
+ const joinedValues = Object.entries(value)
158
+ .map(([key, v]) =>
159
+ serializePrimitiveParam({
160
+ allowReserved,
161
+ name: style === "deepObject" ? `${name}[${key}]` : key,
162
+ value: v as string,
163
+ }),
164
+ )
165
+ .join(separator)
166
+ return style === "label" || style === "matrix" ? separator + joinedValues : joinedValues
167
+ }
@@ -0,0 +1,111 @@
1
+ // This file is auto-generated by @hey-api/openapi-ts
2
+
3
+ /**
4
+ * JSON-friendly union that mirrors what Pinia Colada can hash.
5
+ */
6
+ export type JsonValue = null | string | number | boolean | JsonValue[] | { [key: string]: JsonValue }
7
+
8
+ /**
9
+ * Replacer that converts non-JSON values (bigint, Date, etc.) to safe substitutes.
10
+ */
11
+ export const queryKeyJsonReplacer = (_key: string, value: unknown) => {
12
+ if (value === undefined || typeof value === "function" || typeof value === "symbol") {
13
+ return undefined
14
+ }
15
+ if (typeof value === "bigint") {
16
+ return value.toString()
17
+ }
18
+ if (value instanceof Date) {
19
+ return value.toISOString()
20
+ }
21
+ return value
22
+ }
23
+
24
+ /**
25
+ * Safely stringifies a value and parses it back into a JsonValue.
26
+ */
27
+ export const stringifyToJsonValue = (input: unknown): JsonValue | undefined => {
28
+ try {
29
+ const json = JSON.stringify(input, queryKeyJsonReplacer)
30
+ if (json === undefined) {
31
+ return undefined
32
+ }
33
+ return JSON.parse(json) as JsonValue
34
+ } catch {
35
+ return undefined
36
+ }
37
+ }
38
+
39
+ /**
40
+ * Detects plain objects (including objects with a null prototype).
41
+ */
42
+ const isPlainObject = (value: unknown): value is Record<string, unknown> => {
43
+ if (value === null || typeof value !== "object") {
44
+ return false
45
+ }
46
+ const prototype = Object.getPrototypeOf(value as object)
47
+ return prototype === Object.prototype || prototype === null
48
+ }
49
+
50
+ /**
51
+ * Turns URLSearchParams into a sorted JSON object for deterministic keys.
52
+ */
53
+ const serializeSearchParams = (params: URLSearchParams): JsonValue => {
54
+ const entries = Array.from(params.entries()).sort(([a], [b]) => a.localeCompare(b))
55
+ const result: Record<string, JsonValue> = {}
56
+
57
+ for (const [key, value] of entries) {
58
+ const existing = result[key]
59
+ if (existing === undefined) {
60
+ result[key] = value
61
+ continue
62
+ }
63
+
64
+ if (Array.isArray(existing)) {
65
+ ;(existing as string[]).push(value)
66
+ } else {
67
+ result[key] = [existing, value]
68
+ }
69
+ }
70
+
71
+ return result
72
+ }
73
+
74
+ /**
75
+ * Normalizes any accepted value into a JSON-friendly shape for query keys.
76
+ */
77
+ export const serializeQueryKeyValue = (value: unknown): JsonValue | undefined => {
78
+ if (value === null) {
79
+ return null
80
+ }
81
+
82
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
83
+ return value
84
+ }
85
+
86
+ if (value === undefined || typeof value === "function" || typeof value === "symbol") {
87
+ return undefined
88
+ }
89
+
90
+ if (typeof value === "bigint") {
91
+ return value.toString()
92
+ }
93
+
94
+ if (value instanceof Date) {
95
+ return value.toISOString()
96
+ }
97
+
98
+ if (Array.isArray(value)) {
99
+ return stringifyToJsonValue(value)
100
+ }
101
+
102
+ if (typeof URLSearchParams !== "undefined" && value instanceof URLSearchParams) {
103
+ return serializeSearchParams(value)
104
+ }
105
+
106
+ if (isPlainObject(value)) {
107
+ return stringifyToJsonValue(value)
108
+ }
109
+
110
+ return undefined
111
+ }
@@ -0,0 +1,239 @@
1
+ // This file is auto-generated by @hey-api/openapi-ts
2
+
3
+ import type { Config } from "./types.gen.js"
4
+
5
+ export type ServerSentEventsOptions<TData = unknown> = Omit<RequestInit, "method"> &
6
+ Pick<Config, "method" | "responseTransformer" | "responseValidator"> & {
7
+ /**
8
+ * Fetch API implementation. You can use this option to provide a custom
9
+ * fetch instance.
10
+ *
11
+ * @default globalThis.fetch
12
+ */
13
+ fetch?: typeof fetch
14
+ /**
15
+ * Implementing clients can call request interceptors inside this hook.
16
+ */
17
+ onRequest?: (url: string, init: RequestInit) => Promise<Request>
18
+ /**
19
+ * Callback invoked when a network or parsing error occurs during streaming.
20
+ *
21
+ * This option applies only if the endpoint returns a stream of events.
22
+ *
23
+ * @param error The error that occurred.
24
+ */
25
+ onSseError?: (error: unknown) => void
26
+ /**
27
+ * Callback invoked when an event is streamed from the server.
28
+ *
29
+ * This option applies only if the endpoint returns a stream of events.
30
+ *
31
+ * @param event Event streamed from the server.
32
+ * @returns Nothing (void).
33
+ */
34
+ onSseEvent?: (event: StreamEvent<TData>) => void
35
+ serializedBody?: RequestInit["body"]
36
+ /**
37
+ * Default retry delay in milliseconds.
38
+ *
39
+ * This option applies only if the endpoint returns a stream of events.
40
+ *
41
+ * @default 3000
42
+ */
43
+ sseDefaultRetryDelay?: number
44
+ /**
45
+ * Maximum number of retry attempts before giving up.
46
+ */
47
+ sseMaxRetryAttempts?: number
48
+ /**
49
+ * Maximum retry delay in milliseconds.
50
+ *
51
+ * Applies only when exponential backoff is used.
52
+ *
53
+ * This option applies only if the endpoint returns a stream of events.
54
+ *
55
+ * @default 30000
56
+ */
57
+ sseMaxRetryDelay?: number
58
+ /**
59
+ * Optional sleep function for retry backoff.
60
+ *
61
+ * Defaults to using `setTimeout`.
62
+ */
63
+ sseSleepFn?: (ms: number) => Promise<void>
64
+ url: string
65
+ }
66
+
67
+ export interface StreamEvent<TData = unknown> {
68
+ data: TData
69
+ event?: string
70
+ id?: string
71
+ retry?: number
72
+ }
73
+
74
+ export type ServerSentEventsResult<TData = unknown, TReturn = void, TNext = unknown> = {
75
+ stream: AsyncGenerator<TData extends Record<string, unknown> ? TData[keyof TData] : TData, TReturn, TNext>
76
+ }
77
+
78
+ export const createSseClient = <TData = unknown>({
79
+ onRequest,
80
+ onSseError,
81
+ onSseEvent,
82
+ responseTransformer,
83
+ responseValidator,
84
+ sseDefaultRetryDelay,
85
+ sseMaxRetryAttempts,
86
+ sseMaxRetryDelay,
87
+ sseSleepFn,
88
+ url,
89
+ ...options
90
+ }: ServerSentEventsOptions): ServerSentEventsResult<TData> => {
91
+ let lastEventId: string | undefined
92
+
93
+ const sleep = sseSleepFn ?? ((ms: number) => new Promise((resolve) => setTimeout(resolve, ms)))
94
+
95
+ const createStream = async function* () {
96
+ let retryDelay: number = sseDefaultRetryDelay ?? 3000
97
+ let attempt = 0
98
+ const signal = options.signal ?? new AbortController().signal
99
+
100
+ while (true) {
101
+ if (signal.aborted) break
102
+
103
+ attempt++
104
+
105
+ const headers =
106
+ options.headers instanceof Headers
107
+ ? options.headers
108
+ : new Headers(options.headers as Record<string, string> | undefined)
109
+
110
+ if (lastEventId !== undefined) {
111
+ headers.set("Last-Event-ID", lastEventId)
112
+ }
113
+
114
+ try {
115
+ const requestInit: RequestInit = {
116
+ redirect: "follow",
117
+ ...options,
118
+ body: options.serializedBody,
119
+ headers,
120
+ signal,
121
+ }
122
+ let request = new Request(url, requestInit)
123
+ if (onRequest) {
124
+ request = await onRequest(url, requestInit)
125
+ }
126
+ // fetch must be assigned here, otherwise it would throw the error:
127
+ // TypeError: Failed to execute 'fetch' on 'Window': Illegal invocation
128
+ const _fetch = options.fetch ?? globalThis.fetch
129
+ const response = await _fetch(request)
130
+
131
+ if (!response.ok) throw new Error(`SSE failed: ${response.status} ${response.statusText}`)
132
+
133
+ if (!response.body) throw new Error("No body in SSE response")
134
+
135
+ const reader = response.body.pipeThrough(new TextDecoderStream()).getReader()
136
+
137
+ let buffer = ""
138
+
139
+ const abortHandler = () => {
140
+ try {
141
+ reader.cancel()
142
+ } catch {
143
+ // noop
144
+ }
145
+ }
146
+
147
+ signal.addEventListener("abort", abortHandler)
148
+
149
+ try {
150
+ while (true) {
151
+ const { done, value } = await reader.read()
152
+ if (done) break
153
+ buffer += value
154
+ // Normalize line endings: CRLF -> LF, then CR -> LF
155
+ buffer = buffer.replace(/\r\n/g, "\n").replace(/\r/g, "\n")
156
+
157
+ const chunks = buffer.split("\n\n")
158
+ buffer = chunks.pop() ?? ""
159
+
160
+ for (const chunk of chunks) {
161
+ const lines = chunk.split("\n")
162
+ const dataLines: Array<string> = []
163
+ let eventName: string | undefined
164
+
165
+ for (const line of lines) {
166
+ if (line.startsWith("data:")) {
167
+ dataLines.push(line.replace(/^data:\s*/, ""))
168
+ } else if (line.startsWith("event:")) {
169
+ eventName = line.replace(/^event:\s*/, "")
170
+ } else if (line.startsWith("id:")) {
171
+ lastEventId = line.replace(/^id:\s*/, "")
172
+ } else if (line.startsWith("retry:")) {
173
+ const parsed = Number.parseInt(line.replace(/^retry:\s*/, ""), 10)
174
+ if (!Number.isNaN(parsed)) {
175
+ retryDelay = parsed
176
+ }
177
+ }
178
+ }
179
+
180
+ let data: unknown
181
+ let parsedJson = false
182
+
183
+ if (dataLines.length) {
184
+ const rawData = dataLines.join("\n")
185
+ try {
186
+ data = JSON.parse(rawData)
187
+ parsedJson = true
188
+ } catch {
189
+ data = rawData
190
+ }
191
+ }
192
+
193
+ if (parsedJson) {
194
+ if (responseValidator) {
195
+ await responseValidator(data)
196
+ }
197
+
198
+ if (responseTransformer) {
199
+ data = await responseTransformer(data)
200
+ }
201
+ }
202
+
203
+ onSseEvent?.({
204
+ data,
205
+ event: eventName,
206
+ id: lastEventId,
207
+ retry: retryDelay,
208
+ })
209
+
210
+ if (dataLines.length) {
211
+ yield data as any
212
+ }
213
+ }
214
+ }
215
+ } finally {
216
+ signal.removeEventListener("abort", abortHandler)
217
+ reader.releaseLock()
218
+ }
219
+
220
+ break // exit loop on normal completion
221
+ } catch (error) {
222
+ // connection failed or aborted; retry after delay
223
+ onSseError?.(error)
224
+
225
+ if (sseMaxRetryAttempts !== undefined && attempt >= sseMaxRetryAttempts) {
226
+ break // stop after firing error
227
+ }
228
+
229
+ // exponential backoff: double retry each attempt, cap at 30s
230
+ const backoff = Math.min(retryDelay * 2 ** (attempt - 1), sseMaxRetryDelay ?? 30000)
231
+ await sleep(backoff)
232
+ }
233
+ }
234
+ }
235
+
236
+ const stream = createStream()
237
+
238
+ return { stream }
239
+ }