@restatedev/restate-sdk-cloudflare-workers 1.13.0 → 1.14.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.
@@ -12,6 +12,7 @@ import { CommandError, ContextImpl } from "../../context_impl.js";
12
12
  import { restoreError, sanitizeError } from "../../error_sanitization.js";
13
13
  import { errorResponse, invocationIdFromHeaders, simpleResponse, tryCreateContextualLogger } from "./utils.js";
14
14
  import { handleDiscovery } from "./discovery.js";
15
+ import { handlePreview } from "./preview.js";
15
16
  import { millisOrDurationToMillis } from "@restatedev/restate-sdk-core";
16
17
 
17
18
  //#region src/endpoint/handlers/generic.ts
@@ -48,7 +49,7 @@ var RestateHandlerImpl = class {
48
49
  const path = new URL(request.url, "https://example.com").pathname;
49
50
  const parsed = parseUrlComponents(path);
50
51
  if (parsed.type === "unknown") {
51
- const msg = `Invalid path. Allowed are /health, or /discover, or /invoke/SvcName/handlerName, but was: ${path}`;
52
+ const msg = "Invalid path. Allowed are /health, /discover, /invoke/SvcName/handlerName, /serdes/SvcName/decode/<serdeName>, or /serdes/SvcName/encode/<serdeName>, but was: " + path;
52
53
  this.endpoint.rlog.trace(msg);
53
54
  return errorResponse(404, msg);
54
55
  }
@@ -59,6 +60,7 @@ var RestateHandlerImpl = class {
59
60
  const error = this.validateConnectionSignature(path, request.headers);
60
61
  if (error !== null) return error;
61
62
  if (parsed.type === "discover") return handleDiscovery(this.endpoint, this.protocolMode, this.additionalDiscoveryFields, request.headers["accept"]);
63
+ if (parsed.type === "preview") return handlePreview(this.endpoint, parsed);
62
64
  return this.handleInvoke(parsed, request.headers, request.extraArgs, context ?? {});
63
65
  }
64
66
  validateConnectionSignature(path, headers) {
@@ -84,9 +86,9 @@ var RestateHandlerImpl = class {
84
86
  this.endpoint.rlog.error(msg);
85
87
  return errorResponse(404, msg);
86
88
  }
87
- const handler = service?.handlerMatching(invokePathComponent);
89
+ const handler = service.handlerMatching(invokePathComponent);
88
90
  if (!handler) {
89
- const msg = `No service found for URL: ${JSON.stringify(invokePathComponent)}`;
91
+ const msg = `No handler found for URL: ${JSON.stringify(invokePathComponent)}`;
90
92
  this.endpoint.rlog.error(msg);
91
93
  return errorResponse(404, msg);
92
94
  }
@@ -119,7 +121,8 @@ var RestateInvokeResponse = class {
119
121
  }), { "x-restate-server": X_RESTATE_SERVER });
120
122
  this.vmLogger = createLogger(this.loggerTransport, LogSource.JOURNAL, new LoggerContext(invocationIdFromHeaders(this.attemptHeaders), this.service.name(), this.handler.name(), void 0, void 0, this.additionalContext));
121
123
  }
122
- async process({ inputReader, outputWriter, abortSignal }) {
124
+ async process({ inputReader, outputWriter, writeHead, abortSignal }) {
125
+ writeHead(this.statusCode, this.headers);
123
126
  abortSignal.addEventListener("abort", () => {
124
127
  destroyLogger(this.loggerId);
125
128
  setImmediate(() => {
@@ -1 +1 @@
1
- {"version":3,"file":"generic.js","names":["endpoint: Endpoint","protocolMode: ProtocolMode","additionalDiscoveryFields: Partial<EndpointManifest>","vm.WasmIdentityVerifier","vm.WasmHeader","service: Component","handler: ComponentHandler","attemptHeaders: Headers","extraArgs: unknown[]","additionalContext: AdditionalContext","journalValueCodecInit:\n | Promise<JournalValueCodec>\n | undefined","loggerTransport: LoggerTransport","vm.WasmVM","journalValueCodec: JournalValueCodec","ctx: ContextImpl","invocationRequest: Request","hooks: Hooks[]","hookContext: { request: Request }","encodedOutput: Uint8Array | undefined","originalError: unknown"],"sources":["../../../src/endpoint/handlers/generic.ts"],"sourcesContent":["/*\n * Copyright (c) 2023-2024 - Restate Software, Inc., Restate GmbH\n *\n * This file is part of the Restate SDK for Node.js/TypeScript,\n * which is released under the MIT license.\n *\n * You can find a copy of the license in file LICENSE in the root\n * directory of this repository or package, or at\n * https://github.com/restatedev/sdk-typescript/blob/main/LICENSE\n */\n\nimport {\n ensureError,\n logError,\n RestateError,\n RetryableError,\n TerminalError,\n} from \"../../types/errors.js\";\nimport type {\n Endpoint as EndpointManifest,\n ProtocolMode,\n} from \"../discovery.js\";\nimport {\n Component,\n ComponentHandler,\n InvokePathComponents,\n} from \"../components.js\";\nimport { parseUrlComponents } from \"../components.js\";\nimport { X_RESTATE_SERVER } from \"../../user_agent.js\";\nimport { CommandError, ContextImpl } from \"../../context_impl.js\";\nimport { restoreError, sanitizeError } from \"../../error_sanitization.js\";\nimport type { InvocationId, Request } from \"../../context.js\";\nimport * as vm from \"./vm/sdk_shared_core_wasm_bindings.js\";\nimport { CompletablePromise } from \"../../utils/completable_promise.js\";\nimport { HandlerKind } from \"../../types/rpc.js\";\nimport { createLogger, type Logger } from \"../../logging/logger.js\";\nimport { DEFAULT_CONSOLE_LOGGER_LOG_LEVEL } from \"../../logging/console_logger_transport.js\";\nimport {\n LoggerContext,\n LoggerTransport,\n LogSource,\n RestateLogLevel,\n} from \"../../logging/logger_transport.js\";\nimport {\n type JournalValueCodec,\n millisOrDurationToMillis,\n} from \"@restatedev/restate-sdk-core\";\nimport type { Endpoint } from \"../endpoint.js\";\nimport {\n type RestateHandler,\n type Headers,\n type RestateRequest,\n type AdditionalContext,\n type RestateResponse,\n ResponseHeaders,\n InputReader,\n OutputWriter,\n} from \"./types.js\";\nimport { handleDiscovery } from \"./discovery.js\";\nimport {\n errorResponse,\n invocationIdFromHeaders,\n simpleResponse,\n tryCreateContextualLogger,\n} from \"./utils.js\";\nimport { destroyLogger, registerLogger } from \"./core_logging.js\";\nimport type { Hooks } from \"../../hooks.js\";\n\n// Hidden symbol key used by first-party hooks to read the live\n// replay/processing phase without widening the public HooksProvider API.\nconst HOOK_CONTEXT_IS_PROCESSING_SYMBOL = Symbol.for(\n \"@restatedev/restate-sdk/hooks.isProcessing\"\n);\n\nexport function createRestateHandler(\n endpoint: Endpoint,\n protocolMode: ProtocolMode,\n additionalDiscoveryFields: Partial<EndpointManifest>\n): RestateHandler {\n return new RestateHandlerImpl(\n endpoint,\n protocolMode,\n additionalDiscoveryFields\n );\n}\n\n/**\n * This is the RestateHandler implementation\n */\nclass RestateHandlerImpl implements RestateHandler {\n private readonly identityVerifier?: vm.WasmIdentityVerifier;\n\n constructor(\n readonly endpoint: Endpoint,\n private readonly protocolMode: ProtocolMode,\n private readonly additionalDiscoveryFields: Partial<EndpointManifest>\n ) {\n // Setup identity verifier\n if (\n this.endpoint.keySet === undefined ||\n this.endpoint.keySet.length === 0\n ) {\n this.endpoint.rlog.warn(\n `Accepting requests without validating request signatures; handler access must be restricted`\n );\n } else {\n this.endpoint.rlog.info(\n `Validating requests using signing keys [${this.endpoint.keySet}]`\n );\n this.identityVerifier = new vm.WasmIdentityVerifier(this.endpoint.keySet);\n }\n\n // Set the logging level in the shared core too!\n vm.set_log_level(\n restateLogLevelToWasmLogLevel(DEFAULT_CONSOLE_LOGGER_LOG_LEVEL)\n );\n }\n\n // handle does not throw.\n public handle(\n request: RestateRequest,\n context?: AdditionalContext\n ): RestateResponse {\n try {\n return this._handle(request, context);\n } catch (e) {\n const error = ensureError(e);\n (\n tryCreateContextualLogger(\n this.endpoint.loggerTransport,\n request.url,\n request.headers\n ) ?? this.endpoint.rlog\n ).error(\n \"Error while handling request: \" + (error.stack ?? error.message)\n );\n return errorResponse(\n error instanceof RestateError ? error.code : 500,\n error.message\n );\n }\n }\n\n private _handle(\n request: RestateRequest,\n context?: AdditionalContext\n ): RestateResponse {\n // this is the recommended way to get the relative path from a url that may be relative or absolute\n const path = new URL(request.url, \"https://example.com\").pathname;\n const parsed = parseUrlComponents(path);\n\n if (parsed.type === \"unknown\") {\n const msg = `Invalid path. Allowed are /health, or /discover, or /invoke/SvcName/handlerName, but was: ${path}`;\n this.endpoint.rlog.trace(msg);\n return errorResponse(404, msg);\n }\n if (parsed.type === \"health\") {\n return simpleResponse(\n 200,\n {\n \"content-type\": \"application/text\",\n \"x-restate-server\": X_RESTATE_SERVER,\n },\n new TextEncoder().encode(\"OK\")\n );\n }\n\n // Discovery and handling invocations require identity verification\n const error = this.validateConnectionSignature(path, request.headers);\n if (error !== null) {\n return error;\n }\n if (parsed.type === \"discover\") {\n return handleDiscovery(\n this.endpoint,\n this.protocolMode,\n this.additionalDiscoveryFields,\n request.headers[\"accept\"]\n );\n }\n\n return this.handleInvoke(\n parsed,\n request.headers,\n request.extraArgs,\n context ?? {}\n );\n }\n\n private validateConnectionSignature(\n path: string,\n headers: Headers\n ): RestateResponse | null {\n if (!this.identityVerifier) {\n // not validating\n return null;\n }\n\n const vmHeaders = Object.entries(headers)\n .filter(([, v]) => v !== undefined)\n .map(\n ([k, v]) =>\n new vm.WasmHeader(k, v instanceof Array ? v[0]! : (v as string))\n );\n\n try {\n this.identityVerifier.verify_identity(path, vmHeaders);\n return null;\n } catch (e) {\n this.endpoint.rlog.error(\n // eslint-disable-next-line @typescript-eslint/restrict-template-expressions\n `Rejecting request as its JWT did not validate: ${e}`\n );\n return errorResponse(401, \"Unauthorized\");\n }\n }\n\n private handleInvoke(\n invokePathComponent: InvokePathComponents,\n headers: Headers,\n extraArgs: unknown[],\n additionalContext: AdditionalContext\n ): RestateResponse {\n // Check if we support this protocol version\n const serviceProtocolVersionString = headers[\"content-type\"];\n if (typeof serviceProtocolVersionString !== \"string\") {\n const errorMessage = \"Missing content-type header\";\n this.endpoint.rlog.warn(errorMessage);\n return errorResponse(415, errorMessage);\n }\n\n // Resolve service and handler\n const service = this.endpoint.components.get(\n invokePathComponent.componentName\n );\n if (!service) {\n const msg = `No service found for URL: ${JSON.stringify(invokePathComponent)}`;\n this.endpoint.rlog.error(msg);\n return errorResponse(404, msg);\n }\n const handler = service?.handlerMatching(invokePathComponent);\n if (!handler) {\n const msg = `No service found for URL: ${JSON.stringify(invokePathComponent)}`;\n this.endpoint.rlog.error(msg);\n return errorResponse(404, msg);\n }\n\n return new RestateInvokeResponse(\n service,\n handler,\n headers,\n extraArgs,\n additionalContext,\n this.endpoint.journalValueCodec,\n this.endpoint.loggerTransport\n );\n }\n}\n\nclass RestateInvokeResponse implements RestateResponse {\n public headers: ResponseHeaders;\n public statusCode: number;\n\n private readonly loggerId: number;\n private vmLogger: Logger;\n private readonly coreVm: vm.WasmVM;\n\n constructor(\n private readonly service: Component,\n private readonly handler: ComponentHandler,\n private readonly attemptHeaders: Headers,\n private readonly extraArgs: unknown[],\n private readonly additionalContext: AdditionalContext,\n private readonly journalValueCodecInit:\n | Promise<JournalValueCodec>\n | undefined,\n private readonly loggerTransport: LoggerTransport\n ) {\n this.loggerId = Math.floor(Math.random() * 4_294_967_295 /* u32::MAX */);\n const isJournalCodecDefined = this.journalValueCodecInit !== undefined;\n\n // Instantiate core vm and prepare response headers\n const vmHeaders = Object.entries(this.attemptHeaders)\n .filter(([, v]) => v !== undefined)\n .map(\n ([k, v]) =>\n new vm.WasmHeader(k, v instanceof Array ? v[0]! : (v as string))\n );\n this.coreVm = new vm.WasmVM(\n vmHeaders,\n restateLogLevelToWasmLogLevel(DEFAULT_CONSOLE_LOGGER_LOG_LEVEL),\n this.loggerId,\n isJournalCodecDefined,\n handler.executionOptions.explicitCancellation ?? false\n );\n const responseHead = this.coreVm.get_response_head();\n this.statusCode = responseHead.status_code;\n this.headers = responseHead.headers.reduce(\n (headers, { key, value }) => ({\n [key]: value,\n ...headers,\n }),\n {\n \"x-restate-server\": X_RESTATE_SERVER,\n }\n );\n this.vmLogger = createLogger(\n this.loggerTransport,\n LogSource.JOURNAL,\n new LoggerContext(\n invocationIdFromHeaders(this.attemptHeaders),\n this.service.name(),\n this.handler.name(),\n undefined,\n undefined,\n this.additionalContext\n )\n );\n }\n\n async process({\n inputReader,\n outputWriter,\n abortSignal,\n }: {\n inputReader: InputReader;\n outputWriter: OutputWriter;\n abortSignal: AbortSignal;\n }): Promise<void> {\n abortSignal.addEventListener(\n \"abort\",\n () => {\n // In any case, on abort remove the invocation logger to avoid memory leaks\n destroyLogger(this.loggerId);\n\n // When the HTTP connection closes (e.g. abort timeout), poison the VM.\n // We only read new input from the server when a Restate command is\n // waiting for a response. If no command has been issued, the server's\n // abort signal is never read. Poisoning the VM ensures the handler\n // fails on the next VM call (e.g. ctx.run, ctx.sleep).\n setImmediate(() => {\n const msg = \"Connection closed\";\n this.coreVm.notify_error(msg, msg);\n });\n },\n { once: true }\n );\n // Use a default logger that still respects the endpoint custom logger\n // We will override this later with a logger that has a LoggerContext\n // See vm_log below for more details\n registerLogger(this.loggerId, this.vmLogger);\n\n const journalValueCodec: JournalValueCodec = this.journalValueCodecInit\n ? await this.journalValueCodecInit\n : {\n encode: (entry) => entry,\n decode: (entry) => Promise.resolve(entry),\n };\n\n // This promise is used to signal the end of the computation,\n // which can be either the user returns a value,\n // or an exception gets caught, or the state machine fails/suspends.\n //\n // The last case is handled internally within the ContextImpl.\n const invocationEndPromise = new CompletablePromise<void>();\n let ctx: ContextImpl;\n\n // Initial phase before running user code\n // -> Buffer in shared core the journal entries\n // -> Initiate loggers\n // -> Initialize the ContextImpl\n try {\n // Buffer journal inside shared core\n await bufferJournalReplayInCoreVm(this.coreVm, inputReader);\n\n // Get input from coreVm to build the request object\n const input = this.coreVm.sys_input();\n const invocationRequest: Request = {\n target: {\n service: this.service.name(),\n handler: this.handler.name(),\n key: input.key || undefined,\n toString() {\n return this.key !== undefined\n ? `${this.service}/${this.key}/${this.handler}`\n : `${this.service}/${this.handler}`;\n },\n },\n id: input.invocation_id as InvocationId,\n headers: input.headers.reduce((headers, { key, value }) => {\n headers.set(key, value);\n return headers;\n }, new Map()),\n attemptHeaders: Object.entries(this.attemptHeaders).reduce(\n (headers, [key, value]) => {\n if (value !== undefined) {\n headers.set(key, value instanceof Array ? value[0] : value);\n }\n return headers;\n },\n new Map()\n ),\n body: input.input,\n extraArgs: this.extraArgs,\n attemptCompletedSignal: abortSignal,\n };\n\n // Prepare logger\n const loggerContext = new LoggerContext(\n input.invocation_id,\n this.handler.component().name(),\n this.handler.name(),\n this.handler.kind() === HandlerKind.SERVICE ? undefined : input.key,\n invocationRequest,\n this.additionalContext\n );\n const ctxLogger = createLogger(\n this.loggerTransport,\n LogSource.USER,\n loggerContext,\n () => !this.coreVm.is_processing()\n );\n // Override the vmLogger created before with more info!\n this.vmLogger = createLogger(\n this.loggerTransport,\n LogSource.JOURNAL,\n loggerContext\n // Filtering is done within the shared core\n );\n\n // See vm_log below for more details\n registerLogger(this.loggerId, this.vmLogger);\n if (!this.coreVm.is_processing()) {\n this.vmLogger.info(\"Replaying invocation.\");\n } else {\n this.vmLogger.info(\"Starting invocation.\");\n }\n\n // Prepare context\n ctx = new ContextImpl(\n this.coreVm,\n input,\n ctxLogger,\n this.handler.kind(),\n this.vmLogger,\n invocationRequest,\n invocationEndPromise,\n inputReader,\n outputWriter,\n journalValueCodec,\n this.handler.executionOptions\n );\n } catch (e) {\n // That's \"preflight\" failure cases, where stuff fails before running user code\n // In this scenario, we close the coreVm, then flush and close\n const error = ensureError(e);\n this.coreVm.notify_error(error.message, error.message);\n await flushAndClose(\n this.coreVm,\n this.vmLogger,\n inputReader,\n outputWriter\n );\n return;\n }\n\n // Run user code. Errors that reach the handler or interceptor code\n // (handler throws, interceptor throws, entry completes with terminal\n // failure) propagate naturally. Errors where the handler is stuck on\n // an await that will never settle (e.g. suspension, retryable run\n // error) are broken out by raceWithAttemptEnd, which races against\n // invocationEndPromise — rejected by ContextImpl when the attempt ends.\n try {\n await startUserHandler(\n ctx,\n this.service,\n this.handler,\n journalValueCodec\n );\n } catch (e) {\n notifyError(e, ctx, this.handler.executionOptions.asTerminalError);\n } finally {\n await flushAndClose(\n this.coreVm,\n this.vmLogger,\n inputReader,\n outputWriter\n );\n }\n }\n}\n\nasync function bufferJournalReplayInCoreVm(\n coreVm: vm.WasmVM,\n inputReader: InputReader\n) {\n while (!coreVm.is_ready_to_execute()) {\n const nextValue = await inputReader.next();\n if (nextValue.done) {\n coreVm.notify_input_closed();\n break;\n }\n if (nextValue.value !== undefined) {\n coreVm.notify_input(nextValue.value);\n }\n }\n}\n\nasync function startUserHandler(\n ctx: ContextImpl,\n service: Component,\n handler: ComponentHandler,\n journalValueCodec: JournalValueCodec\n) {\n // Instantiate hooks from providers.\n // If a provider throws, the same rules as handler failures apply:\n // TerminalError → terminate invocation, other errors → retry.\n const hooks: Hooks[] = [];\n for (const provider of handler.executionOptions.hooks ?? []) {\n const hookContext: { request: Request } = {\n request: ctx.request(),\n };\n Object.defineProperty(hookContext, HOOK_CONTEXT_IS_PROCESSING_SYMBOL, {\n value: () => ctx.isProcessing(),\n enumerable: false,\n });\n hooks.push(provider(hookContext));\n }\n\n // Compose interceptor.handler into a single interceptor (first = outermost)\n const handlerInterceptor = composeInterceptors(\n hooks.map((h) => h.interceptor?.handler).filter(isDefined)\n );\n\n ctx.setRunInterceptor(\n composeInterceptors(hooks.map((h) => h.interceptor?.run).filter(isDefined))\n );\n\n let encodedOutput: Uint8Array | undefined;\n await raceWithAttemptEnd(\n ctx,\n handlerInterceptor\n )(async () => {\n const decodedInput = await journalValueCodec\n .decode(ctx.request().body)\n .catch((e) =>\n // Re-throw as terminal error, to fail on input errors\n Promise.reject(\n new TerminalError(\n `Failed to decode input using journal value codec: ${\n ensureError(e).message\n }`,\n {\n errorCode: 400,\n }\n )\n )\n );\n\n // Then run user code\n const output = await handler.invoke(ctx, decodedInput);\n\n // Encode user code output\n encodedOutput = journalValueCodec.encode(output);\n });\n\n // Interceptor chain completed without error — commit the result.\n // sys_end() is called here (after interceptors) so that interceptor\n // errors after next() correctly prevent the invocation from succeeding.\n ctx.coreVm.sys_write_output_success(encodedOutput!);\n ctx.coreVm.sys_end();\n ctx.vmLogger.info(\"Invocation completed successfully.\");\n}\n\n/**\n * Classifies the error and notifies the VM. Called from the process() catch\n * block — the single place that decides how to report errors to the VM.\n */\nfunction notifyError(\n e: unknown,\n ctx: ContextImpl,\n asTerminalError?: (error: unknown) => TerminalError | undefined\n) {\n // Command-specific errors from ContextImpl carry metadata the VM needs\n // for command correlation. Check before ensureError to preserve the type.\n if (e instanceof CommandError) {\n const cause = ensureError(e.cause);\n logError(ctx.vmLogger, cause);\n if (e.hasCommandIndex) {\n // Completion failure — command exists in the journal\n ctx.coreVm.notify_error_for_specific_command(\n cause.message,\n cause.stack,\n e.commandType,\n e.commandIndex!,\n null\n );\n } else {\n // Preparation failure — command not yet issued\n ctx.coreVm.notify_error_for_next_command(\n cause.message,\n cause.stack,\n e.commandType\n );\n }\n return;\n }\n\n // Handler/interceptor errors\n const error = ensureError(e, asTerminalError);\n logError(ctx.vmLogger, error);\n\n try {\n if (error instanceof TerminalError) {\n // Terminal: write the failure as the invocation output\n ctx.coreVm.sys_write_output_failure({\n code: error.code,\n message: error.message,\n metadata: Object.entries(error.metadata ?? {}).map(([key, value]) => ({\n key,\n value,\n })),\n });\n ctx.coreVm.sys_end();\n } else if (error instanceof RetryableError) {\n // Retryable with explicit delay\n ctx.coreVm.notify_error_with_delay_override(\n error.message,\n error.stack,\n error.retryAfter !== undefined\n ? BigInt(millisOrDurationToMillis(error.retryAfter))\n : undefined\n );\n } else {\n // Transient error — VM decides retry policy\n ctx.coreVm.notify_error(error.message, error.stack);\n }\n } catch (vmError) {\n // Safety net: if sys_write_output_failure or other VM calls fail,\n // fall back to notify_error.\n const inner = ensureError(vmError);\n ctx.coreVm.notify_error(inner.message, inner.stack);\n }\n}\n\nasync function flushAndClose(\n coreVm: vm.WasmVM,\n vmLogger: Logger,\n inputReader: InputReader,\n outputWriter: OutputWriter\n): Promise<void> {\n let inputClosed = false;\n try {\n // Consume output till the end, write it out, then close the stream\n let nextOutput = coreVm.take_output() as Uint8Array | null | undefined;\n while (nextOutput !== null && nextOutput !== undefined) {\n await outputWriter.write(nextOutput);\n nextOutput = coreVm.take_output() as Uint8Array | null | undefined;\n }\n\n // --- After this point, we should have flushed the shared core internal buffer\n\n // Let's make sure we properly close the request stream before closing the response stream\n while (!inputClosed) {\n try {\n const res = await inputReader.next();\n inputClosed = res.done ?? false;\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n } catch (e) {\n inputClosed = true;\n }\n }\n\n // Close the response stream\n await outputWriter.close();\n } catch (e) {\n // In case of failure, we can do little here except just logging stuff out,\n // because outputWriter is not usable here.\n const error = ensureError(e);\n const abortErrorOnWrite = isAbortErrorOnWrite(error);\n\n if (inputClosed && abortErrorOnWrite) {\n // Because we closed the input already,\n // these errors are benign and are caused by\n // synchronization issues wrt closing the response stream in the runtime\n // This will be fixed in the runtime with https://github.com/restatedev/restate/issues/4456\n return;\n }\n\n if (abortErrorOnWrite) {\n vmLogger.error(\n \"Got abort error from connection: \" +\n error.message +\n \"\\n\" +\n \"This might indicate that:\\n\" +\n \"* The restate-server aborted the connection after hitting the 'abort-timeout'\\n\" +\n \"* The connection with the restate-server was lost\\n\" +\n \"\\n\" +\n \"Please check the invocation in the Restate UI for more details.\"\n );\n } else {\n vmLogger.error(\n \"Error while handling request: \" + (error.stack ?? error.message)\n );\n }\n }\n}\n\nfunction isAbortErrorOnWrite(error: Error) {\n return (\n error.name === \"AbortError\" ||\n error.message === \"Invalid state: WritableStream is closed\" ||\n /**\n * Node stream closed error thrown on writes\n */\n (error as { code?: string }).code === \"ERR_HTTP2_INVALID_STREAM\"\n );\n}\n\n// -- Hook composition utils --------------------------------------------------\n\ntype InterceptorFn<Args extends unknown[]> = (\n ...args: [...Args, () => Promise<void>]\n) => Promise<void>;\n\nfunction composeInterceptors<Args extends unknown[]>(\n interceptors: InterceptorFn<Args>[]\n): InterceptorFn<Args> {\n return interceptors.reduceRight<InterceptorFn<Args>>(\n (innerInterceptor, interceptor) =>\n (...args) => {\n const context = args.slice(0, -1) as unknown as Args;\n const callback = args.at(-1) as () => Promise<void>;\n return interceptor(...context, () =>\n innerInterceptor(...context, callback)\n );\n },\n (...args) => (args.at(-1) as () => Promise<void>)()\n );\n}\n\n/**\n * Wraps an interceptor so that both `next()` and the interceptor body race\n * against `invocationEndPromise`. When the attempt ends (suspension, retryable\n * error, etc.), the promise rejects and the interceptor chain unwinds through\n * catch/finally blocks — preventing interceptors from hanging on a `next()`\n * that will never settle.\n *\n * SDK-internal metadata (CommandError, retryAfter) is stripped before the\n * interceptor sees the error and restored after the chain exits.\n */\nfunction raceWithAttemptEnd<Args extends unknown[]>(\n ctx: ContextImpl,\n interceptor: InterceptorFn<Args>\n): InterceptorFn<Args> {\n return (...args) => {\n let originalError: unknown;\n\n // Strip SDK metadata before interceptors see the error.\n // Store the original in the closure for restoration after the chain.\n const signal = ctx.invocationEndPromise.promise.catch((e) => {\n originalError = e;\n throw sanitizeError(e);\n }) as Promise<never>;\n\n const originalNext = args.at(-1) as () => Promise<void>;\n const racingNext = () => Promise.race([originalNext(), signal]);\n const newArgs = [...args.slice(0, -1), racingNext] as unknown as [\n ...Args,\n () => Promise<void>,\n ];\n\n return Promise.race([interceptor(...newArgs), signal]).catch((e) => {\n // Restore SDK metadata after the interceptor chain exits.\n if (originalError !== undefined) {\n throw restoreError(e, originalError);\n }\n throw e;\n });\n };\n}\n\nfunction isDefined<T>(value: T | undefined | null): value is T {\n return value != null;\n}\n\n// -- Logging utils -----------------------------------------------------------\n\nfunction restateLogLevelToWasmLogLevel(level: RestateLogLevel): vm.LogLevel {\n switch (level) {\n case RestateLogLevel.TRACE:\n return vm.LogLevel.TRACE;\n case RestateLogLevel.DEBUG:\n return vm.LogLevel.DEBUG;\n case RestateLogLevel.INFO:\n return vm.LogLevel.INFO;\n case RestateLogLevel.WARN:\n return vm.LogLevel.WARN;\n case RestateLogLevel.ERROR:\n return vm.LogLevel.ERROR;\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"generic.js","names":["endpoint: Endpoint","protocolMode: ProtocolMode","additionalDiscoveryFields: Partial<EndpointManifest>","vm.WasmIdentityVerifier","vm.WasmHeader","service: Component","handler: ComponentHandler","attemptHeaders: Headers","extraArgs: unknown[]","additionalContext: AdditionalContext","journalValueCodecInit:\n | Promise<JournalValueCodec>\n | undefined","loggerTransport: LoggerTransport","vm.WasmVM","journalValueCodec: JournalValueCodec","ctx: ContextImpl","invocationRequest: Request","hooks: Hooks[]","hookContext: { request: Request }","encodedOutput: Uint8Array | undefined","originalError: unknown"],"sources":["../../../src/endpoint/handlers/generic.ts"],"sourcesContent":["/*\n * Copyright (c) 2023-2024 - Restate Software, Inc., Restate GmbH\n *\n * This file is part of the Restate SDK for Node.js/TypeScript,\n * which is released under the MIT license.\n *\n * You can find a copy of the license in file LICENSE in the root\n * directory of this repository or package, or at\n * https://github.com/restatedev/sdk-typescript/blob/main/LICENSE\n */\n\nimport {\n ensureError,\n logError,\n RestateError,\n RetryableError,\n TerminalError,\n} from \"../../types/errors.js\";\nimport type {\n Endpoint as EndpointManifest,\n ProtocolMode,\n} from \"../discovery.js\";\nimport {\n Component,\n ComponentHandler,\n InvokePathComponents,\n} from \"../components.js\";\nimport { parseUrlComponents } from \"../components.js\";\nimport { X_RESTATE_SERVER } from \"../../user_agent.js\";\nimport { CommandError, ContextImpl } from \"../../context_impl.js\";\nimport { restoreError, sanitizeError } from \"../../error_sanitization.js\";\nimport type { InvocationId, Request } from \"../../context.js\";\nimport * as vm from \"./vm/sdk_shared_core_wasm_bindings.js\";\nimport { CompletablePromise } from \"../../utils/completable_promise.js\";\nimport { HandlerKind } from \"../../types/rpc.js\";\nimport { createLogger, type Logger } from \"../../logging/logger.js\";\nimport { DEFAULT_CONSOLE_LOGGER_LOG_LEVEL } from \"../../logging/console_logger_transport.js\";\nimport {\n LoggerContext,\n LoggerTransport,\n LogSource,\n RestateLogLevel,\n} from \"../../logging/logger_transport.js\";\nimport {\n type JournalValueCodec,\n millisOrDurationToMillis,\n} from \"@restatedev/restate-sdk-core\";\nimport type { Endpoint } from \"../endpoint.js\";\nimport {\n type RestateHandler,\n type Headers,\n type RestateRequest,\n type AdditionalContext,\n type RestateResponse,\n ResponseHeaders,\n InputReader,\n OutputWriter,\n} from \"./types.js\";\nimport { handleDiscovery } from \"./discovery.js\";\nimport { handlePreview } from \"./preview.js\";\nimport {\n errorResponse,\n invocationIdFromHeaders,\n simpleResponse,\n tryCreateContextualLogger,\n} from \"./utils.js\";\nimport { destroyLogger, registerLogger } from \"./core_logging.js\";\nimport type { Hooks } from \"../../hooks.js\";\n\n// Hidden symbol key used by first-party hooks to read the live\n// replay/processing phase without widening the public HooksProvider API.\nconst HOOK_CONTEXT_IS_PROCESSING_SYMBOL = Symbol.for(\n \"@restatedev/restate-sdk/hooks.isProcessing\"\n);\n\nexport function createRestateHandler(\n endpoint: Endpoint,\n protocolMode: ProtocolMode,\n additionalDiscoveryFields: Partial<EndpointManifest>\n): RestateHandler {\n return new RestateHandlerImpl(\n endpoint,\n protocolMode,\n additionalDiscoveryFields\n );\n}\n\n/**\n * This is the RestateHandler implementation\n */\nclass RestateHandlerImpl implements RestateHandler {\n private readonly identityVerifier?: vm.WasmIdentityVerifier;\n\n constructor(\n readonly endpoint: Endpoint,\n private readonly protocolMode: ProtocolMode,\n private readonly additionalDiscoveryFields: Partial<EndpointManifest>\n ) {\n // Setup identity verifier\n if (\n this.endpoint.keySet === undefined ||\n this.endpoint.keySet.length === 0\n ) {\n this.endpoint.rlog.warn(\n `Accepting requests without validating request signatures; handler access must be restricted`\n );\n } else {\n this.endpoint.rlog.info(\n `Validating requests using signing keys [${this.endpoint.keySet}]`\n );\n this.identityVerifier = new vm.WasmIdentityVerifier(this.endpoint.keySet);\n }\n\n // Set the logging level in the shared core too!\n vm.set_log_level(\n restateLogLevelToWasmLogLevel(DEFAULT_CONSOLE_LOGGER_LOG_LEVEL)\n );\n }\n\n // handle does not throw.\n public handle(\n request: RestateRequest,\n context?: AdditionalContext\n ): RestateResponse {\n try {\n return this._handle(request, context);\n } catch (e) {\n const error = ensureError(e);\n (\n tryCreateContextualLogger(\n this.endpoint.loggerTransport,\n request.url,\n request.headers\n ) ?? this.endpoint.rlog\n ).error(\n \"Error while handling request: \" + (error.stack ?? error.message)\n );\n return errorResponse(\n error instanceof RestateError ? error.code : 500,\n error.message\n );\n }\n }\n\n private _handle(\n request: RestateRequest,\n context?: AdditionalContext\n ): RestateResponse {\n // this is the recommended way to get the relative path from a url that may be relative or absolute\n const path = new URL(request.url, \"https://example.com\").pathname;\n const parsed = parseUrlComponents(path);\n\n if (parsed.type === \"unknown\") {\n const msg =\n \"Invalid path. Allowed are /health, /discover, /invoke/SvcName/handlerName, \" +\n \"/serdes/SvcName/decode/<serdeName>, or \" +\n \"/serdes/SvcName/encode/<serdeName>, but was: \" +\n path;\n this.endpoint.rlog.trace(msg);\n return errorResponse(404, msg);\n }\n if (parsed.type === \"health\") {\n return simpleResponse(\n 200,\n {\n \"content-type\": \"application/text\",\n \"x-restate-server\": X_RESTATE_SERVER,\n },\n new TextEncoder().encode(\"OK\")\n );\n }\n\n // Discovery, preview, and handling invocations require identity verification\n const error = this.validateConnectionSignature(path, request.headers);\n if (error !== null) {\n return error;\n }\n if (parsed.type === \"discover\") {\n return handleDiscovery(\n this.endpoint,\n this.protocolMode,\n this.additionalDiscoveryFields,\n request.headers[\"accept\"]\n );\n }\n if (parsed.type === \"preview\") {\n return handlePreview(this.endpoint, parsed);\n }\n\n return this.handleInvoke(\n parsed,\n request.headers,\n request.extraArgs,\n context ?? {}\n );\n }\n\n private validateConnectionSignature(\n path: string,\n headers: Headers\n ): RestateResponse | null {\n if (!this.identityVerifier) {\n // not validating\n return null;\n }\n\n const vmHeaders = Object.entries(headers)\n .filter(([, v]) => v !== undefined)\n .map(\n ([k, v]) =>\n new vm.WasmHeader(k, v instanceof Array ? v[0]! : (v as string))\n );\n\n try {\n this.identityVerifier.verify_identity(path, vmHeaders);\n return null;\n } catch (e) {\n this.endpoint.rlog.error(\n // eslint-disable-next-line @typescript-eslint/restrict-template-expressions\n `Rejecting request as its JWT did not validate: ${e}`\n );\n return errorResponse(401, \"Unauthorized\");\n }\n }\n\n private handleInvoke(\n invokePathComponent: InvokePathComponents,\n headers: Headers,\n extraArgs: unknown[],\n additionalContext: AdditionalContext\n ): RestateResponse {\n // Check if we support this protocol version\n const serviceProtocolVersionString = headers[\"content-type\"];\n if (typeof serviceProtocolVersionString !== \"string\") {\n const errorMessage = \"Missing content-type header\";\n this.endpoint.rlog.warn(errorMessage);\n return errorResponse(415, errorMessage);\n }\n\n const service = this.endpoint.components.get(\n invokePathComponent.componentName\n );\n if (!service) {\n const msg = `No service found for URL: ${JSON.stringify(invokePathComponent)}`;\n this.endpoint.rlog.error(msg);\n return errorResponse(404, msg);\n }\n\n const handler = service.handlerMatching(invokePathComponent);\n if (!handler) {\n const msg = `No handler found for URL: ${JSON.stringify(invokePathComponent)}`;\n this.endpoint.rlog.error(msg);\n return errorResponse(404, msg);\n }\n\n return new RestateInvokeResponse(\n service,\n handler,\n headers,\n extraArgs,\n additionalContext,\n this.endpoint.journalValueCodec,\n this.endpoint.loggerTransport\n );\n }\n}\n\nclass RestateInvokeResponse implements RestateResponse {\n private readonly headers: ResponseHeaders;\n private readonly statusCode: number;\n\n private readonly loggerId: number;\n private vmLogger: Logger;\n private readonly coreVm: vm.WasmVM;\n\n constructor(\n private readonly service: Component,\n private readonly handler: ComponentHandler,\n private readonly attemptHeaders: Headers,\n private readonly extraArgs: unknown[],\n private readonly additionalContext: AdditionalContext,\n private readonly journalValueCodecInit:\n | Promise<JournalValueCodec>\n | undefined,\n private readonly loggerTransport: LoggerTransport\n ) {\n this.loggerId = Math.floor(Math.random() * 4_294_967_295 /* u32::MAX */);\n const isJournalCodecDefined = this.journalValueCodecInit !== undefined;\n\n // Instantiate core vm and prepare response headers\n const vmHeaders = Object.entries(this.attemptHeaders)\n .filter(([, v]) => v !== undefined)\n .map(\n ([k, v]) =>\n new vm.WasmHeader(k, v instanceof Array ? v[0]! : (v as string))\n );\n this.coreVm = new vm.WasmVM(\n vmHeaders,\n restateLogLevelToWasmLogLevel(DEFAULT_CONSOLE_LOGGER_LOG_LEVEL),\n this.loggerId,\n isJournalCodecDefined,\n handler.executionOptions.explicitCancellation ?? false\n );\n const responseHead = this.coreVm.get_response_head();\n this.statusCode = responseHead.status_code;\n this.headers = responseHead.headers.reduce(\n (headers, { key, value }) => ({\n [key]: value,\n ...headers,\n }),\n {\n \"x-restate-server\": X_RESTATE_SERVER,\n }\n );\n this.vmLogger = createLogger(\n this.loggerTransport,\n LogSource.JOURNAL,\n new LoggerContext(\n invocationIdFromHeaders(this.attemptHeaders),\n this.service.name(),\n this.handler.name(),\n undefined,\n undefined,\n this.additionalContext\n )\n );\n }\n\n async process({\n inputReader,\n outputWriter,\n writeHead,\n abortSignal,\n }: {\n inputReader: InputReader;\n outputWriter: OutputWriter;\n writeHead: (statusCode: number, headers: ResponseHeaders) => void;\n abortSignal: AbortSignal;\n }): Promise<void> {\n // Commit the response head immediately — the VM-determined head is known\n // at construction time and never changes during processing.\n writeHead(this.statusCode, this.headers);\n\n abortSignal.addEventListener(\n \"abort\",\n () => {\n // In any case, on abort remove the invocation logger to avoid memory leaks\n destroyLogger(this.loggerId);\n\n // When the HTTP connection closes (e.g. abort timeout), poison the VM.\n // We only read new input from the server when a Restate command is\n // waiting for a response. If no command has been issued, the server's\n // abort signal is never read. Poisoning the VM ensures the handler\n // fails on the next VM call (e.g. ctx.run, ctx.sleep).\n setImmediate(() => {\n const msg = \"Connection closed\";\n this.coreVm.notify_error(msg, msg);\n });\n },\n { once: true }\n );\n // Use a default logger that still respects the endpoint custom logger\n // We will override this later with a logger that has a LoggerContext\n // See vm_log below for more details\n registerLogger(this.loggerId, this.vmLogger);\n\n const journalValueCodec: JournalValueCodec = this.journalValueCodecInit\n ? await this.journalValueCodecInit\n : {\n encode: (entry) => entry,\n decode: (entry) => Promise.resolve(entry),\n };\n\n // This promise is used to signal the end of the computation,\n // which can be either the user returns a value,\n // or an exception gets caught, or the state machine fails/suspends.\n //\n // The last case is handled internally within the ContextImpl.\n const invocationEndPromise = new CompletablePromise<void>();\n let ctx: ContextImpl;\n\n // Initial phase before running user code\n // -> Buffer in shared core the journal entries\n // -> Initiate loggers\n // -> Initialize the ContextImpl\n try {\n // Buffer journal inside shared core\n await bufferJournalReplayInCoreVm(this.coreVm, inputReader);\n\n // Get input from coreVm to build the request object\n const input = this.coreVm.sys_input();\n const invocationRequest: Request = {\n target: {\n service: this.service.name(),\n handler: this.handler.name(),\n key: input.key || undefined,\n toString() {\n return this.key !== undefined\n ? `${this.service}/${this.key}/${this.handler}`\n : `${this.service}/${this.handler}`;\n },\n },\n id: input.invocation_id as InvocationId,\n headers: input.headers.reduce((headers, { key, value }) => {\n headers.set(key, value);\n return headers;\n }, new Map()),\n attemptHeaders: Object.entries(this.attemptHeaders).reduce(\n (headers, [key, value]) => {\n if (value !== undefined) {\n headers.set(key, value instanceof Array ? value[0] : value);\n }\n return headers;\n },\n new Map()\n ),\n body: input.input,\n extraArgs: this.extraArgs,\n attemptCompletedSignal: abortSignal,\n };\n\n // Prepare logger\n const loggerContext = new LoggerContext(\n input.invocation_id,\n this.handler.component().name(),\n this.handler.name(),\n this.handler.kind() === HandlerKind.SERVICE ? undefined : input.key,\n invocationRequest,\n this.additionalContext\n );\n const ctxLogger = createLogger(\n this.loggerTransport,\n LogSource.USER,\n loggerContext,\n () => !this.coreVm.is_processing()\n );\n // Override the vmLogger created before with more info!\n this.vmLogger = createLogger(\n this.loggerTransport,\n LogSource.JOURNAL,\n loggerContext\n // Filtering is done within the shared core\n );\n\n // See vm_log below for more details\n registerLogger(this.loggerId, this.vmLogger);\n if (!this.coreVm.is_processing()) {\n this.vmLogger.info(\"Replaying invocation.\");\n } else {\n this.vmLogger.info(\"Starting invocation.\");\n }\n\n // Prepare context\n ctx = new ContextImpl(\n this.coreVm,\n input,\n ctxLogger,\n this.handler.kind(),\n this.vmLogger,\n invocationRequest,\n invocationEndPromise,\n inputReader,\n outputWriter,\n journalValueCodec,\n this.handler.executionOptions\n );\n } catch (e) {\n // That's \"preflight\" failure cases, where stuff fails before running user code\n // In this scenario, we close the coreVm, then flush and close\n const error = ensureError(e);\n this.coreVm.notify_error(error.message, error.message);\n await flushAndClose(\n this.coreVm,\n this.vmLogger,\n inputReader,\n outputWriter\n );\n return;\n }\n\n // Run user code. Errors that reach the handler or interceptor code\n // (handler throws, interceptor throws, entry completes with terminal\n // failure) propagate naturally. Errors where the handler is stuck on\n // an await that will never settle (e.g. suspension, retryable run\n // error) are broken out by raceWithAttemptEnd, which races against\n // invocationEndPromise — rejected by ContextImpl when the attempt ends.\n try {\n await startUserHandler(\n ctx,\n this.service,\n this.handler,\n journalValueCodec\n );\n } catch (e) {\n notifyError(e, ctx, this.handler.executionOptions.asTerminalError);\n } finally {\n await flushAndClose(\n this.coreVm,\n this.vmLogger,\n inputReader,\n outputWriter\n );\n }\n }\n}\n\nasync function bufferJournalReplayInCoreVm(\n coreVm: vm.WasmVM,\n inputReader: InputReader\n) {\n while (!coreVm.is_ready_to_execute()) {\n const nextValue = await inputReader.next();\n if (nextValue.done) {\n coreVm.notify_input_closed();\n break;\n }\n if (nextValue.value !== undefined) {\n coreVm.notify_input(nextValue.value);\n }\n }\n}\n\nasync function startUserHandler(\n ctx: ContextImpl,\n service: Component,\n handler: ComponentHandler,\n journalValueCodec: JournalValueCodec\n) {\n // Instantiate hooks from providers.\n // If a provider throws, the same rules as handler failures apply:\n // TerminalError → terminate invocation, other errors → retry.\n const hooks: Hooks[] = [];\n for (const provider of handler.executionOptions.hooks ?? []) {\n const hookContext: { request: Request } = {\n request: ctx.request(),\n };\n Object.defineProperty(hookContext, HOOK_CONTEXT_IS_PROCESSING_SYMBOL, {\n value: () => ctx.isProcessing(),\n enumerable: false,\n });\n hooks.push(provider(hookContext));\n }\n\n // Compose interceptor.handler into a single interceptor (first = outermost)\n const handlerInterceptor = composeInterceptors(\n hooks.map((h) => h.interceptor?.handler).filter(isDefined)\n );\n\n ctx.setRunInterceptor(\n composeInterceptors(hooks.map((h) => h.interceptor?.run).filter(isDefined))\n );\n\n let encodedOutput: Uint8Array | undefined;\n await raceWithAttemptEnd(\n ctx,\n handlerInterceptor\n )(async () => {\n const decodedInput = await journalValueCodec\n .decode(ctx.request().body)\n .catch((e) =>\n // Re-throw as terminal error, to fail on input errors\n Promise.reject(\n new TerminalError(\n `Failed to decode input using journal value codec: ${\n ensureError(e).message\n }`,\n {\n errorCode: 400,\n }\n )\n )\n );\n\n // Then run user code\n const output = await handler.invoke(ctx, decodedInput);\n\n // Encode user code output\n encodedOutput = journalValueCodec.encode(output);\n });\n\n // Interceptor chain completed without error — commit the result.\n // sys_end() is called here (after interceptors) so that interceptor\n // errors after next() correctly prevent the invocation from succeeding.\n ctx.coreVm.sys_write_output_success(encodedOutput!);\n ctx.coreVm.sys_end();\n ctx.vmLogger.info(\"Invocation completed successfully.\");\n}\n\n/**\n * Classifies the error and notifies the VM. Called from the process() catch\n * block — the single place that decides how to report errors to the VM.\n */\nfunction notifyError(\n e: unknown,\n ctx: ContextImpl,\n asTerminalError?: (error: unknown) => TerminalError | undefined\n) {\n // Command-specific errors from ContextImpl carry metadata the VM needs\n // for command correlation. Check before ensureError to preserve the type.\n if (e instanceof CommandError) {\n const cause = ensureError(e.cause);\n logError(ctx.vmLogger, cause);\n if (e.hasCommandIndex) {\n // Completion failure — command exists in the journal\n ctx.coreVm.notify_error_for_specific_command(\n cause.message,\n cause.stack,\n e.commandType,\n e.commandIndex!,\n null\n );\n } else {\n // Preparation failure — command not yet issued\n ctx.coreVm.notify_error_for_next_command(\n cause.message,\n cause.stack,\n e.commandType\n );\n }\n return;\n }\n\n // Handler/interceptor errors\n const error = ensureError(e, asTerminalError);\n logError(ctx.vmLogger, error);\n\n try {\n if (error instanceof TerminalError) {\n // Terminal: write the failure as the invocation output\n ctx.coreVm.sys_write_output_failure({\n code: error.code,\n message: error.message,\n metadata: Object.entries(error.metadata ?? {}).map(([key, value]) => ({\n key,\n value,\n })),\n });\n ctx.coreVm.sys_end();\n } else if (error instanceof RetryableError) {\n // Retryable with explicit delay\n ctx.coreVm.notify_error_with_delay_override(\n error.message,\n error.stack,\n error.retryAfter !== undefined\n ? BigInt(millisOrDurationToMillis(error.retryAfter))\n : undefined\n );\n } else {\n // Transient error — VM decides retry policy\n ctx.coreVm.notify_error(error.message, error.stack);\n }\n } catch (vmError) {\n // Safety net: if sys_write_output_failure or other VM calls fail,\n // fall back to notify_error.\n const inner = ensureError(vmError);\n ctx.coreVm.notify_error(inner.message, inner.stack);\n }\n}\n\nasync function flushAndClose(\n coreVm: vm.WasmVM,\n vmLogger: Logger,\n inputReader: InputReader,\n outputWriter: OutputWriter\n): Promise<void> {\n let inputClosed = false;\n try {\n // Consume output till the end, write it out, then close the stream\n let nextOutput = coreVm.take_output() as Uint8Array | null | undefined;\n while (nextOutput !== null && nextOutput !== undefined) {\n await outputWriter.write(nextOutput);\n nextOutput = coreVm.take_output() as Uint8Array | null | undefined;\n }\n\n // --- After this point, we should have flushed the shared core internal buffer\n\n // Let's make sure we properly close the request stream before closing the response stream\n while (!inputClosed) {\n try {\n const res = await inputReader.next();\n inputClosed = res.done ?? false;\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n } catch (e) {\n inputClosed = true;\n }\n }\n\n // Close the response stream\n await outputWriter.close();\n } catch (e) {\n // In case of failure, we can do little here except just logging stuff out,\n // because outputWriter is not usable here.\n const error = ensureError(e);\n const abortErrorOnWrite = isAbortErrorOnWrite(error);\n\n if (inputClosed && abortErrorOnWrite) {\n // Because we closed the input already,\n // these errors are benign and are caused by\n // synchronization issues wrt closing the response stream in the runtime\n // This will be fixed in the runtime with https://github.com/restatedev/restate/issues/4456\n return;\n }\n\n if (abortErrorOnWrite) {\n vmLogger.error(\n \"Got abort error from connection: \" +\n error.message +\n \"\\n\" +\n \"This might indicate that:\\n\" +\n \"* The restate-server aborted the connection after hitting the 'abort-timeout'\\n\" +\n \"* The connection with the restate-server was lost\\n\" +\n \"\\n\" +\n \"Please check the invocation in the Restate UI for more details.\"\n );\n } else {\n vmLogger.error(\n \"Error while handling request: \" + (error.stack ?? error.message)\n );\n }\n }\n}\n\nfunction isAbortErrorOnWrite(error: Error) {\n return (\n error.name === \"AbortError\" ||\n error.message === \"Invalid state: WritableStream is closed\" ||\n /**\n * Node stream closed error thrown on writes\n */\n (error as { code?: string }).code === \"ERR_HTTP2_INVALID_STREAM\"\n );\n}\n\n// -- Hook composition utils --------------------------------------------------\n\ntype InterceptorFn<Args extends unknown[]> = (\n ...args: [...Args, () => Promise<void>]\n) => Promise<void>;\n\nfunction composeInterceptors<Args extends unknown[]>(\n interceptors: InterceptorFn<Args>[]\n): InterceptorFn<Args> {\n return interceptors.reduceRight<InterceptorFn<Args>>(\n (innerInterceptor, interceptor) =>\n (...args) => {\n const context = args.slice(0, -1) as unknown as Args;\n const callback = args.at(-1) as () => Promise<void>;\n return interceptor(...context, () =>\n innerInterceptor(...context, callback)\n );\n },\n (...args) => (args.at(-1) as () => Promise<void>)()\n );\n}\n\n/**\n * Wraps an interceptor so that both `next()` and the interceptor body race\n * against `invocationEndPromise`. When the attempt ends (suspension, retryable\n * error, etc.), the promise rejects and the interceptor chain unwinds through\n * catch/finally blocks — preventing interceptors from hanging on a `next()`\n * that will never settle.\n *\n * SDK-internal metadata (CommandError, retryAfter) is stripped before the\n * interceptor sees the error and restored after the chain exits.\n */\nfunction raceWithAttemptEnd<Args extends unknown[]>(\n ctx: ContextImpl,\n interceptor: InterceptorFn<Args>\n): InterceptorFn<Args> {\n return (...args) => {\n let originalError: unknown;\n\n // Strip SDK metadata before interceptors see the error.\n // Store the original in the closure for restoration after the chain.\n const signal = ctx.invocationEndPromise.promise.catch((e) => {\n originalError = e;\n throw sanitizeError(e);\n }) as Promise<never>;\n\n const originalNext = args.at(-1) as () => Promise<void>;\n const racingNext = () => Promise.race([originalNext(), signal]);\n const newArgs = [...args.slice(0, -1), racingNext] as unknown as [\n ...Args,\n () => Promise<void>,\n ];\n\n return Promise.race([interceptor(...newArgs), signal]).catch((e) => {\n // Restore SDK metadata after the interceptor chain exits.\n if (originalError !== undefined) {\n throw restoreError(e, originalError);\n }\n throw e;\n });\n };\n}\n\nfunction isDefined<T>(value: T | undefined | null): value is T {\n return value != null;\n}\n\n// -- Logging utils -----------------------------------------------------------\n\nfunction restateLogLevelToWasmLogLevel(level: RestateLogLevel): vm.LogLevel {\n switch (level) {\n case RestateLogLevel.TRACE:\n return vm.LogLevel.TRACE;\n case RestateLogLevel.DEBUG:\n return vm.LogLevel.DEBUG;\n case RestateLogLevel.INFO:\n return vm.LogLevel.INFO;\n case RestateLogLevel.WARN:\n return vm.LogLevel.WARN;\n case RestateLogLevel.ERROR:\n return vm.LogLevel.ERROR;\n }\n}\n"],"mappings":"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"}
@@ -46,6 +46,7 @@ var LambdaHandler = class {
46
46
  return Promise.resolve();
47
47
  }
48
48
  };
49
+ const { writeHead, head: headPromise } = require_utils.captureHead();
49
50
  const response = this.handler.handle({
50
51
  headers: event.headers,
51
52
  url: path,
@@ -55,6 +56,7 @@ var LambdaHandler = class {
55
56
  await response.process({
56
57
  inputReader,
57
58
  outputWriter,
59
+ writeHead,
58
60
  abortSignal: abortController.signal
59
61
  });
60
62
  } catch (e) {
@@ -70,15 +72,16 @@ var LambdaHandler = class {
70
72
  body: JSON.stringify({ message: error.message })
71
73
  };
72
74
  }
75
+ const head = await headPromise;
73
76
  const responseBodyBuffer = node_buffer.Buffer.concat(chunks);
74
77
  let responseBody;
75
78
  if (this.compressionSupported && responseBodyBuffer.length > RESPONSE_COMPRESSION_THRESHOLD && requestAcceptEncoding && requestAcceptEncoding.includes("zstd")) {
76
- response.headers["content-encoding"] = "zstd";
79
+ head.headers["content-encoding"] = "zstd";
77
80
  responseBody = node_zlib.zstdCompressSync(responseBodyBuffer).toString("base64");
78
81
  } else responseBody = responseBodyBuffer.toString("base64");
79
82
  return {
80
- headers: response.headers,
81
- statusCode: response.statusCode,
83
+ headers: head.headers,
84
+ statusCode: head.statusCode,
82
85
  isBase64Encoded: true,
83
86
  body: responseBody
84
87
  };
@@ -1,6 +1,6 @@
1
1
  import { ensureError } from "../../types/errors.js";
2
2
  import { X_RESTATE_SERVER } from "../../user_agent.js";
3
- import { emptyInputReader, tryCreateContextualLogger } from "./utils.js";
3
+ import { captureHead, emptyInputReader, tryCreateContextualLogger } from "./utils.js";
4
4
  import { Buffer } from "node:buffer";
5
5
  import * as zlib from "node:zlib";
6
6
 
@@ -43,6 +43,7 @@ var LambdaHandler = class {
43
43
  return Promise.resolve();
44
44
  }
45
45
  };
46
+ const { writeHead, head: headPromise } = captureHead();
46
47
  const response = this.handler.handle({
47
48
  headers: event.headers,
48
49
  url: path,
@@ -52,6 +53,7 @@ var LambdaHandler = class {
52
53
  await response.process({
53
54
  inputReader,
54
55
  outputWriter,
56
+ writeHead,
55
57
  abortSignal: abortController.signal
56
58
  });
57
59
  } catch (e) {
@@ -67,15 +69,16 @@ var LambdaHandler = class {
67
69
  body: JSON.stringify({ message: error.message })
68
70
  };
69
71
  }
72
+ const head = await headPromise;
70
73
  const responseBodyBuffer = Buffer.concat(chunks);
71
74
  let responseBody;
72
75
  if (this.compressionSupported && responseBodyBuffer.length > RESPONSE_COMPRESSION_THRESHOLD && requestAcceptEncoding && requestAcceptEncoding.includes("zstd")) {
73
- response.headers["content-encoding"] = "zstd";
76
+ head.headers["content-encoding"] = "zstd";
74
77
  responseBody = zlib.zstdCompressSync(responseBodyBuffer).toString("base64");
75
78
  } else responseBody = responseBodyBuffer.toString("base64");
76
79
  return {
77
- headers: response.headers,
78
- statusCode: response.statusCode,
80
+ headers: head.headers,
81
+ statusCode: head.statusCode,
79
82
  isBase64Encoded: true,
80
83
  body: responseBody
81
84
  };
@@ -1 +1 @@
1
- {"version":3,"file":"lambda.js","names":["handler: RestateHandler","compressionSupported: boolean","inputReader: InputReader","bodyBuffer: Buffer | undefined","chunks: Uint8Array[]","outputWriter: OutputWriter"],"sources":["../../../src/endpoint/handlers/lambda.ts"],"sourcesContent":["/*\n * Copyright (c) 2023-2024 - Restate Software, Inc., Restate GmbH\n *\n * This file is part of the Restate SDK for Node.js/TypeScript,\n * which is released under the MIT license.\n *\n * You can find a copy of the license in file LICENSE in the root\n * directory of this repository or package, or at\n * https://github.com/restatedev/sdk-typescript/blob/main/LICENSE\n */\n\nimport type {\n APIGatewayProxyEvent,\n APIGatewayProxyEventV2,\n APIGatewayProxyResult,\n APIGatewayProxyStructuredResultV2,\n Context,\n} from \"aws-lambda\";\nimport { Buffer } from \"node:buffer\";\nimport { X_RESTATE_SERVER } from \"../../user_agent.js\";\nimport { ensureError } from \"../../types/errors.js\";\nimport * as zlib from \"node:zlib\";\nimport { InputReader, OutputWriter, RestateHandler } from \"./types.js\";\nimport { emptyInputReader, tryCreateContextualLogger } from \"./utils.js\";\n\nconst RESPONSE_COMPRESSION_THRESHOLD = 3 * 1024 * 1024;\n\nexport class LambdaHandler {\n constructor(\n private readonly handler: RestateHandler,\n private readonly compressionSupported: boolean\n ) {}\n\n async handleRequest(\n event: APIGatewayProxyEvent | APIGatewayProxyEventV2,\n context: Context\n ): Promise<APIGatewayProxyResult | APIGatewayProxyStructuredResultV2> {\n const abortController = new AbortController();\n try {\n //\n // Request path\n //\n const path = \"path\" in event ? event.path : event.rawPath;\n\n // Deal with content-encoding\n let requestContentEncoding;\n let requestAcceptEncoding;\n for (const [key, value] of Object.entries(event.headers)) {\n if (\n key.localeCompare(\"content-encoding\", undefined, {\n sensitivity: \"accent\",\n }) === 0\n ) {\n requestContentEncoding = value;\n } else if (\n key.localeCompare(\"accept-encoding\", undefined, {\n sensitivity: \"accent\",\n }) === 0\n ) {\n requestAcceptEncoding = value;\n }\n }\n\n //\n // Convert the request body to a Uint8Array stream\n // Lambda functions receive the body as base64 encoded string\n //\n let inputReader: InputReader;\n if (!event.body) {\n inputReader = emptyInputReader();\n } else {\n let bodyBuffer: Buffer | undefined;\n if (event.isBase64Encoded) {\n bodyBuffer = Buffer.from(event.body, \"base64\");\n } else {\n bodyBuffer = Buffer.from(new TextEncoder().encode(event.body));\n }\n\n // Now decode if needed\n if (requestContentEncoding && requestContentEncoding.includes(\"zstd\")) {\n if (!this.compressionSupported) {\n throw new Error(\n \"The input is compressed using zstd, but this lambda deployment doesn't support compression. Make sure to deploy the Lambda using Node > 22\"\n );\n }\n\n // Input encoded with zstd, let's decode it!\n bodyBuffer = (\n zlib as unknown as { zstdDecompressSync: (b: Buffer) => Buffer }\n ).zstdDecompressSync(bodyBuffer);\n }\n\n // Prep the stream to pass through the endpoint handler\n // eslint-disable-next-line @typescript-eslint/require-await\n inputReader = (async function* () {\n yield bodyBuffer as Uint8Array;\n })()[Symbol.asyncIterator]();\n }\n\n const chunks: Uint8Array[] = [];\n const outputWriter: OutputWriter = {\n write: function (value: Uint8Array): Promise<void> {\n chunks.push(value);\n return Promise.resolve();\n },\n close: function (): Promise<void> {\n return Promise.resolve();\n },\n };\n\n const response = this.handler.handle(\n {\n headers: event.headers,\n url: path,\n extraArgs: [context],\n },\n {\n AWSRequestId: context.awsRequestId,\n }\n );\n\n try {\n await response.process({\n inputReader,\n outputWriter,\n abortSignal: abortController.signal,\n });\n } catch (e) {\n // handle should never throw\n const error = ensureError(e);\n const logger =\n tryCreateContextualLogger(\n this.handler.endpoint.loggerTransport,\n path,\n event.headers\n ) ?? this.handler.endpoint.rlog;\n logger.error(\"Unexpected error: \" + (error.stack ?? error.message));\n return {\n headers: {\n \"content-type\": \"application/json\",\n \"x-restate-server\": X_RESTATE_SERVER,\n },\n statusCode: 500,\n isBase64Encoded: false,\n body: JSON.stringify({ message: error.message }),\n };\n }\n\n const responseBodyBuffer = Buffer.concat(chunks);\n let responseBody;\n\n // Now let's encode if we need to.\n if (\n this.compressionSupported &&\n responseBodyBuffer.length > RESPONSE_COMPRESSION_THRESHOLD &&\n requestAcceptEncoding &&\n requestAcceptEncoding.includes(\"zstd\")\n ) {\n response.headers[\"content-encoding\"] = \"zstd\";\n\n responseBody = (\n zlib as unknown as { zstdCompressSync: (b: Buffer) => Buffer }\n )\n .zstdCompressSync(responseBodyBuffer)\n .toString(\"base64\");\n } else {\n responseBody = responseBodyBuffer.toString(\"base64\");\n }\n return {\n headers: response.headers,\n statusCode: response.statusCode,\n isBase64Encoded: true,\n body: responseBody,\n };\n } finally {\n abortController.abort();\n }\n }\n}\n\nexport function isCompressionSupported() {\n return \"zstdDecompressSync\" in zlib && \"zstdCompressSync\" in zlib;\n}\n"],"mappings":";;;;;;;AAyBA,MAAM,iCAAiC,IAAI,OAAO;AAElD,IAAa,gBAAb,MAA2B;CACzB,YACE,AAAiBA,SACjB,AAAiBC,sBACjB;EAFiB;EACA;;CAGnB,MAAM,cACJ,OACA,SACoE;EACpE,MAAM,kBAAkB,IAAI,iBAAiB;AAC7C,MAAI;GAIF,MAAM,OAAO,UAAU,QAAQ,MAAM,OAAO,MAAM;GAGlD,IAAI;GACJ,IAAI;AACJ,QAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,MAAM,QAAQ,CACtD,KACE,IAAI,cAAc,oBAAoB,QAAW,EAC/C,aAAa,UACd,CAAC,KAAK,EAEP,0BAAyB;YAEzB,IAAI,cAAc,mBAAmB,QAAW,EAC9C,aAAa,UACd,CAAC,KAAK,EAEP,yBAAwB;GAQ5B,IAAIC;AACJ,OAAI,CAAC,MAAM,KACT,eAAc,kBAAkB;QAC3B;IACL,IAAIC;AACJ,QAAI,MAAM,gBACR,cAAa,OAAO,KAAK,MAAM,MAAM,SAAS;QAE9C,cAAa,OAAO,KAAK,IAAI,aAAa,CAAC,OAAO,MAAM,KAAK,CAAC;AAIhE,QAAI,0BAA0B,uBAAuB,SAAS,OAAO,EAAE;AACrE,SAAI,CAAC,KAAK,qBACR,OAAM,IAAI,MACR,6IACD;AAIH,kBACE,KACA,mBAAmB,WAAW;;AAKlC,mBAAe,mBAAmB;AAChC,WAAM;QACJ,CAAC,OAAO,gBAAgB;;GAG9B,MAAMC,SAAuB,EAAE;GAC/B,MAAMC,eAA6B;IACjC,OAAO,SAAU,OAAkC;AACjD,YAAO,KAAK,MAAM;AAClB,YAAO,QAAQ,SAAS;;IAE1B,OAAO,WAA2B;AAChC,YAAO,QAAQ,SAAS;;IAE3B;GAED,MAAM,WAAW,KAAK,QAAQ,OAC5B;IACE,SAAS,MAAM;IACf,KAAK;IACL,WAAW,CAAC,QAAQ;IACrB,EACD,EACE,cAAc,QAAQ,cACvB,CACF;AAED,OAAI;AACF,UAAM,SAAS,QAAQ;KACrB;KACA;KACA,aAAa,gBAAgB;KAC9B,CAAC;YACK,GAAG;IAEV,MAAM,QAAQ,YAAY,EAAE;AAO5B,KALE,0BACE,KAAK,QAAQ,SAAS,iBACtB,MACA,MAAM,QACP,IAAI,KAAK,QAAQ,SAAS,MACtB,MAAM,wBAAwB,MAAM,SAAS,MAAM,SAAS;AACnE,WAAO;KACL,SAAS;MACP,gBAAgB;MAChB,oBAAoB;MACrB;KACD,YAAY;KACZ,iBAAiB;KACjB,MAAM,KAAK,UAAU,EAAE,SAAS,MAAM,SAAS,CAAC;KACjD;;GAGH,MAAM,qBAAqB,OAAO,OAAO,OAAO;GAChD,IAAI;AAGJ,OACE,KAAK,wBACL,mBAAmB,SAAS,kCAC5B,yBACA,sBAAsB,SAAS,OAAO,EACtC;AACA,aAAS,QAAQ,sBAAsB;AAEvC,mBACE,KAEC,iBAAiB,mBAAmB,CACpC,SAAS,SAAS;SAErB,gBAAe,mBAAmB,SAAS,SAAS;AAEtD,UAAO;IACL,SAAS,SAAS;IAClB,YAAY,SAAS;IACrB,iBAAiB;IACjB,MAAM;IACP;YACO;AACR,mBAAgB,OAAO;;;;AAK7B,SAAgB,yBAAyB;AACvC,QAAO,wBAAwB,QAAQ,sBAAsB"}
1
+ {"version":3,"file":"lambda.js","names":["handler: RestateHandler","compressionSupported: boolean","inputReader: InputReader","bodyBuffer: Buffer | undefined","chunks: Uint8Array[]","outputWriter: OutputWriter"],"sources":["../../../src/endpoint/handlers/lambda.ts"],"sourcesContent":["/*\n * Copyright (c) 2023-2024 - Restate Software, Inc., Restate GmbH\n *\n * This file is part of the Restate SDK for Node.js/TypeScript,\n * which is released under the MIT license.\n *\n * You can find a copy of the license in file LICENSE in the root\n * directory of this repository or package, or at\n * https://github.com/restatedev/sdk-typescript/blob/main/LICENSE\n */\n\nimport type {\n APIGatewayProxyEvent,\n APIGatewayProxyEventV2,\n APIGatewayProxyResult,\n APIGatewayProxyStructuredResultV2,\n Context,\n} from \"aws-lambda\";\nimport { Buffer } from \"node:buffer\";\nimport { X_RESTATE_SERVER } from \"../../user_agent.js\";\nimport { ensureError } from \"../../types/errors.js\";\nimport * as zlib from \"node:zlib\";\nimport { InputReader, OutputWriter, RestateHandler } from \"./types.js\";\nimport {\n captureHead,\n emptyInputReader,\n tryCreateContextualLogger,\n} from \"./utils.js\";\n\nconst RESPONSE_COMPRESSION_THRESHOLD = 3 * 1024 * 1024;\n\nexport class LambdaHandler {\n constructor(\n private readonly handler: RestateHandler,\n private readonly compressionSupported: boolean\n ) {}\n\n async handleRequest(\n event: APIGatewayProxyEvent | APIGatewayProxyEventV2,\n context: Context\n ): Promise<APIGatewayProxyResult | APIGatewayProxyStructuredResultV2> {\n const abortController = new AbortController();\n try {\n //\n // Request path\n //\n const path = \"path\" in event ? event.path : event.rawPath;\n\n // Deal with content-encoding\n let requestContentEncoding;\n let requestAcceptEncoding;\n for (const [key, value] of Object.entries(event.headers)) {\n if (\n key.localeCompare(\"content-encoding\", undefined, {\n sensitivity: \"accent\",\n }) === 0\n ) {\n requestContentEncoding = value;\n } else if (\n key.localeCompare(\"accept-encoding\", undefined, {\n sensitivity: \"accent\",\n }) === 0\n ) {\n requestAcceptEncoding = value;\n }\n }\n\n //\n // Convert the request body to a Uint8Array stream\n // Lambda functions receive the body as base64 encoded string\n //\n let inputReader: InputReader;\n if (!event.body) {\n inputReader = emptyInputReader();\n } else {\n let bodyBuffer: Buffer | undefined;\n if (event.isBase64Encoded) {\n bodyBuffer = Buffer.from(event.body, \"base64\");\n } else {\n bodyBuffer = Buffer.from(new TextEncoder().encode(event.body));\n }\n\n // Now decode if needed\n if (requestContentEncoding && requestContentEncoding.includes(\"zstd\")) {\n if (!this.compressionSupported) {\n throw new Error(\n \"The input is compressed using zstd, but this lambda deployment doesn't support compression. Make sure to deploy the Lambda using Node > 22\"\n );\n }\n\n // Input encoded with zstd, let's decode it!\n bodyBuffer = (\n zlib as unknown as { zstdDecompressSync: (b: Buffer) => Buffer }\n ).zstdDecompressSync(bodyBuffer);\n }\n\n // Prep the stream to pass through the endpoint handler\n // eslint-disable-next-line @typescript-eslint/require-await\n inputReader = (async function* () {\n yield bodyBuffer as Uint8Array;\n })()[Symbol.asyncIterator]();\n }\n\n const chunks: Uint8Array[] = [];\n const outputWriter: OutputWriter = {\n write: function (value: Uint8Array): Promise<void> {\n chunks.push(value);\n return Promise.resolve();\n },\n close: function (): Promise<void> {\n return Promise.resolve();\n },\n };\n\n const { writeHead, head: headPromise } = captureHead();\n\n // handle should never throw\n const response = this.handler.handle(\n {\n headers: event.headers,\n url: path,\n extraArgs: [context],\n },\n {\n AWSRequestId: context.awsRequestId,\n }\n );\n\n try {\n await response.process({\n inputReader,\n outputWriter,\n writeHead,\n abortSignal: abortController.signal,\n });\n } catch (e) {\n // Lambda always has to return a result object, so we convert\n // process() failures into a 500 response here rather than log-only.\n const error = ensureError(e);\n const logger =\n tryCreateContextualLogger(\n this.handler.endpoint.loggerTransport,\n path,\n event.headers\n ) ?? this.handler.endpoint.rlog;\n logger.error(\"Unexpected error: \" + (error.stack ?? error.message));\n return {\n headers: {\n \"content-type\": \"application/json\",\n \"x-restate-server\": X_RESTATE_SERVER,\n },\n statusCode: 500,\n isBase64Encoded: false,\n body: JSON.stringify({ message: error.message }),\n };\n }\n\n // Responses always call writeHead, so this resolves on the next\n // microtask.\n const head = await headPromise;\n\n const responseBodyBuffer = Buffer.concat(chunks);\n let responseBody;\n\n // Now let's encode if we need to.\n if (\n this.compressionSupported &&\n responseBodyBuffer.length > RESPONSE_COMPRESSION_THRESHOLD &&\n requestAcceptEncoding &&\n requestAcceptEncoding.includes(\"zstd\")\n ) {\n head.headers[\"content-encoding\"] = \"zstd\";\n\n responseBody = (\n zlib as unknown as { zstdCompressSync: (b: Buffer) => Buffer }\n )\n .zstdCompressSync(responseBodyBuffer)\n .toString(\"base64\");\n } else {\n responseBody = responseBodyBuffer.toString(\"base64\");\n }\n return {\n headers: head.headers,\n statusCode: head.statusCode,\n isBase64Encoded: true,\n body: responseBody,\n };\n } finally {\n abortController.abort();\n }\n }\n}\n\nexport function isCompressionSupported() {\n return \"zstdDecompressSync\" in zlib && \"zstdCompressSync\" in zlib;\n}\n"],"mappings":";;;;;;;AA6BA,MAAM,iCAAiC,IAAI,OAAO;AAElD,IAAa,gBAAb,MAA2B;CACzB,YACE,AAAiBA,SACjB,AAAiBC,sBACjB;EAFiB;EACA;;CAGnB,MAAM,cACJ,OACA,SACoE;EACpE,MAAM,kBAAkB,IAAI,iBAAiB;AAC7C,MAAI;GAIF,MAAM,OAAO,UAAU,QAAQ,MAAM,OAAO,MAAM;GAGlD,IAAI;GACJ,IAAI;AACJ,QAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,MAAM,QAAQ,CACtD,KACE,IAAI,cAAc,oBAAoB,QAAW,EAC/C,aAAa,UACd,CAAC,KAAK,EAEP,0BAAyB;YAEzB,IAAI,cAAc,mBAAmB,QAAW,EAC9C,aAAa,UACd,CAAC,KAAK,EAEP,yBAAwB;GAQ5B,IAAIC;AACJ,OAAI,CAAC,MAAM,KACT,eAAc,kBAAkB;QAC3B;IACL,IAAIC;AACJ,QAAI,MAAM,gBACR,cAAa,OAAO,KAAK,MAAM,MAAM,SAAS;QAE9C,cAAa,OAAO,KAAK,IAAI,aAAa,CAAC,OAAO,MAAM,KAAK,CAAC;AAIhE,QAAI,0BAA0B,uBAAuB,SAAS,OAAO,EAAE;AACrE,SAAI,CAAC,KAAK,qBACR,OAAM,IAAI,MACR,6IACD;AAIH,kBACE,KACA,mBAAmB,WAAW;;AAKlC,mBAAe,mBAAmB;AAChC,WAAM;QACJ,CAAC,OAAO,gBAAgB;;GAG9B,MAAMC,SAAuB,EAAE;GAC/B,MAAMC,eAA6B;IACjC,OAAO,SAAU,OAAkC;AACjD,YAAO,KAAK,MAAM;AAClB,YAAO,QAAQ,SAAS;;IAE1B,OAAO,WAA2B;AAChC,YAAO,QAAQ,SAAS;;IAE3B;GAED,MAAM,EAAE,WAAW,MAAM,gBAAgB,aAAa;GAGtD,MAAM,WAAW,KAAK,QAAQ,OAC5B;IACE,SAAS,MAAM;IACf,KAAK;IACL,WAAW,CAAC,QAAQ;IACrB,EACD,EACE,cAAc,QAAQ,cACvB,CACF;AAED,OAAI;AACF,UAAM,SAAS,QAAQ;KACrB;KACA;KACA;KACA,aAAa,gBAAgB;KAC9B,CAAC;YACK,GAAG;IAGV,MAAM,QAAQ,YAAY,EAAE;AAO5B,KALE,0BACE,KAAK,QAAQ,SAAS,iBACtB,MACA,MAAM,QACP,IAAI,KAAK,QAAQ,SAAS,MACtB,MAAM,wBAAwB,MAAM,SAAS,MAAM,SAAS;AACnE,WAAO;KACL,SAAS;MACP,gBAAgB;MAChB,oBAAoB;MACrB;KACD,YAAY;KACZ,iBAAiB;KACjB,MAAM,KAAK,UAAU,EAAE,SAAS,MAAM,SAAS,CAAC;KACjD;;GAKH,MAAM,OAAO,MAAM;GAEnB,MAAM,qBAAqB,OAAO,OAAO,OAAO;GAChD,IAAI;AAGJ,OACE,KAAK,wBACL,mBAAmB,SAAS,kCAC5B,yBACA,sBAAsB,SAAS,OAAO,EACtC;AACA,SAAK,QAAQ,sBAAsB;AAEnC,mBACE,KAEC,iBAAiB,mBAAmB,CACpC,SAAS,SAAS;SAErB,gBAAe,mBAAmB,SAAS,SAAS;AAEtD,UAAO;IACL,SAAS,KAAK;IACd,YAAY,KAAK;IACjB,iBAAiB;IACjB,MAAM;IACP;YACO;AACR,mBAAgB,OAAO;;;;AAK7B,SAAgB,yBAAyB;AACvC,QAAO,wBAAwB,QAAQ,sBAAsB"}
@@ -0,0 +1,97 @@
1
+ const require_errors = require('../../types/errors.cjs');
2
+ const require_user_agent = require('../../user_agent.cjs');
3
+ const require_utils = require('./utils.cjs');
4
+
5
+ //#region src/endpoint/handlers/preview.ts
6
+ function handlePreview(endpoint, previewPathComponent) {
7
+ const service = endpoint.components.get(previewPathComponent.componentName);
8
+ if (!service) {
9
+ const msg = `No service found for name: ${previewPathComponent.componentName}`;
10
+ endpoint.rlog.error(msg);
11
+ return require_utils.errorResponse(404, msg);
12
+ }
13
+ const resolvedSerde = service.serdeMatching(previewPathComponent.serdeName);
14
+ if (!resolvedSerde?.preview) {
15
+ const msg = `No previewable serde found for URL: ${JSON.stringify(previewPathComponent)}`;
16
+ endpoint.rlog.error(msg);
17
+ return require_utils.errorResponse(404, msg);
18
+ }
19
+ return new RestatePreviewResponse(resolvedSerde, previewPathComponent.operation);
20
+ }
21
+ var RestatePreviewResponse = class {
22
+ constructor(serde, operation) {
23
+ this.serde = serde;
24
+ this.operation = operation;
25
+ }
26
+ async process({ inputReader, outputWriter, writeHead }) {
27
+ const response = await this.computeResponse(inputReader);
28
+ writeHead(response.statusCode, response.headers);
29
+ try {
30
+ await outputWriter.write(response.body);
31
+ } finally {
32
+ await outputWriter.close();
33
+ }
34
+ }
35
+ async computeResponse(inputReader) {
36
+ try {
37
+ const body = await readAllInput(inputReader);
38
+ return this.operation === "decode" ? await this.decode(body) : await this.encode(body);
39
+ } catch (e) {
40
+ return previewErrorResponse(require_errors.ensureError(e).message);
41
+ }
42
+ }
43
+ async decode(requestBody) {
44
+ const value = this.serde.deserialize(requestBody);
45
+ const json = await this.serde.preview.toJsonString(value);
46
+ return {
47
+ statusCode: 200,
48
+ headers: {
49
+ "content-type": "application/json",
50
+ "x-restate-server": require_user_agent.X_RESTATE_SERVER
51
+ },
52
+ body: new TextEncoder().encode(json)
53
+ };
54
+ }
55
+ async encode(requestBody) {
56
+ const json = new TextDecoder().decode(requestBody);
57
+ const value = await this.serde.preview.fromJsonString(json);
58
+ return {
59
+ statusCode: 200,
60
+ headers: {
61
+ "content-type": this.serde.contentType ?? "application/octet-stream",
62
+ "x-restate-server": require_user_agent.X_RESTATE_SERVER
63
+ },
64
+ body: this.serde.serialize(value)
65
+ };
66
+ }
67
+ };
68
+ function previewErrorResponse(message) {
69
+ return {
70
+ statusCode: 422,
71
+ headers: {
72
+ "content-type": "application/json",
73
+ "x-restate-server": require_user_agent.X_RESTATE_SERVER
74
+ },
75
+ body: new TextEncoder().encode(JSON.stringify({ message }))
76
+ };
77
+ }
78
+ async function readAllInput(inputReader) {
79
+ const chunks = [];
80
+ let totalLength = 0;
81
+ while (true) {
82
+ const chunk = await inputReader.next();
83
+ if (chunk.done) break;
84
+ chunks.push(chunk.value);
85
+ totalLength += chunk.value.length;
86
+ }
87
+ const combined = new Uint8Array(totalLength);
88
+ let offset = 0;
89
+ for (const chunk of chunks) {
90
+ combined.set(chunk, offset);
91
+ offset += chunk.length;
92
+ }
93
+ return combined;
94
+ }
95
+
96
+ //#endregion
97
+ exports.handlePreview = handlePreview;
@@ -0,0 +1,98 @@
1
+ import { ensureError } from "../../types/errors.js";
2
+ import { X_RESTATE_SERVER } from "../../user_agent.js";
3
+ import { errorResponse } from "./utils.js";
4
+
5
+ //#region src/endpoint/handlers/preview.ts
6
+ function handlePreview(endpoint, previewPathComponent) {
7
+ const service = endpoint.components.get(previewPathComponent.componentName);
8
+ if (!service) {
9
+ const msg = `No service found for name: ${previewPathComponent.componentName}`;
10
+ endpoint.rlog.error(msg);
11
+ return errorResponse(404, msg);
12
+ }
13
+ const resolvedSerde = service.serdeMatching(previewPathComponent.serdeName);
14
+ if (!resolvedSerde?.preview) {
15
+ const msg = `No previewable serde found for URL: ${JSON.stringify(previewPathComponent)}`;
16
+ endpoint.rlog.error(msg);
17
+ return errorResponse(404, msg);
18
+ }
19
+ return new RestatePreviewResponse(resolvedSerde, previewPathComponent.operation);
20
+ }
21
+ var RestatePreviewResponse = class {
22
+ constructor(serde, operation) {
23
+ this.serde = serde;
24
+ this.operation = operation;
25
+ }
26
+ async process({ inputReader, outputWriter, writeHead }) {
27
+ const response = await this.computeResponse(inputReader);
28
+ writeHead(response.statusCode, response.headers);
29
+ try {
30
+ await outputWriter.write(response.body);
31
+ } finally {
32
+ await outputWriter.close();
33
+ }
34
+ }
35
+ async computeResponse(inputReader) {
36
+ try {
37
+ const body = await readAllInput(inputReader);
38
+ return this.operation === "decode" ? await this.decode(body) : await this.encode(body);
39
+ } catch (e) {
40
+ return previewErrorResponse(ensureError(e).message);
41
+ }
42
+ }
43
+ async decode(requestBody) {
44
+ const value = this.serde.deserialize(requestBody);
45
+ const json = await this.serde.preview.toJsonString(value);
46
+ return {
47
+ statusCode: 200,
48
+ headers: {
49
+ "content-type": "application/json",
50
+ "x-restate-server": X_RESTATE_SERVER
51
+ },
52
+ body: new TextEncoder().encode(json)
53
+ };
54
+ }
55
+ async encode(requestBody) {
56
+ const json = new TextDecoder().decode(requestBody);
57
+ const value = await this.serde.preview.fromJsonString(json);
58
+ return {
59
+ statusCode: 200,
60
+ headers: {
61
+ "content-type": this.serde.contentType ?? "application/octet-stream",
62
+ "x-restate-server": X_RESTATE_SERVER
63
+ },
64
+ body: this.serde.serialize(value)
65
+ };
66
+ }
67
+ };
68
+ function previewErrorResponse(message) {
69
+ return {
70
+ statusCode: 422,
71
+ headers: {
72
+ "content-type": "application/json",
73
+ "x-restate-server": X_RESTATE_SERVER
74
+ },
75
+ body: new TextEncoder().encode(JSON.stringify({ message }))
76
+ };
77
+ }
78
+ async function readAllInput(inputReader) {
79
+ const chunks = [];
80
+ let totalLength = 0;
81
+ while (true) {
82
+ const chunk = await inputReader.next();
83
+ if (chunk.done) break;
84
+ chunks.push(chunk.value);
85
+ totalLength += chunk.value.length;
86
+ }
87
+ const combined = new Uint8Array(totalLength);
88
+ let offset = 0;
89
+ for (const chunk of chunks) {
90
+ combined.set(chunk, offset);
91
+ offset += chunk.length;
92
+ }
93
+ return combined;
94
+ }
95
+
96
+ //#endregion
97
+ export { handlePreview };
98
+ //# sourceMappingURL=preview.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"preview.js","names":["serde: Serde<unknown>","operation: \"decode\" | \"encode\"","chunks: Uint8Array[]"],"sources":["../../../src/endpoint/handlers/preview.ts"],"sourcesContent":["/*\n * Copyright (c) 2023-2024 - Restate Software, Inc., Restate GmbH\n *\n * This file is part of the Restate SDK for Node.js/TypeScript,\n * which is released under the MIT license.\n *\n * You can find a copy of the license in file LICENSE in the root\n * directory of this repository or package, or at\n * https://github.com/restatedev/sdk-typescript/blob/main/LICENSE\n */\n\nimport type { Serde } from \"@restatedev/restate-sdk-core\";\nimport type { Endpoint } from \"../endpoint.js\";\nimport type { PreviewPathComponents } from \"../components.js\";\nimport { X_RESTATE_SERVER } from \"../../user_agent.js\";\nimport { ensureError } from \"../../types/errors.js\";\nimport type {\n InputReader,\n OutputWriter,\n ResponseHeaders,\n RestateResponse,\n} from \"./types.js\";\nimport { errorResponse } from \"./utils.js\";\n\nexport function handlePreview(\n endpoint: Endpoint,\n previewPathComponent: PreviewPathComponents\n): RestateResponse {\n const service = endpoint.components.get(previewPathComponent.componentName);\n if (!service) {\n const msg = `No service found for name: ${previewPathComponent.componentName}`;\n endpoint.rlog.error(msg);\n return errorResponse(404, msg);\n }\n\n const resolvedSerde = service.serdeMatching(previewPathComponent.serdeName);\n if (!resolvedSerde?.preview) {\n const msg = `No previewable serde found for URL: ${JSON.stringify(previewPathComponent)}`;\n endpoint.rlog.error(msg);\n return errorResponse(404, msg);\n }\n\n return new RestatePreviewResponse(\n resolvedSerde,\n previewPathComponent.operation\n );\n}\n\ntype PreviewResponse = {\n body: Uint8Array;\n headers: ResponseHeaders;\n statusCode: number;\n};\n\nclass RestatePreviewResponse implements RestateResponse {\n constructor(\n private readonly serde: Serde<unknown>,\n private readonly operation: \"decode\" | \"encode\"\n ) {}\n\n async process({\n inputReader,\n outputWriter,\n writeHead,\n }: {\n inputReader: InputReader;\n outputWriter: OutputWriter;\n writeHead: (statusCode: number, headers: ResponseHeaders) => void;\n abortSignal: AbortSignal;\n }): Promise<void> {\n const response = await this.computeResponse(inputReader);\n\n writeHead(response.statusCode, response.headers);\n try {\n await outputWriter.write(response.body);\n } finally {\n await outputWriter.close();\n }\n }\n\n private async computeResponse(\n inputReader: InputReader\n ): Promise<PreviewResponse> {\n try {\n const body = await readAllInput(inputReader);\n return this.operation === \"decode\"\n ? await this.decode(body)\n : await this.encode(body);\n } catch (e) {\n return previewErrorResponse(ensureError(e).message);\n }\n }\n\n private async decode(requestBody: Uint8Array): Promise<PreviewResponse> {\n const value = this.serde.deserialize(requestBody);\n const json = await this.serde.preview!.toJsonString(value);\n return {\n statusCode: 200,\n headers: {\n \"content-type\": \"application/json\",\n \"x-restate-server\": X_RESTATE_SERVER,\n },\n body: new TextEncoder().encode(json),\n };\n }\n\n private async encode(requestBody: Uint8Array): Promise<PreviewResponse> {\n const json = new TextDecoder().decode(requestBody);\n const value = await this.serde.preview!.fromJsonString(json);\n return {\n statusCode: 200,\n headers: {\n \"content-type\": this.serde.contentType ?? \"application/octet-stream\",\n \"x-restate-server\": X_RESTATE_SERVER,\n },\n body: this.serde.serialize(value),\n };\n }\n}\n\nfunction previewErrorResponse(message: string): PreviewResponse {\n return {\n statusCode: 422,\n headers: {\n \"content-type\": \"application/json\",\n \"x-restate-server\": X_RESTATE_SERVER,\n },\n body: new TextEncoder().encode(JSON.stringify({ message })),\n };\n}\n\nasync function readAllInput(inputReader: InputReader): Promise<Uint8Array> {\n const chunks: Uint8Array[] = [];\n let totalLength = 0;\n\n while (true) {\n const chunk = await inputReader.next();\n if (chunk.done) {\n break;\n }\n chunks.push(chunk.value);\n totalLength += chunk.value.length;\n }\n\n const combined = new Uint8Array(totalLength);\n let offset = 0;\n for (const chunk of chunks) {\n combined.set(chunk, offset);\n offset += chunk.length;\n }\n return combined;\n}\n"],"mappings":";;;;;AAwBA,SAAgB,cACd,UACA,sBACiB;CACjB,MAAM,UAAU,SAAS,WAAW,IAAI,qBAAqB,cAAc;AAC3E,KAAI,CAAC,SAAS;EACZ,MAAM,MAAM,8BAA8B,qBAAqB;AAC/D,WAAS,KAAK,MAAM,IAAI;AACxB,SAAO,cAAc,KAAK,IAAI;;CAGhC,MAAM,gBAAgB,QAAQ,cAAc,qBAAqB,UAAU;AAC3E,KAAI,CAAC,eAAe,SAAS;EAC3B,MAAM,MAAM,uCAAuC,KAAK,UAAU,qBAAqB;AACvF,WAAS,KAAK,MAAM,IAAI;AACxB,SAAO,cAAc,KAAK,IAAI;;AAGhC,QAAO,IAAI,uBACT,eACA,qBAAqB,UACtB;;AASH,IAAM,yBAAN,MAAwD;CACtD,YACE,AAAiBA,OACjB,AAAiBC,WACjB;EAFiB;EACA;;CAGnB,MAAM,QAAQ,EACZ,aACA,cACA,aAMgB;EAChB,MAAM,WAAW,MAAM,KAAK,gBAAgB,YAAY;AAExD,YAAU,SAAS,YAAY,SAAS,QAAQ;AAChD,MAAI;AACF,SAAM,aAAa,MAAM,SAAS,KAAK;YAC/B;AACR,SAAM,aAAa,OAAO;;;CAI9B,MAAc,gBACZ,aAC0B;AAC1B,MAAI;GACF,MAAM,OAAO,MAAM,aAAa,YAAY;AAC5C,UAAO,KAAK,cAAc,WACtB,MAAM,KAAK,OAAO,KAAK,GACvB,MAAM,KAAK,OAAO,KAAK;WACpB,GAAG;AACV,UAAO,qBAAqB,YAAY,EAAE,CAAC,QAAQ;;;CAIvD,MAAc,OAAO,aAAmD;EACtE,MAAM,QAAQ,KAAK,MAAM,YAAY,YAAY;EACjD,MAAM,OAAO,MAAM,KAAK,MAAM,QAAS,aAAa,MAAM;AAC1D,SAAO;GACL,YAAY;GACZ,SAAS;IACP,gBAAgB;IAChB,oBAAoB;IACrB;GACD,MAAM,IAAI,aAAa,CAAC,OAAO,KAAK;GACrC;;CAGH,MAAc,OAAO,aAAmD;EACtE,MAAM,OAAO,IAAI,aAAa,CAAC,OAAO,YAAY;EAClD,MAAM,QAAQ,MAAM,KAAK,MAAM,QAAS,eAAe,KAAK;AAC5D,SAAO;GACL,YAAY;GACZ,SAAS;IACP,gBAAgB,KAAK,MAAM,eAAe;IAC1C,oBAAoB;IACrB;GACD,MAAM,KAAK,MAAM,UAAU,MAAM;GAClC;;;AAIL,SAAS,qBAAqB,SAAkC;AAC9D,QAAO;EACL,YAAY;EACZ,SAAS;GACP,gBAAgB;GAChB,oBAAoB;GACrB;EACD,MAAM,IAAI,aAAa,CAAC,OAAO,KAAK,UAAU,EAAE,SAAS,CAAC,CAAC;EAC5D;;AAGH,eAAe,aAAa,aAA+C;CACzE,MAAMC,SAAuB,EAAE;CAC/B,IAAI,cAAc;AAElB,QAAO,MAAM;EACX,MAAM,QAAQ,MAAM,YAAY,MAAM;AACtC,MAAI,MAAM,KACR;AAEF,SAAO,KAAK,MAAM,MAAM;AACxB,iBAAe,MAAM,MAAM;;CAG7B,MAAM,WAAW,IAAI,WAAW,YAAY;CAC5C,IAAI,SAAS;AACb,MAAK,MAAM,SAAS,QAAQ;AAC1B,WAAS,IAAI,OAAO,OAAO;AAC3B,YAAU,MAAM;;AAElB,QAAO"}
@@ -26,24 +26,39 @@ function errorResponse(code, message) {
26
26
  }, new TextEncoder().encode(JSON.stringify({ message })));
27
27
  }
28
28
  function simpleResponse(statusCode, headers, body) {
29
- return {
30
- headers,
31
- statusCode,
32
- async process({ inputReader, outputWriter }) {
33
- if (inputReader !== void 0) while (true) {
34
- const { done } = await inputReader.next();
35
- if (done) break;
36
- }
37
- await outputWriter.write(body);
38
- await outputWriter.close();
29
+ return { async process({ inputReader, outputWriter, writeHead }) {
30
+ writeHead(statusCode, headers);
31
+ while (true) {
32
+ const { done } = await inputReader.next();
33
+ if (done) break;
39
34
  }
40
- };
35
+ await outputWriter.write(body);
36
+ await outputWriter.close();
37
+ } };
41
38
  }
42
39
  function emptyInputReader() {
43
40
  return (async function* () {})()[Symbol.asyncIterator]();
44
41
  }
42
+ /**
43
+ * Bundles a `writeHead` callback with a Promise that resolves once the head
44
+ * is committed. Used by adapters (fetch, lambda) that need to observe the
45
+ * head commit from outside the `process()` call.
46
+ */
47
+ function captureHead() {
48
+ const { promise, resolve } = Promise.withResolvers();
49
+ return {
50
+ writeHead: (statusCode, headers) => {
51
+ resolve({
52
+ statusCode,
53
+ headers: { ...headers }
54
+ });
55
+ },
56
+ head: promise
57
+ };
58
+ }
45
59
 
46
60
  //#endregion
61
+ exports.captureHead = captureHead;
47
62
  exports.emptyInputReader = emptyInputReader;
48
63
  exports.errorResponse = errorResponse;
49
64
  exports.invocationIdFromHeaders = invocationIdFromHeaders;