lakebed 0.0.18 → 0.0.19

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.
@@ -312,6 +312,103 @@ function createBrokeredResponse(response) {
312
312
  };
313
313
  }
314
314
 
315
+ function endpointBodyToBase64(body) {
316
+ if (body === undefined || body === null) {
317
+ return "";
318
+ }
319
+ if (typeof body === "string") {
320
+ return nodeBuffer.from(body, "utf8").toString("base64");
321
+ }
322
+ if (body instanceof ArrayBuffer) {
323
+ return nodeBuffer.from(body).toString("base64");
324
+ }
325
+ if (ArrayBuffer.isView(body)) {
326
+ return nodeBuffer.from(body.buffer, body.byteOffset, body.byteLength).toString("base64");
327
+ }
328
+ return nodeBuffer.from(JSON.stringify(body ?? null), "utf8").toString("base64");
329
+ }
330
+
331
+ function endpointStatus(status, fallback = 200) {
332
+ const parsed = Number(status);
333
+ return Number.isInteger(parsed) && parsed >= 100 && parsed <= 599 ? parsed : fallback;
334
+ }
335
+
336
+ async function normalizeEndpointResponse(result) {
337
+ if (result === undefined || result === null) {
338
+ return { bodyBase64: "", headers: {}, status: 204 };
339
+ }
340
+
341
+ if (
342
+ result &&
343
+ typeof result === "object" &&
344
+ result.kind !== "response" &&
345
+ typeof result.arrayBuffer === "function" &&
346
+ typeof result.status === "number"
347
+ ) {
348
+ const body = nodeBuffer.from(await result.arrayBuffer());
349
+ return {
350
+ bodyBase64: body.toString("base64"),
351
+ headers: headersToObject(result.headers),
352
+ status: endpointStatus(result.status)
353
+ };
354
+ }
355
+
356
+ if (isPlainObject(result) && result.kind === "response") {
357
+ return {
358
+ bodyBase64: endpointBodyToBase64(result.body),
359
+ headers: headersToObject(result.headers),
360
+ status: endpointStatus(result.status)
361
+ };
362
+ }
363
+
364
+ if (typeof result === "string") {
365
+ return {
366
+ bodyBase64: endpointBodyToBase64(result),
367
+ headers: { "Content-Type": "text/plain; charset=utf-8" },
368
+ status: 200
369
+ };
370
+ }
371
+
372
+ return {
373
+ bodyBase64: endpointBodyToBase64(result),
374
+ headers: { "Content-Type": "application/json; charset=utf-8" },
375
+ status: 200
376
+ };
377
+ }
378
+
379
+ function createEndpointRequest(request) {
380
+ const url = new URL(request.url ?? request.path ?? "/", "http://lakebed.local");
381
+ const body = nodeBuffer.from(request.bodyBase64 ?? "", "base64");
382
+ const headers = new Map(Object.entries(headersToObject(request.headers)).map(([key, value]) => [key.toLowerCase(), String(value)]));
383
+
384
+ return {
385
+ headers: {
386
+ entries() {
387
+ return headers.entries();
388
+ },
389
+ get(name) {
390
+ return headers.get(String(name).toLowerCase()) ?? null;
391
+ },
392
+ has(name) {
393
+ return headers.has(String(name).toLowerCase());
394
+ }
395
+ },
396
+ method: String(request.method ?? "GET").toUpperCase(),
397
+ path: request.path ?? url.pathname,
398
+ query: new URLSearchParams(url.searchParams),
399
+ url: url.href,
400
+ async bytes() {
401
+ return new Uint8Array(body);
402
+ },
403
+ async json() {
404
+ return JSON.parse(body.toString("utf8"));
405
+ },
406
+ async text() {
407
+ return body.toString("utf8");
408
+ }
409
+ };
410
+ }
411
+
315
412
  function brokeredFetch(input, init = {}) {
316
413
  if (!allowBrokeredFetch) {
317
414
  return Promise.reject(new Error("Outbound fetch is disabled for anonymous deploys."));
@@ -389,7 +486,8 @@ function sendError(error) {
389
486
  sendToParent?.({
390
487
  error: {
391
488
  message: error instanceof Error ? error.message : String(error),
392
- name: error instanceof Error ? error.name : "Error"
489
+ name: error instanceof Error ? error.name : "Error",
490
+ stack: error instanceof Error ? error.stack : undefined
393
491
  },
394
492
  ok: false,
395
493
  type: "source-runtime.result"
@@ -429,6 +527,21 @@ async function runSource(request) {
429
527
  return;
430
528
  }
431
529
 
530
+ if (request.op === "endpoint") {
531
+ const definition = app.endpoints?.[request.name];
532
+ const handler = definition?.handler ?? definition;
533
+ if (typeof handler !== "function") {
534
+ throw new Error(`Unknown endpoint: ${request.name}`);
535
+ }
536
+ const result = await handler(source.ctx, createEndpointRequest(request.endpointRequest ?? {}));
537
+ const response = await normalizeEndpointResponse(result);
538
+ sendResult({
539
+ operations: jsonSafe(source.operations),
540
+ response: jsonSafe(response)
541
+ });
542
+ return;
543
+ }
544
+
432
545
  throw new Error(`Unsupported source operation: ${request.op}`);
433
546
  }
434
547
 
@@ -493,6 +493,9 @@ async function brokeredFetch(request, options) {
493
493
  function workerError(payload) {
494
494
  const error = new Error(payload?.message ?? "Source runtime failed.");
495
495
  error.name = payload?.name ?? "SourceRuntimeError";
496
+ if (payload?.stack) {
497
+ error.stack = payload.stack;
498
+ }
496
499
  return error;
497
500
  }
498
501
 
@@ -557,6 +560,29 @@ export class ChildProcessSourceRuntime {
557
560
  }, mutationTransactionOptions(limits));
558
561
  }
559
562
 
563
+ async executeEndpoint({ artifact, auth, deployId, limits = DEFAULT_ANONYMOUS_LIMITS, name, request, state }) {
564
+ return state.transaction(deployId, async (tx) => {
565
+ const snapshot = await snapshotSourceState({ artifact, deployId, state: tx });
566
+ const response = await this.runWorker({
567
+ allowFetch: artifact.deployTarget === "claimed-source",
568
+ artifact,
569
+ auth,
570
+ endpointRequest: request,
571
+ env: snapshot.env,
572
+ name,
573
+ op: "endpoint",
574
+ rows: snapshot.rows
575
+ });
576
+ await applySourceOperations({
577
+ artifact,
578
+ deployId,
579
+ operations: response.operations ?? [],
580
+ tx
581
+ });
582
+ return response.response ?? { bodyBase64: "", headers: {}, status: 204 };
583
+ }, mutationTransactionOptions(limits));
584
+ }
585
+
560
586
  runWorker(request) {
561
587
  return new Promise((resolveRun, rejectRun) => {
562
588
  const child = fork(this.workerPath, [], {
@@ -603,6 +629,7 @@ export class ChildProcessSourceRuntime {
603
629
  if (message.ok) {
604
630
  finish(resolveRun, {
605
631
  operations: message.operations,
632
+ response: message.response,
606
633
  result: message.result
607
634
  });
608
635
  } else {
package/src/version.js CHANGED
@@ -1 +1 @@
1
- export const LAKEBED_VERSION = "0.0.18";
1
+ export const LAKEBED_VERSION = "0.0.19";