Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,18 @@
> ship. Full per-platform walk-through:
> [`docs/installing.md`](docs/installing.md).

## Unreleased

### Added

- **`readAttachmentBytes(slotId)` public accessor (`@apicircle/ui-components`).**
A narrow, additive export that reads a stored attachment's raw bytes by its
`GlobalFileAsset.slotId` (or `null` when absent), so an edition / overlay can
resolve an asset's bytes — e.g. parse a stored OpenAPI spec for code-vs-spec
drift — without reaching into the internal `AttachmentRecord` shape or the
IndexedDB layer. Purely additive; no existing behavior changes.
(`@apicircle/ui-components`)

## 1.3.0 - 2026-07-18

_All workspace packages move to **1.3.0** in lockstep — the published
Expand Down
1 change: 1 addition & 0 deletions packages/ui-components/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export { applyFont, ALL_FONTS } from './theme/applyFont';
export type { FontFamilyId, FontFamilyDef } from './theme/applyFont';
export { applyFontSize, clampFontSizePercent } from './theme/applyFontSize';
export { useWorkspaceStore } from './store/workspaceStore';
export { readAttachmentBytes } from './persistence/attachments';
export { Button, Input, Modal, cn } from './primitives';
export { PANELS, getPanel } from './layout/panels';
export type { PanelDef } from './layout/panels';
Expand Down
17 changes: 17 additions & 0 deletions packages/ui-components/src/persistence/attachments.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
getAttachment,
materializeAttachment,
putAttachment,
readAttachmentBytes,
} from './attachments';

const sampleBytes = (str: string) => new TextEncoder().encode(str);
Expand All @@ -32,6 +33,22 @@ describe('attachments IDB store', () => {
expect(await getAttachment('nope')).toBeNull();
});

it('readAttachmentBytes returns the stored bytes, or null when absent', async () => {
await putAttachment({
slotId: 'slot-bytes',
filename: 'spec.json',
mimeType: 'application/json',
size: 5,
sha256: 'ab',
savedAt: '2026-04-27T00:00:00.000Z',
bytes: sampleBytes('hello'),
});
const bytes = await readAttachmentBytes('slot-bytes');
expect(bytes).not.toBeNull();
expect(new TextDecoder().decode(bytes!)).toBe('hello');
expect(await readAttachmentBytes('missing')).toBeNull();
});

it('deleteAttachment removes the record', async () => {
await putAttachment({
slotId: 'slot-2',
Expand Down
13 changes: 13 additions & 0 deletions packages/ui-components/src/persistence/attachments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,19 @@ export async function getAttachment(slotId: string): Promise<AttachmentRecord |
});
}

/**
* Read a stored attachment's raw bytes by its `slotId` (`GlobalFileAsset.slotId`),
* or `null` when the slot has no bytes on this device (e.g. an asset uploaded on
* another machine and not yet pulled). A narrow public accessor — re-exported from
* the package root — so an edition / overlay can resolve an asset's bytes (e.g.
* parse a stored OpenAPI spec for code-vs-spec drift) without depending on the
* internal `AttachmentRecord` shape or the IndexedDB layer.
*/
export async function readAttachmentBytes(slotId: string): Promise<Uint8Array | null> {
const record = await getAttachment(slotId);
return record ? record.bytes : null;
}

export async function deleteAttachment(slotId: string): Promise<void> {
const db = await openDb();
return new Promise<void>((resolve, reject) => {
Expand Down
Loading