Repository navigation
feat(mobile): one cached native-client install command for agents and machines #24965
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 2 commits
4a82af4
391a759
f2df420
4ca936b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,135 @@ | ||
| import { expect, it } from "@effect/vitest"; | ||
| import * as Effect from "effect/Effect"; | ||
| import * as Layer from "effect/Layer"; | ||
| import * as NativeClients from "./NativeClients.ts"; | ||
|
|
||
| const tokenDigest = "210eb3d2c67a7207923e1999751300a87a2dd22839b258b80c3d805a6fe64984"; | ||
| const auth = (name: string) => ({ | ||
| authorization: `Basic ${btoa(`${name}:fixture-token-012345678901234567890123456789`)}`, | ||
| }); | ||
| const key = `ios/development/${"a".repeat(40)}`; | ||
| const base = `https://cache.example${NativeClients.routePrefix}${key}`; | ||
|
|
||
| function memoryBucket() { | ||
| const objects = new Map<string, { bytes: ArrayBuffer; customMetadata: Record<string, string> }>(); | ||
| const uploads = new Map< | ||
| string, | ||
| { key: string; customMetadata: Record<string, string>; parts: Map<number, ArrayBuffer> } | ||
| >(); | ||
| const resume = (objectKey: string, uploadId: string) => ({ | ||
| uploadPart: async (partNumber: number, body: ReadableStream) => { | ||
| uploads.get(uploadId)!.parts.set(partNumber, await new Response(body).arrayBuffer()); | ||
| return { partNumber, etag: `etag-${partNumber}` }; | ||
| }, | ||
| complete: async (parts: ReadonlyArray<{ partNumber: number }>) => { | ||
| const upload = uploads.get(uploadId)!; | ||
| const chunks = parts.map(({ partNumber }) => upload.parts.get(partNumber)!); | ||
| objects.set(objectKey, { | ||
| bytes: await new Blob(chunks).arrayBuffer(), | ||
| customMetadata: upload.customMetadata, | ||
| }); | ||
| uploads.delete(uploadId); | ||
| }, | ||
| abort: async () => void uploads.delete(uploadId), | ||
| }); | ||
| return { | ||
| head: async (objectKey: string) => (objects.has(objectKey) ? {} : null), | ||
| get: async (objectKey: string) => { | ||
| const object = objects.get(objectKey); | ||
| return object | ||
| ? { | ||
| size: object.bytes.byteLength, | ||
| body: new Blob([object.bytes]).stream(), | ||
| customMetadata: object.customMetadata, | ||
| } | ||
| : null; | ||
| }, | ||
| createMultipartUpload: async ( | ||
| objectKey: string, | ||
| options: { customMetadata: Record<string, string> }, | ||
| ) => { | ||
| const uploadId = `upload/${uploads.size}+`; | ||
| uploads.set(uploadId, { | ||
| key: objectKey, | ||
| customMetadata: options.customMetadata, | ||
| parts: new Map(), | ||
| }); | ||
| return { uploadId }; | ||
| }, | ||
| resumeMultipartUpload: resume, | ||
| uploads, | ||
| }; | ||
| } | ||
|
|
||
| const request = (path: string, init: RequestInit & { name: string }) => | ||
| Effect.gen(function* () { | ||
| const native = yield* NativeClients.NativeClients; | ||
| return yield* native.handle( | ||
| new Request(`${base}${path}`, { | ||
| ...init, | ||
| headers: { ...auth(init.name), ...init.headers }, | ||
| }), | ||
| ); | ||
| }); | ||
|
|
||
| it.effect("serves a writer's multipart upload to readers with its digest", () => { | ||
| const bucket = memoryBucket(); | ||
| const bindings = { | ||
| CLIENTS: bucket, | ||
| developerKeys: JSON.stringify({ julius: tokenDigest }), | ||
| readerKeys: JSON.stringify({ "ci-reader": tokenDigest }), | ||
| readLimit: { limit: async () => ({ success: true }) }, | ||
| storeLimit: { limit: async () => ({ success: true }) }, | ||
| } as unknown as NativeClients.Bindings["Service"]; | ||
| const digest = "d".repeat(64); | ||
| return Effect.gen(function* () { | ||
| const denied = yield* request("/uploads", { | ||
| name: "ci-reader", | ||
| method: "POST", | ||
| headers: { "x-t3-digest": digest }, | ||
| }); | ||
| expect(denied.status).toBe(403); | ||
|
|
||
| const created = yield* request("/uploads", { | ||
| name: "julius", | ||
| method: "POST", | ||
| headers: { "x-t3-digest": digest, "x-t3-build-ms": "540000" }, | ||
| }); | ||
| const { uploadId } = (yield* Effect.promise(() => created.json())) as { uploadId: string }; | ||
| const parts = []; | ||
| for (const [index, chunk] of ["native ", "client"].entries()) { | ||
| const response = yield* request(`/uploads/${encodeURIComponent(uploadId)}/${index + 1}`, { | ||
| name: "julius", | ||
| method: "PUT", | ||
| body: chunk, | ||
| headers: { "content-length": String(chunk.length) }, | ||
| }); | ||
| const { etag } = (yield* Effect.promise(() => response.json())) as { etag: string }; | ||
| parts.push({ partNumber: index + 1, etag }); | ||
| } | ||
| const completed = yield* request(`/uploads/${encodeURIComponent(uploadId)}/complete`, { | ||
| name: "julius", | ||
| method: "POST", | ||
| body: JSON.stringify({ parts }), | ||
| }); | ||
| expect(completed.status).toBe(204); | ||
|
|
||
| const download = yield* request("", { name: "ci-reader", method: "GET" }); | ||
| expect(download.status).toBe(200); | ||
| expect(download.headers.get("x-t3-digest")).toBe(digest); | ||
| expect(download.headers.get("x-t3-build-ms")).toBe("540000"); | ||
| expect(yield* Effect.promise(() => download.text())).toBe("native client"); | ||
|
|
||
| const again = yield* request("/uploads", { | ||
| name: "julius", | ||
| method: "POST", | ||
| headers: { "x-t3-digest": digest }, | ||
| }); | ||
| expect(yield* Effect.promise(() => again.json())).toEqual({ exists: true }); | ||
| expect(bucket.uploads.size).toBe(0); | ||
| }).pipe( | ||
| Effect.provide( | ||
| NativeClients.layer.pipe(Layer.provide(Layer.succeed(NativeClients.Bindings, bindings))), | ||
| ), | ||
| ); | ||
| }); |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,183 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||
| import type * as Cloudflare from "@cloudflare/workers-types"; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| import * as Context from "effect/Context"; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| import * as Effect from "effect/Effect"; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| import * as Layer from "effect/Layer"; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| import * as Schema from "effect/Schema"; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| import { developerIdentity } from "./developerAuth.ts"; | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| export const routePrefix = "/projects/t3code/native-clients/"; | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| export class NativeClientsError extends Schema.TaggedError<NativeClientsError>()( | ||||||||||||||||||||||||||||||||||||||||||||||||||
| "NativeClientsError", | ||||||||||||||||||||||||||||||||||||||||||||||||||
| { status: Schema.Number, reason: Schema.String }, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| ) {} | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| export class Bindings extends Context.Service< | ||||||||||||||||||||||||||||||||||||||||||||||||||
| Bindings, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| readonly CLIENTS: Cloudflare.R2Bucket; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| readonly developerKeys: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| readonly readerKeys: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| readonly readLimit: Cloudflare.RateLimit; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| readonly storeLimit: Cloudflare.RateLimit; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
| >()("@t3tools/task-cache/NativeClients/Bindings") {} | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| export class NativeClients extends Context.Service< | ||||||||||||||||||||||||||||||||||||||||||||||||||
| NativeClients, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| { readonly handle: (request: Request) => Effect.Effect<Response> } | ||||||||||||||||||||||||||||||||||||||||||||||||||
| >()("@t3tools/task-cache/NativeClients") {} | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| const CompleteBody = Schema.fromJsonString( | ||||||||||||||||||||||||||||||||||||||||||||||||||
| Schema.Struct({ | ||||||||||||||||||||||||||||||||||||||||||||||||||
| parts: Schema.Array(Schema.Struct({ partNumber: Schema.Number, etag: Schema.String })).check( | ||||||||||||||||||||||||||||||||||||||||||||||||||
| Schema.isMinLength(1), | ||||||||||||||||||||||||||||||||||||||||||||||||||
| Schema.isMaxLength(64), | ||||||||||||||||||||||||||||||||||||||||||||||||||
| ), | ||||||||||||||||||||||||||||||||||||||||||||||||||
| }), | ||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| const decodeComplete = Schema.decodeUnknownEffect(CompleteBody); | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| // <platform>/<variant>/<fingerprint>[/uploads[/<uploadId>[/<part>|/complete]]] | ||||||||||||||||||||||||||||||||||||||||||||||||||
| const route = | ||||||||||||||||||||||||||||||||||||||||||||||||||
| /^((?:ios|android)\/[a-z0-9_-]{1,32}\/[a-f0-9]{40,64})(?:\/uploads(?:\/([^/]{1,1024})(?:\/(complete|[1-9][0-9]?))?)?)?$/; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| const digestPattern = /^[a-f0-9]{64}$/; | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| const make = Effect.gen(function* () { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| const bindings = yield* Bindings; | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| const r2 = <A>(run: () => Promise<A>) => | ||||||||||||||||||||||||||||||||||||||||||||||||||
| Effect.tryPromise({ | ||||||||||||||||||||||||||||||||||||||||||||||||||
| try: run, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| catch: () => new NativeClientsError({ status: 503, reason: "storage_unavailable" }), | ||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| const limit = Effect.fn("NativeClients.limit")(function* (writing: boolean) { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| const allowed = yield* r2(() => | ||||||||||||||||||||||||||||||||||||||||||||||||||
| (writing ? bindings.storeLimit : bindings.readLimit).limit({ | ||||||||||||||||||||||||||||||||||||||||||||||||||
| key: writing ? "t3code:native-store" : "t3code:native-read", | ||||||||||||||||||||||||||||||||||||||||||||||||||
| }), | ||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!allowed.success) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| return yield* new NativeClientsError({ status: 429, reason: "rate_limit" }); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| const handle = Effect.fn("NativeClients.handle")( | ||||||||||||||||||||||||||||||||||||||||||||||||||
| function* (request: Request) { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| const url = new URL(request.url); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| const match = route.exec(url.pathname.slice(routePrefix.length)); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!match || url.search || !url.pathname.startsWith(routePrefix)) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| return yield* new NativeClientsError({ status: 404, reason: "unknown_route" }); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| const [, key, uploadId, part] = match as unknown as [ | ||||||||||||||||||||||||||||||||||||||||||||||||||
| string, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| string, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| string | undefined, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| string | undefined, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| ]; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| const operation = | ||||||||||||||||||||||||||||||||||||||||||||||||||
| uploadId === undefined | ||||||||||||||||||||||||||||||||||||||||||||||||||
| ? url.pathname.endsWith("/uploads") | ||||||||||||||||||||||||||||||||||||||||||||||||||
| ? "create" | ||||||||||||||||||||||||||||||||||||||||||||||||||
| : "download" | ||||||||||||||||||||||||||||||||||||||||||||||||||
| : part === undefined | ||||||||||||||||||||||||||||||||||||||||||||||||||
| ? "abort" | ||||||||||||||||||||||||||||||||||||||||||||||||||
| : part === "complete" | ||||||||||||||||||||||||||||||||||||||||||||||||||
| ? "complete" | ||||||||||||||||||||||||||||||||||||||||||||||||||
| : "part"; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| const method = { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| download: "GET", | ||||||||||||||||||||||||||||||||||||||||||||||||||
| create: "POST", | ||||||||||||||||||||||||||||||||||||||||||||||||||
| part: "PUT", | ||||||||||||||||||||||||||||||||||||||||||||||||||
| complete: "POST", | ||||||||||||||||||||||||||||||||||||||||||||||||||
| abort: "DELETE", | ||||||||||||||||||||||||||||||||||||||||||||||||||
| }[operation]; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| yield* Effect.annotateCurrentSpan({ "native.operation": operation, "native.key": key }); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| if (request.method !== method) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| return yield* new NativeClientsError({ status: 405, reason: "method_not_allowed" }); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!request.headers.has("authorization")) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| return yield* new NativeClientsError({ status: 401, reason: "missing_identity" }); | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| const developer = yield* developerIdentity(request, bindings.developerKeys); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| const writing = operation !== "download"; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| if (writing) { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| if (typeof developer !== "string") | ||||||||||||||||||||||||||||||||||||||||||||||||||
| return yield* new NativeClientsError({ status: 403, reason: "write_denied" }); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } else if ( | ||||||||||||||||||||||||||||||||||||||||||||||||||
| typeof developer !== "string" && | ||||||||||||||||||||||||||||||||||||||||||||||||||
| typeof (yield* developerIdentity(request, bindings.readerKeys)) !== "string" | ||||||||||||||||||||||||||||||||||||||||||||||||||
| ) { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| return yield* new NativeClientsError({ status: 403, reason: "read_denied" }); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
| yield* limit(writing); | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| if (operation === "download") { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| const object = yield* r2(() => bindings.CLIENTS.get(key)); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| yield* Effect.annotateCurrentSpan({ "native.result": object ? "hit" : "miss" }); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!object) return yield* new NativeClientsError({ status: 404, reason: "missing" }); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| return new Response(object.body as unknown as ReadableStream, { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| headers: { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| "Content-Type": "application/octet-stream", | ||||||||||||||||||||||||||||||||||||||||||||||||||
| "Content-Length": String(object.size), | ||||||||||||||||||||||||||||||||||||||||||||||||||
| "Cache-Control": "no-store", | ||||||||||||||||||||||||||||||||||||||||||||||||||
| "X-T3-Digest": object.customMetadata?.digest ?? "", | ||||||||||||||||||||||||||||||||||||||||||||||||||
| "X-T3-Build-Ms": object.customMetadata?.buildMs ?? "0", | ||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
| if (operation === "create") { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| const digest = request.headers.get("x-t3-digest") ?? ""; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!digestPattern.test(digest)) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| return yield* new NativeClientsError({ status: 400, reason: "invalid_digest" }); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| if (yield* r2(() => bindings.CLIENTS.head(key))) { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| yield* Effect.annotateCurrentSpan({ "native.result": "exists" }); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| return Response.json({ exists: true }); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
| const buildMs = request.headers.get("x-t3-build-ms") ?? ""; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| const upload = yield* r2(() => | ||||||||||||||||||||||||||||||||||||||||||||||||||
| bindings.CLIENTS.createMultipartUpload(key, { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| customMetadata: { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| digest, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| writer: developer!, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| buildMs: /^\d{1,10}$/.test(buildMs) ? buildMs : "0", | ||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| }), | ||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| return Response.json({ uploadId: upload.uploadId }); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| const upload = bindings.CLIENTS.resumeMultipartUpload(key, decodeURIComponent(uploadId!)); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| if (operation === "abort") { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| yield* r2(() => upload.abort()); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| return new Response(null, { status: 204 }); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
| if (operation === "part") { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!request.body || !request.headers.has("content-length")) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| return yield* new NativeClientsError({ status: 411, reason: "length_required" }); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| const uploaded = yield* r2(() => | ||||||||||||||||||||||||||||||||||||||||||||||||||
| upload.uploadPart(Number(part), request.body as unknown as Cloudflare.ReadableStream), | ||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| return Response.json({ etag: uploaded.etag }); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
| const body = yield* decodeComplete( | ||||||||||||||||||||||||||||||||||||||||||||||||||
| yield* r2(() => request.text().then((text) => text.slice(0, 64 * 1024))), | ||||||||||||||||||||||||||||||||||||||||||||||||||
| ).pipe( | ||||||||||||||||||||||||||||||||||||||||||||||||||
| Effect.mapError(() => new NativeClientsError({ status: 400, reason: "invalid_parts" })), | ||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Limit the actual bytes read for the
Reject the request by declared 🛡️ Sketch of a bounded read
Based on learnings: "Post-parse checks cannot prevent excessive buffering or parsing, and header-only checks do not bound actual bytes read." Committable suggestion
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| yield* r2(() => upload.complete([...body.parts])); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| yield* Effect.annotateCurrentSpan({ "native.result": "stored" }); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| return new Response(null, { status: 204 }); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| Effect.catchTags({ | ||||||||||||||||||||||||||||||||||||||||||||||||||
| NativeClientsError: (error) => | ||||||||||||||||||||||||||||||||||||||||||||||||||
| Effect.gen(function* () { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| if (error.status >= 500) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| yield* Effect.logWarning("Native client request failed", { reason: error.reason }); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| return Response.json({ error: error.reason }, { status: error.status }); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| }), | ||||||||||||||||||||||||||||||||||||||||||||||||||
| }), | ||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| return NativeClients.of({ handle }); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| export const layer = Layer.effect(NativeClients, make); | ||||||||||||||||||||||||||||||||||||||||||||||||||
Uh oh!
There was an error while loading. Please reload this page.