@raster-app/sdk 0.1.0-canary-2e1834e
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.
- package/LICENSE +21 -0
- package/README.md +83 -0
- package/dist/__generated__/openapi.d.ts +1096 -0
- package/dist/__generated__/openapi.js +2 -0
- package/dist/__generated__/openapi.js.map +1 -0
- package/dist/adapters/index.d.ts +31 -0
- package/dist/adapters/index.js +2 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/adapters/local-storage.d.ts +3 -0
- package/dist/adapters/local-storage.js +49 -0
- package/dist/adapters/local-storage.js.map +1 -0
- package/dist/adapters/memory.d.ts +7 -0
- package/dist/adapters/memory.js +19 -0
- package/dist/adapters/memory.js.map +1 -0
- package/dist/api/assets.d.ts +41 -0
- package/dist/api/assets.js +70 -0
- package/dist/api/assets.js.map +1 -0
- package/dist/api/errors.d.ts +22 -0
- package/dist/api/errors.js +80 -0
- package/dist/api/errors.js.map +1 -0
- package/dist/api/libraries.d.ts +16 -0
- package/dist/api/libraries.js +22 -0
- package/dist/api/libraries.js.map +1 -0
- package/dist/api/me.d.ts +40 -0
- package/dist/api/me.js +39 -0
- package/dist/api/me.js.map +1 -0
- package/dist/api/promote.d.ts +36 -0
- package/dist/api/promote.js +29 -0
- package/dist/api/promote.js.map +1 -0
- package/dist/api/search.d.ts +25 -0
- package/dist/api/search.js +21 -0
- package/dist/api/search.js.map +1 -0
- package/dist/api/transport.d.ts +43 -0
- package/dist/api/transport.js +90 -0
- package/dist/api/transport.js.map +1 -0
- package/dist/api/upload.d.ts +40 -0
- package/dist/api/upload.js +60 -0
- package/dist/api/upload.js.map +1 -0
- package/dist/auth/device.d.ts +32 -0
- package/dist/auth/device.js +70 -0
- package/dist/auth/device.js.map +1 -0
- package/dist/auth/http.d.ts +25 -0
- package/dist/auth/http.js +70 -0
- package/dist/auth/http.js.map +1 -0
- package/dist/auth/register.d.ts +14 -0
- package/dist/auth/register.js +21 -0
- package/dist/auth/register.js.map +1 -0
- package/dist/auth/session.d.ts +30 -0
- package/dist/auth/session.js +231 -0
- package/dist/auth/session.js.map +1 -0
- package/dist/auth/tokens.d.ts +10 -0
- package/dist/auth/tokens.js +20 -0
- package/dist/auth/tokens.js.map +1 -0
- package/dist/client.d.ts +138 -0
- package/dist/client.js +122 -0
- package/dist/client.js.map +1 -0
- package/dist/constants.d.ts +18 -0
- package/dist/constants.js +24 -0
- package/dist/constants.js.map +1 -0
- package/dist/format.d.ts +13 -0
- package/dist/format.js +83 -0
- package/dist/format.js.map +1 -0
- package/dist/images/blurhash-placeholder.d.ts +5 -0
- package/dist/images/blurhash-placeholder.js +47 -0
- package/dist/images/blurhash-placeholder.js.map +1 -0
- package/dist/images/blurhash.d.ts +2 -0
- package/dist/images/blurhash.js +77 -0
- package/dist/images/blurhash.js.map +1 -0
- package/dist/images/image-src.d.ts +42 -0
- package/dist/images/image-src.js +83 -0
- package/dist/images/image-src.js.map +1 -0
- package/dist/images/sizes.d.ts +15 -0
- package/dist/images/sizes.js +41 -0
- package/dist/images/sizes.js.map +1 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.js +17 -0
- package/dist/index.js.map +1 -0
- package/dist/types/asset.d.ts +50 -0
- package/dist/types/asset.js +23 -0
- package/dist/types/asset.js.map +1 -0
- package/dist/types/library.d.ts +21 -0
- package/dist/types/library.js +2 -0
- package/dist/types/library.js.map +1 -0
- package/dist/types/session.d.ts +59 -0
- package/dist/types/session.js +21 -0
- package/dist/types/session.js.map +1 -0
- package/package.json +42 -0
|
@@ -0,0 +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"]}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { StoredCredentials } from '../types/session.js';
|
|
2
|
+
/**
|
|
3
|
+
* Where a host keeps the Raster credential. Async, because a Figma plugin reaches
|
|
4
|
+
* `figma.clientStorage` only through a message to its sandbox.
|
|
5
|
+
*
|
|
6
|
+
* The credential holds bearer tokens in plain text, so it is as private as the store:
|
|
7
|
+
* `localStorage` exposes it to every script on the origin.
|
|
8
|
+
*/
|
|
9
|
+
export interface CredentialStore {
|
|
10
|
+
/** Null when nothing has been saved yet. */
|
|
11
|
+
load(): Promise<StoredCredentials | null>;
|
|
12
|
+
save(credentials: StoredCredentials): Promise<void>;
|
|
13
|
+
clear(): Promise<void>;
|
|
14
|
+
}
|
|
15
|
+
/** The host application the SDK is running inside. */
|
|
16
|
+
export interface RasterHost {
|
|
17
|
+
/**
|
|
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.
|
|
21
|
+
*/
|
|
22
|
+
name: string;
|
|
23
|
+
/**
|
|
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.
|
|
26
|
+
*
|
|
27
|
+
* Typed `void`, which any return type satisfies, so `(url) => window.open(url)` and an
|
|
28
|
+
* async function both fit. The SDK never awaits it.
|
|
29
|
+
*/
|
|
30
|
+
openExternal(url: string): void;
|
|
31
|
+
}
|
|
@@ -0,0 +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"]}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
const CREDENTIALS_KEY = 'raster.credentials';
|
|
2
|
+
/**
|
|
3
|
+
* Read a key, as nothing stored whenever `localStorage` fails: it is absent outside a browser,
|
|
4
|
+
* throws in a Safari private window or wherever site data is blocked, and a value an older
|
|
5
|
+
* version wrote may no longer parse.
|
|
6
|
+
*/
|
|
7
|
+
function readJson(key) {
|
|
8
|
+
try {
|
|
9
|
+
const raw = globalThis.localStorage?.getItem(key);
|
|
10
|
+
if (raw == null)
|
|
11
|
+
return null;
|
|
12
|
+
return JSON.parse(raw);
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
function writeJson(key, value) {
|
|
19
|
+
try {
|
|
20
|
+
globalThis.localStorage?.setItem(key, JSON.stringify(value));
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
// Quota exceeded, or storage blocked. The session stays live in memory, so a sign-in
|
|
24
|
+
// that otherwise succeeded must not fail here.
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function removeKey(key) {
|
|
28
|
+
try {
|
|
29
|
+
globalThis.localStorage?.removeItem(key);
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
// See `writeJson`.
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
/** Credentials in `localStorage`, where every script on the origin can read them. */
|
|
36
|
+
export function localStorageCredentialStore(key = CREDENTIALS_KEY) {
|
|
37
|
+
return {
|
|
38
|
+
async load() {
|
|
39
|
+
return readJson(key);
|
|
40
|
+
},
|
|
41
|
+
async save(credentials) {
|
|
42
|
+
writeJson(key, credentials);
|
|
43
|
+
},
|
|
44
|
+
async clear() {
|
|
45
|
+
removeKey(key);
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=local-storage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"local-storage.js","sourceRoot":"","sources":["../../src/adapters/local-storage.ts"],"names":[],"mappings":"AAGA,MAAM,eAAe,GAAG,oBAAoB,CAAA;AAE5C;;;;GAIG;AACH,SAAS,QAAQ,CAAI,GAAW;IAC/B,IAAI,CAAC;QACJ,MAAM,GAAG,GAAG,UAAU,CAAC,YAAY,EAAE,OAAO,CAAC,GAAG,CAAC,CAAA;QACjD,IAAI,GAAG,IAAI,IAAI;YAAE,OAAO,IAAI,CAAA;QAC5B,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;IACvB,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,IAAI,CAAA;IACZ,CAAC;AACF,CAAC;AAED,SAAS,SAAS,CAAC,GAAW,EAAE,KAAc;IAC7C,IAAI,CAAC;QACJ,UAAU,CAAC,YAAY,EAAE,OAAO,CAAC,GAAG,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAA;IAC7D,CAAC;IAAC,MAAM,CAAC;QACR,qFAAqF;QACrF,+CAA+C;IAChD,CAAC;AACF,CAAC;AAED,SAAS,SAAS,CAAC,GAAW;IAC7B,IAAI,CAAC;QACJ,UAAU,CAAC,YAAY,EAAE,UAAU,CAAC,GAAG,CAAC,CAAA;IACzC,CAAC;IAAC,MAAM,CAAC;QACR,mBAAmB;IACpB,CAAC;AACF,CAAC;AAED,qFAAqF;AACrF,MAAM,UAAU,2BAA2B,CAAC,MAAc,eAAe;IACxE,OAAO;QACN,KAAK,CAAC,IAAI;YACT,OAAO,QAAQ,CAAoB,GAAG,CAAC,CAAA;QACxC,CAAC;QACD,KAAK,CAAC,IAAI,CAAC,WAAW;YACrB,SAAS,CAAC,GAAG,EAAE,WAAW,CAAC,CAAA;QAC5B,CAAC;QACD,KAAK,CAAC,KAAK;YACV,SAAS,CAAC,GAAG,CAAC,CAAA;QACf,CAAC;KACD,CAAA;AACF,CAAC","sourcesContent":["import type { StoredCredentials } from '../types/session.js'\nimport type { CredentialStore } from './index.js'\n\nconst CREDENTIALS_KEY = 'raster.credentials'\n\n/**\n * Read a key, as nothing stored whenever `localStorage` fails: it is absent outside a browser,\n * throws in a Safari private window or wherever site data is blocked, and a value an older\n * version wrote may no longer parse.\n */\nfunction readJson<T>(key: string): T | null {\n\ttry {\n\t\tconst raw = globalThis.localStorage?.getItem(key)\n\t\tif (raw == null) return null\n\t\treturn JSON.parse(raw)\n\t} catch {\n\t\treturn null\n\t}\n}\n\nfunction writeJson(key: string, value: unknown): void {\n\ttry {\n\t\tglobalThis.localStorage?.setItem(key, JSON.stringify(value))\n\t} catch {\n\t\t// Quota exceeded, or storage blocked. The session stays live in memory, so a sign-in\n\t\t// that otherwise succeeded must not fail here.\n\t}\n}\n\nfunction removeKey(key: string): void {\n\ttry {\n\t\tglobalThis.localStorage?.removeItem(key)\n\t} catch {\n\t\t// See `writeJson`.\n\t}\n}\n\n/** Credentials in `localStorage`, where every script on the origin can read them. */\nexport function localStorageCredentialStore(key: string = CREDENTIALS_KEY): CredentialStore {\n\treturn {\n\t\tasync load() {\n\t\t\treturn readJson<StoredCredentials>(key)\n\t\t},\n\t\tasync save(credentials) {\n\t\t\twriteJson(key, credentials)\n\t\t},\n\t\tasync clear() {\n\t\t\tremoveKey(key)\n\t\t},\n\t}\n}\n"]}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { StoredCredentials } from '../types/session.js';
|
|
2
|
+
import type { CredentialStore } from './index.js';
|
|
3
|
+
/**
|
|
4
|
+
* Credentials held in memory only: the user signs in again each session, and no bearer token
|
|
5
|
+
* is written anywhere persistent.
|
|
6
|
+
*/
|
|
7
|
+
export declare function memoryCredentialStore(initial?: StoredCredentials | null): CredentialStore;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Credentials held in memory only: the user signs in again each session, and no bearer token
|
|
3
|
+
* is written anywhere persistent.
|
|
4
|
+
*/
|
|
5
|
+
export function memoryCredentialStore(initial) {
|
|
6
|
+
let current = initial ?? null;
|
|
7
|
+
return {
|
|
8
|
+
async load() {
|
|
9
|
+
return current;
|
|
10
|
+
},
|
|
11
|
+
async save(credentials) {
|
|
12
|
+
current = credentials;
|
|
13
|
+
},
|
|
14
|
+
async clear() {
|
|
15
|
+
current = null;
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=memory.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory.js","sourceRoot":"","sources":["../../src/adapters/memory.ts"],"names":[],"mappings":"AAGA;;;GAGG;AACH,MAAM,UAAU,qBAAqB,CAAC,OAAkC;IACvE,IAAI,OAAO,GAA6B,OAAO,IAAI,IAAI,CAAA;IACvD,OAAO;QACN,KAAK,CAAC,IAAI;YACT,OAAO,OAAO,CAAA;QACf,CAAC;QACD,KAAK,CAAC,IAAI,CAAC,WAAW;YACrB,OAAO,GAAG,WAAW,CAAA;QACtB,CAAC;QACD,KAAK,CAAC,KAAK;YACV,OAAO,GAAG,IAAI,CAAA;QACf,CAAC;KACD,CAAA;AACF,CAAC","sourcesContent":["import type { StoredCredentials } from '../types/session.js'\nimport type { CredentialStore } from './index.js'\n\n/**\n * Credentials held in memory only: the user signs in again each session, and no bearer token\n * is written anywhere persistent.\n */\nexport function memoryCredentialStore(initial?: StoredCredentials | null): CredentialStore {\n\tlet current: StoredCredentials | null = initial ?? null\n\treturn {\n\t\tasync load() {\n\t\t\treturn current\n\t\t},\n\t\tasync save(credentials) {\n\t\t\tcurrent = credentials\n\t\t},\n\t\tasync clear() {\n\t\t\tcurrent = null\n\t\t},\n\t}\n}\n"]}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { Asset } from '../types/asset.js';
|
|
2
|
+
import type { Transport } from './transport.js';
|
|
3
|
+
/** Assets per page. Tuned for a grid: enough to fill it, small enough to feel instant. */
|
|
4
|
+
export declare const ASSETS_PAGE_SIZE = 50;
|
|
5
|
+
/**
|
|
6
|
+
* One page of a library's assets. Pages are 1-based.
|
|
7
|
+
*
|
|
8
|
+
* The endpoint returns a bare array with no total or cursor, so "is there more?" is inferred
|
|
9
|
+
* by the caller from a full page coming back — see `hasMorePages`.
|
|
10
|
+
*/
|
|
11
|
+
export declare function listAssets(transport: Transport, options: {
|
|
12
|
+
organizationId: string;
|
|
13
|
+
libraryId: string;
|
|
14
|
+
page?: number;
|
|
15
|
+
pageSize?: number;
|
|
16
|
+
signal?: AbortSignal;
|
|
17
|
+
}): Promise<Array<Asset>>;
|
|
18
|
+
/**
|
|
19
|
+
* Whether another page is worth requesting, given the length of the page just returned. With
|
|
20
|
+
* no total in the response, a total that is an exact multiple of `pageSize` costs one more
|
|
21
|
+
* request, which comes back empty.
|
|
22
|
+
*/
|
|
23
|
+
export declare function hasMorePages(pageLength: number, pageSize?: number): boolean;
|
|
24
|
+
/** One asset, by id. Always carries the asset's `variants` (the list route may not). */
|
|
25
|
+
export declare function getAsset(transport: Transport, options: {
|
|
26
|
+
organizationId: string;
|
|
27
|
+
libraryId: string;
|
|
28
|
+
assetId: string;
|
|
29
|
+
signal?: AbortSignal;
|
|
30
|
+
}): Promise<Asset>;
|
|
31
|
+
/**
|
|
32
|
+
* Read an asset on `POLL_SCHEDULE_MS` until `done` accepts it. Resolves with that read, or
|
|
33
|
+
* with null once the schedule runs out. A failed read waits for the next one, since an asset
|
|
34
|
+
* still processing can read as a 404; only the signal ends the wait early.
|
|
35
|
+
*/
|
|
36
|
+
export declare function pollAsset(transport: Transport, options: {
|
|
37
|
+
organizationId: string;
|
|
38
|
+
libraryId: string;
|
|
39
|
+
assetId: string;
|
|
40
|
+
signal?: AbortSignal;
|
|
41
|
+
}, done: (asset: Asset) => boolean): Promise<Asset | null>;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { toFetchError } from './errors.js';
|
|
2
|
+
/** Assets per page. Tuned for a grid: enough to fill it, small enough to feel instant. */
|
|
3
|
+
export const ASSETS_PAGE_SIZE = 50;
|
|
4
|
+
/**
|
|
5
|
+
* One page of a library's assets. Pages are 1-based.
|
|
6
|
+
*
|
|
7
|
+
* The endpoint returns a bare array with no total or cursor, so "is there more?" is inferred
|
|
8
|
+
* by the caller from a full page coming back — see `hasMorePages`.
|
|
9
|
+
*/
|
|
10
|
+
export async function listAssets(transport, options) {
|
|
11
|
+
const { organizationId, libraryId, page = 1, pageSize = ASSETS_PAGE_SIZE, signal } = options;
|
|
12
|
+
return transport.request(`/organizations/${encodeURIComponent(organizationId)}/libraries/${encodeURIComponent(libraryId)}/assets`, { searchParams: { page, pageSize }, signal });
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Whether another page is worth requesting, given the length of the page just returned. With
|
|
16
|
+
* no total in the response, a total that is an exact multiple of `pageSize` costs one more
|
|
17
|
+
* request, which comes back empty.
|
|
18
|
+
*/
|
|
19
|
+
export function hasMorePages(pageLength, pageSize = ASSETS_PAGE_SIZE) {
|
|
20
|
+
return pageLength === pageSize;
|
|
21
|
+
}
|
|
22
|
+
/** One asset, by id. Always carries the asset's `variants` (the list route may not). */
|
|
23
|
+
export async function getAsset(transport, options) {
|
|
24
|
+
const { organizationId, libraryId, assetId, signal } = options;
|
|
25
|
+
return transport.request(`/organizations/${encodeURIComponent(organizationId)}/libraries/${encodeURIComponent(libraryId)}/assets/${encodeURIComponent(assetId)}`, { signal });
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* The delays between reads while Raster finishes work on an asset in the background:
|
|
29
|
+
* front-loaded, since most of it lands early, and bounded, so work that never lands stops
|
|
30
|
+
* costing requests.
|
|
31
|
+
*/
|
|
32
|
+
const POLL_SCHEDULE_MS = [1000, 1500, 2000, 3000, 4000, 5000, 5000, 5000];
|
|
33
|
+
/**
|
|
34
|
+
* Read an asset on `POLL_SCHEDULE_MS` until `done` accepts it. Resolves with that read, or
|
|
35
|
+
* with null once the schedule runs out. A failed read waits for the next one, since an asset
|
|
36
|
+
* still processing can read as a 404; only the signal ends the wait early.
|
|
37
|
+
*/
|
|
38
|
+
export async function pollAsset(transport, options, done) {
|
|
39
|
+
for (const delay of POLL_SCHEDULE_MS) {
|
|
40
|
+
await sleep(delay, options.signal);
|
|
41
|
+
try {
|
|
42
|
+
const asset = await getAsset(transport, options);
|
|
43
|
+
if (done(asset))
|
|
44
|
+
return asset;
|
|
45
|
+
}
|
|
46
|
+
catch (caught) {
|
|
47
|
+
if (options.signal?.aborted)
|
|
48
|
+
throw caught;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
/** An abort rejects as an aborted request would, so `isAbortError` recognises both. */
|
|
54
|
+
function sleep(ms, signal) {
|
|
55
|
+
return new Promise((resolve, reject) => {
|
|
56
|
+
const abort = () => {
|
|
57
|
+
clearTimeout(timer);
|
|
58
|
+
reject(toFetchError(signal?.reason));
|
|
59
|
+
};
|
|
60
|
+
const timer = setTimeout(() => {
|
|
61
|
+
signal?.removeEventListener('abort', abort);
|
|
62
|
+
resolve();
|
|
63
|
+
}, ms);
|
|
64
|
+
if (signal?.aborted)
|
|
65
|
+
abort();
|
|
66
|
+
else
|
|
67
|
+
signal?.addEventListener('abort', abort, { once: true });
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=assets.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"assets.js","sourceRoot":"","sources":["../../src/api/assets.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAG1C,0FAA0F;AAC1F,MAAM,CAAC,MAAM,gBAAgB,GAAG,EAAE,CAAA;AAElC;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAC/B,SAAoB,EACpB,OAMC;IAED,MAAM,EAAE,cAAc,EAAE,SAAS,EAAE,IAAI,GAAG,CAAC,EAAE,QAAQ,GAAG,gBAAgB,EAAE,MAAM,EAAE,GAAG,OAAO,CAAA;IAE5F,OAAO,SAAS,CAAC,OAAO,CACvB,kBAAkB,kBAAkB,CAAC,cAAc,CAAC,cAAc,kBAAkB,CACnF,SAAS,CACT,SAAS,EACV,EAAE,YAAY,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,MAAM,EAAE,CAC5C,CAAA;AACF,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,YAAY,CAAC,UAAkB,EAAE,WAAmB,gBAAgB;IACnF,OAAO,UAAU,KAAK,QAAQ,CAAA;AAC/B,CAAC;AAED,wFAAwF;AACxF,MAAM,CAAC,KAAK,UAAU,QAAQ,CAC7B,SAAoB,EACpB,OAKC;IAED,MAAM,EAAE,cAAc,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,OAAO,CAAA;IAE9D,OAAO,SAAS,CAAC,OAAO,CACvB,kBAAkB,kBAAkB,CAAC,cAAc,CAAC,cAAc,kBAAkB,CACnF,SAAS,CACT,WAAW,kBAAkB,CAAC,OAAO,CAAC,EAAE,EACzC,EAAE,MAAM,EAAE,CACV,CAAA;AACF,CAAC;AAED;;;;GAIG;AACH,MAAM,gBAAgB,GAAG,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAA;AAEzE;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,SAAS,CAC9B,SAAoB,EACpB,OAA6F,EAC7F,IAA+B;IAE/B,KAAK,MAAM,KAAK,IAAI,gBAAgB,EAAE,CAAC;QACtC,MAAM,KAAK,CAAC,KAAK,EAAE,OAAO,CAAC,MAAM,CAAC,CAAA;QAClC,IAAI,CAAC;YACJ,MAAM,KAAK,GAAG,MAAM,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC,CAAA;YAChD,IAAI,IAAI,CAAC,KAAK,CAAC;gBAAE,OAAO,KAAK,CAAA;QAC9B,CAAC;QAAC,OAAO,MAAM,EAAE,CAAC;YACjB,IAAI,OAAO,CAAC,MAAM,EAAE,OAAO;gBAAE,MAAM,MAAM,CAAA;QAC1C,CAAC;IACF,CAAC;IACD,OAAO,IAAI,CAAA;AACZ,CAAC;AAED,uFAAuF;AACvF,SAAS,KAAK,CAAC,EAAU,EAAE,MAA+B;IACzD,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACtC,MAAM,KAAK,GAAG,GAAG,EAAE;YAClB,YAAY,CAAC,KAAK,CAAC,CAAA;YACnB,MAAM,CAAC,YAAY,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;QACrC,CAAC,CAAA;QACD,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;YAC7B,MAAM,EAAE,mBAAmB,CAAC,OAAO,EAAE,KAAK,CAAC,CAAA;YAC3C,OAAO,EAAE,CAAA;QACV,CAAC,EAAE,EAAE,CAAC,CAAA;QACN,IAAI,MAAM,EAAE,OAAO;YAAE,KAAK,EAAE,CAAA;;YACvB,MAAM,EAAE,gBAAgB,CAAC,OAAO,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAA;IAC9D,CAAC,CAAC,CAAA;AACH,CAAC","sourcesContent":["import type { Asset } from '../types/asset.js'\nimport { toFetchError } from './errors.js'\nimport type { Transport } from './transport.js'\n\n/** Assets per page. Tuned for a grid: enough to fill it, small enough to feel instant. */\nexport const ASSETS_PAGE_SIZE = 50\n\n/**\n * One page of a library's assets. Pages are 1-based.\n *\n * The endpoint returns a bare array with no total or cursor, so \"is there more?\" is inferred\n * by the caller from a full page coming back — see `hasMorePages`.\n */\nexport async function listAssets(\n\ttransport: Transport,\n\toptions: {\n\t\torganizationId: string\n\t\tlibraryId: string\n\t\tpage?: number\n\t\tpageSize?: number\n\t\tsignal?: AbortSignal\n\t}\n): Promise<Array<Asset>> {\n\tconst { organizationId, libraryId, page = 1, pageSize = ASSETS_PAGE_SIZE, signal } = options\n\n\treturn transport.request<Array<Asset>>(\n\t\t`/organizations/${encodeURIComponent(organizationId)}/libraries/${encodeURIComponent(\n\t\t\tlibraryId\n\t\t)}/assets`,\n\t\t{ searchParams: { page, pageSize }, signal }\n\t)\n}\n\n/**\n * Whether another page is worth requesting, given the length of the page just returned. With\n * no total in the response, a total that is an exact multiple of `pageSize` costs one more\n * request, which comes back empty.\n */\nexport function hasMorePages(pageLength: number, pageSize: number = ASSETS_PAGE_SIZE): boolean {\n\treturn pageLength === pageSize\n}\n\n/** One asset, by id. Always carries the asset's `variants` (the list route may not). */\nexport async function getAsset(\n\ttransport: Transport,\n\toptions: {\n\t\torganizationId: string\n\t\tlibraryId: string\n\t\tassetId: string\n\t\tsignal?: AbortSignal\n\t}\n): Promise<Asset> {\n\tconst { organizationId, libraryId, assetId, signal } = options\n\n\treturn transport.request<Asset>(\n\t\t`/organizations/${encodeURIComponent(organizationId)}/libraries/${encodeURIComponent(\n\t\t\tlibraryId\n\t\t)}/assets/${encodeURIComponent(assetId)}`,\n\t\t{ signal }\n\t)\n}\n\n/**\n * The delays between reads while Raster finishes work on an asset in the background:\n * front-loaded, since most of it lands early, and bounded, so work that never lands stops\n * costing requests.\n */\nconst POLL_SCHEDULE_MS = [1000, 1500, 2000, 3000, 4000, 5000, 5000, 5000]\n\n/**\n * Read an asset on `POLL_SCHEDULE_MS` until `done` accepts it. Resolves with that read, or\n * with null once the schedule runs out. A failed read waits for the next one, since an asset\n * still processing can read as a 404; only the signal ends the wait early.\n */\nexport async function pollAsset(\n\ttransport: Transport,\n\toptions: { organizationId: string; libraryId: string; assetId: string; signal?: AbortSignal },\n\tdone: (asset: Asset) => boolean\n): Promise<Asset | null> {\n\tfor (const delay of POLL_SCHEDULE_MS) {\n\t\tawait sleep(delay, options.signal)\n\t\ttry {\n\t\t\tconst asset = await getAsset(transport, options)\n\t\t\tif (done(asset)) return asset\n\t\t} catch (caught) {\n\t\t\tif (options.signal?.aborted) throw caught\n\t\t}\n\t}\n\treturn null\n}\n\n/** An abort rejects as an aborted request would, so `isAbortError` recognises both. */\nfunction sleep(ms: number, signal: AbortSignal | undefined): Promise<void> {\n\treturn new Promise((resolve, reject) => {\n\t\tconst abort = () => {\n\t\t\tclearTimeout(timer)\n\t\t\treject(toFetchError(signal?.reason))\n\t\t}\n\t\tconst timer = setTimeout(() => {\n\t\t\tsignal?.removeEventListener('abort', abort)\n\t\t\tresolve()\n\t\t}, ms)\n\t\tif (signal?.aborted) abort()\n\t\telse signal?.addEventListener('abort', abort, { once: true })\n\t})\n}\n"]}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/** A failed call to the Raster API or its sign-in server: a `code` to branch on, and a message. */
|
|
2
|
+
export declare class RasterApiError extends Error {
|
|
3
|
+
readonly code: string;
|
|
4
|
+
readonly status: number;
|
|
5
|
+
constructor(message: string, code: string, status: number);
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Map a `fetch` rejection to a `RasterApiError`. An abort is a deliberate cancellation,
|
|
9
|
+
* kept distinct (`ABORTED`) from a real transport failure (`NETWORK_ERROR`) so callers
|
|
10
|
+
* that did the aborting can swallow it rather than show an error. A timeout
|
|
11
|
+
* (`AbortSignal.timeout()`) is a failure the user should hear about, so it is `TIMEOUT`.
|
|
12
|
+
*/
|
|
13
|
+
export declare function toFetchError(caught: unknown): RasterApiError;
|
|
14
|
+
/** Anything thrown, as a `RasterApiError`. One that is not already is a generic `UNKNOWN`. */
|
|
15
|
+
export declare function toRasterApiError(caught: unknown): RasterApiError;
|
|
16
|
+
/** Whether this error is the SDK's own abort, which a cancelling caller should swallow. */
|
|
17
|
+
export declare function isAbortError(error: unknown): boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Message to show the user for a failed call: the API's own `message`, which the API documents
|
|
20
|
+
* as safe to show, unless a specific message tells the user what to do.
|
|
21
|
+
*/
|
|
22
|
+
export declare function toUserMessage(caught: unknown): string;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/** A failed call to the Raster API or its sign-in server: a `code` to branch on, and a message. */
|
|
2
|
+
export class RasterApiError extends Error {
|
|
3
|
+
constructor(message, code, status) {
|
|
4
|
+
super(message);
|
|
5
|
+
this.name = 'RasterApiError';
|
|
6
|
+
this.code = code;
|
|
7
|
+
this.status = status;
|
|
8
|
+
// Keeps `instanceof` working through TypeScript's ES5 class emit, which some consumers
|
|
9
|
+
// still compile through.
|
|
10
|
+
Object.setPrototypeOf(this, RasterApiError.prototype);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Map a `fetch` rejection to a `RasterApiError`. An abort is a deliberate cancellation,
|
|
15
|
+
* kept distinct (`ABORTED`) from a real transport failure (`NETWORK_ERROR`) so callers
|
|
16
|
+
* that did the aborting can swallow it rather than show an error. A timeout
|
|
17
|
+
* (`AbortSignal.timeout()`) is a failure the user should hear about, so it is `TIMEOUT`.
|
|
18
|
+
*/
|
|
19
|
+
export function toFetchError(caught) {
|
|
20
|
+
// `DOMException` is not defined in every Node version the SDK supports, and an abort
|
|
21
|
+
// raised by undici is an `Error` with `name === 'AbortError'` rather than a
|
|
22
|
+
// `DOMException`. Checking the name alone covers both and cannot throw.
|
|
23
|
+
if (caught instanceof Error && caught.name === 'AbortError') {
|
|
24
|
+
return new RasterApiError('Request aborted.', 'ABORTED', 0);
|
|
25
|
+
}
|
|
26
|
+
if (caught instanceof Error && caught.name === 'TimeoutError') {
|
|
27
|
+
return new RasterApiError('Request timed out.', 'TIMEOUT', 0);
|
|
28
|
+
}
|
|
29
|
+
// fetch otherwise only rejects on transport failure — offline, DNS, or (in Figma) a host
|
|
30
|
+
// missing from `networkAccess.allowedDomains`.
|
|
31
|
+
return new RasterApiError('Could not reach Raster.', 'NETWORK_ERROR', 0);
|
|
32
|
+
}
|
|
33
|
+
/** Anything thrown, as a `RasterApiError`. One that is not already is a generic `UNKNOWN`. */
|
|
34
|
+
export function toRasterApiError(caught) {
|
|
35
|
+
if (caught instanceof RasterApiError)
|
|
36
|
+
return caught;
|
|
37
|
+
return new RasterApiError('Something went wrong. Please try again.', 'UNKNOWN', 0);
|
|
38
|
+
}
|
|
39
|
+
/** Whether this error is the SDK's own abort, which a cancelling caller should swallow. */
|
|
40
|
+
export function isAbortError(error) {
|
|
41
|
+
return error instanceof RasterApiError && error.code === 'ABORTED';
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Message to show the user for a failed call: the API's own `message`, which the API documents
|
|
45
|
+
* as safe to show, unless a specific message tells the user what to do.
|
|
46
|
+
*/
|
|
47
|
+
export function toUserMessage(caught) {
|
|
48
|
+
const error = toRasterApiError(caught);
|
|
49
|
+
switch (error.code) {
|
|
50
|
+
case 'UNAUTHENTICATED':
|
|
51
|
+
return 'Raster did not accept your credentials. Sign in again, or check your API key.';
|
|
52
|
+
case 'INVALID_API_KEY':
|
|
53
|
+
return 'That API key was not accepted. Check it in Raster under Organization settings → API keys.';
|
|
54
|
+
// An asset that does not exist and one this credential cannot see both come back as a
|
|
55
|
+
// 404, so they are indistinguishable from here. Promote names it `NOT_FOUND`.
|
|
56
|
+
case 'RESOURCE_NOT_FOUND':
|
|
57
|
+
case 'NOT_FOUND':
|
|
58
|
+
return 'Not found — it may have been deleted, or this account may not have access to it.';
|
|
59
|
+
// The API's 404 for any library the credential cannot reach: reads and search as well
|
|
60
|
+
// as uploads, and for a sign-in as well as an API key.
|
|
61
|
+
case 'API_KEY_NOT_AUTHORIZED_FOR_LIBRARY':
|
|
62
|
+
return 'This account can’t access this library. Ask an owner for access.';
|
|
63
|
+
case 'NETWORK_ERROR':
|
|
64
|
+
return 'Could not reach Raster. Check your connection and try again.';
|
|
65
|
+
case 'TIMEOUT':
|
|
66
|
+
return 'Raster took too long to respond. Try again.';
|
|
67
|
+
case 'NOT_SIGNED_IN':
|
|
68
|
+
return 'Sign in to Raster first.';
|
|
69
|
+
case 'SESSION_EXPIRED':
|
|
70
|
+
return 'Your Raster session has ended. Sign in again.';
|
|
71
|
+
// The browser sign-in did not complete (device grant, RFC 8628 §3.5).
|
|
72
|
+
case 'access_denied':
|
|
73
|
+
return 'The sign-in was denied in the browser.';
|
|
74
|
+
case 'expired_token':
|
|
75
|
+
return 'That sign-in code expired. Try again.';
|
|
76
|
+
default:
|
|
77
|
+
return error.message;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/api/errors.ts"],"names":[],"mappings":"AAAA,mGAAmG;AACnG,MAAM,OAAO,cAAe,SAAQ,KAAK;IAIxC,YAAY,OAAe,EAAE,IAAY,EAAE,MAAc;QACxD,KAAK,CAAC,OAAO,CAAC,CAAA;QACd,IAAI,CAAC,IAAI,GAAG,gBAAgB,CAAA;QAC5B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAA;QAChB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAA;QACpB,uFAAuF;QACvF,yBAAyB;QACzB,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,cAAc,CAAC,SAAS,CAAC,CAAA;IACtD,CAAC;CACD;AAED;;;;;GAKG;AACH,MAAM,UAAU,YAAY,CAAC,MAAe;IAC3C,qFAAqF;IACrF,4EAA4E;IAC5E,wEAAwE;IACxE,IAAI,MAAM,YAAY,KAAK,IAAI,MAAM,CAAC,IAAI,KAAK,YAAY,EAAE,CAAC;QAC7D,OAAO,IAAI,cAAc,CAAC,kBAAkB,EAAE,SAAS,EAAE,CAAC,CAAC,CAAA;IAC5D,CAAC;IACD,IAAI,MAAM,YAAY,KAAK,IAAI,MAAM,CAAC,IAAI,KAAK,cAAc,EAAE,CAAC;QAC/D,OAAO,IAAI,cAAc,CAAC,oBAAoB,EAAE,SAAS,EAAE,CAAC,CAAC,CAAA;IAC9D,CAAC;IACD,yFAAyF;IACzF,+CAA+C;IAC/C,OAAO,IAAI,cAAc,CAAC,yBAAyB,EAAE,eAAe,EAAE,CAAC,CAAC,CAAA;AACzE,CAAC;AAED,8FAA8F;AAC9F,MAAM,UAAU,gBAAgB,CAAC,MAAe;IAC/C,IAAI,MAAM,YAAY,cAAc;QAAE,OAAO,MAAM,CAAA;IACnD,OAAO,IAAI,cAAc,CAAC,yCAAyC,EAAE,SAAS,EAAE,CAAC,CAAC,CAAA;AACnF,CAAC;AAED,2FAA2F;AAC3F,MAAM,UAAU,YAAY,CAAC,KAAc;IAC1C,OAAO,KAAK,YAAY,cAAc,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS,CAAA;AACnE,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,aAAa,CAAC,MAAe;IAC5C,MAAM,KAAK,GAAG,gBAAgB,CAAC,MAAM,CAAC,CAAA;IACtC,QAAQ,KAAK,CAAC,IAAI,EAAE,CAAC;QACpB,KAAK,iBAAiB;YACrB,OAAO,+EAA+E,CAAA;QAEvF,KAAK,iBAAiB;YACrB,OAAO,2FAA2F,CAAA;QAEnG,sFAAsF;QACtF,8EAA8E;QAC9E,KAAK,oBAAoB,CAAC;QAC1B,KAAK,WAAW;YACf,OAAO,kFAAkF,CAAA;QAE1F,sFAAsF;QACtF,uDAAuD;QACvD,KAAK,oCAAoC;YACxC,OAAO,kEAAkE,CAAA;QAE1E,KAAK,eAAe;YACnB,OAAO,8DAA8D,CAAA;QAEtE,KAAK,SAAS;YACb,OAAO,6CAA6C,CAAA;QAErD,KAAK,eAAe;YACnB,OAAO,0BAA0B,CAAA;QAElC,KAAK,iBAAiB;YACrB,OAAO,+CAA+C,CAAA;QAEvD,sEAAsE;QACtE,KAAK,eAAe;YACnB,OAAO,wCAAwC,CAAA;QAChD,KAAK,eAAe;YACnB,OAAO,uCAAuC,CAAA;QAE/C;YACC,OAAO,KAAK,CAAC,OAAO,CAAA;IACtB,CAAC;AACF,CAAC","sourcesContent":["/** A failed call to the Raster API or its sign-in server: a `code` to branch on, and a message. */\nexport class RasterApiError extends Error {\n\treadonly code: string\n\treadonly status: number\n\n\tconstructor(message: string, code: string, status: number) {\n\t\tsuper(message)\n\t\tthis.name = 'RasterApiError'\n\t\tthis.code = code\n\t\tthis.status = status\n\t\t// Keeps `instanceof` working through TypeScript's ES5 class emit, which some consumers\n\t\t// still compile through.\n\t\tObject.setPrototypeOf(this, RasterApiError.prototype)\n\t}\n}\n\n/**\n * Map a `fetch` rejection to a `RasterApiError`. An abort is a deliberate cancellation,\n * kept distinct (`ABORTED`) from a real transport failure (`NETWORK_ERROR`) so callers\n * that did the aborting can swallow it rather than show an error. A timeout\n * (`AbortSignal.timeout()`) is a failure the user should hear about, so it is `TIMEOUT`.\n */\nexport function toFetchError(caught: unknown): RasterApiError {\n\t// `DOMException` is not defined in every Node version the SDK supports, and an abort\n\t// raised by undici is an `Error` with `name === 'AbortError'` rather than a\n\t// `DOMException`. Checking the name alone covers both and cannot throw.\n\tif (caught instanceof Error && caught.name === 'AbortError') {\n\t\treturn new RasterApiError('Request aborted.', 'ABORTED', 0)\n\t}\n\tif (caught instanceof Error && caught.name === 'TimeoutError') {\n\t\treturn new RasterApiError('Request timed out.', 'TIMEOUT', 0)\n\t}\n\t// fetch otherwise only rejects on transport failure — offline, DNS, or (in Figma) a host\n\t// missing from `networkAccess.allowedDomains`.\n\treturn new RasterApiError('Could not reach Raster.', 'NETWORK_ERROR', 0)\n}\n\n/** Anything thrown, as a `RasterApiError`. One that is not already is a generic `UNKNOWN`. */\nexport function toRasterApiError(caught: unknown): RasterApiError {\n\tif (caught instanceof RasterApiError) return caught\n\treturn new RasterApiError('Something went wrong. Please try again.', 'UNKNOWN', 0)\n}\n\n/** Whether this error is the SDK's own abort, which a cancelling caller should swallow. */\nexport function isAbortError(error: unknown): boolean {\n\treturn error instanceof RasterApiError && error.code === 'ABORTED'\n}\n\n/**\n * Message to show the user for a failed call: the API's own `message`, which the API documents\n * as safe to show, unless a specific message tells the user what to do.\n */\nexport function toUserMessage(caught: unknown): string {\n\tconst error = toRasterApiError(caught)\n\tswitch (error.code) {\n\t\tcase 'UNAUTHENTICATED':\n\t\t\treturn 'Raster did not accept your credentials. Sign in again, or check your API key.'\n\n\t\tcase 'INVALID_API_KEY':\n\t\t\treturn 'That API key was not accepted. Check it in Raster under Organization settings → API keys.'\n\n\t\t// An asset that does not exist and one this credential cannot see both come back as a\n\t\t// 404, so they are indistinguishable from here. Promote names it `NOT_FOUND`.\n\t\tcase 'RESOURCE_NOT_FOUND':\n\t\tcase 'NOT_FOUND':\n\t\t\treturn 'Not found — it may have been deleted, or this account may not have access to it.'\n\n\t\t// The API's 404 for any library the credential cannot reach: reads and search as well\n\t\t// as uploads, and for a sign-in as well as an API key.\n\t\tcase 'API_KEY_NOT_AUTHORIZED_FOR_LIBRARY':\n\t\t\treturn 'This account can’t access this library. Ask an owner for access.'\n\n\t\tcase 'NETWORK_ERROR':\n\t\t\treturn 'Could not reach Raster. Check your connection and try again.'\n\n\t\tcase 'TIMEOUT':\n\t\t\treturn 'Raster took too long to respond. Try again.'\n\n\t\tcase 'NOT_SIGNED_IN':\n\t\t\treturn 'Sign in to Raster first.'\n\n\t\tcase 'SESSION_EXPIRED':\n\t\t\treturn 'Your Raster session has ended. Sign in again.'\n\n\t\t// The browser sign-in did not complete (device grant, RFC 8628 §3.5).\n\t\tcase 'access_denied':\n\t\t\treturn 'The sign-in was denied in the browser.'\n\t\tcase 'expired_token':\n\t\t\treturn 'That sign-in code expired. Try again.'\n\n\t\tdefault:\n\t\t\treturn error.message\n\t}\n}\n"]}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Library } from '../types/library.js';
|
|
2
|
+
import type { Transport } from './transport.js';
|
|
3
|
+
/**
|
|
4
|
+
* Libraries per request: the endpoint's documented maximum (`https://api.raster.app/openapi.json`),
|
|
5
|
+
* so listing an organization takes as few requests as it can.
|
|
6
|
+
*/
|
|
7
|
+
export declare const LIBRARIES_PAGE_SIZE = 50;
|
|
8
|
+
/**
|
|
9
|
+
* Every library in an organization. Pages are 0-based, and like the asset list they carry no
|
|
10
|
+
* total, so this reads them until a short one comes back.
|
|
11
|
+
*/
|
|
12
|
+
export declare function listLibraries(transport: Transport, options: {
|
|
13
|
+
organizationId: string;
|
|
14
|
+
organizationName?: string | null;
|
|
15
|
+
signal?: AbortSignal;
|
|
16
|
+
}): Promise<Array<Library>>;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { hasMorePages } from './assets.js';
|
|
2
|
+
/**
|
|
3
|
+
* Libraries per request: the endpoint's documented maximum (`https://api.raster.app/openapi.json`),
|
|
4
|
+
* so listing an organization takes as few requests as it can.
|
|
5
|
+
*/
|
|
6
|
+
export const LIBRARIES_PAGE_SIZE = 50;
|
|
7
|
+
/**
|
|
8
|
+
* Every library in an organization. Pages are 0-based, and like the asset list they carry no
|
|
9
|
+
* total, so this reads them until a short one comes back.
|
|
10
|
+
*/
|
|
11
|
+
export async function listLibraries(transport, options) {
|
|
12
|
+
const { organizationId, organizationName = null, signal } = options;
|
|
13
|
+
const libraries = [];
|
|
14
|
+
for (let page = 0;; page++) {
|
|
15
|
+
const wire = await transport.request(`/organizations/${encodeURIComponent(organizationId)}/libraries`, { searchParams: { page, pageSize: LIBRARIES_PAGE_SIZE }, signal });
|
|
16
|
+
for (const library of wire)
|
|
17
|
+
libraries.push({ ...library, organizationId, organizationName });
|
|
18
|
+
if (!hasMorePages(wire.length, LIBRARIES_PAGE_SIZE))
|
|
19
|
+
return libraries;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=libraries.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"libraries.js","sourceRoot":"","sources":["../../src/api/libraries.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAG1C;;;GAGG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,EAAE,CAAA;AAKrC;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAClC,SAAoB,EACpB,OAIC;IAED,MAAM,EAAE,cAAc,EAAE,gBAAgB,GAAG,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAA;IAEnE,MAAM,SAAS,GAAmB,EAAE,CAAA;IACpC,KAAK,IAAI,IAAI,GAAG,CAAC,GAAI,IAAI,EAAE,EAAE,CAAC;QAC7B,MAAM,IAAI,GAAG,MAAM,SAAS,CAAC,OAAO,CACnC,kBAAkB,kBAAkB,CAAC,cAAc,CAAC,YAAY,EAChE,EAAE,YAAY,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,mBAAmB,EAAE,EAAE,MAAM,EAAE,CACjE,CAAA;QACD,KAAK,MAAM,OAAO,IAAI,IAAI;YAAE,SAAS,CAAC,IAAI,CAAC,EAAE,GAAG,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,CAAC,CAAA;QAC5F,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,MAAM,EAAE,mBAAmB,CAAC;YAAE,OAAO,SAAS,CAAA;IACtE,CAAC;AACF,CAAC","sourcesContent":["import type { Library } from '../types/library.js'\nimport { hasMorePages } from './assets.js'\nimport type { Transport } from './transport.js'\n\n/**\n * Libraries per request: the endpoint's documented maximum (`https://api.raster.app/openapi.json`),\n * so listing an organization takes as few requests as it can.\n */\nexport const LIBRARIES_PAGE_SIZE = 50\n\n/** The API's Library object, before the organization it came from is attached. */\ntype WireLibrary = Omit<Library, 'organizationId' | 'organizationName'>\n\n/**\n * Every library in an organization. Pages are 0-based, and like the asset list they carry no\n * total, so this reads them until a short one comes back.\n */\nexport async function listLibraries(\n\ttransport: Transport,\n\toptions: {\n\t\torganizationId: string\n\t\torganizationName?: string | null\n\t\tsignal?: AbortSignal\n\t}\n): Promise<Array<Library>> {\n\tconst { organizationId, organizationName = null, signal } = options\n\n\tconst libraries: Array<Library> = []\n\tfor (let page = 0; ; page++) {\n\t\tconst wire = await transport.request<Array<WireLibrary>>(\n\t\t\t`/organizations/${encodeURIComponent(organizationId)}/libraries`,\n\t\t\t{ searchParams: { page, pageSize: LIBRARIES_PAGE_SIZE }, signal }\n\t\t)\n\t\tfor (const library of wire) libraries.push({ ...library, organizationId, organizationName })\n\t\tif (!hasMorePages(wire.length, LIBRARIES_PAGE_SIZE)) return libraries\n\t}\n}\n"]}
|
package/dist/api/me.d.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { components } from '../__generated__/openapi.js';
|
|
2
|
+
import type { Organization } from '../types/library.js';
|
|
3
|
+
import type { CredentialOrganization } from '../types/session.js';
|
|
4
|
+
import type { Transport } from './transport.js';
|
|
5
|
+
/**
|
|
6
|
+
* The credential's scope, in one of two shapes. An API key, or a sign-in narrowed to one
|
|
7
|
+
* organization at consent, has that organization on the top-level fields. A sign-in granted
|
|
8
|
+
* the whole account has `organizations`, one entry per membership. Read either through
|
|
9
|
+
* `meOrganizations`.
|
|
10
|
+
*/
|
|
11
|
+
export type Me = components['schemas']['Me'];
|
|
12
|
+
/**
|
|
13
|
+
* One organization the credential can act on, with the libraries it reaches inside it. Its
|
|
14
|
+
* `organizationId` is the slug every other endpoint takes in its path.
|
|
15
|
+
*/
|
|
16
|
+
export type MeOrganization = Extract<Me, {
|
|
17
|
+
organizations: unknown;
|
|
18
|
+
}>['organizations'][number];
|
|
19
|
+
/**
|
|
20
|
+
* The credential's access to one library. `library` is the id the library list reports, unique
|
|
21
|
+
* only within its organization; `write` covers reading too.
|
|
22
|
+
*/
|
|
23
|
+
export type MeLibraryAccess = MeOrganization['libraries'][number];
|
|
24
|
+
/** Every organization `/me` reported, whichever shape it used. */
|
|
25
|
+
export declare function meOrganizations(me: Me): Array<MeOrganization>;
|
|
26
|
+
/** The same list, as an organization picker needs it. */
|
|
27
|
+
export declare function meOrganizationList(me: Me): Array<Organization>;
|
|
28
|
+
/** The same list, reduced to what is persisted alongside the credential. */
|
|
29
|
+
export declare function meCredentialOrganizations(me: Me): Array<CredentialOrganization>;
|
|
30
|
+
/**
|
|
31
|
+
* The credential's access to one library. Null when `/me` does not list it, which for a
|
|
32
|
+
* library the list returned means this `/me` was read before the grant: treat it as no access.
|
|
33
|
+
*/
|
|
34
|
+
export declare function meLibraryAccess(me: Me, organizationId: string, libraryId: string): MeLibraryAccess['access'] | null;
|
|
35
|
+
/**
|
|
36
|
+
* Resolve what a credential can reach. It takes nothing but the credential, so it doubles as
|
|
37
|
+
* validation: it returns the scope or throws (`INVALID_API_KEY`, `UNAUTHENTICATED`). Pass
|
|
38
|
+
* `bearer` to check a credential before it is saved; otherwise the session's own is sent.
|
|
39
|
+
*/
|
|
40
|
+
export declare function getMe(transport: Transport, bearer?: string, signal?: AbortSignal): Promise<Me>;
|
package/dist/api/me.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/** Every organization `/me` reported, whichever shape it used. */
|
|
2
|
+
export function meOrganizations(me) {
|
|
3
|
+
if ('organizations' in me)
|
|
4
|
+
return me.organizations;
|
|
5
|
+
const { organizationId, organizationName, plan, libraries } = me;
|
|
6
|
+
return [{ organizationId, organizationName, plan, libraries }];
|
|
7
|
+
}
|
|
8
|
+
/** The same list, as an organization picker needs it. */
|
|
9
|
+
export function meOrganizationList(me) {
|
|
10
|
+
return meOrganizations(me).map((organization) => ({
|
|
11
|
+
id: organization.organizationId,
|
|
12
|
+
name: organization.organizationName,
|
|
13
|
+
libraryCount: organization.libraries.length,
|
|
14
|
+
}));
|
|
15
|
+
}
|
|
16
|
+
/** The same list, reduced to what is persisted alongside the credential. */
|
|
17
|
+
export function meCredentialOrganizations(me) {
|
|
18
|
+
return meOrganizations(me).map((organization) => ({
|
|
19
|
+
id: organization.organizationId,
|
|
20
|
+
name: organization.organizationName,
|
|
21
|
+
}));
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* The credential's access to one library. Null when `/me` does not list it, which for a
|
|
25
|
+
* library the list returned means this `/me` was read before the grant: treat it as no access.
|
|
26
|
+
*/
|
|
27
|
+
export function meLibraryAccess(me, organizationId, libraryId) {
|
|
28
|
+
const organization = meOrganizations(me).find((entry) => entry.organizationId === organizationId);
|
|
29
|
+
return organization?.libraries.find((entry) => entry.library === libraryId)?.access ?? null;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Resolve what a credential can reach. It takes nothing but the credential, so it doubles as
|
|
33
|
+
* validation: it returns the scope or throws (`INVALID_API_KEY`, `UNAUTHENTICATED`). Pass
|
|
34
|
+
* `bearer` to check a credential before it is saved; otherwise the session's own is sent.
|
|
35
|
+
*/
|
|
36
|
+
export async function getMe(transport, bearer, signal) {
|
|
37
|
+
return transport.request('/me', { bearer, signal });
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=me.js.map
|