feat(sdk): restore session runtime operations (#33777)
This commit is contained in:
parent
44806777ca
commit
f44423609b
20 changed files with 1099 additions and 92 deletions
|
|
@ -37,6 +37,6 @@ The existing public `generate(Api, { directory })` operation writes the rich Eff
|
|||
|
||||
Generation formats TypeScript with Prettier before writing. Output paths are flat, unique, and checked against traversal, reserved manifest names, and existing symbolic links.
|
||||
|
||||
Generated source starts with one self-contained module per `HttpApiGroup`, plus root client and index modules. Schema dependencies may be duplicated across group modules. Cross-group schema partitioning is deferred until measured output or bundle cost requires it.
|
||||
Portable Effect output uses one self-contained module per `HttpApiGroup`, plus root client and index modules. Promise output uses shared type and client modules, while imported Effect output keeps adapters in the root client module. Schema dependencies may be duplicated across portable Effect group modules. Cross-group schema partitioning is deferred until measured output or bundle cost requires it.
|
||||
|
||||
Codegen preserves group and endpoint identifiers exactly. The composed remote `HttpApi` owns public names such as `session` and `get`; the generator performs no prefix stripping, casing conversion, or public-name annotation mapping.
|
||||
Codegen preserves transport identifiers internally. `compile` may explicitly map consumer-facing group names, and endpoint operation IDs are projected to their final dot-delimited segment. The generator performs no other implicit product-specific naming or public-name annotation mapping.
|
||||
|
|
|
|||
|
|
@ -69,6 +69,7 @@ type Slot = {
|
|||
|
||||
const resolveHttpApiStatus = SchemaAST.resolveAt<number>("httpApiStatus")
|
||||
const resolveHttpApiEncoding = SchemaAST.resolveAt<HttpApiSchema.Encoding>("~httpApiEncoding")
|
||||
const resolveContentSchema = SchemaAST.resolveAt<SchemaAST.AST>("contentSchema")
|
||||
const Manifest = Schema.fromJsonString(Schema.Array(Schema.String))
|
||||
const manifestName = ".httpapi-codegen.json"
|
||||
|
||||
|
|
@ -125,9 +126,10 @@ export function compile<Id extends string, Groups extends HttpApiGroup.Any>(
|
|||
...responseSchemas(success.schema, `${name}.success`),
|
||||
...errorSchemas.map((item) => [`${name}.error`, item.schema] as const),
|
||||
]
|
||||
const effectPortable = [params, query, headers, ...payloads, success, ...errorSchemas].every(
|
||||
(item) => item?.effectPortable !== false,
|
||||
)
|
||||
const effectPortable =
|
||||
[params, query, headers, ...payloads, success, ...errorSchemas].every(
|
||||
(item) => item?.effectPortable !== false,
|
||||
) && streamEffectPortable(success.schema)
|
||||
if (effectPortable) {
|
||||
for (const [path, schema] of schemaPaths) assertPortable(schema, path, portable)
|
||||
}
|
||||
|
|
@ -454,7 +456,7 @@ function renderPromiseTypes(groups: ReadonlyArray<Group>) {
|
|||
const success = typeOf(
|
||||
isStreamSchema(successSchema) && successSchema._tag === "StreamSse"
|
||||
? successSchema.sseMode === "data"
|
||||
? streamDataSchema(successSchema)
|
||||
? streamEncodedDataSchema(successSchema)
|
||||
: successSchema.events
|
||||
: successSchema,
|
||||
)
|
||||
|
|
@ -782,17 +784,6 @@ function responseSchemas(schema: Schema.Top, path: string): Array<readonly [stri
|
|||
if (!isStreamSchema(schema)) return [[path, schema]]
|
||||
if (schema._tag === "StreamUint8Array") return []
|
||||
const value = schema.sseMode === "data" ? streamDataSchema(schema) : schema.events
|
||||
const rebuilt =
|
||||
schema.sseMode === "data"
|
||||
? HttpApiSchema.StreamSse({ data: value, error: schema.error, contentType: schema.contentType })
|
||||
: HttpApiSchema.StreamSse({
|
||||
events: schema.events,
|
||||
error: schema.error,
|
||||
contentType: schema.contentType,
|
||||
})
|
||||
if (!sameEncoding(schema.events.ast, rebuilt.events.ast)) {
|
||||
throw new GenerationError({ reason: `Unportable schema: ${path}.${schema.sseMode}` })
|
||||
}
|
||||
return [
|
||||
[`${path}.${schema.sseMode}`, value],
|
||||
[`${path}.error`, schema.error],
|
||||
|
|
@ -964,11 +955,33 @@ function isStreamSchema(schema: Schema.Top): schema is HttpApiSchema.StreamSchem
|
|||
}
|
||||
|
||||
function streamDataSchema(schema: Extract<HttpApiSchema.StreamSchema, { readonly _tag: "StreamSse" }>) {
|
||||
const ast = Schema.toType(schema.events).ast
|
||||
return Schema.make(streamDataAst(Schema.toType(schema.events).ast))
|
||||
}
|
||||
|
||||
function streamEncodedDataSchema(schema: Extract<HttpApiSchema.StreamSchema, { readonly _tag: "StreamSse" }>) {
|
||||
const data = streamDataAst(schema.events.ast)
|
||||
const encodedAst = data.encoding?.at(-1)?.to
|
||||
if (encodedAst === undefined) throw new GenerationError({ reason: "Invalid SSE data schema" })
|
||||
const encoded = resolveContentSchema(encodedAst)
|
||||
if (!SchemaAST.isAST(encoded)) throw new GenerationError({ reason: "Invalid SSE data schema" })
|
||||
return Schema.make(encoded)
|
||||
}
|
||||
|
||||
function streamDataAst(ast: SchemaAST.AST) {
|
||||
if (!SchemaAST.isObjects(ast)) throw new GenerationError({ reason: "Invalid SSE data schema" })
|
||||
const data = ast.propertySignatures.find((field) => field.name === "data")?.type
|
||||
if (data === undefined) throw new GenerationError({ reason: "Invalid SSE data schema" })
|
||||
return Schema.make(data)
|
||||
return data
|
||||
}
|
||||
|
||||
function streamEffectPortable(schema: Schema.Top) {
|
||||
if (!isStreamSchema(schema) || schema._tag === "StreamUint8Array" || schema.sseMode === "events") return true
|
||||
const rebuilt = HttpApiSchema.StreamSse({
|
||||
data: streamDataSchema(schema),
|
||||
error: schema.error,
|
||||
contentType: schema.contentType,
|
||||
})
|
||||
return sameEncoding(schema.events.ast, rebuilt.events.ast)
|
||||
}
|
||||
|
||||
function renderGroup(group: Group, groupIndex: number) {
|
||||
|
|
|
|||
|
|
@ -395,7 +395,9 @@ describe("HttpApiCodegen.generate", () => {
|
|||
api(
|
||||
HttpApiEndpoint.get("subscribe", "/event", {
|
||||
query: { after: Schema.optional(Schema.Number) },
|
||||
success: HttpApiSchema.StreamSse({ data: Schema.Struct({ type: Schema.String }) }),
|
||||
success: HttpApiSchema.StreamSse({
|
||||
data: Schema.Struct({ type: Schema.String, count: Schema.NumberFromString }),
|
||||
}),
|
||||
}),
|
||||
),
|
||||
),
|
||||
|
|
@ -416,7 +418,7 @@ describe("HttpApiCodegen.generate", () => {
|
|||
return new Response(
|
||||
new ReadableStream({
|
||||
start(controller) {
|
||||
controller.enqueue(encoder.encode('data: {"type":"ready"}\r'))
|
||||
controller.enqueue(encoder.encode('data: {"type":"ready","count":"1"}\r'))
|
||||
controller.enqueue(encoder.encode("\n\r\n"))
|
||||
controller.close()
|
||||
},
|
||||
|
|
@ -430,7 +432,7 @@ describe("HttpApiCodegen.generate", () => {
|
|||
expect(requests).toBe(0)
|
||||
const received = []
|
||||
for await (const event of events) received.push(event)
|
||||
expect(received).toEqual([{ type: "ready" }])
|
||||
expect(received).toEqual([{ type: "ready", count: "1" }])
|
||||
expect(requests).toBe(1)
|
||||
expect(url).toBe("https://example.com/event?after=2")
|
||||
} finally {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue