@devultur/convex 1.2.0 → 1.3.1
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/README.md +6 -7
- package/dist/pdf-render-CiCK6V1X.d.ts +43 -0
- package/dist/pdfjs.d.ts +11 -0
- package/dist/pdfjs.js +11 -0
- package/dist/react.d.ts +181 -1
- package/dist/react.js +757 -90
- package/dist/validators.d.ts +5 -5
- package/package.json +13 -4
package/README.md
CHANGED
|
@@ -83,8 +83,8 @@ await saveCover({ cover: media }); // a DevulturMedia: stores as-is in a dv.medi
|
|
|
83
83
|
```
|
|
84
84
|
|
|
85
85
|
The PUT goes through `XMLHttpRequest` (`fetch` reports no upload progress), in parts for large files. `key` is an
|
|
86
|
-
optional field of `DevulturMedia` and of `dv.media()
|
|
87
|
-
|
|
86
|
+
optional field of `DevulturMedia` and of `dv.media()`. It is informational: delivery URLs are derived from `id`,
|
|
87
|
+
`kind`, `visibility` and `filename`.
|
|
88
88
|
|
|
89
89
|
## Media library
|
|
90
90
|
|
|
@@ -148,12 +148,11 @@ exposed functions: export your own action that checks `ctx.auth` and then calls
|
|
|
148
148
|
The caller only names the key: the record's visibility, size and content type come from the Worker's `HEAD` of the
|
|
149
149
|
object, never from the arguments.
|
|
150
150
|
|
|
151
|
-
`deleteMedia` removes an image's or a file's record once
|
|
152
|
-
|
|
151
|
+
`deleteMedia` removes an image's or a file's record once the Worker
|
|
152
|
+
deleted the object (a delete refused by a lock keeps it). `deleteVideo` removes a video's.
|
|
153
153
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
report an object's lock: read it from an action with `devultur.getLock(ctx, record.objectKey)`.
|
|
154
|
+
A video only `processVideo` recorded has no `visibility` or `objectKey`, and `mediaFromRecord` returns `null` for
|
|
155
|
+
it. `listMedia` doesn't report an object's lock: read it from an action with `devultur.getLock(ctx, record.objectKey)`.
|
|
157
156
|
|
|
158
157
|
## Using a self-hosted Worker
|
|
159
158
|
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
interface PdfViewport {
|
|
2
|
+
width: number;
|
|
3
|
+
height: number;
|
|
4
|
+
}
|
|
5
|
+
interface PdfRenderTask {
|
|
6
|
+
promise: Promise<void>;
|
|
7
|
+
cancel(): void;
|
|
8
|
+
}
|
|
9
|
+
interface PdfPage {
|
|
10
|
+
getViewport(parameters: {
|
|
11
|
+
scale: number;
|
|
12
|
+
}): PdfViewport;
|
|
13
|
+
render(parameters: {
|
|
14
|
+
canvas: HTMLCanvasElement | null;
|
|
15
|
+
viewport: PdfViewport;
|
|
16
|
+
}): PdfRenderTask;
|
|
17
|
+
getTextContent(): Promise<{
|
|
18
|
+
items: ReadonlyArray<object>;
|
|
19
|
+
}>;
|
|
20
|
+
cleanup(): unknown;
|
|
21
|
+
}
|
|
22
|
+
interface PdfDocument {
|
|
23
|
+
numPages: number;
|
|
24
|
+
getPage(pageNumber: number): Promise<PdfPage>;
|
|
25
|
+
}
|
|
26
|
+
interface PdfLoadingTask {
|
|
27
|
+
promise: Promise<PdfDocument>;
|
|
28
|
+
/** Destroys the document and its worker (pdf.js 6 has no `PDFDocumentProxy.destroy`). */
|
|
29
|
+
destroy(): Promise<void>;
|
|
30
|
+
}
|
|
31
|
+
/** The parts of `pdfjs-dist` (legacy build) that the previews use. */
|
|
32
|
+
interface PdfjsModule {
|
|
33
|
+
GlobalWorkerOptions: {
|
|
34
|
+
workerSrc: string;
|
|
35
|
+
};
|
|
36
|
+
getDocument(source: {
|
|
37
|
+
data: Uint8Array;
|
|
38
|
+
}): PdfLoadingTask;
|
|
39
|
+
}
|
|
40
|
+
/** Imports pdf.js, e.g. `() => import("pdfjs-dist/legacy/build/pdf.mjs")`. */
|
|
41
|
+
type PdfjsLoader = () => Promise<PdfjsModule>;
|
|
42
|
+
|
|
43
|
+
export type { PdfjsModule as P, PdfjsLoader as a };
|
package/dist/pdfjs.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { P as PdfjsModule } from './pdf-render-CiCK6V1X.js';
|
|
2
|
+
|
|
3
|
+
/** The worker next to the legacy build, resolved by your bundler from this package. */
|
|
4
|
+
declare const defaultPdfWorkerSrc: () => string;
|
|
5
|
+
/**
|
|
6
|
+
* Imports the pdf.js legacy build (Safari and iOS 18+; the modern build needs iOS 26.2) and, unless a worker is set
|
|
7
|
+
* already, points `GlobalWorkerOptions.workerSrc` at `defaultPdfWorkerSrc()`.
|
|
8
|
+
*/
|
|
9
|
+
declare function loadPdfjs(): Promise<PdfjsModule>;
|
|
10
|
+
|
|
11
|
+
export { defaultPdfWorkerSrc, loadPdfjs };
|
package/dist/pdfjs.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// src/pdfjs.ts
|
|
2
|
+
var defaultPdfWorkerSrc = () => new URL("pdfjs-dist/legacy/build/pdf.worker.min.mjs", import.meta.url).href;
|
|
3
|
+
async function loadPdfjs() {
|
|
4
|
+
const pdfjs = await import("pdfjs-dist/legacy/build/pdf.mjs");
|
|
5
|
+
if (!pdfjs.GlobalWorkerOptions.workerSrc) pdfjs.GlobalWorkerOptions.workerSrc = defaultPdfWorkerSrc();
|
|
6
|
+
return pdfjs;
|
|
7
|
+
}
|
|
8
|
+
export {
|
|
9
|
+
defaultPdfWorkerSrc,
|
|
10
|
+
loadPdfjs
|
|
11
|
+
};
|
package/dist/react.d.ts
CHANGED
|
@@ -3,6 +3,8 @@ import { Visibility, DevulturMedia, MediaUrlOptions, ProcessingPhase, FailureRea
|
|
|
3
3
|
import { BakeOutputFormat, MediaUploadState, MediaUploadError, UploadProgress } from '@devultur/react';
|
|
4
4
|
export { MediaUploadError, MediaUploadState, UploadProgress } from '@devultur/react';
|
|
5
5
|
import { FunctionReference } from 'convex/server';
|
|
6
|
+
import { a as PdfjsLoader } from './pdf-render-CiCK6V1X.js';
|
|
7
|
+
export { P as PdfjsModule } from './pdf-render-CiCK6V1X.js';
|
|
6
8
|
|
|
7
9
|
interface CropUploadRenderProps {
|
|
8
10
|
/** The state machine of the full cycle (idle → uploading → ready / failed). */
|
|
@@ -51,6 +53,184 @@ interface CropUploadProps {
|
|
|
51
53
|
*/
|
|
52
54
|
declare function CropUpload({ aspectRatio, visibility, outputFormat, maxWidth, minSourceWidth, replaces, onComplete, onReject, accept, cropperClassName, labels, children, }: CropUploadProps): React.JSX.Element;
|
|
53
55
|
|
|
56
|
+
/** Why a media fetch was refused. Stable: match on it, not on `message`. */
|
|
57
|
+
type MediaFetchErrorCode =
|
|
58
|
+
/** The delivery answered with a non-2xx status (`status` has it). */
|
|
59
|
+
"http_error"
|
|
60
|
+
/** `Content-Length`, or the streamed body, exceeds `maxBytes`. */
|
|
61
|
+
| "too_large"
|
|
62
|
+
/** The expected content type isn't one that can be previewed inline. */
|
|
63
|
+
| "unsupported_type"
|
|
64
|
+
/** The response's `Content-Type` doesn't agree with the expected one. */
|
|
65
|
+
| "type_mismatch";
|
|
66
|
+
declare class MediaFetchError extends Error {
|
|
67
|
+
readonly code: MediaFetchErrorCode;
|
|
68
|
+
/** HTTP status, for `http_error`. */
|
|
69
|
+
readonly status?: number;
|
|
70
|
+
constructor(code: MediaFetchErrorCode, message: string, status?: number);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
interface UseMediaObjectUrlOptions {
|
|
74
|
+
/** `false` → nothing is fetched (e.g. a list row that isn't selected). Default `true`. */
|
|
75
|
+
enabled?: boolean;
|
|
76
|
+
/** Refuses bodies larger than this, by `Content-Length` and while streaming (`MediaFetchError` `too_large`). */
|
|
77
|
+
maxBytes?: number;
|
|
78
|
+
/**
|
|
79
|
+
* The type you know the media has. The response's `Content-Type` must agree with it, and the object URL is
|
|
80
|
+
* created with it (`type_mismatch` / `unsupported_type` otherwise). Without it, the object URL keeps the
|
|
81
|
+
* response's type only when previewable (see the guide's allowlist), else `application/octet-stream`.
|
|
82
|
+
* With it, public media is fetched and verified too, instead of returning its unverified delivery URL.
|
|
83
|
+
*/
|
|
84
|
+
contentType?: string;
|
|
85
|
+
}
|
|
86
|
+
interface MediaObjectUrl {
|
|
87
|
+
/**
|
|
88
|
+
* `blob:` URL for private media (and public media with `contentType`); the delivery URL for other public media,
|
|
89
|
+
* whose type is whatever the server says, so don't frame it. `null` while loading, disabled or failed.
|
|
90
|
+
*/
|
|
91
|
+
objectUrl: string | null;
|
|
92
|
+
/** The type the object URL was created with (not the server's verbatim). `null` for an unverified public URL. */
|
|
93
|
+
contentType: string | null;
|
|
94
|
+
isLoading: boolean;
|
|
95
|
+
/** A `MediaFetchError` (stable `code`), or a network error. */
|
|
96
|
+
error: Error | null;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* An in-memory URL for any media, for elements that can't send headers (`<iframe>`, `<audio>`, `<img>`). Private
|
|
100
|
+
* media is fetched with the session header (one re-issue and retry on a 401 `session_mismatch`) into an object
|
|
101
|
+
* URL that is revoked when the media changes and on unmount. Public media returns its delivery URL, unfetched,
|
|
102
|
+
* unless `contentType` asks for it to be verified.
|
|
103
|
+
*
|
|
104
|
+
* @example
|
|
105
|
+
* const { objectUrl, error } = useMediaObjectUrl(document.file, { contentType: "application/pdf", maxBytes: 20e6 });
|
|
106
|
+
* return objectUrl && <iframe src={objectUrl} title={document.file.filename} />;
|
|
107
|
+
*/
|
|
108
|
+
declare function useMediaObjectUrl(media: DevulturMedia | null | undefined, options?: UseMediaObjectUrlOptions): MediaObjectUrl;
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Every string `<Preview>`, `<PdfThumbnail>` and their PDF pages show or announce. Pass any subset as `labels` on a
|
|
112
|
+
* component or on `PdfPreviewProvider`; the component's wins over the provider's, which wins over the English defaults.
|
|
113
|
+
*/
|
|
114
|
+
interface PreviewLabels {
|
|
115
|
+
/** Accessible name of a pdf.js document: its title and page count. */
|
|
116
|
+
document: (title: string, pageCount: number) => string;
|
|
117
|
+
/** Accessible name of one pdf.js page. */
|
|
118
|
+
page: (page: number, pageCount: number) => string;
|
|
119
|
+
/** Announced (visually hidden) while a preview loads, when no `loading` is passed. */
|
|
120
|
+
loading: string;
|
|
121
|
+
/** Note after the last rendered page when a PDF has more than `maxPages`; `fallback` follows it. */
|
|
122
|
+
pageCap: (shown: number, total: number) => string;
|
|
123
|
+
/** `title` of the default fallback's download link when a fetch was refused or the type can't be previewed. */
|
|
124
|
+
error: (code: MediaFetchErrorCode) => string;
|
|
125
|
+
/** Accessible name of a `<PdfThumbnail>`, from its title. */
|
|
126
|
+
thumbnail: (title: string) => string;
|
|
127
|
+
/** Name of a PDF (pdf.js document, `<iframe>` title, thumbnail) with no `title` and no filename. */
|
|
128
|
+
untitledPdf: string;
|
|
129
|
+
/** Name of an image or audio preview with no `title` and no filename. */
|
|
130
|
+
untitledMedia: string;
|
|
131
|
+
/** Text of the default fallback's download link when the media has no filename. */
|
|
132
|
+
download: string;
|
|
133
|
+
}
|
|
134
|
+
declare const defaultPreviewLabels: PreviewLabels;
|
|
135
|
+
|
|
136
|
+
interface PdfPreviewConfig {
|
|
137
|
+
/**
|
|
138
|
+
* Imports pdf.js. Without it, PDFs render in an `<iframe>` (the browser's own viewer) and `<PdfThumbnail>` renders
|
|
139
|
+
* its fallback. `loadPdfjs` from `@devultur/convex/pdfjs` imports the legacy build and sets a default worker.
|
|
140
|
+
*/
|
|
141
|
+
pdfjs?: PdfjsLoader;
|
|
142
|
+
/** URL of `pdf.worker.min.mjs`, set on `GlobalWorkerOptions.workerSrc` before a document opens. */
|
|
143
|
+
workerSrc?: string;
|
|
144
|
+
/** Labels for every `<Preview>` and `<PdfThumbnail>` below it; a component's own `labels` win over these. */
|
|
145
|
+
labels?: Partial<PreviewLabels>;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Renders the PDFs of every `<Preview>` and `<PdfThumbnail>` below it with pdf.js, on a `<canvas>`, and gives them
|
|
149
|
+
* default `labels`.
|
|
150
|
+
*
|
|
151
|
+
* @example
|
|
152
|
+
* import { loadPdfjs } from "@devultur/convex/pdfjs";
|
|
153
|
+
* <PdfPreviewProvider pdfjs={loadPdfjs}>{children}</PdfPreviewProvider>
|
|
154
|
+
*/
|
|
155
|
+
declare function PdfPreviewProvider({ pdfjs, workerSrc, labels, children, }: PdfPreviewConfig & {
|
|
156
|
+
children: React.ReactNode;
|
|
157
|
+
}): React.JSX.Element;
|
|
158
|
+
|
|
159
|
+
interface PdfThumbnailProps {
|
|
160
|
+
/** Typed reference. `null`/`undefined` → renders nothing. */
|
|
161
|
+
media: DevulturMedia | null | undefined;
|
|
162
|
+
/** The type you know the media has. Default: inferred from `media.filename`'s extension. */
|
|
163
|
+
contentType?: string;
|
|
164
|
+
/** CSS width of the thumbnail in pixels; the height follows the page. Default 96. */
|
|
165
|
+
width?: number;
|
|
166
|
+
/** Larger files render `fallback`. Default 50 MiB. */
|
|
167
|
+
maxBytes?: number;
|
|
168
|
+
/** Shown when the media isn't a PDF, pdf.js isn't available (no `PdfPreviewProvider`) or loading fails. */
|
|
169
|
+
fallback?: React.ReactNode;
|
|
170
|
+
/** Shown until page 1 is drawn, including while off-screen. Default: nothing. */
|
|
171
|
+
loading?: React.ReactNode;
|
|
172
|
+
/** Title the accessible name is built from (`labels.thumbnail`). Default: the filename. */
|
|
173
|
+
title?: string;
|
|
174
|
+
className?: string;
|
|
175
|
+
/** Overrides the provider's `workerSrc`. */
|
|
176
|
+
pdfWorkerSrc?: string;
|
|
177
|
+
/** Overrides `thumbnail` and `untitledPdf`; wins over `PdfPreviewProvider`'s `labels`. English by default. */
|
|
178
|
+
labels?: Partial<PreviewLabels>;
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Page 1 of a PDF on a small canvas, for lists and upload queues. Nothing is fetched until it nears the viewport.
|
|
182
|
+
* Needs pdf.js from a `PdfPreviewProvider`; without it, renders `fallback`.
|
|
183
|
+
*
|
|
184
|
+
* @example
|
|
185
|
+
* <PdfThumbnail media={row.file} width={64} fallback={<FileIcon />} />
|
|
186
|
+
*/
|
|
187
|
+
declare function PdfThumbnail(props: PdfThumbnailProps): React.JSX.Element | null;
|
|
188
|
+
|
|
189
|
+
interface PreviewProps {
|
|
190
|
+
/** Typed reference. `null`/`undefined` → renders nothing. */
|
|
191
|
+
media: DevulturMedia | null | undefined;
|
|
192
|
+
/** The type you know the media has (e.g. stored with it). Default: inferred from `media.filename`'s extension. */
|
|
193
|
+
contentType?: string;
|
|
194
|
+
/** Size cap for images, PDFs and audio; larger files render `fallback`. Default 50 MiB. */
|
|
195
|
+
maxBytes?: number;
|
|
196
|
+
/** Size cap for text previews. Default 1 MiB. */
|
|
197
|
+
maxTextBytes?: number;
|
|
198
|
+
/**
|
|
199
|
+
* Shown for types that don't preview inline and when the fetch fails. Default: a `<DownloadLink>`.
|
|
200
|
+
* As a function it receives why: a `MediaFetchError` when the fetch was refused (`http_error`, `too_large`,
|
|
201
|
+
* `type_mismatch`) or the type doesn't preview inline (`unsupported_type`, nothing fetched), `null` otherwise
|
|
202
|
+
* (a network failure, a PDF pdf.js can't open, or the link after the `maxPages` note).
|
|
203
|
+
*/
|
|
204
|
+
fallback?: React.ReactNode | ((error: MediaFetchError | null) => React.ReactNode);
|
|
205
|
+
/** Shown while a file loads. Default: `labels.loading`, visually hidden, in a `role="status"`. `null` → nothing. */
|
|
206
|
+
loading?: React.ReactNode;
|
|
207
|
+
/** Accessible name of the preview (`alt`, `title`, `aria-label`). Default: the filename (text: none). */
|
|
208
|
+
title?: string;
|
|
209
|
+
className?: string;
|
|
210
|
+
/**
|
|
211
|
+
* PDF pages rendered with pdf.js; past it, a "first N of M pages" note and `fallback`. Default 500 (a tiny file
|
|
212
|
+
* can declare millions of pages).
|
|
213
|
+
*/
|
|
214
|
+
maxPages?: number;
|
|
215
|
+
/** URL of pdf.js's worker for this preview; overrides `PdfPreviewProvider`'s `workerSrc`. */
|
|
216
|
+
pdfWorkerSrc?: string;
|
|
217
|
+
/** Overrides any string shown or announced; wins over `PdfPreviewProvider`'s `labels`. English by default. */
|
|
218
|
+
labels?: Partial<PreviewLabels>;
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Renders a media inline by its content type: images in an `<img>`, PDFs on `<canvas>` pages with pdf.js under a
|
|
222
|
+
* `PdfPreviewProvider` (otherwise in an `<iframe>`), audio in an `<audio>`,
|
|
223
|
+
* plain text, CSV and JSON in a `<pre>`, and video in the streaming `<VideoPlayer>`. Everything else (HTML, SVG,
|
|
224
|
+
* XML, unknown types) and every refused fetch render `fallback`. Private media is token-transparent.
|
|
225
|
+
*
|
|
226
|
+
* @example
|
|
227
|
+
* <Preview media={document.file} maxBytes={20_000_000} className="h-[80vh] w-full" />
|
|
228
|
+
*/
|
|
229
|
+
declare function Preview({ media, contentType, fallback, loading, labels, ...viewProps }: PreviewProps): React.JSX.Element | null;
|
|
230
|
+
|
|
231
|
+
/** How a content type renders inline: `<img>`, a PDF view (pdf.js canvases or `<iframe>`), `<audio>` or `<pre>`. */
|
|
232
|
+
type PreviewKind = "image" | "pdf" | "audio" | "text";
|
|
233
|
+
|
|
54
234
|
/** Transform/delivery options for media URLs (without `token`, which the context injects). */
|
|
55
235
|
type MediaRenderOptions = Omit<MediaUrlOptions, "token">;
|
|
56
236
|
type AnyAction = FunctionReference<"action">;
|
|
@@ -291,4 +471,4 @@ interface UseMediaUploadReturn {
|
|
|
291
471
|
*/
|
|
292
472
|
declare function useMediaUpload(options?: UseMediaUploadOptions): UseMediaUploadReturn;
|
|
293
473
|
|
|
294
|
-
export { type CaptionTrack, CropUpload, type CropUploadLabels, type CropUploadProps, type CropUploadRenderProps, DevulturProvider, DownloadLink, Image, type UploadedMedia, type UseMediaUploadOptions, type UseMediaUploadReturn, type VideoProps, type VideoState, useDevultur, useDevulturMedia, useDevulturProgress, useMediaUpload, useMediaUrl, useVideoProps, useVideoState };
|
|
474
|
+
export { type CaptionTrack, CropUpload, type CropUploadLabels, type CropUploadProps, type CropUploadRenderProps, DevulturProvider, DownloadLink, Image, MediaFetchError, type MediaFetchErrorCode, type MediaObjectUrl, type PdfPreviewConfig, PdfPreviewProvider, PdfThumbnail, type PdfThumbnailProps, PdfjsLoader, Preview, type PreviewKind, type PreviewLabels, type PreviewProps, type UploadedMedia, type UseMediaObjectUrlOptions, type UseMediaUploadOptions, type UseMediaUploadReturn, type VideoProps, type VideoState, defaultPreviewLabels, useDevultur, useDevulturMedia, useDevulturProgress, useMediaObjectUrl, useMediaUpload, useMediaUrl, useVideoProps, useVideoState };
|