@raster-app/sdk 0.1.0-rc.0 → 0.1.0

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.
@@ -1 +1 @@
1
- {"version":3,"file":"openapi.js","sourceRoot":"","sources":["../../src/__generated__/openapi.ts"],"names":[],"mappings":"","sourcesContent":["export interface paths {\n \"/me\": {\n parameters: {\n query?: never;\n header?: never;\n path?: never;\n cookie?: never;\n };\n /** Resolve the credential's organization and library scope. */\n get: {\n parameters: {\n query?: never;\n header: {\n \"Api-Version\": components[\"parameters\"][\"ApiVersion\"];\n };\n path?: never;\n cookie?: never;\n };\n requestBody?: never;\n responses: {\n /** @description OK */\n 200: {\n headers: {\n [name: string]: unknown;\n };\n content: {\n \"application/json\": {\n data: components[\"schemas\"][\"Me\"];\n };\n };\n };\n 401: components[\"responses\"][\"Unauthorized\"];\n };\n };\n put?: never;\n post?: never;\n delete?: never;\n options?: never;\n head?: never;\n patch?: never;\n trace?: never;\n };\n \"/libraries\": {\n parameters: {\n query?: never;\n header?: never;\n path?: never;\n cookie?: never;\n };\n get?: never;\n put?: never;\n /**\n * Create an organization, library, and API key with no account.\n * @description Anonymous — mints the key, so send no Authorization header. https://raster.app/docs/guides/start-without-an-account\n */\n post: {\n parameters: {\n query?: never;\n header: {\n \"Api-Version\": components[\"parameters\"][\"ApiVersion\"];\n };\n path?: never;\n cookie?: never;\n };\n requestBody: {\n content: {\n \"application/json\": {\n /** Format: email */\n email: string;\n name?: string;\n };\n };\n };\n responses: {\n /** @description OK */\n 200: {\n headers: {\n [name: string]: unknown;\n };\n content: {\n \"application/json\": {\n data: {\n organizationId: string;\n libraryId: string;\n apiKey: string;\n /** Format: uri */\n claimUrl: string;\n /** Format: date-time */\n expiresAt: string;\n emailSent: boolean;\n };\n };\n };\n };\n 400: components[\"responses\"][\"BadRequest\"];\n };\n };\n delete?: never;\n options?: never;\n head?: never;\n patch?: never;\n trace?: never;\n };\n \"/organizations/{organizationId}/libraries\": {\n parameters: {\n query?: never;\n header?: never;\n path?: never;\n cookie?: never;\n };\n /** List libraries the key can reach. */\n get: {\n parameters: {\n query?: {\n /** @description Zero-based page index. */\n page?: number;\n pageSize?: number;\n };\n header: {\n \"Api-Version\": components[\"parameters\"][\"ApiVersion\"];\n };\n path: {\n organizationId: components[\"parameters\"][\"OrganizationId\"];\n };\n cookie?: never;\n };\n requestBody?: never;\n responses: {\n /** @description OK */\n 200: {\n headers: {\n [name: string]: unknown;\n };\n content: {\n \"application/json\": {\n data: components[\"schemas\"][\"Library\"][];\n };\n };\n };\n 401: components[\"responses\"][\"Unauthorized\"];\n };\n };\n put?: never;\n /** Create a library in this organization. */\n post: {\n parameters: {\n query?: never;\n header: {\n \"Api-Version\": components[\"parameters\"][\"ApiVersion\"];\n };\n path: {\n organizationId: components[\"parameters\"][\"OrganizationId\"];\n };\n cookie?: never;\n };\n requestBody: {\n content: {\n \"application/json\": {\n name: string;\n slug?: string;\n };\n };\n };\n responses: {\n /** @description Created */\n 201: {\n headers: {\n [name: string]: unknown;\n };\n content: {\n \"application/json\": {\n data: components[\"schemas\"][\"Library\"];\n };\n };\n };\n 400: components[\"responses\"][\"BadRequest\"];\n 401: components[\"responses\"][\"Unauthorized\"];\n };\n };\n delete?: never;\n options?: never;\n head?: never;\n patch?: never;\n trace?: never;\n };\n \"/organizations/{organizationId}/libraries/{libraryId}\": {\n parameters: {\n query?: never;\n header?: never;\n path?: never;\n cookie?: never;\n };\n get?: never;\n put?: never;\n post?: never;\n delete?: never;\n options?: never;\n head?: never;\n /** Rename a library. */\n patch: {\n parameters: {\n query?: never;\n header: {\n \"Api-Version\": components[\"parameters\"][\"ApiVersion\"];\n };\n path: {\n organizationId: components[\"parameters\"][\"OrganizationId\"];\n libraryId: components[\"parameters\"][\"LibraryId\"];\n };\n cookie?: never;\n };\n requestBody: {\n content: {\n \"application/json\": {\n name: string;\n };\n };\n };\n responses: {\n /** @description OK */\n 200: {\n headers: {\n [name: string]: unknown;\n };\n content: {\n \"application/json\": {\n data: components[\"schemas\"][\"Library\"];\n };\n };\n };\n 404: components[\"responses\"][\"NotFound\"];\n };\n };\n trace?: never;\n };\n \"/organizations/{organizationId}/libraries/{libraryId}/assets\": {\n parameters: {\n query?: never;\n header?: never;\n path?: never;\n cookie?: never;\n };\n /** List assets (paginated, optional tag filter). */\n get: {\n parameters: {\n query?: {\n page?: number;\n pageSize?: number;\n /** @description Comma-separated tag filter, up to 50. */\n tags?: string;\n };\n header: {\n \"Api-Version\": components[\"parameters\"][\"ApiVersion\"];\n };\n path: {\n organizationId: components[\"parameters\"][\"OrganizationId\"];\n libraryId: components[\"parameters\"][\"LibraryId\"];\n };\n cookie?: never;\n };\n requestBody?: never;\n responses: {\n /** @description OK */\n 200: {\n headers: {\n [name: string]: unknown;\n };\n content: {\n \"application/json\": {\n data: components[\"schemas\"][\"Asset\"][];\n };\n };\n };\n 401: components[\"responses\"][\"Unauthorized\"];\n };\n };\n put?: never;\n /** Upload up to 20 files. */\n post: {\n parameters: {\n query?: never;\n header: {\n \"Api-Version\": components[\"parameters\"][\"ApiVersion\"];\n };\n path: {\n organizationId: components[\"parameters\"][\"OrganizationId\"];\n libraryId: components[\"parameters\"][\"LibraryId\"];\n };\n cookie?: never;\n };\n requestBody: {\n content: {\n \"multipart/form-data\": {\n files: string[];\n /** @description Attach the uploaded file as a variant of this asset. Send exactly one file with it. The asset must be an original in this library, of the same media kind as the file. */\n parentId?: string;\n };\n };\n };\n responses: {\n /** @description OK */\n 200: {\n headers: {\n [name: string]: unknown;\n };\n content: {\n \"application/json\": {\n data: {\n responseText: string;\n assets: components[\"schemas\"][\"Asset\"][];\n };\n };\n };\n };\n 400: components[\"responses\"][\"BadRequest\"];\n };\n };\n /**\n * Move up to 100 assets to trash (soft delete).\n * @description Soft delete — assets stay recoverable from trash until the 30-day reaper. Trashing an asset also trashes its variants; a variant id on its own is rejected with 400.\n */\n delete: {\n parameters: {\n query?: never;\n header: {\n \"Api-Version\": components[\"parameters\"][\"ApiVersion\"];\n };\n path: {\n organizationId: components[\"parameters\"][\"OrganizationId\"];\n libraryId: components[\"parameters\"][\"LibraryId\"];\n };\n cookie?: never;\n };\n requestBody: {\n content: {\n \"application/json\": {\n ids: string[];\n };\n };\n };\n responses: {\n /** @description OK */\n 200: {\n headers: {\n [name: string]: unknown;\n };\n content: {\n \"application/json\": {\n data: {\n success: boolean;\n message: string;\n ids: string[];\n };\n };\n };\n };\n };\n };\n options?: never;\n head?: never;\n patch?: never;\n trace?: never;\n };\n \"/organizations/{organizationId}/libraries/{libraryId}/assets/{assetId}\": {\n parameters: {\n query?: never;\n header?: never;\n path?: never;\n cookie?: never;\n };\n /**\n * Get one asset by id.\n * @description Returns the full asset, including its non-destructive variants in the `variants` field; each variant carries its own tags/description — fetch a variant by its id for those.\n */\n get: {\n parameters: {\n query?: never;\n header: {\n \"Api-Version\": components[\"parameters\"][\"ApiVersion\"];\n };\n path: {\n organizationId: components[\"parameters\"][\"OrganizationId\"];\n libraryId: components[\"parameters\"][\"LibraryId\"];\n assetId: components[\"parameters\"][\"AssetId\"];\n };\n cookie?: never;\n };\n requestBody?: never;\n responses: {\n /** @description OK */\n 200: {\n headers: {\n [name: string]: unknown;\n };\n content: {\n \"application/json\": {\n data: components[\"schemas\"][\"Asset\"];\n };\n };\n };\n 404: components[\"responses\"][\"NotFound\"];\n };\n };\n put?: never;\n post?: never;\n delete?: never;\n options?: never;\n head?: never;\n patch?: never;\n trace?: never;\n };\n \"/organizations/{organizationId}/libraries/{libraryId}/assets/{assetId}/description\": {\n parameters: {\n query?: never;\n header?: never;\n path?: never;\n cookie?: never;\n };\n get?: never;\n put?: never;\n post?: never;\n delete?: never;\n options?: never;\n head?: never;\n /** Replace one asset's description. */\n patch: {\n parameters: {\n query?: never;\n header: {\n \"Api-Version\": components[\"parameters\"][\"ApiVersion\"];\n };\n path: {\n organizationId: components[\"parameters\"][\"OrganizationId\"];\n libraryId: components[\"parameters\"][\"LibraryId\"];\n assetId: components[\"parameters\"][\"AssetId\"];\n };\n cookie?: never;\n };\n requestBody: {\n content: {\n \"application/json\": {\n description: string;\n };\n };\n };\n responses: {\n /** @description OK */\n 200: {\n headers: {\n [name: string]: unknown;\n };\n content: {\n \"application/json\": {\n data: {\n assetId: string;\n description: string;\n };\n };\n };\n };\n };\n };\n trace?: never;\n };\n \"/organizations/{organizationId}/libraries/{libraryId}/assets/{assetId}/promote\": {\n parameters: {\n query?: never;\n header?: never;\n path?: never;\n cookie?: never;\n };\n get?: never;\n put?: never;\n /**\n * Make a variant the asset's default.\n * @description The variant's pixels replace what the asset serves, at the SAME URL, so anything already embedding that URL shows the new file. The previous default is preserved as a new variant and keeps the name it had; the asset keeps its id and URL and takes the promoted variant's name. Its description and the tags generated from its image follow the picture — the promoted variant hands over any it carries, and the preserved variant keeps the ones that described it, while tags you added yourself stay on the asset. Rejected while the asset is itself still being processed (a video whose poster has not landed yet). The promote is accepted and the swap completes a moment later; re-fetch the asset to see it reflected.\n */\n post: {\n parameters: {\n query?: never;\n header: {\n \"Api-Version\": components[\"parameters\"][\"ApiVersion\"];\n };\n path: {\n organizationId: components[\"parameters\"][\"OrganizationId\"];\n libraryId: components[\"parameters\"][\"LibraryId\"];\n assetId: components[\"parameters\"][\"AssetId\"];\n };\n cookie?: never;\n };\n requestBody: {\n content: {\n \"application/json\": {\n variantId: string;\n };\n };\n };\n responses: {\n /** @description OK */\n 200: {\n headers: {\n [name: string]: unknown;\n };\n content: {\n \"application/json\": {\n data: {\n assetId: string;\n promotedVariantId: string;\n demotedVariantId: string;\n };\n };\n };\n };\n };\n };\n delete?: never;\n options?: never;\n head?: never;\n patch?: never;\n trace?: never;\n };\n \"/organizations/{organizationId}/libraries/{libraryId}/assets/tag\": {\n parameters: {\n query?: never;\n header?: never;\n path?: never;\n cookie?: never;\n };\n get?: never;\n put?: never;\n /** Apply up to 20 tags to up to 100 assets. */\n post: {\n parameters: {\n query?: never;\n header: {\n \"Api-Version\": components[\"parameters\"][\"ApiVersion\"];\n };\n path: {\n organizationId: components[\"parameters\"][\"OrganizationId\"];\n libraryId: components[\"parameters\"][\"LibraryId\"];\n };\n cookie?: never;\n };\n requestBody: {\n content: {\n \"application/json\": {\n assetIds: string[];\n tags: string[];\n };\n };\n };\n responses: {\n /** @description OK */\n 200: {\n headers: {\n [name: string]: unknown;\n };\n content: {\n \"application/json\": {\n data: {\n taggedCount: number;\n };\n };\n };\n };\n };\n };\n delete?: never;\n options?: never;\n head?: never;\n patch?: never;\n trace?: never;\n };\n \"/organizations/{organizationId}/libraries/{libraryId}/assets/untag\": {\n parameters: {\n query?: never;\n header?: never;\n path?: never;\n cookie?: never;\n };\n get?: never;\n put?: never;\n /** Remove up to 20 tags from up to 100 assets. */\n post: {\n parameters: {\n query?: never;\n header: {\n \"Api-Version\": components[\"parameters\"][\"ApiVersion\"];\n };\n path: {\n organizationId: components[\"parameters\"][\"OrganizationId\"];\n libraryId: components[\"parameters\"][\"LibraryId\"];\n };\n cookie?: never;\n };\n requestBody: {\n content: {\n \"application/json\": {\n assetIds: string[];\n tags: string[];\n };\n };\n };\n responses: {\n /** @description OK */\n 200: {\n headers: {\n [name: string]: unknown;\n };\n content: {\n \"application/json\": {\n data: {\n untaggedCount: number;\n };\n };\n };\n };\n };\n };\n delete?: never;\n options?: never;\n head?: never;\n patch?: never;\n trace?: never;\n };\n \"/organizations/{organizationId}/libraries/{libraryId}/assets/transfer\": {\n parameters: {\n query?: never;\n header?: never;\n path?: never;\n cookie?: never;\n };\n get?: never;\n put?: never;\n /**\n * Move up to 100 assets to another library.\n * @description An asset's variants move with it; a variant cannot be transferred on its own — transfer its original and the variants follow.\n */\n post: {\n parameters: {\n query?: never;\n header: {\n \"Api-Version\": components[\"parameters\"][\"ApiVersion\"];\n };\n path: {\n organizationId: components[\"parameters\"][\"OrganizationId\"];\n libraryId: components[\"parameters\"][\"LibraryId\"];\n };\n cookie?: never;\n };\n requestBody: {\n content: {\n \"application/json\": {\n targetLibraryId: string;\n assetIds: string[];\n };\n };\n };\n responses: {\n /** @description OK */\n 200: {\n headers: {\n [name: string]: unknown;\n };\n content: {\n \"application/json\": {\n data: {\n transferredCount: number;\n sourceLibraryId: string;\n targetLibraryId: string;\n };\n };\n };\n };\n };\n };\n delete?: never;\n options?: never;\n head?: never;\n patch?: never;\n trace?: never;\n };\n \"/organizations/{organizationId}/libraries/{libraryId}/tags\": {\n parameters: {\n query?: never;\n header?: never;\n path?: never;\n cookie?: never;\n };\n /** List a library's tags by usage count. */\n get: {\n parameters: {\n query?: {\n limit?: number;\n };\n header: {\n \"Api-Version\": components[\"parameters\"][\"ApiVersion\"];\n };\n path: {\n organizationId: components[\"parameters\"][\"OrganizationId\"];\n libraryId: components[\"parameters\"][\"LibraryId\"];\n };\n cookie?: never;\n };\n requestBody?: never;\n responses: {\n /** @description OK */\n 200: {\n headers: {\n [name: string]: unknown;\n };\n content: {\n \"application/json\": {\n data: components[\"schemas\"][\"Tag\"][];\n };\n };\n };\n };\n };\n put?: never;\n post?: never;\n delete?: never;\n options?: never;\n head?: never;\n patch?: never;\n trace?: never;\n };\n \"/organizations/{organizationId}/search/assets\": {\n parameters: {\n query?: never;\n header?: never;\n path?: never;\n cookie?: never;\n };\n /** Search assets across libraries (ranked, highlighted). */\n get: {\n parameters: {\n query: {\n q: string;\n /** @description Comma-separated library ids. */\n libraries?: string;\n page?: number;\n pageSize?: number;\n };\n header: {\n \"Api-Version\": components[\"parameters\"][\"ApiVersion\"];\n };\n path: {\n organizationId: components[\"parameters\"][\"OrganizationId\"];\n };\n cookie?: never;\n };\n requestBody?: never;\n responses: {\n /** @description OK */\n 200: {\n headers: {\n [name: string]: unknown;\n };\n content: {\n \"application/json\": {\n data: components[\"schemas\"][\"SearchResult\"];\n };\n };\n };\n };\n };\n put?: never;\n post?: never;\n delete?: never;\n options?: never;\n head?: never;\n patch?: never;\n trace?: never;\n };\n}\nexport type webhooks = Record<string, never>;\nexport interface components {\n schemas: {\n Error: {\n error: {\n /** @example API_KEY_NOT_AUTHORIZED_FOR_LIBRARY */\n code: string;\n message: string;\n };\n };\n /** @description What the credential can act on. An API key, or an OAuth token bound to one organization, resolves that organization; a token granted the whole account resolves every organization it belongs to under `organizations`. An OAuth token also carries `user`; an API key does not. */\n Me: {\n organizationId: string;\n organizationName: string | null;\n plan: string | null;\n libraries: {\n library: string;\n /** @enum {string} */\n access: \"read\" | \"write\";\n }[];\n /** @description The account an OAuth access token acts as. */\n user?: {\n name: string | null;\n };\n } | {\n organizations: {\n organizationId: string;\n organizationName: string | null;\n plan: string | null;\n libraries: {\n library: string;\n /** @enum {string} */\n access: \"read\" | \"write\";\n }[];\n }[];\n /** @description The account an OAuth access token acts as. */\n user: {\n name: string | null;\n };\n };\n /** @description Represents a digital asset with associated metadata. */\n Asset: {\n id: string;\n width: number | null;\n height: number | null;\n name: string | null;\n libraryId: string | null;\n blurhash: string | null;\n /**\n * Format: uri\n * @description Permanent, public CDN URL for the full-resolution asset. It needs no\n * authentication and never expires, so reference it directly as the source URL\n * rather than downloading and re-hosting the file.\n */\n url: string | null;\n /**\n * Format: uri\n * @deprecated\n */\n thumbUrlBlurred: string | null;\n /**\n * @description JSON-encoded map of size (the image's longest edge, in pixels) to the CDN\n * URL of the image at that size (parse before use). Each URL carries the same\n * permanent, public, embed-directly semantics as `url`; pick the size closest to\n * your display target.\n */\n sizes: string | null;\n /**\n * Format: uri\n * @description Absolute link that opens this asset in the Raster app, for a person to view,\n * edit, or manage it. Give it to the user as a link; the source URL is `url`.\n */\n appUrl: string | null;\n /**\n * @description Non-destructive variants of the asset. Each entry carries its own\n * `parentId` pointing back to this asset.\n */\n variants: (components[\"schemas\"][\"AssetVariant\"] | null)[] | null;\n /**\n * @description The parentId-model discriminator: `null` on a top-level original, the parent\n * asset's id on a variant. A variant is a first-class Asset — tag-filtered and search\n * queries return it as its own entry — and `parentId` relates it back to its\n * original. May be `null` for originals uploaded before the delta-001 backfill.\n */\n parentId: string | null;\n description: string | null;\n tags: (string | null)[] | null;\n /**\n * @description MIME content type of the asset (e.g. `image/jpeg`, `image/png`).\n * @example image/jpeg\n */\n contentType: string | null;\n /** @description File size in bytes. May be `null` for older assets with no recorded size. */\n size: number | null;\n /**\n * @description Length of the asset in seconds, for video and audio. `null` on images and on\n * videos still being processed.\n */\n duration: number | null;\n /**\n * @description Details read from the asset's own bytes — how it was captured, and its\n * technical format. `null` on assets uploaded before this data was captured.\n */\n metadata: components[\"schemas\"][\"AssetMetadata\"] | null;\n /** @description Creation time as unix milliseconds. May be `null` for some older assets. */\n created: number | null;\n /**\n * @description Last-modified time as unix milliseconds. Bumped on any field change\n * (rename, tag edit, variant-edit save). Useful for cache invalidation and\n * \"sort by recently updated\" UX.\n */\n updated: number | null;\n /**\n * @description Counter bumped each time the asset's BYTES are replaced at an unchanged\n * url — a promoted variant, an edit re-save. Append it to any `url` or\n * `sizes` entry as `?v=` to render the current pixels: the same url keeps\n * serving the previous bytes to every cache that already holds it.\n *\n * Null on an asset whose bytes have never been replaced — render the bare\n * url there, so those assets keep the cache entry they already have.\n */\n contentVersion: number | null;\n /** @description Attribution metadata for the user who uploaded the asset. */\n uploadedBy: components[\"schemas\"][\"Uploader\"] | null;\n approval: components[\"schemas\"][\"Approval\"] | null;\n };\n /** @description Represents a tag associated with assets or libraries. */\n Tag: {\n id: string;\n count: number | null;\n type: string | null;\n };\n /** @description Represents a collection of assets. */\n Library: {\n id: string;\n name: string | null;\n assetsCount: number | null;\n /** @deprecated */\n photosCount: number | null;\n trashCount: number | null;\n tags: (components[\"schemas\"][\"Tag\"] | null)[] | null;\n };\n /**\n * @description Wire shape returned by the `searchAssets` query — `hits` plus the\n * corpus-wide `found` total and the resolved 1-based `page`.\n */\n SearchResult: {\n hits: components[\"schemas\"][\"SearchHit\"][];\n found: number;\n page: number;\n };\n /** @description Represents a variant of an asset. */\n AssetVariant: {\n id: string;\n parentId: string | null;\n width: number | null;\n height: number | null;\n name: string | null;\n /** Format: uri */\n url: string | null;\n sizes: string | null;\n /** Format: uri */\n appUrl: string | null;\n };\n /** @description Details read from the asset's own bytes — how it was captured, and its technical format. */\n AssetMetadata: {\n /**\n * @description How the asset was captured, when it carried any such details. `null` when\n * it carried none, which is common for screen recordings and anything\n * re-encoded.\n */\n capture: components[\"schemas\"][\"AssetCapture\"] | null;\n /** @description The video's technical stream details. `null` on anything that is not a video. */\n video: components[\"schemas\"][\"VideoStream\"] | null;\n };\n /** @description Attribution metadata for the user or system that uploaded an asset. */\n Uploader: {\n /** @description Display name of the uploader. May be `null`. */\n name: string | null;\n /**\n * Format: uri\n * @description URL of the uploader's current avatar, read live from their account. May be `null`.\n */\n avatarUrl: string | null;\n };\n /** @description An asset's approval (delivery) state — who set it, when, and why. */\n Approval: {\n state: string;\n /** @description Email address of the person who set the approval. `null` on a search hit, or when their account has no email address. */\n setBy: string | null;\n setAt: number | null;\n note?: string;\n };\n /**\n * @description A search hit — every public `Asset` field plus optional highlights and a\n * relevance score.\n */\n SearchHit: {\n id: string;\n width: number | null;\n height: number | null;\n name: string | null;\n libraryId: string | null;\n blurhash: string | null;\n /** Format: uri */\n url: string | null;\n /**\n * Format: uri\n * @deprecated\n */\n thumbUrlBlurred: string | null;\n sizes: string | null;\n /** Format: uri */\n appUrl: string | null;\n variants: (components[\"schemas\"][\"AssetVariant\"] | null)[] | null;\n parentId: string | null;\n description: string | null;\n tags: (string | null)[] | null;\n contentType: string | null;\n size: number | null;\n duration: number | null;\n metadata: components[\"schemas\"][\"AssetMetadata\"] | null;\n created: number | null;\n updated: number | null;\n contentVersion: number | null;\n uploadedBy: components[\"schemas\"][\"Uploader\"] | null;\n approval: components[\"schemas\"][\"Approval\"] | null;\n /**\n * @description Highlight snippets — present only when the search matched on the\n * corresponding field. Matched terms are wrapped in `<mark>` tags.\n */\n highlights?: components[\"schemas\"][\"SearchHitHighlights\"];\n /** @description Relevance score for the hit. Optional. */\n score?: number;\n };\n /** @description How an asset was captured — when, on what device, and where. */\n AssetCapture: {\n /**\n * @description When the asset says it was recorded, as written by the device. On a\n * re-encoded file this is the time of that encode rather than the original\n * recording, so treat it as a hint rather than a reliable capture date.\n */\n recordedAt: string | null;\n /** @description Device maker, e.g. `Apple`. */\n make: string | null;\n /** @description Device model, e.g. `iPhone 17 Pro`. */\n model: string | null;\n /** @description Capture software or OS version reported by the device. */\n software: string | null;\n /** @description Signed decimal degrees. Present only alongside `longitude`. */\n latitude: number | null;\n /** @description Signed decimal degrees. */\n longitude: number | null;\n /** @description Metres above sea level, when the container recorded one. */\n altitude: number | null;\n };\n /** @description A video's technical stream details — codec, frame rate, bitrate, and audio. */\n VideoStream: {\n /** @description Video codec, e.g. `h264`, `hevc`, `vp9`, `av1`. */\n codec: string;\n /** @description Frames per second, e.g. `30` or `23.98`. */\n frameRate: number | null;\n /** @description Overall bitrate in bits per second, across all streams. */\n bitrate: number | null;\n /** @description Whether the video carries an audio track. */\n hasAudio: boolean;\n /** @description Audio codec, e.g. `aac`, `opus`. `null` when there is no audio track. */\n audioCodec: string | null;\n /** @description Audio channel layout, e.g. `mono`, `stereo`, `5.1`. */\n audioLayout: string | null;\n /** @description Audio sample rate in hertz, e.g. `48000`. */\n audioSampleRate: number | null;\n };\n /** @description Highlight snippets returned alongside each `SearchHit`. */\n SearchHitHighlights: {\n name?: string;\n tags?: string[];\n };\n };\n responses: {\n /** @description Missing or invalid API key. */\n Unauthorized: {\n headers: {\n [name: string]: unknown;\n };\n content: {\n \"application/json\": components[\"schemas\"][\"Error\"];\n };\n };\n /** @description Resource not found, or the key is not authorized for it. */\n NotFound: {\n headers: {\n [name: string]: unknown;\n };\n content: {\n \"application/json\": components[\"schemas\"][\"Error\"];\n };\n };\n /** @description Invalid input (BAD_USER_INPUT), quota, or API-version error. */\n BadRequest: {\n headers: {\n [name: string]: unknown;\n };\n content: {\n \"application/json\": components[\"schemas\"][\"Error\"];\n };\n };\n };\n parameters: {\n ApiVersion: string;\n OrganizationId: string;\n LibraryId: string;\n AssetId: string;\n };\n requestBodies: never;\n headers: never;\n pathItems: never;\n}\nexport type $defs = Record<string, never>;\nexport type operations = Record<string, never>;\n"]}
1
+ {"version":3,"file":"openapi.js","sourceRoot":"","sources":["../../src/__generated__/openapi.ts"],"names":[],"mappings":"","sourcesContent":["export interface paths {\n \"/me\": {\n parameters: {\n query?: never;\n header?: never;\n path?: never;\n cookie?: never;\n };\n /**\n * Resolve the credential's organization and library scope.\n * @description Returns the organization the presented credential belongs to and the libraries it can reach, each with a read or write access level. Call this first to discover ids — every other operation needs an organizationId and libraryId.\n */\n get: operations[\"getMe\"];\n put?: never;\n post?: never;\n delete?: never;\n options?: never;\n head?: never;\n patch?: never;\n trace?: never;\n };\n \"/libraries\": {\n parameters: {\n query?: never;\n header?: never;\n path?: never;\n cookie?: never;\n };\n get?: never;\n put?: never;\n /**\n * Create an organization, library, and API key with no account.\n * @description Anonymous — mints the key, so send no Authorization header. Give it an email address and it returns a ready-to-use organization, library, API key, and a claim URL to hand the user so they can attach the library to a real account later. Use this when an agent needs somewhere to put images before the user has signed up. https://raster.app/docs/guides/start-without-an-account\n */\n post: operations[\"createEphemeralLibrary\"];\n delete?: never;\n options?: never;\n head?: never;\n patch?: never;\n trace?: never;\n };\n \"/organizations/{organizationId}/libraries\": {\n parameters: {\n query?: never;\n header?: never;\n path?: never;\n cookie?: never;\n };\n /**\n * List libraries the credential can reach.\n * @description Paginated list of the libraries inside one organization that the presented credential is allowed to see. Use it to resolve a human-supplied library name to the libraryId every asset operation needs.\n */\n get: operations[\"listLibraries\"];\n put?: never;\n /**\n * Create a library in this organization.\n * @description Creates an empty library. Requires a write-scoped credential; a read-only key returns API_KEY_READ_ONLY, and the free plan returns LIBRARY_LIMIT_EXCEEDED once its library allowance is used up.\n */\n post: operations[\"createLibrary\"];\n delete?: never;\n options?: never;\n head?: never;\n patch?: never;\n trace?: never;\n };\n \"/organizations/{organizationId}/libraries/{libraryId}\": {\n parameters: {\n query?: never;\n header?: never;\n path?: never;\n cookie?: never;\n };\n get?: never;\n put?: never;\n post?: never;\n delete?: never;\n options?: never;\n head?: never;\n /**\n * Rename a library.\n * @description Changes a library's display name. The libraryId is unchanged, so existing integrations and asset URLs keep working.\n */\n patch: operations[\"renameLibrary\"];\n trace?: never;\n };\n \"/organizations/{organizationId}/libraries/{libraryId}/assets\": {\n parameters: {\n query?: never;\n header?: never;\n path?: never;\n cookie?: never;\n };\n /**\n * List assets (paginated, optional tag filter).\n * @description Pages through one library's assets, newest first, optionally narrowed to assets carrying every tag in the `tags` filter. Each asset carries a permanent public CDN `url` you can embed directly. To find an asset by what it depicts rather than by tag, use searchAssets instead.\n */\n get: operations[\"listAssets\"];\n put?: never;\n /**\n * Upload up to 20 files.\n * @description Uploads image or video files as multipart/form-data under the `files` field, up to 20 per call. Each returned asset carries a permanent public CDN `url` — embed that rather than re-hosting the bytes. Processing (thumbnails, AI tags, descriptions) continues after the response, so re-fetch an asset to see it fully populated.\n */\n post: operations[\"uploadAssets\"];\n /**\n * Move up to 100 assets to trash (soft delete).\n * @description Soft delete — assets stay recoverable from trash for 30 days, then are permanently removed. Trashing an asset also trashes its variants; a variant id on its own is rejected with 400.\n */\n delete: operations[\"deleteAssets\"];\n options?: never;\n head?: never;\n patch?: never;\n trace?: never;\n };\n \"/organizations/{organizationId}/libraries/{libraryId}/assets/{assetId}\": {\n parameters: {\n query?: never;\n header?: never;\n path?: never;\n cookie?: never;\n };\n /**\n * Get one asset by id.\n * @description Returns the full asset, including its non-destructive variants in the `variants` field; each variant carries its own tags/description — fetch a variant by its id for those.\n */\n get: operations[\"getAsset\"];\n put?: never;\n post?: never;\n delete?: never;\n options?: never;\n head?: never;\n patch?: never;\n trace?: never;\n };\n \"/organizations/{organizationId}/libraries/{libraryId}/assets/{assetId}/description\": {\n parameters: {\n query?: never;\n header?: never;\n path?: never;\n cookie?: never;\n };\n get?: never;\n put?: never;\n post?: never;\n delete?: never;\n options?: never;\n head?: never;\n /**\n * Replace one asset's description.\n * @description Overwrites the asset's description with the supplied text — this is a replace, not an append. Descriptions are indexed, so a good one makes the asset findable through searchAssets.\n */\n patch: operations[\"updateAssetDescription\"];\n trace?: never;\n };\n \"/organizations/{organizationId}/libraries/{libraryId}/assets/{assetId}/promote\": {\n parameters: {\n query?: never;\n header?: never;\n path?: never;\n cookie?: never;\n };\n get?: never;\n put?: never;\n /**\n * Make a variant the asset's default.\n * @description The variant's pixels replace what the asset serves, at the SAME URL, so anything already embedding that URL shows the new file. The previous default is preserved as a new variant and keeps the name it had; the asset keeps its id and URL and takes the promoted variant's name. Its description and the tags generated from its image follow the picture — the promoted variant hands over any it carries, and the preserved variant keeps the ones that described it, while tags you added yourself stay on the asset. Rejected while the asset is a video that is still being processed. The promote is accepted and the swap completes a moment later; re-fetch the asset to see it reflected.\n */\n post: operations[\"promoteVariant\"];\n delete?: never;\n options?: never;\n head?: never;\n patch?: never;\n trace?: never;\n };\n \"/organizations/{organizationId}/libraries/{libraryId}/assets/{assetId}/approval\": {\n parameters: {\n query?: never;\n header?: never;\n path?: never;\n cookie?: never;\n };\n get?: never;\n put?: never;\n post?: never;\n delete?: never;\n options?: never;\n head?: never;\n /**\n * Set one asset's approval state.\n * @description Sets the asset's approval to one of the organization's approval states, with an optional note; each call replaces the previous approval. Works on an original or a variant. Needs an OAuth access token — an API key can read approval but not set it (403 APPROVAL_REQUIRES_USER). Setting approval needs a paid plan (403 PAID_PLAN_REQUIRED).\n */\n patch: operations[\"setAssetApproval\"];\n trace?: never;\n };\n \"/organizations/{organizationId}/libraries/{libraryId}/assets/tag\": {\n parameters: {\n query?: never;\n header?: never;\n path?: never;\n cookie?: never;\n };\n get?: never;\n put?: never;\n /**\n * Apply up to 20 tags to up to 100 assets.\n * @description Adds tags to assets in bulk. Tags already present are left alone, so the call is safe to repeat. Tags are how listAssets filters, and they feed searchAssets ranking.\n */\n post: operations[\"tagAssets\"];\n delete?: never;\n options?: never;\n head?: never;\n patch?: never;\n trace?: never;\n };\n \"/organizations/{organizationId}/libraries/{libraryId}/assets/untag\": {\n parameters: {\n query?: never;\n header?: never;\n path?: never;\n cookie?: never;\n };\n get?: never;\n put?: never;\n /**\n * Remove up to 20 tags from up to 100 assets.\n * @description Removes tags from assets in bulk. Tags that were not present are ignored, so the call is safe to repeat. Only the named tags are removed; every other tag on the asset stays.\n */\n post: operations[\"untagAssets\"];\n delete?: never;\n options?: never;\n head?: never;\n patch?: never;\n trace?: never;\n };\n \"/organizations/{organizationId}/libraries/{libraryId}/assets/transfer\": {\n parameters: {\n query?: never;\n header?: never;\n path?: never;\n cookie?: never;\n };\n get?: never;\n put?: never;\n /**\n * Move up to 100 assets to another library.\n * @description Moves assets from the library in the path to another library in the same organization. An asset's variants move with it; a variant cannot be transferred on its own — transfer its original and the variants follow. The credential needs write access to both libraries.\n */\n post: operations[\"transferAssets\"];\n delete?: never;\n options?: never;\n head?: never;\n patch?: never;\n trace?: never;\n };\n \"/organizations/{organizationId}/libraries/{libraryId}/tags\": {\n parameters: {\n query?: never;\n header?: never;\n path?: never;\n cookie?: never;\n };\n /**\n * List a library's tags by usage count.\n * @description The library's tag vocabulary, most-used first. Read it before tagging so you reuse the team's existing tags instead of inventing near-duplicates, and to discover what a `tags` filter on listAssets can select.\n */\n get: operations[\"listTags\"];\n put?: never;\n post?: never;\n delete?: never;\n options?: never;\n head?: never;\n patch?: never;\n trace?: never;\n };\n \"/organizations/{organizationId}/search/assets\": {\n parameters: {\n query?: never;\n header?: never;\n path?: never;\n cookie?: never;\n };\n /**\n * Search assets across libraries (ranked, highlighted).\n * @description Full-text, relevance-ranked search over filenames, descriptions, and tags across every library the credential can reach, or the subset named in `libraries`. This is the right call for a natural-language request such as \"the photo of the blue packaging\" — use listAssets when you already know the exact tag to filter on.\n */\n get: operations[\"searchAssets\"];\n put?: never;\n post?: never;\n delete?: never;\n options?: never;\n head?: never;\n patch?: never;\n trace?: never;\n };\n}\nexport type webhooks = Record<string, never>;\nexport interface components {\n schemas: {\n Error: {\n error: {\n /** @example API_KEY_NOT_AUTHORIZED_FOR_LIBRARY */\n code: string;\n message: string;\n };\n };\n /** @description What the credential can act on. An API key, or an OAuth token bound to one organization, resolves that organization; a token granted the whole account resolves every organization it belongs to under `organizations`. An OAuth token also carries `user`; an API key does not. */\n Me: {\n organizationId: string;\n organizationName: string | null;\n plan: string | null;\n libraries: {\n library: string;\n /** @enum {string} */\n access: \"read\" | \"write\";\n }[];\n /** @description The account an OAuth access token acts as. */\n user?: {\n name: string | null;\n };\n } | {\n organizations: {\n organizationId: string;\n organizationName: string | null;\n plan: string | null;\n libraries: {\n library: string;\n /** @enum {string} */\n access: \"read\" | \"write\";\n }[];\n }[];\n /** @description The account an OAuth access token acts as. */\n user: {\n name: string | null;\n };\n };\n /** @description Represents a digital asset with associated metadata. */\n Asset: {\n id: string;\n width: number | null;\n height: number | null;\n name: string | null;\n libraryId: string | null;\n blurhash: string | null;\n /**\n * Format: uri\n * @description Permanent, public CDN URL for the full-resolution asset. It needs no\n * authentication and never expires, so reference it directly as the source URL\n * rather than downloading and re-hosting the file.\n */\n url: string | null;\n /**\n * Format: uri\n * @deprecated\n */\n thumbUrlBlurred: string | null;\n /**\n * @description JSON-encoded map of size (the image's longest edge, in pixels) to the CDN\n * URL of the image at that size (parse before use). Each URL carries the same\n * permanent, public, embed-directly semantics as `url`; pick the size closest to\n * your display target.\n */\n sizes: string | null;\n /**\n * Format: uri\n * @description Absolute link that opens this asset in the Raster app, for a person to view,\n * edit, or manage it. Give it to the user as a link; the source URL is `url`.\n */\n appUrl: string | null;\n /**\n * @description Non-destructive variants of the asset. Each entry carries its own\n * `parentId` pointing back to this asset.\n */\n variants: (components[\"schemas\"][\"AssetVariant\"] | null)[] | null;\n /**\n * @description `null` on a top-level original, the parent asset's id on a variant. A variant\n * is a first-class Asset — tag-filtered and search queries return it as its own\n * entry — and `parentId` relates it back to its original.\n */\n parentId: string | null;\n description: string | null;\n tags: (string | null)[] | null;\n /**\n * @description MIME content type of the asset (e.g. `image/jpeg`, `image/png`).\n * @example image/jpeg\n */\n contentType: string | null;\n /** @description File size in bytes. May be `null` for older assets with no recorded size. */\n size: number | null;\n /**\n * @description Length of the asset in seconds, for video. `null` on images and on\n * videos still being processed.\n */\n duration: number | null;\n /**\n * @description Details read from the asset's own bytes — how it was captured, and its\n * technical format. `null` on assets uploaded before this data was captured.\n */\n metadata: components[\"schemas\"][\"AssetMetadata\"] | null;\n /** @description Creation time as unix milliseconds. May be `null` for some older assets. */\n created: number | null;\n /**\n * @description Last-modified time as unix milliseconds. Bumped on any field change\n * (rename, tag edit, variant-edit save). Useful for cache invalidation and\n * \"sort by recently updated\" UX.\n */\n updated: number | null;\n /**\n * @description Counter bumped each time the asset's BYTES are replaced at an unchanged\n * url — a promoted variant, an edit re-save. Append it to any `url` or\n * `sizes` entry as `?v=` to render the current pixels.\n *\n * Null on an asset whose bytes have never been replaced — render the bare\n * url there.\n */\n contentVersion: number | null;\n /** @description Attribution metadata for the user who uploaded the asset. */\n uploadedBy: components[\"schemas\"][\"Uploader\"] | null;\n approval: components[\"schemas\"][\"Approval\"] | null;\n };\n /** @description Represents a tag associated with assets or libraries. */\n Tag: {\n id: string;\n count: number | null;\n type: string | null;\n };\n /** @description Represents a collection of assets. */\n Library: {\n id: string;\n name: string | null;\n assetsCount: number | null;\n /** @deprecated */\n photosCount: number | null;\n trashCount: number | null;\n tags: (components[\"schemas\"][\"Tag\"] | null)[] | null;\n };\n /**\n * @description Wire shape returned by the `searchAssets` query — `hits` plus the\n * corpus-wide `found` total and the resolved 1-based `page`.\n */\n SearchResult: {\n hits: components[\"schemas\"][\"SearchHit\"][];\n found: number;\n page: number;\n };\n /** @description Represents a variant of an asset. */\n AssetVariant: {\n id: string;\n parentId: string | null;\n width: number | null;\n height: number | null;\n name: string | null;\n /** Format: uri */\n url: string | null;\n sizes: string | null;\n /** Format: uri */\n appUrl: string | null;\n };\n /** @description Details read from the asset's own bytes — how it was captured, and its technical format. */\n AssetMetadata: {\n /**\n * @description How the asset was captured, when it carried any such details. `null` when\n * it carried none, which is common for screen recordings and anything\n * re-encoded.\n */\n capture: components[\"schemas\"][\"AssetCapture\"] | null;\n /** @description The video's technical stream details. `null` on anything that is not a video. */\n video: components[\"schemas\"][\"VideoStream\"] | null;\n };\n /** @description Attribution metadata for the user or system that uploaded an asset. */\n Uploader: {\n /** @description Display name of the uploader. May be `null`. */\n name: string | null;\n /**\n * Format: uri\n * @description URL of the uploader's current avatar, read live from their account. May be `null`.\n */\n avatarUrl: string | null;\n };\n /** @description An asset's approval (delivery) state — who set it, when, and why. */\n Approval: {\n state: string;\n /** @description Email address of the person who set the approval. `null` when their account has no email address. */\n setBy: string | null;\n setAt: number | null;\n note?: string;\n };\n /**\n * @description A search hit — every public `Asset` field plus optional highlights and a\n * relevance score.\n */\n SearchHit: {\n id: string;\n width: number | null;\n height: number | null;\n name: string | null;\n libraryId: string | null;\n blurhash: string | null;\n /** Format: uri */\n url: string | null;\n /**\n * Format: uri\n * @deprecated\n */\n thumbUrlBlurred: string | null;\n sizes: string | null;\n /** Format: uri */\n appUrl: string | null;\n variants: (components[\"schemas\"][\"AssetVariant\"] | null)[] | null;\n parentId: string | null;\n description: string | null;\n tags: (string | null)[] | null;\n contentType: string | null;\n size: number | null;\n duration: number | null;\n metadata: components[\"schemas\"][\"AssetMetadata\"] | null;\n created: number | null;\n updated: number | null;\n contentVersion: number | null;\n uploadedBy: components[\"schemas\"][\"Uploader\"] | null;\n approval: components[\"schemas\"][\"Approval\"] | null;\n /**\n * @description Highlight snippets — present only when the search matched on the\n * corresponding field. Matched terms are wrapped in `<mark>` tags.\n */\n highlights?: components[\"schemas\"][\"SearchHitHighlights\"];\n /** @description Relevance score for the hit. Optional. */\n score?: number;\n };\n /** @description How an asset was captured — when, on what device, and where. */\n AssetCapture: {\n /**\n * @description When the asset says it was recorded, as written by the device. On a\n * re-encoded file this is the time of that encode rather than the original\n * recording, so treat it as a hint rather than a reliable capture date.\n */\n recordedAt: string | null;\n /** @description Device maker, e.g. `Apple`. */\n make: string | null;\n /** @description Device model, e.g. `iPhone 17 Pro`. */\n model: string | null;\n /** @description Capture software or OS version reported by the device. */\n software: string | null;\n /** @description Signed decimal degrees. Present only alongside `longitude`. */\n latitude: number | null;\n /** @description Signed decimal degrees. */\n longitude: number | null;\n /** @description Metres above sea level, when the container recorded one. */\n altitude: number | null;\n };\n /** @description A video's technical stream details — codec, frame rate, bitrate, and audio. */\n VideoStream: {\n /** @description Video codec, e.g. `h264`, `hevc`, `vp9`, `av1`. */\n codec: string;\n /** @description Frames per second, e.g. `30` or `23.98`. */\n frameRate: number | null;\n /** @description Overall bitrate in bits per second, across all streams. */\n bitrate: number | null;\n /** @description Whether the video carries an audio track. */\n hasAudio: boolean;\n /** @description Audio codec, e.g. `aac`, `opus`. `null` when there is no audio track. */\n audioCodec: string | null;\n /** @description Audio channel layout, e.g. `mono`, `stereo`, `5.1`. */\n audioLayout: string | null;\n /** @description Audio sample rate in hertz, e.g. `48000`. */\n audioSampleRate: number | null;\n };\n /** @description Highlight snippets returned alongside each `SearchHit`. */\n SearchHitHighlights: {\n name?: string;\n tags?: string[];\n };\n };\n responses: {\n /** @description Missing or invalid API key or access token. */\n Unauthorized: {\n headers: {\n [name: string]: unknown;\n };\n content: {\n \"application/json\": components[\"schemas\"][\"Error\"];\n };\n };\n /** @description The credential may not perform this operation, or the organization's plan does not allow it. A key without access to a library gets 404, not 403. */\n Forbidden: {\n headers: {\n [name: string]: unknown;\n };\n content: {\n \"application/json\": components[\"schemas\"][\"Error\"];\n };\n };\n /** @description Resource not found, or the credential is not authorized for it. */\n NotFound: {\n headers: {\n [name: string]: unknown;\n };\n content: {\n \"application/json\": components[\"schemas\"][\"Error\"];\n };\n };\n /** @description Invalid input (BAD_USER_INPUT), quota, or API-version error. */\n BadRequest: {\n headers: {\n [name: string]: unknown;\n };\n content: {\n \"application/json\": components[\"schemas\"][\"Error\"];\n };\n };\n /** @description Rate limit exceeded. Wait for the number of seconds in `Retry-After` before retrying. */\n TooManyRequests: {\n headers: {\n /** @description Seconds to wait before retrying. */\n \"Retry-After\"?: number;\n /** @description Remaining quota for the active policy, e.g. `\"key\";r=0;t=17`. The legacy `RateLimit-Limit` / `-Remaining` / `-Reset` triplet is sent alongside it. */\n RateLimit?: string;\n /** @description The quota policy in force, e.g. `\"key\";q=600;w=60`. */\n \"RateLimit-Policy\"?: string;\n [name: string]: unknown;\n };\n content: {\n \"application/json\": components[\"schemas\"][\"Error\"];\n };\n };\n /** @description Unexpected server fault (INTERNAL_SERVER_ERROR). Safe to retry an idempotent operation. */\n ServerError: {\n headers: {\n [name: string]: unknown;\n };\n content: {\n \"application/json\": components[\"schemas\"][\"Error\"];\n };\n };\n };\n parameters: {\n ApiVersion: string;\n OrganizationId: string;\n LibraryId: string;\n AssetId: string;\n };\n requestBodies: never;\n headers: never;\n pathItems: never;\n}\nexport type $defs = Record<string, never>;\nexport interface operations {\n getMe: {\n parameters: {\n query?: never;\n header: {\n \"Api-Version\": components[\"parameters\"][\"ApiVersion\"];\n };\n path?: never;\n cookie?: never;\n };\n requestBody?: never;\n responses: {\n /** @description OK */\n 200: {\n headers: {\n [name: string]: unknown;\n };\n content: {\n \"application/json\": {\n data: components[\"schemas\"][\"Me\"];\n };\n };\n };\n 400: components[\"responses\"][\"BadRequest\"];\n 401: components[\"responses\"][\"Unauthorized\"];\n 403: components[\"responses\"][\"Forbidden\"];\n 429: components[\"responses\"][\"TooManyRequests\"];\n 500: components[\"responses\"][\"ServerError\"];\n };\n };\n createEphemeralLibrary: {\n parameters: {\n query?: never;\n header: {\n \"Api-Version\": components[\"parameters\"][\"ApiVersion\"];\n };\n path?: never;\n cookie?: never;\n };\n requestBody: {\n content: {\n \"application/json\": {\n /** Format: email */\n email: string;\n name?: string;\n };\n };\n };\n responses: {\n /** @description OK */\n 200: {\n headers: {\n [name: string]: unknown;\n };\n content: {\n \"application/json\": {\n data: {\n organizationId: string;\n libraryId: string;\n apiKey: string;\n /** Format: uri */\n claimUrl: string;\n /** Format: date-time */\n expiresAt: string;\n emailSent: boolean;\n };\n };\n };\n };\n 400: components[\"responses\"][\"BadRequest\"];\n 500: components[\"responses\"][\"ServerError\"];\n };\n };\n listLibraries: {\n parameters: {\n query?: {\n /** @description Zero-based page index. */\n page?: number;\n pageSize?: number;\n };\n header: {\n \"Api-Version\": components[\"parameters\"][\"ApiVersion\"];\n };\n path: {\n organizationId: components[\"parameters\"][\"OrganizationId\"];\n };\n cookie?: never;\n };\n requestBody?: never;\n responses: {\n /** @description OK */\n 200: {\n headers: {\n [name: string]: unknown;\n };\n content: {\n \"application/json\": {\n data: components[\"schemas\"][\"Library\"][];\n };\n };\n };\n 400: components[\"responses\"][\"BadRequest\"];\n 401: components[\"responses\"][\"Unauthorized\"];\n 403: components[\"responses\"][\"Forbidden\"];\n 404: components[\"responses\"][\"NotFound\"];\n 429: components[\"responses\"][\"TooManyRequests\"];\n 500: components[\"responses\"][\"ServerError\"];\n };\n };\n createLibrary: {\n parameters: {\n query?: never;\n header: {\n \"Api-Version\": components[\"parameters\"][\"ApiVersion\"];\n };\n path: {\n organizationId: components[\"parameters\"][\"OrganizationId\"];\n };\n cookie?: never;\n };\n requestBody: {\n content: {\n \"application/json\": {\n name: string;\n slug?: string;\n };\n };\n };\n responses: {\n /** @description Created */\n 201: {\n headers: {\n [name: string]: unknown;\n };\n content: {\n \"application/json\": {\n data: components[\"schemas\"][\"Library\"];\n };\n };\n };\n 400: components[\"responses\"][\"BadRequest\"];\n 401: components[\"responses\"][\"Unauthorized\"];\n 403: components[\"responses\"][\"Forbidden\"];\n 404: components[\"responses\"][\"NotFound\"];\n 429: components[\"responses\"][\"TooManyRequests\"];\n 500: components[\"responses\"][\"ServerError\"];\n };\n };\n renameLibrary: {\n parameters: {\n query?: never;\n header: {\n \"Api-Version\": components[\"parameters\"][\"ApiVersion\"];\n };\n path: {\n organizationId: components[\"parameters\"][\"OrganizationId\"];\n libraryId: components[\"parameters\"][\"LibraryId\"];\n };\n cookie?: never;\n };\n requestBody: {\n content: {\n \"application/json\": {\n name: string;\n };\n };\n };\n responses: {\n /** @description OK */\n 200: {\n headers: {\n [name: string]: unknown;\n };\n content: {\n \"application/json\": {\n data: components[\"schemas\"][\"Library\"];\n };\n };\n };\n 400: components[\"responses\"][\"BadRequest\"];\n 401: components[\"responses\"][\"Unauthorized\"];\n 403: components[\"responses\"][\"Forbidden\"];\n 404: components[\"responses\"][\"NotFound\"];\n 429: components[\"responses\"][\"TooManyRequests\"];\n 500: components[\"responses\"][\"ServerError\"];\n };\n };\n listAssets: {\n parameters: {\n query?: {\n page?: number;\n pageSize?: number;\n /** @description Comma-separated tag filter, up to 50. */\n tags?: string;\n };\n header: {\n \"Api-Version\": components[\"parameters\"][\"ApiVersion\"];\n };\n path: {\n organizationId: components[\"parameters\"][\"OrganizationId\"];\n libraryId: components[\"parameters\"][\"LibraryId\"];\n };\n cookie?: never;\n };\n requestBody?: never;\n responses: {\n /** @description OK */\n 200: {\n headers: {\n [name: string]: unknown;\n };\n content: {\n \"application/json\": {\n data: components[\"schemas\"][\"Asset\"][];\n };\n };\n };\n 400: components[\"responses\"][\"BadRequest\"];\n 401: components[\"responses\"][\"Unauthorized\"];\n 403: components[\"responses\"][\"Forbidden\"];\n 404: components[\"responses\"][\"NotFound\"];\n 429: components[\"responses\"][\"TooManyRequests\"];\n 500: components[\"responses\"][\"ServerError\"];\n };\n };\n uploadAssets: {\n parameters: {\n query?: never;\n header: {\n \"Api-Version\": components[\"parameters\"][\"ApiVersion\"];\n };\n path: {\n organizationId: components[\"parameters\"][\"OrganizationId\"];\n libraryId: components[\"parameters\"][\"LibraryId\"];\n };\n cookie?: never;\n };\n requestBody: {\n content: {\n \"multipart/form-data\": {\n files: string[];\n /** @description Attach the uploaded file as a variant of this asset. Send exactly one file with it. The asset must be an original in this library, of the same media kind as the file. */\n parentId?: string;\n };\n };\n };\n responses: {\n /** @description OK */\n 200: {\n headers: {\n [name: string]: unknown;\n };\n content: {\n \"application/json\": {\n data: {\n responseText: string;\n assets: components[\"schemas\"][\"Asset\"][];\n };\n };\n };\n };\n 400: components[\"responses\"][\"BadRequest\"];\n 401: components[\"responses\"][\"Unauthorized\"];\n 403: components[\"responses\"][\"Forbidden\"];\n 404: components[\"responses\"][\"NotFound\"];\n 429: components[\"responses\"][\"TooManyRequests\"];\n 500: components[\"responses\"][\"ServerError\"];\n };\n };\n deleteAssets: {\n parameters: {\n query?: never;\n header: {\n \"Api-Version\": components[\"parameters\"][\"ApiVersion\"];\n };\n path: {\n organizationId: components[\"parameters\"][\"OrganizationId\"];\n libraryId: components[\"parameters\"][\"LibraryId\"];\n };\n cookie?: never;\n };\n requestBody: {\n content: {\n \"application/json\": {\n ids: string[];\n };\n };\n };\n responses: {\n /** @description OK */\n 200: {\n headers: {\n [name: string]: unknown;\n };\n content: {\n \"application/json\": {\n data: {\n success: boolean;\n message: string;\n ids: string[];\n };\n };\n };\n };\n 400: components[\"responses\"][\"BadRequest\"];\n 401: components[\"responses\"][\"Unauthorized\"];\n 403: components[\"responses\"][\"Forbidden\"];\n 404: components[\"responses\"][\"NotFound\"];\n 429: components[\"responses\"][\"TooManyRequests\"];\n 500: components[\"responses\"][\"ServerError\"];\n };\n };\n getAsset: {\n parameters: {\n query?: never;\n header: {\n \"Api-Version\": components[\"parameters\"][\"ApiVersion\"];\n };\n path: {\n organizationId: components[\"parameters\"][\"OrganizationId\"];\n libraryId: components[\"parameters\"][\"LibraryId\"];\n assetId: components[\"parameters\"][\"AssetId\"];\n };\n cookie?: never;\n };\n requestBody?: never;\n responses: {\n /** @description OK */\n 200: {\n headers: {\n [name: string]: unknown;\n };\n content: {\n \"application/json\": {\n data: components[\"schemas\"][\"Asset\"];\n };\n };\n };\n 400: components[\"responses\"][\"BadRequest\"];\n 401: components[\"responses\"][\"Unauthorized\"];\n 403: components[\"responses\"][\"Forbidden\"];\n 404: components[\"responses\"][\"NotFound\"];\n 429: components[\"responses\"][\"TooManyRequests\"];\n 500: components[\"responses\"][\"ServerError\"];\n };\n };\n updateAssetDescription: {\n parameters: {\n query?: never;\n header: {\n \"Api-Version\": components[\"parameters\"][\"ApiVersion\"];\n };\n path: {\n organizationId: components[\"parameters\"][\"OrganizationId\"];\n libraryId: components[\"parameters\"][\"LibraryId\"];\n assetId: components[\"parameters\"][\"AssetId\"];\n };\n cookie?: never;\n };\n requestBody: {\n content: {\n \"application/json\": {\n description: string;\n };\n };\n };\n responses: {\n /** @description OK */\n 200: {\n headers: {\n [name: string]: unknown;\n };\n content: {\n \"application/json\": {\n data: {\n assetId: string;\n description: string;\n };\n };\n };\n };\n 400: components[\"responses\"][\"BadRequest\"];\n 401: components[\"responses\"][\"Unauthorized\"];\n 403: components[\"responses\"][\"Forbidden\"];\n 404: components[\"responses\"][\"NotFound\"];\n 429: components[\"responses\"][\"TooManyRequests\"];\n 500: components[\"responses\"][\"ServerError\"];\n };\n };\n promoteVariant: {\n parameters: {\n query?: never;\n header: {\n \"Api-Version\": components[\"parameters\"][\"ApiVersion\"];\n };\n path: {\n organizationId: components[\"parameters\"][\"OrganizationId\"];\n libraryId: components[\"parameters\"][\"LibraryId\"];\n assetId: components[\"parameters\"][\"AssetId\"];\n };\n cookie?: never;\n };\n requestBody: {\n content: {\n \"application/json\": {\n variantId: string;\n };\n };\n };\n responses: {\n /** @description OK */\n 200: {\n headers: {\n [name: string]: unknown;\n };\n content: {\n \"application/json\": {\n data: {\n assetId: string;\n promotedVariantId: string;\n demotedVariantId: string;\n };\n };\n };\n };\n 400: components[\"responses\"][\"BadRequest\"];\n 401: components[\"responses\"][\"Unauthorized\"];\n 403: components[\"responses\"][\"Forbidden\"];\n 404: components[\"responses\"][\"NotFound\"];\n 429: components[\"responses\"][\"TooManyRequests\"];\n 500: components[\"responses\"][\"ServerError\"];\n };\n };\n setAssetApproval: {\n parameters: {\n query?: never;\n header: {\n \"Api-Version\": components[\"parameters\"][\"ApiVersion\"];\n };\n path: {\n organizationId: components[\"parameters\"][\"OrganizationId\"];\n libraryId: components[\"parameters\"][\"LibraryId\"];\n assetId: components[\"parameters\"][\"AssetId\"];\n };\n cookie?: never;\n };\n requestBody: {\n content: {\n \"application/json\": {\n state: string;\n note?: string | null;\n };\n };\n };\n responses: {\n /** @description OK */\n 200: {\n headers: {\n [name: string]: unknown;\n };\n content: {\n \"application/json\": {\n data: {\n assetId: string;\n approval: {\n state: string;\n /** @description Email address of the person who set the approval. `null` when their account has no email address. */\n setBy: string | null;\n setAt: number;\n note?: string;\n };\n };\n };\n };\n };\n 400: components[\"responses\"][\"BadRequest\"];\n 401: components[\"responses\"][\"Unauthorized\"];\n 403: components[\"responses\"][\"Forbidden\"];\n 404: components[\"responses\"][\"NotFound\"];\n 429: components[\"responses\"][\"TooManyRequests\"];\n 500: components[\"responses\"][\"ServerError\"];\n };\n };\n tagAssets: {\n parameters: {\n query?: never;\n header: {\n \"Api-Version\": components[\"parameters\"][\"ApiVersion\"];\n };\n path: {\n organizationId: components[\"parameters\"][\"OrganizationId\"];\n libraryId: components[\"parameters\"][\"LibraryId\"];\n };\n cookie?: never;\n };\n requestBody: {\n content: {\n \"application/json\": {\n assetIds: string[];\n tags: string[];\n };\n };\n };\n responses: {\n /** @description OK */\n 200: {\n headers: {\n [name: string]: unknown;\n };\n content: {\n \"application/json\": {\n data: {\n taggedCount: number;\n };\n };\n };\n };\n 400: components[\"responses\"][\"BadRequest\"];\n 401: components[\"responses\"][\"Unauthorized\"];\n 403: components[\"responses\"][\"Forbidden\"];\n 404: components[\"responses\"][\"NotFound\"];\n 429: components[\"responses\"][\"TooManyRequests\"];\n 500: components[\"responses\"][\"ServerError\"];\n };\n };\n untagAssets: {\n parameters: {\n query?: never;\n header: {\n \"Api-Version\": components[\"parameters\"][\"ApiVersion\"];\n };\n path: {\n organizationId: components[\"parameters\"][\"OrganizationId\"];\n libraryId: components[\"parameters\"][\"LibraryId\"];\n };\n cookie?: never;\n };\n requestBody: {\n content: {\n \"application/json\": {\n assetIds: string[];\n tags: string[];\n };\n };\n };\n responses: {\n /** @description OK */\n 200: {\n headers: {\n [name: string]: unknown;\n };\n content: {\n \"application/json\": {\n data: {\n untaggedCount: number;\n };\n };\n };\n };\n 400: components[\"responses\"][\"BadRequest\"];\n 401: components[\"responses\"][\"Unauthorized\"];\n 403: components[\"responses\"][\"Forbidden\"];\n 404: components[\"responses\"][\"NotFound\"];\n 429: components[\"responses\"][\"TooManyRequests\"];\n 500: components[\"responses\"][\"ServerError\"];\n };\n };\n transferAssets: {\n parameters: {\n query?: never;\n header: {\n \"Api-Version\": components[\"parameters\"][\"ApiVersion\"];\n };\n path: {\n organizationId: components[\"parameters\"][\"OrganizationId\"];\n libraryId: components[\"parameters\"][\"LibraryId\"];\n };\n cookie?: never;\n };\n requestBody: {\n content: {\n \"application/json\": {\n targetLibraryId: string;\n assetIds: string[];\n };\n };\n };\n responses: {\n /** @description OK */\n 200: {\n headers: {\n [name: string]: unknown;\n };\n content: {\n \"application/json\": {\n data: {\n transferredCount: number;\n sourceLibraryId: string;\n targetLibraryId: string;\n };\n };\n };\n };\n 400: components[\"responses\"][\"BadRequest\"];\n 401: components[\"responses\"][\"Unauthorized\"];\n 403: components[\"responses\"][\"Forbidden\"];\n 404: components[\"responses\"][\"NotFound\"];\n 429: components[\"responses\"][\"TooManyRequests\"];\n 500: components[\"responses\"][\"ServerError\"];\n };\n };\n listTags: {\n parameters: {\n query?: {\n limit?: number;\n };\n header: {\n \"Api-Version\": components[\"parameters\"][\"ApiVersion\"];\n };\n path: {\n organizationId: components[\"parameters\"][\"OrganizationId\"];\n libraryId: components[\"parameters\"][\"LibraryId\"];\n };\n cookie?: never;\n };\n requestBody?: never;\n responses: {\n /** @description OK */\n 200: {\n headers: {\n [name: string]: unknown;\n };\n content: {\n \"application/json\": {\n data: components[\"schemas\"][\"Tag\"][];\n };\n };\n };\n 400: components[\"responses\"][\"BadRequest\"];\n 401: components[\"responses\"][\"Unauthorized\"];\n 403: components[\"responses\"][\"Forbidden\"];\n 404: components[\"responses\"][\"NotFound\"];\n 429: components[\"responses\"][\"TooManyRequests\"];\n 500: components[\"responses\"][\"ServerError\"];\n };\n };\n searchAssets: {\n parameters: {\n query: {\n q: string;\n /** @description Comma-separated library ids. */\n libraries?: string;\n page?: number;\n pageSize?: number;\n /** @description Only assets in this approval state; `none` matches assets with no approval. */\n approval?: \"in_review\" | \"approved\" | \"needs_changes\" | \"none\";\n };\n header: {\n \"Api-Version\": components[\"parameters\"][\"ApiVersion\"];\n };\n path: {\n organizationId: components[\"parameters\"][\"OrganizationId\"];\n };\n cookie?: never;\n };\n requestBody?: never;\n responses: {\n /** @description OK */\n 200: {\n headers: {\n [name: string]: unknown;\n };\n content: {\n \"application/json\": {\n data: components[\"schemas\"][\"SearchResult\"];\n };\n };\n };\n 400: components[\"responses\"][\"BadRequest\"];\n 401: components[\"responses\"][\"Unauthorized\"];\n 403: components[\"responses\"][\"Forbidden\"];\n 404: components[\"responses\"][\"NotFound\"];\n 429: components[\"responses\"][\"TooManyRequests\"];\n 500: components[\"responses\"][\"ServerError\"];\n };\n };\n}\n"]}
@@ -16,13 +16,13 @@ export interface CredentialStore {
16
16
  export interface RasterHost {
17
17
  /**
18
18
  * The OAuth `client_name`, which Raster's consent page reads as `Connect {name}` and
19
- * Connected apps lists. It names the app being connected — "Figma", "Sanity" — since the
20
- * user reaches that page from Raster.
19
+ * Connected apps lists. It names the app being connected, since the user reaches that page
20
+ * from Raster.
21
21
  */
22
22
  name: string;
23
23
  /**
24
24
  * Open a URL in the user's browser, as the device flow does for its consent page: through
25
- * `figma.openExternal` in a Figma plugin's sandbox, or `window.open` in a CMS plugin.
25
+ * `window.open` in a page, or the host's own API in a sandbox without one.
26
26
  *
27
27
  * Typed `void`, which any return type satisfies, so `(url) => window.open(url)` and an
28
28
  * async function both fit. The SDK never awaits it.
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/adapters/index.ts"],"names":[],"mappings":"","sourcesContent":["import type { StoredCredentials } from '../types/session.js'\n\n/**\n * Where a host keeps the Raster credential. Async, because a Figma plugin reaches\n * `figma.clientStorage` only through a message to its sandbox.\n *\n * The credential holds bearer tokens in plain text, so it is as private as the store:\n * `localStorage` exposes it to every script on the origin.\n */\nexport interface CredentialStore {\n\t/** Null when nothing has been saved yet. */\n\tload(): Promise<StoredCredentials | null>\n\tsave(credentials: StoredCredentials): Promise<void>\n\tclear(): Promise<void>\n}\n\n/** The host application the SDK is running inside. */\nexport interface RasterHost {\n\t/**\n\t * The OAuth `client_name`, which Raster's consent page reads as `Connect {name}` and\n\t * Connected apps lists. It names the app being connected — \"Figma\", \"Sanity\" — since the\n\t * user reaches that page from Raster.\n\t */\n\tname: string\n\t/**\n\t * Open a URL in the user's browser, as the device flow does for its consent page: through\n\t * `figma.openExternal` in a Figma plugin's sandbox, or `window.open` in a CMS plugin.\n\t *\n\t * Typed `void`, which any return type satisfies, so `(url) => window.open(url)` and an\n\t * async function both fit. The SDK never awaits it.\n\t */\n\topenExternal(url: string): void\n}\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/adapters/index.ts"],"names":[],"mappings":"","sourcesContent":["import type { StoredCredentials } from '../types/session.js'\n\n/**\n * Where a host keeps the Raster credential. Async, because a Figma plugin reaches\n * `figma.clientStorage` only through a message to its sandbox.\n *\n * The credential holds bearer tokens in plain text, so it is as private as the store:\n * `localStorage` exposes it to every script on the origin.\n */\nexport interface CredentialStore {\n\t/** Null when nothing has been saved yet. */\n\tload(): Promise<StoredCredentials | null>\n\tsave(credentials: StoredCredentials): Promise<void>\n\tclear(): Promise<void>\n}\n\n/** The host application the SDK is running inside. */\nexport interface RasterHost {\n\t/**\n\t * The OAuth `client_name`, which Raster's consent page reads as `Connect {name}` and\n\t * Connected apps lists. It names the app being connected, since the user reaches that page\n\t * from Raster.\n\t */\n\tname: string\n\t/**\n\t * Open a URL in the user's browser, as the device flow does for its consent page: through\n\t * `window.open` in a page, or the host's own API in a sandbox without one.\n\t *\n\t * Typed `void`, which any return type satisfies, so `(url) => window.open(url)` and an\n\t * async function both fit. The SDK never awaits it.\n\t */\n\topenExternal(url: string): void\n}\n"]}
@@ -3,8 +3,8 @@ import { type StoredCredentials } from '../types/session.js';
3
3
  import type { AuthContext } from './http.js';
4
4
  /**
5
5
  * The credential a client holds, and everything that keeps it valid. Each
6
- * `createRasterClient` has its own, so clients on one page, such as one per Sanity Studio
7
- * workspace, never share a token.
6
+ * `createRasterClient` has its own, so clients on one page, such as one per workspace, never
7
+ * share a token.
8
8
  */
9
9
  export interface SessionStore {
10
10
  /** The credentials in memory. `undefined` until `hydrate()` has run. */
@@ -1 +1 @@
1
- {"version":3,"file":"session.js","sourceRoot":"","sources":["../../src/auth/session.ts"],"names":[],"mappings":"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","sourcesContent":["import type { CredentialStore } from '../adapters/index.js'\nimport { RasterApiError } from '../api/errors.js'\nimport {\n\ttype OAuthSession,\n\tSIGNED_OUT,\n\ttype StoredCredentials,\n\temptyCredentials,\n} from '../types/session.js'\nimport type { AuthContext } from './http.js'\nimport { refreshSession, revokeSession } from './tokens.js'\n\n/** Refresh this far ahead of `expiresAt`, so a token is never presented right at its edge. */\nconst EXPIRY_MARGIN_MS = 60 * 1000\n\n/** One name for every client on the origin: refreshes are rare, so queueing costs little. */\nconst REFRESH_LOCK = 'raster-sdk:refresh'\n\n/**\n * Run a refresh under a lock every tab on the origin shares, so two tabs never rotate one\n * refresh token at once. Without the Web Locks API, or where it refuses the request (an\n * opaque origin such as Figma's plugin iframe), it runs unlocked: re-reading the store\n * first then narrows that race rather than closing it.\n */\nasync function withRefreshLock<T>(refresh: () => Promise<T>): Promise<T> {\n\tconst locks = globalThis.navigator?.locks\n\tif (!locks) return refresh()\n\tlet granted = false\n\ttry {\n\t\treturn await locks.request(REFRESH_LOCK, () => {\n\t\t\tgranted = true\n\t\t\treturn refresh()\n\t\t})\n\t} catch (caught) {\n\t\tif (granted) throw caught\n\t\treturn refresh()\n\t}\n}\n\n/**\n * A 64-bit FNV-1a digest of an API key: the same for the same key, different for another in\n * practice, and too short to recover the key from.\n */\nfunction keyDigest(apiKey: string): string {\n\tlet hash = 0xcbf29ce484222325n\n\tfor (const byte of new TextEncoder().encode(apiKey)) {\n\t\thash = ((hash ^ BigInt(byte)) * 0x100000001b3n) & 0xffffffffffffffffn\n\t}\n\treturn hash.toString(36)\n}\n\n/**\n * The credential a client holds, and everything that keeps it valid. Each\n * `createRasterClient` has its own, so clients on one page, such as one per Sanity Studio\n * workspace, never share a token.\n */\nexport interface SessionStore {\n\t/** The credentials in memory. `undefined` until `hydrate()` has run. */\n\tcurrent(): StoredCredentials | undefined\n\t/** Load from the `CredentialStore`. Idempotent; safe to await more than once. */\n\thydrate(): Promise<StoredCredentials>\n\t/** Persist, then replace in memory. A failed write changes nothing and rejects with its error. */\n\tsave(credentials: StoredCredentials): Promise<void>\n\t/** Called on every change — a sign-in, a token refresh, a sign-out. */\n\tsubscribe(listener: (credentials: StoredCredentials) => void): () => void\n\tisSignedIn(): boolean\n\t/** Whether there is any credential at all (a session or an API key). */\n\tisConfigured(): boolean\n\tcredentialIdentity(): string | null\n\tgetAccessToken(options?: { rejected?: string }): Promise<string>\n\tsignOut(): Promise<void>\n}\n\nexport function createSessionStore(options: {\n\tcredentials: CredentialStore\n\tauth: AuthContext\n}): SessionStore {\n\tconst { credentials: store, auth } = options\n\n\tlet current: StoredCredentials | undefined\n\t/** What `save` is writing, which memory takes only once the store has it. */\n\tlet writing: StoredCredentials | undefined\n\tlet hydrating: Promise<StoredCredentials> | null = null\n\tlet refreshing: Promise<string> | null = null\n\tconst listeners = new Set<(credentials: StoredCredentials) => void>()\n\n\tfunction emit(next: StoredCredentials): void {\n\t\tcurrent = next\n\t\tfor (const listener of listeners) listener(next)\n\t}\n\n\t/**\n\t * Write first, then change memory and tell listeners. A failed write leaves memory as it\n\t * was and rejects with the store's own error, so a sign-in or sign-out the store never took\n\t * is not in use either.\n\t */\n\tasync function save(next: StoredCredentials): Promise<void> {\n\t\twriting = next\n\t\ttry {\n\t\t\tawait store.save(next)\n\t\t} finally {\n\t\t\tif (writing === next) writing = undefined\n\t\t}\n\t\temit(next)\n\t}\n\n\t/**\n\t * Read the store, filling in the fields a credential saved before they existed lacks:\n\t * a missing `oauth` is not `null`, so it would read as signed in.\n\t */\n\tasync function load(): Promise<StoredCredentials | null> {\n\t\tconst stored = await store.load()\n\t\treturn stored === null ? null : { ...emptyCredentials(), ...stored }\n\t}\n\n\t/** Signed-out credentials that keep this install's registration (see `clientId`). */\n\tfunction signedOut(): StoredCredentials {\n\t\treturn { ...SIGNED_OUT, clientId: current?.clientId ?? null }\n\t}\n\n\t/** Whether the stored session, or the one being written, is still the one a refresh started from. */\n\tfunction isSameSession(session: OAuthSession): boolean {\n\t\treturn (writing ?? current)?.oauth?.refreshToken === session.refreshToken\n\t}\n\n\t/**\n\t * Single-flight: a provider and a first request can both call this on mount, and two\n\t * concurrent loads would race to `emit` — the later one winning even if it read staler\n\t * data.\n\t */\n\tfunction hydrate(): Promise<StoredCredentials> {\n\t\thydrating ??= load()\n\t\t\t.then((loaded) => {\n\t\t\t\tconst next = loaded ?? emptyCredentials()\n\t\t\t\temit(next)\n\t\t\t\treturn next\n\t\t\t})\n\t\t\t.catch((caught) => {\n\t\t\t\t// Clear the cached promise so a transient read failure can be retried rather\n\t\t\t\t// than poisoning every later call.\n\t\t\t\thydrating = null\n\t\t\t\tthrow caught\n\t\t\t})\n\t\treturn hydrating\n\t}\n\n\tasync function refresh(session: OAuthSession): Promise<OAuthSession> {\n\t\tlet next: OAuthSession\n\t\ttry {\n\t\t\tnext = await refreshSession(auth, session)\n\t\t} catch (caught) {\n\t\t\t// The connection is gone — revoked under Connected apps, membership lost, or the\n\t\t\t// token was spent already. Only signing in again fixes that, so drop the dead\n\t\t\t// session now rather than fail every request with it.\n\t\t\tif (caught instanceof RasterApiError && caught.code === 'invalid_grant') {\n\t\t\t\tif (isSameSession(session)) await save(signedOut())\n\t\t\t\tthrow new RasterApiError(\n\t\t\t\t\t'Your Raster session has ended. Sign in again.',\n\t\t\t\t\t'SESSION_EXPIRED',\n\t\t\t\t\t401\n\t\t\t\t)\n\t\t\t}\n\t\t\tthrow caught\n\t\t}\n\n\t\t// The old refresh token died the moment this one was minted, so memory takes the new\n\t\t// one first and keeps it even if the write fails; the token is handed out only once\n\t\t// written. Skipped if the session changed under us (signed out, or signed in again)\n\t\t// while the refresh was in flight — that newer session must not be clobbered.\n\t\tif (current !== undefined && isSameSession(session)) {\n\t\t\tconst rotated = { ...current, oauth: next }\n\t\t\temit(rotated)\n\t\t\tawait store.save(rotated)\n\t\t}\n\t\treturn next\n\t}\n\n\t/**\n\t * The bearer the credentials can send as they stand, or the session to refresh first:\n\t * its token is about to expire, or is the `rejected` one a request just got a 401 with.\n\t */\n\tfunction tokenOrStale(\n\t\tcredentials: StoredCredentials | undefined,\n\t\trejected: string | undefined\n\t): string | OAuthSession {\n\t\tif (credentials === undefined || credentials.oauth === null) {\n\t\t\tconst apiKey = credentials?.apiKey\n\t\t\tif (!apiKey) throw new RasterApiError('Sign in to Raster first.', 'NOT_SIGNED_IN', 401)\n\t\t\treturn apiKey\n\t\t}\n\n\t\tconst { oauth } = credentials\n\t\tconst fresh = Date.now() < oauth.expiresAt - EXPIRY_MARGIN_MS\n\t\t// A rejected token that has already been replaced here: another request refreshed\n\t\t// first, so this one can simply use the new token.\n\t\treturn fresh && oauth.accessToken !== rejected ? oauth.accessToken : oauth\n\t}\n\n\t/**\n\t * Refresh unless another tab sharing the store got there first. Its copy wins — it may\n\t * have rotated the token, signed out, or signed in as somebody else — unless this tab\n\t * changed its own credentials while waiting. A store that reads back null is unreadable\n\t * (blocked storage), not signed out.\n\t */\n\tasync function refreshLatest(\n\t\tfrom: StoredCredentials | undefined,\n\t\trejected: string | undefined\n\t): Promise<string> {\n\t\tconst stored = await load()\n\t\tif (\n\t\t\tstored !== null &&\n\t\t\tcurrent === from &&\n\t\t\tstored.oauth?.refreshToken !== from?.oauth?.refreshToken\n\t\t) {\n\t\t\temit(stored)\n\t\t}\n\t\tconst found = tokenOrStale(current, rejected)\n\t\treturn typeof found === 'string' ? found : (await refresh(found)).accessToken\n\t}\n\n\treturn {\n\t\tcurrent() {\n\t\t\treturn current\n\t\t},\n\n\t\thydrate,\n\n\t\tsave,\n\n\t\tsubscribe(listener) {\n\t\t\tlisteners.add(listener)\n\t\t\treturn () => {\n\t\t\t\tlisteners.delete(listener)\n\t\t\t}\n\t\t},\n\n\t\tisSignedIn() {\n\t\t\treturn current !== undefined && current.oauth !== null\n\t\t},\n\n\t\tisConfigured() {\n\t\t\treturn (\n\t\t\t\tcurrent !== undefined &&\n\t\t\t\t(current.oauth !== null || (typeof current.apiKey === 'string' && current.apiKey !== ''))\n\t\t\t)\n\t\t},\n\n\t\t/**\n\t\t * Identifies WHOSE credential is in use, so a caller can tell a token refresh apart\n\t\t * from a different account signing in. Stable across refreshes and reloads, and new\n\t\t * with every sign-in, even one through the same client (see `OAuthSession.signInId`).\n\t\t * A session saved before sign-ins had an id falls back to its client. An API key is\n\t\t * named by its digest, so the key itself never lands in a cache key or a log.\n\t\t */\n\t\tcredentialIdentity() {\n\t\t\tif (current === undefined) return null\n\t\t\tconst { oauth, apiKey } = current\n\t\t\tif (oauth !== null) return `oauth:${oauth.signInId ?? oauth.clientId}`\n\t\t\treturn apiKey === null ? null : `key:${keyDigest(apiKey)}`\n\t\t},\n\n\t\t/**\n\t\t * The bearer to send: the API key, or the session's access token, refreshed first when\n\t\t * it is about to expire or is the `rejected` one. Refreshes are single-flight, because\n\t\t * the server treats a second use of a rotated refresh token as theft and revokes the\n\t\t * whole connection; tabs sharing a store share one too (`refreshLatest`).\n\t\t */\n\t\tasync getAccessToken(tokenOptions = {}) {\n\t\t\tif (current === undefined) await hydrate()\n\n\t\t\tconst found = tokenOrStale(current, tokenOptions.rejected)\n\t\t\tif (typeof found === 'string') return found\n\n\t\t\tconst from = current\n\t\t\trefreshing ??= withRefreshLock(() => refreshLatest(from, tokenOptions.rejected)).finally(\n\t\t\t\t() => {\n\t\t\t\t\trefreshing = null\n\t\t\t\t}\n\t\t\t)\n\t\t\treturn refreshing\n\t\t},\n\n\t\t/**\n\t\t * Sign out: clear the session, and revoke it in Raster (best-effort — it is gone\n\t\t * locally either way, and a failed revoke must not leave the user stuck signed in).\n\t\t */\n\t\tasync signOut() {\n\t\t\tconst oauth = current?.oauth ?? null\n\t\t\tawait save(signedOut())\n\t\t\tif (oauth !== null) await revokeSession(auth, oauth).catch(() => {})\n\t\t},\n\t}\n}\n"]}
1
+ {"version":3,"file":"session.js","sourceRoot":"","sources":["../../src/auth/session.ts"],"names":[],"mappings":"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","sourcesContent":["import type { CredentialStore } from '../adapters/index.js'\nimport { RasterApiError } from '../api/errors.js'\nimport {\n\ttype OAuthSession,\n\tSIGNED_OUT,\n\ttype StoredCredentials,\n\temptyCredentials,\n} from '../types/session.js'\nimport type { AuthContext } from './http.js'\nimport { refreshSession, revokeSession } from './tokens.js'\n\n/** Refresh this far ahead of `expiresAt`, so a token is never presented right at its edge. */\nconst EXPIRY_MARGIN_MS = 60 * 1000\n\n/** One name for every client on the origin: refreshes are rare, so queueing costs little. */\nconst REFRESH_LOCK = 'raster-sdk:refresh'\n\n/**\n * Run a refresh under a lock every tab on the origin shares, so two tabs never rotate one\n * refresh token at once. Without the Web Locks API, or where it refuses the request (an\n * opaque origin such as Figma's plugin iframe), it runs unlocked: re-reading the store\n * first then narrows that race rather than closing it.\n */\nasync function withRefreshLock<T>(refresh: () => Promise<T>): Promise<T> {\n\tconst locks = globalThis.navigator?.locks\n\tif (!locks) return refresh()\n\tlet granted = false\n\ttry {\n\t\treturn await locks.request(REFRESH_LOCK, () => {\n\t\t\tgranted = true\n\t\t\treturn refresh()\n\t\t})\n\t} catch (caught) {\n\t\tif (granted) throw caught\n\t\treturn refresh()\n\t}\n}\n\n/**\n * A 64-bit FNV-1a digest of an API key: the same for the same key, different for another in\n * practice, and too short to recover the key from.\n */\nfunction keyDigest(apiKey: string): string {\n\tlet hash = 0xcbf29ce484222325n\n\tfor (const byte of new TextEncoder().encode(apiKey)) {\n\t\thash = ((hash ^ BigInt(byte)) * 0x100000001b3n) & 0xffffffffffffffffn\n\t}\n\treturn hash.toString(36)\n}\n\n/**\n * The credential a client holds, and everything that keeps it valid. Each\n * `createRasterClient` has its own, so clients on one page, such as one per workspace, never\n * share a token.\n */\nexport interface SessionStore {\n\t/** The credentials in memory. `undefined` until `hydrate()` has run. */\n\tcurrent(): StoredCredentials | undefined\n\t/** Load from the `CredentialStore`. Idempotent; safe to await more than once. */\n\thydrate(): Promise<StoredCredentials>\n\t/** Persist, then replace in memory. A failed write changes nothing and rejects with its error. */\n\tsave(credentials: StoredCredentials): Promise<void>\n\t/** Called on every change — a sign-in, a token refresh, a sign-out. */\n\tsubscribe(listener: (credentials: StoredCredentials) => void): () => void\n\tisSignedIn(): boolean\n\t/** Whether there is any credential at all (a session or an API key). */\n\tisConfigured(): boolean\n\tcredentialIdentity(): string | null\n\tgetAccessToken(options?: { rejected?: string }): Promise<string>\n\tsignOut(): Promise<void>\n}\n\nexport function createSessionStore(options: {\n\tcredentials: CredentialStore\n\tauth: AuthContext\n}): SessionStore {\n\tconst { credentials: store, auth } = options\n\n\tlet current: StoredCredentials | undefined\n\t/** What `save` is writing, which memory takes only once the store has it. */\n\tlet writing: StoredCredentials | undefined\n\tlet hydrating: Promise<StoredCredentials> | null = null\n\tlet refreshing: Promise<string> | null = null\n\tconst listeners = new Set<(credentials: StoredCredentials) => void>()\n\n\tfunction emit(next: StoredCredentials): void {\n\t\tcurrent = next\n\t\tfor (const listener of listeners) listener(next)\n\t}\n\n\t/**\n\t * Write first, then change memory and tell listeners. A failed write leaves memory as it\n\t * was and rejects with the store's own error, so a sign-in or sign-out the store never took\n\t * is not in use either.\n\t */\n\tasync function save(next: StoredCredentials): Promise<void> {\n\t\twriting = next\n\t\ttry {\n\t\t\tawait store.save(next)\n\t\t} finally {\n\t\t\tif (writing === next) writing = undefined\n\t\t}\n\t\temit(next)\n\t}\n\n\t/**\n\t * Read the store, filling in the fields a credential saved before they existed lacks:\n\t * a missing `oauth` is not `null`, so it would read as signed in.\n\t */\n\tasync function load(): Promise<StoredCredentials | null> {\n\t\tconst stored = await store.load()\n\t\treturn stored === null ? null : { ...emptyCredentials(), ...stored }\n\t}\n\n\t/** Signed-out credentials that keep this install's registration (see `clientId`). */\n\tfunction signedOut(): StoredCredentials {\n\t\treturn { ...SIGNED_OUT, clientId: current?.clientId ?? null }\n\t}\n\n\t/** Whether the stored session, or the one being written, is still the one a refresh started from. */\n\tfunction isSameSession(session: OAuthSession): boolean {\n\t\treturn (writing ?? current)?.oauth?.refreshToken === session.refreshToken\n\t}\n\n\t/**\n\t * Single-flight: a provider and a first request can both call this on mount, and two\n\t * concurrent loads would race to `emit` — the later one winning even if it read staler\n\t * data.\n\t */\n\tfunction hydrate(): Promise<StoredCredentials> {\n\t\thydrating ??= load()\n\t\t\t.then((loaded) => {\n\t\t\t\tconst next = loaded ?? emptyCredentials()\n\t\t\t\temit(next)\n\t\t\t\treturn next\n\t\t\t})\n\t\t\t.catch((caught) => {\n\t\t\t\t// Clear the cached promise so a transient read failure can be retried rather\n\t\t\t\t// than poisoning every later call.\n\t\t\t\thydrating = null\n\t\t\t\tthrow caught\n\t\t\t})\n\t\treturn hydrating\n\t}\n\n\tasync function refresh(session: OAuthSession): Promise<OAuthSession> {\n\t\tlet next: OAuthSession\n\t\ttry {\n\t\t\tnext = await refreshSession(auth, session)\n\t\t} catch (caught) {\n\t\t\t// The connection is gone — revoked under Connected apps, membership lost, or the\n\t\t\t// token was spent already. Only signing in again fixes that, so drop the dead\n\t\t\t// session now rather than fail every request with it.\n\t\t\tif (caught instanceof RasterApiError && caught.code === 'invalid_grant') {\n\t\t\t\tif (isSameSession(session)) await save(signedOut())\n\t\t\t\tthrow new RasterApiError(\n\t\t\t\t\t'Your Raster session has ended. Sign in again.',\n\t\t\t\t\t'SESSION_EXPIRED',\n\t\t\t\t\t401\n\t\t\t\t)\n\t\t\t}\n\t\t\tthrow caught\n\t\t}\n\n\t\t// The old refresh token died the moment this one was minted, so memory takes the new\n\t\t// one first and keeps it even if the write fails; the token is handed out only once\n\t\t// written. Skipped if the session changed under us (signed out, or signed in again)\n\t\t// while the refresh was in flight — that newer session must not be clobbered.\n\t\tif (current !== undefined && isSameSession(session)) {\n\t\t\tconst rotated = { ...current, oauth: next }\n\t\t\temit(rotated)\n\t\t\tawait store.save(rotated)\n\t\t}\n\t\treturn next\n\t}\n\n\t/**\n\t * The bearer the credentials can send as they stand, or the session to refresh first:\n\t * its token is about to expire, or is the `rejected` one a request just got a 401 with.\n\t */\n\tfunction tokenOrStale(\n\t\tcredentials: StoredCredentials | undefined,\n\t\trejected: string | undefined\n\t): string | OAuthSession {\n\t\tif (credentials === undefined || credentials.oauth === null) {\n\t\t\tconst apiKey = credentials?.apiKey\n\t\t\tif (!apiKey) throw new RasterApiError('Sign in to Raster first.', 'NOT_SIGNED_IN', 401)\n\t\t\treturn apiKey\n\t\t}\n\n\t\tconst { oauth } = credentials\n\t\tconst fresh = Date.now() < oauth.expiresAt - EXPIRY_MARGIN_MS\n\t\t// A rejected token that has already been replaced here: another request refreshed\n\t\t// first, so this one can simply use the new token.\n\t\treturn fresh && oauth.accessToken !== rejected ? oauth.accessToken : oauth\n\t}\n\n\t/**\n\t * Refresh unless another tab sharing the store got there first. Its copy wins — it may\n\t * have rotated the token, signed out, or signed in as somebody else — unless this tab\n\t * changed its own credentials while waiting. A store that reads back null is unreadable\n\t * (blocked storage), not signed out.\n\t */\n\tasync function refreshLatest(\n\t\tfrom: StoredCredentials | undefined,\n\t\trejected: string | undefined\n\t): Promise<string> {\n\t\tconst stored = await load()\n\t\tif (\n\t\t\tstored !== null &&\n\t\t\tcurrent === from &&\n\t\t\tstored.oauth?.refreshToken !== from?.oauth?.refreshToken\n\t\t) {\n\t\t\temit(stored)\n\t\t}\n\t\tconst found = tokenOrStale(current, rejected)\n\t\treturn typeof found === 'string' ? found : (await refresh(found)).accessToken\n\t}\n\n\treturn {\n\t\tcurrent() {\n\t\t\treturn current\n\t\t},\n\n\t\thydrate,\n\n\t\tsave,\n\n\t\tsubscribe(listener) {\n\t\t\tlisteners.add(listener)\n\t\t\treturn () => {\n\t\t\t\tlisteners.delete(listener)\n\t\t\t}\n\t\t},\n\n\t\tisSignedIn() {\n\t\t\treturn current !== undefined && current.oauth !== null\n\t\t},\n\n\t\tisConfigured() {\n\t\t\treturn (\n\t\t\t\tcurrent !== undefined &&\n\t\t\t\t(current.oauth !== null || (typeof current.apiKey === 'string' && current.apiKey !== ''))\n\t\t\t)\n\t\t},\n\n\t\t/**\n\t\t * Identifies WHOSE credential is in use, so a caller can tell a token refresh apart\n\t\t * from a different account signing in. Stable across refreshes and reloads, and new\n\t\t * with every sign-in, even one through the same client (see `OAuthSession.signInId`).\n\t\t * A session saved before sign-ins had an id falls back to its client. An API key is\n\t\t * named by its digest, so the key itself never lands in a cache key or a log.\n\t\t */\n\t\tcredentialIdentity() {\n\t\t\tif (current === undefined) return null\n\t\t\tconst { oauth, apiKey } = current\n\t\t\tif (oauth !== null) return `oauth:${oauth.signInId ?? oauth.clientId}`\n\t\t\treturn apiKey === null ? null : `key:${keyDigest(apiKey)}`\n\t\t},\n\n\t\t/**\n\t\t * The bearer to send: the API key, or the session's access token, refreshed first when\n\t\t * it is about to expire or is the `rejected` one. Refreshes are single-flight, because\n\t\t * the server treats a second use of a rotated refresh token as theft and revokes the\n\t\t * whole connection; tabs sharing a store share one too (`refreshLatest`).\n\t\t */\n\t\tasync getAccessToken(tokenOptions = {}) {\n\t\t\tif (current === undefined) await hydrate()\n\n\t\t\tconst found = tokenOrStale(current, tokenOptions.rejected)\n\t\t\tif (typeof found === 'string') return found\n\n\t\t\tconst from = current\n\t\t\trefreshing ??= withRefreshLock(() => refreshLatest(from, tokenOptions.rejected)).finally(\n\t\t\t\t() => {\n\t\t\t\t\trefreshing = null\n\t\t\t\t}\n\t\t\t)\n\t\t\treturn refreshing\n\t\t},\n\n\t\t/**\n\t\t * Sign out: clear the session, and revoke it in Raster (best-effort — it is gone\n\t\t * locally either way, and a failed revoke must not leave the user stuck signed in).\n\t\t */\n\t\tasync signOut() {\n\t\t\tconst oauth = current?.oauth ?? null\n\t\t\tawait save(signedOut())\n\t\t\tif (oauth !== null) await revokeSession(auth, oauth).catch(() => {})\n\t\t},\n\t}\n}\n"]}
@@ -23,7 +23,8 @@ export function blurhashToDataUrl(hash) {
23
23
  const canvas = document.createElement('canvas');
24
24
  canvas.width = PLACEHOLDER_EDGE;
25
25
  canvas.height = PLACEHOLDER_EDGE;
26
- const context = canvas.getContext('2d');
26
+ // On the CPU: a GPU canvas makes `toDataURL` wait on a readback, ~55ms a call while a grid paints.
27
+ const context = canvas.getContext('2d', { willReadFrequently: true });
27
28
  if (context) {
28
29
  const imageData = context.createImageData(PLACEHOLDER_EDGE, PLACEHOLDER_EDGE);
29
30
  imageData.data.set(pixels);
@@ -1 +1 @@
1
- {"version":3,"file":"blurhash-placeholder.js","sourceRoot":"","sources":["../../src/images/blurhash-placeholder.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA;AAE9C,yFAAyF;AACzF,MAAM,gBAAgB,GAAG,EAAE,CAAA;AAC3B,qFAAqF;AACrF,MAAM,WAAW,GAAG,GAAG,CAAA;AACvB,6FAA6F;AAC7F,MAAM,KAAK,GAAG,IAAI,GAAG,EAAyB,CAAA;AAE9C;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAAC,IAA+B;IAChE,IAAI,CAAC,IAAI;QAAE,OAAO,IAAI,CAAA;IAEtB,IAAI,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC;QAAE,OAAO,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,IAAI,CAAA;IAEnD,qFAAqF;IACrF,IAAI,OAAO,QAAQ,KAAK,WAAW;QAAE,OAAO,IAAI,CAAA;IAEhD,IAAI,OAAO,GAAkB,IAAI,CAAA;IACjC,IAAI,CAAC;QACJ,MAAM,MAAM,GAAG,cAAc,CAAC,IAAI,EAAE,gBAAgB,EAAE,gBAAgB,CAAC,CAAA;QACvE,MAAM,MAAM,GAAG,QAAQ,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAA;QAC/C,MAAM,CAAC,KAAK,GAAG,gBAAgB,CAAA;QAC/B,MAAM,CAAC,MAAM,GAAG,gBAAgB,CAAA;QAChC,MAAM,OAAO,GAAG,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,CAAA;QACvC,IAAI,OAAO,EAAE,CAAC;YACb,MAAM,SAAS,GAAG,OAAO,CAAC,eAAe,CAAC,gBAAgB,EAAE,gBAAgB,CAAC,CAAA;YAC7E,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;YAC1B,OAAO,CAAC,YAAY,CAAC,SAAS,EAAE,CAAC,EAAE,CAAC,CAAC,CAAA;YACrC,OAAO,GAAG,MAAM,CAAC,SAAS,EAAE,CAAA;QAC7B,CAAC;IACF,CAAC;IAAC,MAAM,CAAC;QACR,gCAAgC;QAChC,OAAO,GAAG,IAAI,CAAA;IACf,CAAC;IAED,yEAAyE;IACzE,IAAI,KAAK,CAAC,IAAI,IAAI,WAAW,EAAE,CAAC;QAC/B,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,CAAA;QACxC,IAAI,MAAM,KAAK,SAAS;YAAE,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;IAC/C,CAAC;IACD,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,OAAO,CAAC,CAAA;IACxB,OAAO,OAAO,CAAA;AACf,CAAC","sourcesContent":["import { decodeBlurhash } from './blurhash.js'\n\n/** The browser upscales a smooth gradient with no visible blocks, so 32×32 is plenty. */\nconst PLACEHOLDER_EDGE = 32\n/** Grid cells re-render on scroll, so decodes are cached, up to this many hashes. */\nconst CACHE_LIMIT = 400\n/** A hash that fails to decode is cached as null, so it is not decoded again every frame. */\nconst cache = new Map<string, string | null>()\n\n/**\n * A BlurHash as a tiny PNG `data:` URL, to paint behind a thumbnail while it loads. Null for a\n * missing or malformed hash, or without a `document` to draw it with.\n */\nexport function blurhashToDataUrl(hash: string | null | undefined): string | null {\n\tif (!hash) return null\n\n\tif (cache.has(hash)) return cache.get(hash) ?? null\n\n\t// Not cached: once a server-rendered page hydrates, the first client render retries.\n\tif (typeof document === 'undefined') return null\n\n\tlet dataUrl: string | null = null\n\ttry {\n\t\tconst pixels = decodeBlurhash(hash, PLACEHOLDER_EDGE, PLACEHOLDER_EDGE)\n\t\tconst canvas = document.createElement('canvas')\n\t\tcanvas.width = PLACEHOLDER_EDGE\n\t\tcanvas.height = PLACEHOLDER_EDGE\n\t\tconst context = canvas.getContext('2d')\n\t\tif (context) {\n\t\t\tconst imageData = context.createImageData(PLACEHOLDER_EDGE, PLACEHOLDER_EDGE)\n\t\t\timageData.data.set(pixels)\n\t\t\tcontext.putImageData(imageData, 0, 0)\n\t\t\tdataUrl = canvas.toDataURL()\n\t\t}\n\t} catch {\n\t\t// A render path must not throw.\n\t\tdataUrl = null\n\t}\n\n\t// FIFO-evict the oldest entry when full (Map preserves insertion order).\n\tif (cache.size >= CACHE_LIMIT) {\n\t\tconst oldest = cache.keys().next().value\n\t\tif (oldest !== undefined) cache.delete(oldest)\n\t}\n\tcache.set(hash, dataUrl)\n\treturn dataUrl\n}\n"]}
1
+ {"version":3,"file":"blurhash-placeholder.js","sourceRoot":"","sources":["../../src/images/blurhash-placeholder.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA;AAE9C,yFAAyF;AACzF,MAAM,gBAAgB,GAAG,EAAE,CAAA;AAC3B,qFAAqF;AACrF,MAAM,WAAW,GAAG,GAAG,CAAA;AACvB,6FAA6F;AAC7F,MAAM,KAAK,GAAG,IAAI,GAAG,EAAyB,CAAA;AAE9C;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAAC,IAA+B;IAChE,IAAI,CAAC,IAAI;QAAE,OAAO,IAAI,CAAA;IAEtB,IAAI,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC;QAAE,OAAO,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,IAAI,CAAA;IAEnD,qFAAqF;IACrF,IAAI,OAAO,QAAQ,KAAK,WAAW;QAAE,OAAO,IAAI,CAAA;IAEhD,IAAI,OAAO,GAAkB,IAAI,CAAA;IACjC,IAAI,CAAC;QACJ,MAAM,MAAM,GAAG,cAAc,CAAC,IAAI,EAAE,gBAAgB,EAAE,gBAAgB,CAAC,CAAA;QACvE,MAAM,MAAM,GAAG,QAAQ,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAA;QAC/C,MAAM,CAAC,KAAK,GAAG,gBAAgB,CAAA;QAC/B,MAAM,CAAC,MAAM,GAAG,gBAAgB,CAAA;QAChC,mGAAmG;QACnG,MAAM,OAAO,GAAG,MAAM,CAAC,UAAU,CAAC,IAAI,EAAE,EAAE,kBAAkB,EAAE,IAAI,EAAE,CAAC,CAAA;QACrE,IAAI,OAAO,EAAE,CAAC;YACb,MAAM,SAAS,GAAG,OAAO,CAAC,eAAe,CAAC,gBAAgB,EAAE,gBAAgB,CAAC,CAAA;YAC7E,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;YAC1B,OAAO,CAAC,YAAY,CAAC,SAAS,EAAE,CAAC,EAAE,CAAC,CAAC,CAAA;YACrC,OAAO,GAAG,MAAM,CAAC,SAAS,EAAE,CAAA;QAC7B,CAAC;IACF,CAAC;IAAC,MAAM,CAAC;QACR,gCAAgC;QAChC,OAAO,GAAG,IAAI,CAAA;IACf,CAAC;IAED,yEAAyE;IACzE,IAAI,KAAK,CAAC,IAAI,IAAI,WAAW,EAAE,CAAC;QAC/B,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,CAAA;QACxC,IAAI,MAAM,KAAK,SAAS;YAAE,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;IAC/C,CAAC;IACD,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,OAAO,CAAC,CAAA;IACxB,OAAO,OAAO,CAAA;AACf,CAAC","sourcesContent":["import { decodeBlurhash } from './blurhash.js'\n\n/** The browser upscales a smooth gradient with no visible blocks, so 32×32 is plenty. */\nconst PLACEHOLDER_EDGE = 32\n/** Grid cells re-render on scroll, so decodes are cached, up to this many hashes. */\nconst CACHE_LIMIT = 400\n/** A hash that fails to decode is cached as null, so it is not decoded again every frame. */\nconst cache = new Map<string, string | null>()\n\n/**\n * A BlurHash as a tiny PNG `data:` URL, to paint behind a thumbnail while it loads. Null for a\n * missing or malformed hash, or without a `document` to draw it with.\n */\nexport function blurhashToDataUrl(hash: string | null | undefined): string | null {\n\tif (!hash) return null\n\n\tif (cache.has(hash)) return cache.get(hash) ?? null\n\n\t// Not cached: once a server-rendered page hydrates, the first client render retries.\n\tif (typeof document === 'undefined') return null\n\n\tlet dataUrl: string | null = null\n\ttry {\n\t\tconst pixels = decodeBlurhash(hash, PLACEHOLDER_EDGE, PLACEHOLDER_EDGE)\n\t\tconst canvas = document.createElement('canvas')\n\t\tcanvas.width = PLACEHOLDER_EDGE\n\t\tcanvas.height = PLACEHOLDER_EDGE\n\t\t// On the CPU: a GPU canvas makes `toDataURL` wait on a readback, ~55ms a call while a grid paints.\n\t\tconst context = canvas.getContext('2d', { willReadFrequently: true })\n\t\tif (context) {\n\t\t\tconst imageData = context.createImageData(PLACEHOLDER_EDGE, PLACEHOLDER_EDGE)\n\t\t\timageData.data.set(pixels)\n\t\t\tcontext.putImageData(imageData, 0, 0)\n\t\t\tdataUrl = canvas.toDataURL()\n\t\t}\n\t} catch {\n\t\t// A render path must not throw.\n\t\tdataUrl = null\n\t}\n\n\t// FIFO-evict the oldest entry when full (Map preserves insertion order).\n\tif (cache.size >= CACHE_LIMIT) {\n\t\tconst oldest = cache.keys().next().value\n\t\tif (oldest !== undefined) cache.delete(oldest)\n\t}\n\tcache.set(hash, dataUrl)\n\treturn dataUrl\n}\n"]}
package/package.json CHANGED
@@ -1,20 +1,13 @@
1
1
  {
2
2
  "name": "@raster-app/sdk",
3
- "version": "0.1.0-rc.0",
3
+ "version": "0.1.0",
4
4
  "description": "Framework-free client for the Raster REST API: OAuth, sessions, assets, libraries, search and image helpers.",
5
- "license": "Apache-2.0",
6
- "repository": {
7
- "type": "git",
8
- "url": "git+https://github.com/raster-app/raster-sdk.git",
9
- "directory": "packages/sdk"
10
- },
11
- "homepage": "https://github.com/raster-app/raster-sdk/tree/main/packages/sdk#readme",
5
+ "license": "MIT",
6
+ "homepage": "https://raster.app/docs/api/sdk",
12
7
  "keywords": [
13
8
  "raster",
14
9
  "dam",
15
10
  "assets",
16
- "figma",
17
- "sanity",
18
11
  "plugin"
19
12
  ],
20
13
  "type": "module",
@@ -31,10 +24,11 @@
31
24
  "./package.json": "./package.json"
32
25
  },
33
26
  "engines": {
34
- "node": ">=24"
27
+ "node": ">=22.12"
35
28
  },
36
29
  "scripts": {
37
30
  "build": "rm -rf dist && tsc -p tsconfig.build.json",
31
+ "prepack": "bun run build",
38
32
  "dev": "tsc -p tsconfig.build.json --watch",
39
33
  "check-types": "tsc --noEmit",
40
34
  "lint": "biome lint --error-on-warnings .",
@@ -43,4 +37,4 @@
43
37
  "publishConfig": {
44
38
  "access": "public"
45
39
  }
46
- }
40
+ }