@pantheon.ai/mcp 0.0.1
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/.idea/compiler.xml +6 -0
- package/.idea/modules.xml +8 -0
- package/.idea/pantheon-mcp.iml +8 -0
- package/.idea/vcs.xml +6 -0
- package/package.json +29 -0
- package/packages/sdk/package.json +16 -0
- package/packages/sdk/src/api/auth.ts +22 -0
- package/packages/sdk/src/api/connectors.ts +27 -0
- package/packages/sdk/src/api/environments.ts +53 -0
- package/packages/sdk/src/api/projects.ts +143 -0
- package/packages/sdk/src/api/stream-proxy.ts +17 -0
- package/packages/sdk/src/index.ts +16 -0
- package/packages/sdk/src/lib/api-executor.ts +169 -0
- package/packages/sdk/src/lib/api.ts +265 -0
- package/packages/sdk/src/lib/stream-parser.ts +30 -0
- package/packages/sdk/src/lib/validator.ts +19 -0
- package/packages/sdk/src/lib/vercel-ai-hack.ts +133 -0
- package/packages/sdk/src/lib/zod.ts +32 -0
- package/packages/sdk/src/schemas/ai.ts +473 -0
- package/packages/sdk/src/schemas/branch.ts +99 -0
- package/packages/sdk/src/schemas/common.ts +19 -0
- package/packages/sdk/src/schemas/environments.ts +19 -0
- package/packages/sdk/src/schemas/exploration.ts +54 -0
- package/packages/sdk/src/schemas/fs.ts +38 -0
- package/packages/sdk/src/schemas/project.ts +208 -0
- package/packages/sdk/tsconfig.json +17 -0
- package/patches/ai+6.0.45.patch +163 -0
- package/src/index.ts +34 -0
- package/src/mcp/mcp.ts +172 -0
- package/src/stdio.ts +11 -0
- package/src/streamableHttp.ts +60 -0
- package/tsconfig.json +17 -0
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
import type { ApiExecutorContext } from "@pantheon/sdk/lib/api-executor"
|
|
2
|
+
import { StreamResponseParser } from "@pantheon/sdk/lib/stream-parser"
|
|
3
|
+
import { isValidator, type Validator } from "@pantheon/sdk/lib/validator"
|
|
4
|
+
import { parseTemplate, type PrimitiveValue, type Template } from "url-template"
|
|
5
|
+
import type { ZodType } from "zod"
|
|
6
|
+
|
|
7
|
+
export type ApiMethod = "get" | "post" | "put" | "delete" | "patch"
|
|
8
|
+
|
|
9
|
+
export type PhantomType<T> = {
|
|
10
|
+
readonly __phantom_type__: T
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function typeOf<T>(): PhantomType<T> {
|
|
14
|
+
return null as never as PhantomType<T>
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
type ParamsTypeOf<Params extends readonly string[]> = Params extends []
|
|
18
|
+
? null
|
|
19
|
+
: {
|
|
20
|
+
[K in Params[number] as K extends `${infer Name}?` ? Name : never]?:
|
|
21
|
+
| PrimitiveValue
|
|
22
|
+
| PrimitiveValue[]
|
|
23
|
+
} & {
|
|
24
|
+
[K in Params[number] as K extends `${string}?` ? never : K]:
|
|
25
|
+
| PrimitiveValue
|
|
26
|
+
| PrimitiveValue[]
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
declare global {
|
|
30
|
+
interface RequestInit {
|
|
31
|
+
duplex?: "half"
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface Api<
|
|
36
|
+
Method extends ApiMethod,
|
|
37
|
+
Params extends Record<string, PrimitiveValue | PrimitiveValue[]> | null,
|
|
38
|
+
RequestBody,
|
|
39
|
+
ResponseBody,
|
|
40
|
+
> {
|
|
41
|
+
readonly method: Method
|
|
42
|
+
readonly signature: string
|
|
43
|
+
readonly url: Params extends null ? () => string : (params: Params) => string
|
|
44
|
+
readonly requestInit: Method extends "get" | "delete"
|
|
45
|
+
? () => Pick<RequestInit, "headers" | "duplex" | "method" | "body">
|
|
46
|
+
: (
|
|
47
|
+
body: RequestBody
|
|
48
|
+
) => Pick<RequestInit, "headers" | "duplex" | "method" | "body">
|
|
49
|
+
readonly handleResponse: (
|
|
50
|
+
response: Response,
|
|
51
|
+
context: ApiExecutorContext
|
|
52
|
+
) => Promise<ResponseBody>
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export type ApiParams<API> = API extends Api<any, infer P, any, any> ? P : never
|
|
56
|
+
export type ApiRequestBody<API> =
|
|
57
|
+
API extends Api<any, any, infer B, any> ? B : never
|
|
58
|
+
export type ApiResponseBody<API> =
|
|
59
|
+
API extends Api<any, any, any, infer R> ? R : never
|
|
60
|
+
|
|
61
|
+
interface UrlTemplate<
|
|
62
|
+
Method extends ApiMethod,
|
|
63
|
+
Params extends Record<string, PrimitiveValue | PrimitiveValue[]> | null,
|
|
64
|
+
> {
|
|
65
|
+
readonly method: Method
|
|
66
|
+
readonly templateUrl: string
|
|
67
|
+
readonly signature: string
|
|
68
|
+
readonly template: Template
|
|
69
|
+
readonly __params__: PhantomType<Params>
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function createUrlTemplate<Method extends ApiMethod>(method: Method) {
|
|
73
|
+
return function <Params extends string[]>(
|
|
74
|
+
arr: TemplateStringsArray,
|
|
75
|
+
...params: Params
|
|
76
|
+
): UrlTemplate<Method, ParamsTypeOf<Params>> {
|
|
77
|
+
let url = arr[0]
|
|
78
|
+
let expandedPaths = false
|
|
79
|
+
for (let i = 1; i < arr.length; i++) {
|
|
80
|
+
if (params[i - 1] === "path*") {
|
|
81
|
+
url += "<path*>"
|
|
82
|
+
expandedPaths = true
|
|
83
|
+
} else {
|
|
84
|
+
url += "{" + params[i - 1].replace(/\?$/, "") + "}" + arr[i]
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const [pathPart, searchPart] = url.split("?")
|
|
89
|
+
|
|
90
|
+
const pathPartTemplate: Template = expandedPaths
|
|
91
|
+
? {
|
|
92
|
+
expand: (context) => {
|
|
93
|
+
return parseTemplate(
|
|
94
|
+
pathPart.replace(
|
|
95
|
+
/<path\*>/,
|
|
96
|
+
String(context["path*"]).replace(/^\//, "")
|
|
97
|
+
)
|
|
98
|
+
).expand(context)
|
|
99
|
+
},
|
|
100
|
+
}
|
|
101
|
+
: parseTemplate(pathPart)
|
|
102
|
+
|
|
103
|
+
// key={template_value}, this will allow array values.
|
|
104
|
+
// key=prefix-{value}, this will go through the normal template expansion.
|
|
105
|
+
// key=constant, will be added as is.
|
|
106
|
+
const searchParamsBuilders: Array<
|
|
107
|
+
(usp: URLSearchParams, context: Record<string, any>) => void
|
|
108
|
+
> = []
|
|
109
|
+
|
|
110
|
+
if (searchPart) {
|
|
111
|
+
searchPart.split("&").forEach((entry) => {
|
|
112
|
+
const [key, value = ""] = entry.split("=").map(decodeURIComponent)
|
|
113
|
+
|
|
114
|
+
if (/^\{[^}]+}$/.test(value)) {
|
|
115
|
+
const templateValue = value.slice(1, -1)
|
|
116
|
+
searchParamsBuilders.push((usp, context) => {
|
|
117
|
+
if (templateValue in context) {
|
|
118
|
+
const value = context[templateValue]
|
|
119
|
+
if (value === undefined) {
|
|
120
|
+
return
|
|
121
|
+
}
|
|
122
|
+
if (Array.isArray(value)) {
|
|
123
|
+
value.forEach((v) => usp.append(key, String(v)))
|
|
124
|
+
} else {
|
|
125
|
+
usp.append(key, String(value))
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
})
|
|
129
|
+
} else if (value !== "") {
|
|
130
|
+
const template = parseTemplate(value)
|
|
131
|
+
|
|
132
|
+
searchParamsBuilders.push((usp, context) => {
|
|
133
|
+
usp.append(key, template.expand(context))
|
|
134
|
+
})
|
|
135
|
+
} else {
|
|
136
|
+
searchParamsBuilders.push((usp) => {
|
|
137
|
+
usp.append(key, "")
|
|
138
|
+
})
|
|
139
|
+
}
|
|
140
|
+
})
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return {
|
|
144
|
+
method,
|
|
145
|
+
signature: `${method} ${url}`,
|
|
146
|
+
templateUrl: url,
|
|
147
|
+
template: {
|
|
148
|
+
expand: (context) => {
|
|
149
|
+
const pathPart = pathPartTemplate.expand(context)
|
|
150
|
+
const usp = new URLSearchParams()
|
|
151
|
+
searchParamsBuilders.forEach((template) => {
|
|
152
|
+
template(usp, context)
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
if (Array.from(usp).length > 0) {
|
|
156
|
+
return pathPart + "?" + usp.toString()
|
|
157
|
+
} else {
|
|
158
|
+
return pathPart
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
},
|
|
162
|
+
__params__: typeOf<ParamsTypeOf<Params>>(),
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export const get = createUrlTemplate("get")
|
|
168
|
+
export const post = createUrlTemplate("post")
|
|
169
|
+
export const put = createUrlTemplate("put")
|
|
170
|
+
export const del = createUrlTemplate("delete")
|
|
171
|
+
export const patch = createUrlTemplate("patch")
|
|
172
|
+
|
|
173
|
+
type ParamsOf<T extends UrlTemplate<any, any>> = [T] extends [
|
|
174
|
+
UrlTemplate<any, infer O>,
|
|
175
|
+
]
|
|
176
|
+
? O
|
|
177
|
+
: null
|
|
178
|
+
type MethodOf<T extends UrlTemplate<any, any>> = [T] extends [
|
|
179
|
+
UrlTemplate<infer O, any>,
|
|
180
|
+
]
|
|
181
|
+
? O
|
|
182
|
+
: never
|
|
183
|
+
|
|
184
|
+
type ResponseSchemaType =
|
|
185
|
+
| ZodType<any>
|
|
186
|
+
| StreamResponseParser<any>
|
|
187
|
+
| Validator<any>
|
|
188
|
+
| "raw"
|
|
189
|
+
type inferResponse<ResponseSchema extends ResponseSchemaType> =
|
|
190
|
+
ResponseSchema extends Validator<infer O>
|
|
191
|
+
? O
|
|
192
|
+
: ResponseSchema extends ZodType<infer O>
|
|
193
|
+
? O
|
|
194
|
+
: ResponseSchema extends StreamResponseParser<infer O>
|
|
195
|
+
? ReadableStream<O>
|
|
196
|
+
: ResponseSchema extends "raw"
|
|
197
|
+
? Response
|
|
198
|
+
: never
|
|
199
|
+
|
|
200
|
+
export function defineApi<
|
|
201
|
+
UrlTemplateParameter extends UrlTemplate<any, any>,
|
|
202
|
+
RequestBody = void,
|
|
203
|
+
ResponseSchema extends ResponseSchemaType = "raw",
|
|
204
|
+
>(
|
|
205
|
+
urlTemplate: UrlTemplateParameter,
|
|
206
|
+
_type: MethodOf<UrlTemplateParameter> extends "get" | "delete"
|
|
207
|
+
? null
|
|
208
|
+
: PhantomType<RequestBody> | null,
|
|
209
|
+
responseSchema: ResponseSchema
|
|
210
|
+
) {
|
|
211
|
+
type Method = MethodOf<UrlTemplateParameter>
|
|
212
|
+
type Params = ParamsOf<UrlTemplateParameter>
|
|
213
|
+
type ResponseBody = inferResponse<ResponseSchema>
|
|
214
|
+
const method = urlTemplate.method
|
|
215
|
+
|
|
216
|
+
const api: Api<Method, Params, RequestBody, ResponseBody> = {
|
|
217
|
+
method: urlTemplate.method,
|
|
218
|
+
signature: urlTemplate.signature,
|
|
219
|
+
url: ((params: any | null) =>
|
|
220
|
+
urlTemplate.template.expand(params ?? {})) as any,
|
|
221
|
+
requestInit: (
|
|
222
|
+
body?: any
|
|
223
|
+
): Pick<RequestInit, "headers" | "duplex" | "method" | "body"> => {
|
|
224
|
+
if (method === "get" || method === "delete") {
|
|
225
|
+
return {
|
|
226
|
+
method,
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (body instanceof ReadableStream) {
|
|
231
|
+
return {
|
|
232
|
+
method,
|
|
233
|
+
body,
|
|
234
|
+
duplex: "half",
|
|
235
|
+
}
|
|
236
|
+
} else if (body instanceof FormData) {
|
|
237
|
+
return {
|
|
238
|
+
method,
|
|
239
|
+
body,
|
|
240
|
+
}
|
|
241
|
+
} else {
|
|
242
|
+
return {
|
|
243
|
+
method,
|
|
244
|
+
body: JSON.stringify(body),
|
|
245
|
+
headers: {
|
|
246
|
+
"Content-Type": "application/json",
|
|
247
|
+
},
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
},
|
|
251
|
+
handleResponse: async (response, context) => {
|
|
252
|
+
if (responseSchema instanceof StreamResponseParser) {
|
|
253
|
+
return responseSchema.handleResponse(response, context)
|
|
254
|
+
} else if (responseSchema === "raw") {
|
|
255
|
+
return response
|
|
256
|
+
} else if (isValidator(responseSchema)) {
|
|
257
|
+
return responseSchema.parse(response)
|
|
258
|
+
} else {
|
|
259
|
+
return responseSchema.parse(await response.json())
|
|
260
|
+
}
|
|
261
|
+
},
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return api
|
|
265
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { ApiExecutorContext } from "@pantheon/sdk/lib/api-executor"
|
|
2
|
+
|
|
3
|
+
export interface StreamResponseParserOptions<Output> {
|
|
4
|
+
readonly validateResponse?: (response: Response) => void | Promise<void>
|
|
5
|
+
readonly pipe: (
|
|
6
|
+
input: ReadableStream<BufferSource>,
|
|
7
|
+
context: ApiExecutorContext & { readonly response: Response }
|
|
8
|
+
) => ReadableStream<Output>
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export class StreamResponseParser<Output> {
|
|
12
|
+
private readonly options: StreamResponseParserOptions<Output>
|
|
13
|
+
|
|
14
|
+
constructor(options: StreamResponseParserOptions<Output>) {
|
|
15
|
+
this.options = options
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async handleResponse(response: Response, context: ApiExecutorContext) {
|
|
19
|
+
this.options.validateResponse?.(response)
|
|
20
|
+
|
|
21
|
+
if (!response.body) {
|
|
22
|
+
throw new Error("Response body is missing")
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return this.options.pipe(
|
|
26
|
+
response.body,
|
|
27
|
+
Object.freeze({ ...context, response })
|
|
28
|
+
)
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { PhantomType } from "@pantheon/sdk/lib/api"
|
|
2
|
+
|
|
3
|
+
export const VALIDATOR_SYMBOL = Symbol("validator")
|
|
4
|
+
|
|
5
|
+
export interface Validator<Output> {
|
|
6
|
+
readonly [VALIDATOR_SYMBOL]: true
|
|
7
|
+
readonly __phantom__: PhantomType<Output>
|
|
8
|
+
|
|
9
|
+
parse(input: Response): Output | Promise<Output>
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function isValidator(object: any): object is Validator<unknown> {
|
|
13
|
+
return (
|
|
14
|
+
typeof object === "object" &&
|
|
15
|
+
object !== null &&
|
|
16
|
+
object[VALIDATOR_SYMBOL] === true &&
|
|
17
|
+
typeof object.parse === "function"
|
|
18
|
+
)
|
|
19
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createStreamingUIMessageState,
|
|
3
|
+
processUIMessageStream,
|
|
4
|
+
type StreamingUIMessageState,
|
|
5
|
+
type UIMessage,
|
|
6
|
+
type UIMessageChunk,
|
|
7
|
+
} from "ai"
|
|
8
|
+
|
|
9
|
+
export async function fastReadUIMessageStream<UI_MESSAGE extends UIMessage>({
|
|
10
|
+
stream,
|
|
11
|
+
messageId,
|
|
12
|
+
onState,
|
|
13
|
+
state: propState,
|
|
14
|
+
}: {
|
|
15
|
+
stream: ReadableStream<UIMessageChunk>
|
|
16
|
+
messageId: string
|
|
17
|
+
onState: (state: StreamingUIMessageState<UI_MESSAGE>) => void
|
|
18
|
+
state?: StreamingUIMessageState<UI_MESSAGE>
|
|
19
|
+
}) {
|
|
20
|
+
const state =
|
|
21
|
+
propState ??
|
|
22
|
+
createStreamingUIMessageState<UI_MESSAGE>({
|
|
23
|
+
lastMessage: undefined,
|
|
24
|
+
messageId,
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
const res = processUIMessageStream<UI_MESSAGE>({
|
|
28
|
+
stream,
|
|
29
|
+
runUpdateMessageJob(
|
|
30
|
+
job: (options: {
|
|
31
|
+
state: StreamingUIMessageState<UI_MESSAGE>
|
|
32
|
+
write: () => void
|
|
33
|
+
}) => Promise<void>
|
|
34
|
+
) {
|
|
35
|
+
return job({
|
|
36
|
+
state,
|
|
37
|
+
write: () => {},
|
|
38
|
+
})
|
|
39
|
+
},
|
|
40
|
+
onError: (error) => {
|
|
41
|
+
// ignore error chunk
|
|
42
|
+
state.message.metadata ??= {}
|
|
43
|
+
;(state.message.metadata as any).__stream_error__ = (
|
|
44
|
+
error as Error
|
|
45
|
+
).message
|
|
46
|
+
},
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
let chunks = 0
|
|
50
|
+
|
|
51
|
+
const reader = res.getReader()
|
|
52
|
+
while (true) {
|
|
53
|
+
const chunk = await reader.read()
|
|
54
|
+
|
|
55
|
+
if (chunk.done) break
|
|
56
|
+
chunks++
|
|
57
|
+
onState(state)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return { chunks, state }
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export type UIMessageStreamCursor<UI_MESSAGE extends UIMessage> = {
|
|
64
|
+
offset: number
|
|
65
|
+
state: StreamingUIMessageState<UI_MESSAGE>
|
|
66
|
+
chunk: UIMessageChunk
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function fastResumeReadUIMessageStream<UI_MESSAGE extends UIMessage>({
|
|
70
|
+
stream,
|
|
71
|
+
messageId,
|
|
72
|
+
state: propState,
|
|
73
|
+
offset = 0,
|
|
74
|
+
}: {
|
|
75
|
+
stream: ReadableStream<UIMessageChunk>
|
|
76
|
+
messageId: string
|
|
77
|
+
state?: StreamingUIMessageState<UI_MESSAGE>
|
|
78
|
+
offset?: number
|
|
79
|
+
}) {
|
|
80
|
+
const state =
|
|
81
|
+
propState ??
|
|
82
|
+
createStreamingUIMessageState<UI_MESSAGE>({
|
|
83
|
+
lastMessage: undefined,
|
|
84
|
+
messageId,
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
return new ReadableStream<UIMessageStreamCursor<UI_MESSAGE>>({
|
|
88
|
+
async start(controller) {
|
|
89
|
+
try {
|
|
90
|
+
const res = processUIMessageStream<UI_MESSAGE>({
|
|
91
|
+
stream,
|
|
92
|
+
runUpdateMessageJob(
|
|
93
|
+
job: (options: {
|
|
94
|
+
state: StreamingUIMessageState<UI_MESSAGE>
|
|
95
|
+
write: () => void
|
|
96
|
+
}) => Promise<void>
|
|
97
|
+
) {
|
|
98
|
+
return job({
|
|
99
|
+
state,
|
|
100
|
+
write: () => {},
|
|
101
|
+
})
|
|
102
|
+
},
|
|
103
|
+
onError: (error) => {
|
|
104
|
+
// ignore error chunk
|
|
105
|
+
state.message.metadata ??= {}
|
|
106
|
+
;(state.message.metadata as any).__stream_error__ = (
|
|
107
|
+
error as Error
|
|
108
|
+
).message
|
|
109
|
+
},
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
let chunks = 0
|
|
113
|
+
|
|
114
|
+
const reader = res.getReader()
|
|
115
|
+
|
|
116
|
+
while (true) {
|
|
117
|
+
const chunk = await reader.read()
|
|
118
|
+
if (chunk.done) break
|
|
119
|
+
controller.enqueue({
|
|
120
|
+
offset: chunks + offset,
|
|
121
|
+
state,
|
|
122
|
+
chunk: chunk.value,
|
|
123
|
+
})
|
|
124
|
+
chunks++
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
controller.close()
|
|
128
|
+
} catch (e) {
|
|
129
|
+
controller.error(e)
|
|
130
|
+
}
|
|
131
|
+
},
|
|
132
|
+
})
|
|
133
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { z, type ZodType } from "zod"
|
|
2
|
+
|
|
3
|
+
export interface ZodStreamOptions<Input, Z extends ZodType<unknown, Input>> {
|
|
4
|
+
schema: Z
|
|
5
|
+
onParseFailed?: (
|
|
6
|
+
error: unknown,
|
|
7
|
+
chunk: unknown,
|
|
8
|
+
controller: TransformStreamDefaultController<z.infer<Z>>
|
|
9
|
+
) => void
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export class ZodStream<
|
|
13
|
+
Input,
|
|
14
|
+
Z extends ZodType<unknown, Input>,
|
|
15
|
+
> extends TransformStream<Input, z.infer<Z>> {
|
|
16
|
+
constructor({ schema, onParseFailed }: ZodStreamOptions<Input, Z>) {
|
|
17
|
+
super({
|
|
18
|
+
async transform(chunk, controller) {
|
|
19
|
+
const result = await schema.safeParseAsync(chunk)
|
|
20
|
+
if (result.success) {
|
|
21
|
+
controller.enqueue(result.data)
|
|
22
|
+
} else if (onParseFailed) {
|
|
23
|
+
onParseFailed(result.error, chunk, controller)
|
|
24
|
+
} else {
|
|
25
|
+
controller.error(result.error)
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
})
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const zodJsonDate = z.string() // z.iso.datetime().transform((d) => new Date(d));
|