Implement the Transit decoder and SSE stream parser in src/binfile.ts with unit tests #202
Labels
No labels
bug
chore
documentation
enhancement
feature
ready
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
david/pi-extensions-and-skills#202
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Implement the two pieces of machinery the
.penpotbinfile endpoints need and nothing else does: a minimal Transit decoder (~:keyword,~u<uuid>,~#uri) and a server-sent-events stream parser that readsevent: progresslines and extracts the finalevent: endpayload. Both are pure and unit-tested.Background
Depends on: #188
On Penpot 2.17 the two binfile commands are the one corner of the API that does not speak plain JSON:
import-binfileandexport-binfilerespond with Transit payloads and Transit error bodies, even when the request andAcceptheaders are JSON.event: progressframes, terminated by anevent: endframe whosedata:line carries the result.import-binfile'sendpayload is a Transit vector of created file ids:data: ["~u<uuid>"].export-binfile'sendpayload is a Transit URI:data: {"~#uri":"https://…/assets/by-id/…"}.So a Transit reader is unavoidable, but only a tiny one: three tags. Isolating it here means the import/export tools are thin, and a malformed stream can be debugged in one place. On any parse failure, fail loudly with the raw stream included — a silently empty result would be worse than a crash.
Documentation Required
A separate process downloads these into the listed folders before this issue is implemented. Check the folders for the actual reference material before starting.
docs/reference/transit-format/json-verboseencodings, the ground types, and the tag forms (~:keyword,~ufor UUIDs,~#for extension/custom tags such as URIs). Implement only what is listed above; note in a comment where the full spec differs.docs/reference/penpot-api/<PENPOT_URL>/api/main/doc/openapi.json— theimport-binfile/export-binfileparameters and response types on this instance..penpotZIP withmanifest.json,files/<id>.json,colors/,typographies/), so the values being decoded are recognisable.docs/reference/nodejs/response.body) incrementally.Response.bodyand the reader API used by the stream parser.Implementation Details
Create
extensions/penpot/src/binfile.ts(withsrc/binfile.test.tsbeside it) exporting:Transit decoding
decodeTransit(text)→ the decoded value, handling:"~:keyword"→ a keyword value (decide and document the representation — aSymbol, a{ keyword }wrapper, or a plain string — but keep it consistent and test it),"~u<uuid>"→ the UUID string,{"~#uri": "https://…"}→ the URI as a string,["~#uri", "https://…"]), sincejson-verboseuses that form,^0,^1, …) only if a live response turns out to use it — verify against the instance and document the decision infindings.md.decodeTransitError(rawText)→ a readable message for a Transit error body (delegating to the error decoder shape used by the rest of the extension). Unrecognised Transit must produce a message containing the raw payload, never an empty string.SSE parsing
parseSseStream(body: ReadableStream<Uint8Array> | string, options?)→ an async iterator (or a collector) of frames{ event, data }, handling:event:/data:/ optionalid:/retry:lines,data:fields concatenated with newlines (per the SSE spec),:,awaitStreamEnd(frames, { command })→ resolves theevent: endpayload (decoded viadecodeTransit) or throws a clear error on: a stream that ends with noendframe, anevent: error/event: failframe, or a transport/parse failure. Include the accumulated raw frames in the error so a failure is diagnosable.Keep the module free of HTTP: it takes a body/string and returns values.
Acceptance Criteria
decodeTransitdecodes["~u<uuid>"]to an array containing the UUID string.decodeTransitdecodes{"~#uri":"https://…"}and["~#uri","https://…"]to the URI string.decodeTransitdecodes~:keywordforms consistently and the representation is documented in the code.decodeTransitpasses plain JSON through unchanged.decodeTransitErrorproduces a readable message that includes the raw payload when the Transit body cannot be decoded.parseSseStreamyields the correct frames when adata:line is split across two chunks.data:fields are concatenated, and:comment lines are ignored.awaitStreamEndreturns the decodedendpayload, and fails loudly (with the raw frames attached) when there is noendframe or an error frame appears.ReadableStreams only.Test Plan
Live confirmation (requires
PENPOT_URL/PENPOT_TOKEN) is done in the import/export steps; here, additionally:export-binfilecall withcurland check it into the test suite as a fixture if it reveals encodings not covered above.extensions/penpot/findings.mdwhether the real streams use Transit caching (^N) codes, and what the exactendpayload looked like for both commands.pi-loop opened and merged a pull request for this issue: #235