@devultur/convex 1.5.0 → 1.5.2
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 +40 -3
- package/dist/chunk-AUAWZ5BD.js +65 -0
- package/dist/chunk-FOBFHC4A.js +55 -0
- package/dist/chunk-O76FU54F.js +0 -0
- package/dist/component/http.js +2 -2
- package/dist/component/library.d.ts +2 -2
- package/dist/component/library.js +3 -3
- package/dist/component/lock.js +3 -3
- package/dist/component/mediaClient.js +1 -1
- package/dist/component/metadata.js +2 -2
- package/dist/component/public.js +9 -9
- package/dist/component/uploads.js +6 -6
- package/dist/component/workerErrors.d.ts +1 -1
- package/dist/component/workerErrors.js +1 -1
- package/dist/contexts-6F8TJINX.d.ts +149 -0
- package/dist/index.d.ts +53 -16
- package/dist/index.js +1 -0
- package/dist/react/contexts.d.ts +6 -0
- package/dist/react/contexts.js +22 -0
- package/dist/react/hooks.d.ts +39 -0
- package/dist/react/hooks.js +28 -0
- package/dist/react.d.ts +125 -277
- package/dist/react.js +788 -829
- package/docs/guides/object-lock.md +517 -0
- package/docs/guides/plugins.md +205 -0
- package/docs/guides/self-host.md +523 -0
- package/docs/guides/video-player.md +105 -0
- package/package.json +10 -5
package/README.md
CHANGED
|
@@ -114,8 +114,11 @@ export const { createUploadUrl, confirmUpload, issueViewerToken, getMedia, listM
|
|
|
114
114
|
- `visibility: "internal"` registers them as internal functions, for your own actions and mutations to call.
|
|
115
115
|
- `config` takes what `Devultur.exposeApi` does (transcode preset, caption locales, upload lock and default
|
|
116
116
|
visibility).
|
|
117
|
-
- Each function keeps its arguments and
|
|
118
|
-
|
|
117
|
+
- Each function keeps its arguments and returns the component's result, typed: with no annotation,
|
|
118
|
+
`useQuery(api.devultur.getMedia, { key })` is a `MediaDocument | null` (a `MediaRecord` with its `_id` and
|
|
119
|
+
`_creationTime`), and `listMediaPage` and `searchMedia` are a `PaginationResult<MediaDocument>`.
|
|
120
|
+
`DevulturFunctionResults` names every function's result. `<DevulturProvider api={api.devultur}>` takes them as it
|
|
121
|
+
takes `exposeApi`'s, whose results are the same.
|
|
119
122
|
|
|
120
123
|
> **`Devultur.exposeApi` has no auth.** It returns the same functions with no guard: anyone who can reach your Convex
|
|
121
124
|
> deployment can create upload URLs, confirm uploads, list the whole library and delete media. It keeps working, but
|
|
@@ -273,6 +276,37 @@ function Avatar({ media, fallbackUrl }) {
|
|
|
273
276
|
}
|
|
274
277
|
```
|
|
275
278
|
|
|
279
|
+
`useDevulturOptional()` is non-null under a `<DevulturProvider>` given no `baseUrl` too, where the hooks that build
|
|
280
|
+
URLs throw. `useCanDeliver()` answers that instead: `true` only under a provider with a non-empty `baseUrl`.
|
|
281
|
+
|
|
282
|
+
```tsx
|
|
283
|
+
function Cover({ media, fallbackUrl }) {
|
|
284
|
+
if (!useCanDeliver() || !media) return <img src={fallbackUrl} alt="" />;
|
|
285
|
+
return <Image media={media} alt="" />;
|
|
286
|
+
}
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
Public media needs nothing more. Private media also needs the viewer token, which a provider may not have yet
|
|
290
|
+
(`lazyToken`) or can't issue (a `<DevulturViewerProvider>` without `issueViewerToken`); `tokenStatus` from
|
|
291
|
+
`useDevultur()` tells, and the components show their error state when it fails.
|
|
292
|
+
|
|
293
|
+
### Only the hooks
|
|
294
|
+
|
|
295
|
+
`@devultur/convex/react/hooks` exports the contexts and the hooks that only read them, without the components or
|
|
296
|
+
Convex's client: `useDevultur`, `useDevulturOptional`, `useCanDeliver`, `useDevulturApi`, `useDevulturProgress`,
|
|
297
|
+
`useMediaUrl`, `useVideoProps`, and the `DevulturContext`, `DevulturApiContext` and `DevulturProgressContext` objects.
|
|
298
|
+
The contexts and the hooks that only read them sit in a chunk of their own, apart from `useMediaUrl`, `useVideoProps`
|
|
299
|
+
and `@devultur/core`'s URL helpers, so a shared component that only checks for a provider adds under 0.5 kB gzipped,
|
|
300
|
+
even to the entry of an app that lazy-loads `@devultur/convex/react` on a route:
|
|
301
|
+
|
|
302
|
+
```tsx
|
|
303
|
+
import { useDevulturOptional } from "@devultur/convex/react/hooks";
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
They're the very objects `@devultur/convex/react` exports, so a `<DevulturProvider>` or `<DevulturViewerProvider>`
|
|
307
|
+
imported from there fills them. The hooks that call your Convex functions (`useDevulturMedia`, `useVideoState`,
|
|
308
|
+
`useMediaUpload`, `useMediaLibrary`) stay in `@devultur/convex/react`, with the components.
|
|
309
|
+
|
|
276
310
|
### When the viewer token can't be issued
|
|
277
311
|
|
|
278
312
|
`useDevultur()` tells where the viewer token stands:
|
|
@@ -290,7 +324,8 @@ there until `retryToken()`: a failed issuance isn't retried on its own. Meanwhil
|
|
|
290
324
|
receives it), and `<DownloadLink onDownloadError>` receives it on click. They load once `retryToken()` gets a token.
|
|
291
325
|
|
|
292
326
|
`<Preview>`'s video plays in `<VideoPlayer>` from `@devultur/react`, with its playback metrics on: anonymous, no
|
|
293
|
-
cookies, sent to the media host (see [Playback metrics](https://github.com/devultur/devultur/blob/main/docs/guides/video-player.md#playback-metrics)
|
|
327
|
+
cookies, sent to the media host (see [Playback metrics](https://github.com/devultur/devultur/blob/main/docs/guides/video-player.md#playback-metrics),
|
|
328
|
+
also in `node_modules/@devultur/convex/docs/guides/video-player.md`).
|
|
294
329
|
`<Preview metrics={false}>` turns them off. A private video shows `loading` until its viewer token is issued.
|
|
295
330
|
|
|
296
331
|
`<Preview>`'s video re-issues the token once when the delivery answers the playlist or a segment with a 401
|
|
@@ -796,6 +831,8 @@ what's enforced and the caveats.
|
|
|
796
831
|
## Documentation
|
|
797
832
|
|
|
798
833
|
Guides and API reference: [github.com/devultur/devultur/tree/main/docs](https://github.com/devultur/devultur/tree/main/docs).
|
|
834
|
+
The guides this README links to ship with the package, for the installed version, in
|
|
835
|
+
`node_modules/@devultur/convex/docs/guides/`: `video-player.md`, `self-host.md`, `plugins.md` and `object-lock.md`.
|
|
799
836
|
|
|
800
837
|
## License
|
|
801
838
|
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import {
|
|
2
|
+
useDevultur
|
|
3
|
+
} from "./chunk-FOBFHC4A.js";
|
|
4
|
+
|
|
5
|
+
// src/media-url-hooks.ts
|
|
6
|
+
import React2 from "react";
|
|
7
|
+
import {
|
|
8
|
+
buildMediaUrl,
|
|
9
|
+
CaptionLocaleLabel,
|
|
10
|
+
mediaCaptionPath,
|
|
11
|
+
mediaPath,
|
|
12
|
+
visibilityFromKey
|
|
13
|
+
} from "@devultur/core";
|
|
14
|
+
|
|
15
|
+
// src/request-token.ts
|
|
16
|
+
import React from "react";
|
|
17
|
+
function useRequestTokenWhen(session, isNeeded) {
|
|
18
|
+
const { requestToken, token } = session;
|
|
19
|
+
const hasToken = token !== null;
|
|
20
|
+
React.useEffect(() => {
|
|
21
|
+
if (isNeeded && !hasToken) requestToken();
|
|
22
|
+
}, [isNeeded, hasToken, requestToken]);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// src/media-url-hooks.ts
|
|
26
|
+
function useMediaUrl(key, options) {
|
|
27
|
+
const devultur = useDevultur();
|
|
28
|
+
const { token, baseUrl } = devultur;
|
|
29
|
+
useRequestTokenWhen(devultur, Boolean(key) && visibilityFromKey(key ?? "") !== "public");
|
|
30
|
+
return React2.useMemo(() => {
|
|
31
|
+
if (!key) return null;
|
|
32
|
+
if (!baseUrl) throw new Error("useMediaUrl requires `baseUrl` on <DevulturProvider>");
|
|
33
|
+
const isPublic = visibilityFromKey(key) === "public";
|
|
34
|
+
return buildMediaUrl(baseUrl, key, { ...options, token: isPublic ? void 0 : token ?? void 0 });
|
|
35
|
+
}, [key, baseUrl, token, options]);
|
|
36
|
+
}
|
|
37
|
+
function useVideoProps(media, options) {
|
|
38
|
+
const devultur = useDevultur();
|
|
39
|
+
const { token, baseUrl, sessionSecret } = devultur;
|
|
40
|
+
useRequestTokenWhen(devultur, media?.kind === "video" && media.visibility === "private");
|
|
41
|
+
const locales = options?.locales;
|
|
42
|
+
return React2.useMemo(() => {
|
|
43
|
+
if (media?.kind !== "video") return null;
|
|
44
|
+
if (!baseUrl) throw new Error("useVideoProps requires `baseUrl` on <DevulturProvider>");
|
|
45
|
+
const viewerToken = media.visibility === "private" ? token ?? void 0 : void 0;
|
|
46
|
+
const captions = (locales ?? []).flatMap((locale) => {
|
|
47
|
+
const path = mediaCaptionPath(media, locale);
|
|
48
|
+
if (!path) return [];
|
|
49
|
+
const label = CaptionLocaleLabel[locale] ?? locale;
|
|
50
|
+
return [{ locale, label, src: buildMediaUrl(baseUrl, path, { token: viewerToken }) }];
|
|
51
|
+
});
|
|
52
|
+
return {
|
|
53
|
+
src: buildMediaUrl(baseUrl, mediaPath(media), { token: viewerToken }),
|
|
54
|
+
token: viewerToken,
|
|
55
|
+
captions,
|
|
56
|
+
sessionSecret: media.visibility === "private" ? sessionSecret : void 0
|
|
57
|
+
};
|
|
58
|
+
}, [media, baseUrl, token, locales, sessionSecret]);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export {
|
|
62
|
+
useRequestTokenWhen,
|
|
63
|
+
useMediaUrl,
|
|
64
|
+
useVideoProps
|
|
65
|
+
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// src/capability-error.ts
|
|
2
|
+
var DevulturCapabilityError = class extends Error {
|
|
3
|
+
code = "capability_unavailable";
|
|
4
|
+
capability;
|
|
5
|
+
constructor(capability) {
|
|
6
|
+
super(
|
|
7
|
+
`Devultur: \`${capability}\` isn't available under <DevulturViewerProvider>. Use <DevulturProvider api> with the actions it needs.`
|
|
8
|
+
);
|
|
9
|
+
this.name = "DevulturCapabilityError";
|
|
10
|
+
this.capability = capability;
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
// src/devultur-context.ts
|
|
15
|
+
import React from "react";
|
|
16
|
+
var DevulturContext = /* @__PURE__ */ React.createContext(null);
|
|
17
|
+
var DevulturApiContext = /* @__PURE__ */ React.createContext(null);
|
|
18
|
+
function useDevultur() {
|
|
19
|
+
const context = React.use(DevulturContext);
|
|
20
|
+
if (!context) throw new Error("useDevultur must be used within <DevulturProvider> or <DevulturViewerProvider>");
|
|
21
|
+
return context;
|
|
22
|
+
}
|
|
23
|
+
function useDevulturOptional() {
|
|
24
|
+
return React.use(DevulturContext);
|
|
25
|
+
}
|
|
26
|
+
function useCanDeliver() {
|
|
27
|
+
return Boolean(React.use(DevulturContext)?.baseUrl);
|
|
28
|
+
}
|
|
29
|
+
function useDevulturApi() {
|
|
30
|
+
const api = React.use(DevulturApiContext);
|
|
31
|
+
if (api) return api;
|
|
32
|
+
if (React.use(DevulturContext)) throw new DevulturCapabilityError("api");
|
|
33
|
+
throw new Error("useDevulturApi must be used within <DevulturProvider>");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// src/progress-context.ts
|
|
37
|
+
import React2 from "react";
|
|
38
|
+
var DevulturProgressContext = /* @__PURE__ */ React2.createContext(null);
|
|
39
|
+
function useDevulturProgress() {
|
|
40
|
+
const context = React2.use(DevulturProgressContext);
|
|
41
|
+
if (!context) throw new Error("useDevulturProgress requires progress API in DevulturProvider");
|
|
42
|
+
return context;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export {
|
|
46
|
+
DevulturCapabilityError,
|
|
47
|
+
DevulturContext,
|
|
48
|
+
DevulturApiContext,
|
|
49
|
+
useDevultur,
|
|
50
|
+
useDevulturOptional,
|
|
51
|
+
useCanDeliver,
|
|
52
|
+
useDevulturApi,
|
|
53
|
+
DevulturProgressContext,
|
|
54
|
+
useDevulturProgress
|
|
55
|
+
};
|
|
File without changes
|
package/dist/component/http.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { isBearerOf } from "@devultur/core";
|
|
2
2
|
import { httpRouter } from "convex/server";
|
|
3
|
-
import { internal } from "./_generated/api";
|
|
4
|
-
import { env, httpAction } from "./_generated/server";
|
|
3
|
+
import { internal } from "./_generated/api.js";
|
|
4
|
+
import { env, httpAction } from "./_generated/server.js";
|
|
5
5
|
const http = httpRouter();
|
|
6
6
|
http.route({
|
|
7
7
|
path: "/webhook",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type PaginationOptions, type PaginationResult } from "convex/server";
|
|
2
|
-
import type { Doc } from "./_generated/dataModel";
|
|
3
|
-
import type { QueryCtx } from "./_generated/server";
|
|
2
|
+
import type { Doc } from "./_generated/dataModel.js";
|
|
3
|
+
import type { QueryCtx } from "./_generated/server.js";
|
|
4
4
|
type MediaType = Doc<"media">["type"];
|
|
5
5
|
export declare const BACKFILL_BATCH_SIZE: {
|
|
6
6
|
readonly default: 100;
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
// records from before 1.4.0 the `searchText` they were written without.
|
|
3
3
|
import { paginationOptsValidator } from "convex/server";
|
|
4
4
|
import { v } from "convex/values";
|
|
5
|
-
import { internal } from "./_generated/api";
|
|
6
|
-
import { internalMutation, mutation, query } from "./_generated/server";
|
|
7
|
-
import { missingSearchText } from "./mediaMeta";
|
|
5
|
+
import { internal } from "./_generated/api.js";
|
|
6
|
+
import { internalMutation, mutation, query } from "./_generated/server.js";
|
|
7
|
+
import { missingSearchText } from "./mediaMeta.js";
|
|
8
8
|
const mediaType = v.union(v.literal("video"), v.literal("image"), v.literal("file"));
|
|
9
9
|
/** Longest search kept: Convex matches at most 16 terms, so a longer query only costs. */
|
|
10
10
|
const SEARCH_MAX_LENGTH = 200;
|
package/dist/component/lock.js
CHANGED
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
// `lock_privilege_required`, `lock_conflict`, ...); see workerErrors.ts.
|
|
6
6
|
import { HOLD_PAGE_SIZE } from "@devultur/core";
|
|
7
7
|
import { v } from "convex/values";
|
|
8
|
-
import { derivativesValidator, retentionModeValidator } from "./lockValidators";
|
|
9
|
-
import { getMediaClient } from "./mediaClient";
|
|
10
|
-
import { workerAction } from "./workerErrors";
|
|
8
|
+
import { derivativesValidator, retentionModeValidator } from "./lockValidators.js";
|
|
9
|
+
import { getMediaClient } from "./mediaClient.js";
|
|
10
|
+
import { workerAction } from "./workerErrors.js";
|
|
11
11
|
const keysValidator = v.array(v.string());
|
|
12
12
|
export const getLock = workerAction({
|
|
13
13
|
args: { key: v.string() },
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// The component's client of the media Worker, from the env the app mounts the component with.
|
|
2
2
|
import { createWorkerClient } from "@devultur/core";
|
|
3
|
-
import { env } from "./_generated/server";
|
|
3
|
+
import { env } from "./_generated/server.js";
|
|
4
4
|
export function getMediaClient() {
|
|
5
5
|
return createWorkerClient(env.DEVULTUR_SECRET, env.DEVULTUR_API_URL);
|
|
6
6
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// Editing a media record's descriptive metadata (title, alt text, tags). The upload's own facts (size, dimensions,
|
|
2
2
|
// duration, page count) are recorded by `uploads.ts` and the transcode webhook, never edited here.
|
|
3
3
|
import { v } from "convex/values";
|
|
4
|
-
import { mutation } from "./_generated/server";
|
|
5
|
-
import { mediaMetaPatch, searchTextOf } from "./mediaMeta";
|
|
4
|
+
import { mutation } from "./_generated/server.js";
|
|
5
|
+
import { mediaMetaPatch, searchTextOf } from "./mediaMeta.js";
|
|
6
6
|
/**
|
|
7
7
|
* Sets a record's `title`, `alt` and/or `tags` (only the ones passed). Trimmed; an empty title or alt clears it, and
|
|
8
8
|
* tags are deduplicated. Throws `invalid_media_metadata` past the limits (title 200, alt 1000, 20 tags of 50). Returns
|
package/dist/component/public.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { DEFAULT_TOKEN_EXPIRY_SECONDS, extractId, MediaEventName, MediaStatus } from "@devultur/core";
|
|
2
2
|
import { paginationOptsValidator } from "convex/server";
|
|
3
3
|
import { v } from "convex/values";
|
|
4
|
-
import { api, internal } from "./_generated/api";
|
|
5
|
-
import { internalMutation, mutation, query } from "./_generated/server";
|
|
6
|
-
import { listNewestFirst } from "./library";
|
|
7
|
-
import { objectLockValidator } from "./lockValidators";
|
|
8
|
-
import { getMediaClient } from "./mediaClient";
|
|
9
|
-
import { missingSearchText, searchTextOf } from "./mediaMeta";
|
|
10
|
-
import { transcodeOutputFields } from "./schema";
|
|
11
|
-
import { UPLOAD_CONFIRM_BACKSTOP_MS } from "./uploads";
|
|
12
|
-
import { workerAction } from "./workerErrors";
|
|
4
|
+
import { api, internal } from "./_generated/api.js";
|
|
5
|
+
import { internalMutation, mutation, query } from "./_generated/server.js";
|
|
6
|
+
import { listNewestFirst } from "./library.js";
|
|
7
|
+
import { objectLockValidator } from "./lockValidators.js";
|
|
8
|
+
import { getMediaClient } from "./mediaClient.js";
|
|
9
|
+
import { missingSearchText, searchTextOf } from "./mediaMeta.js";
|
|
10
|
+
import { transcodeOutputFields } from "./schema.js";
|
|
11
|
+
import { UPLOAD_CONFIRM_BACKSTOP_MS } from "./uploads.js";
|
|
12
|
+
import { workerAction } from "./workerErrors.js";
|
|
13
13
|
// Webhook path RELATIVE to the component. Convex gives the component a `CONVEX_SITE_URL` that ALREADY
|
|
14
14
|
// includes its mount prefix (e.g. `${site}/devultur`), so `${CONVEX_SITE_URL}/webhook` resolves to the
|
|
15
15
|
// mounted route (`${site}/devultur/webhook`) without duplicating the prefix. Do NOT add `/devultur/` here
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { extractId, sanitizeFilename } from "@devultur/core";
|
|
2
2
|
import { ConvexError, v } from "convex/values";
|
|
3
|
-
import { internal } from "./_generated/api";
|
|
4
|
-
import { internalAction, internalMutation } from "./_generated/server";
|
|
5
|
-
import { getMediaClient } from "./mediaClient";
|
|
6
|
-
import { searchTextOf, validUploadHints } from "./mediaMeta";
|
|
7
|
-
import { parseUploadKey } from "./uploadKeys";
|
|
8
|
-
import { workerAction } from "./workerErrors";
|
|
3
|
+
import { internal } from "./_generated/api.js";
|
|
4
|
+
import { internalAction, internalMutation } from "./_generated/server.js";
|
|
5
|
+
import { getMediaClient } from "./mediaClient.js";
|
|
6
|
+
import { searchTextOf, validUploadHints } from "./mediaMeta.js";
|
|
7
|
+
import { parseUploadKey } from "./uploadKeys.js";
|
|
8
|
+
import { workerAction } from "./workerErrors.js";
|
|
9
9
|
// Past the Worker's longest upload window (a multipart upload can run for 24 hours), so the scheduled check never runs
|
|
10
10
|
// while the bytes are still on their way.
|
|
11
11
|
export const UPLOAD_CONFIRM_BACKSTOP_MS = 25 * 60 * 60 * 1000;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type Value } from "convex/values";
|
|
2
|
-
import { action } from "./_generated/server";
|
|
2
|
+
import { action } from "./_generated/server.js";
|
|
3
3
|
/** `data` of the `ConvexError` a component action throws when the Worker refuses a request. */
|
|
4
4
|
export interface DevulturErrorData {
|
|
5
5
|
/** The Worker's stable code (`retention_active`, `file_too_large`, ...), or `worker_error` when it sent none. */
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// (`retention_active`, `lock_conflict`, `compliance_extend_only`, ...) and `error.data.details`.
|
|
5
5
|
import { DevulturApiError } from "@devultur/core";
|
|
6
6
|
import { ConvexError } from "convex/values";
|
|
7
|
-
import { action } from "./_generated/server";
|
|
7
|
+
import { action } from "./_generated/server.js";
|
|
8
8
|
function isApiError(error) {
|
|
9
9
|
if (error instanceof DevulturApiError)
|
|
10
10
|
return true;
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { DevulturMedia, MediaUrlOptions, Visibility } from '@devultur/core';
|
|
3
|
+
import { UploadProgress } from '@devultur/react';
|
|
4
|
+
import { FunctionReference } from 'convex/server';
|
|
5
|
+
import { Value } from 'convex/values';
|
|
6
|
+
|
|
7
|
+
/** What a viewer token action returns: the token with its lifetime, or the bare token (default lifetime). */
|
|
8
|
+
type ViewerTokenResult = {
|
|
9
|
+
token: string;
|
|
10
|
+
expiresIn: number;
|
|
11
|
+
} | string;
|
|
12
|
+
/** Extra arguments sent with `sessionSecret` to the viewer token action. */
|
|
13
|
+
type ViewerTokenArgs = Record<string, Value>;
|
|
14
|
+
/**
|
|
15
|
+
* Where the viewer token for the current `tokenArgs` stands: not requested yet, being issued (no token yet), held, or
|
|
16
|
+
* failed (the action threw or returned no token). A renewal in flight, or one that failed, keeps `"ready"`.
|
|
17
|
+
*/
|
|
18
|
+
type TokenStatus = "idle" | "loading" | "ready" | "error";
|
|
19
|
+
|
|
20
|
+
/** Transform/delivery options for media URLs (without `token`, which the context injects). */
|
|
21
|
+
type MediaRenderOptions = Omit<MediaUrlOptions, "token">;
|
|
22
|
+
/**
|
|
23
|
+
* The viewer token action: the component's `issueViewerToken` (as `defineDevulturFunctions` or `exposeApi` export it,
|
|
24
|
+
* where `sessionSecret` is optional) or an app's own, which may require it. The providers always send it.
|
|
25
|
+
*/
|
|
26
|
+
type ViewerTokenAction = FunctionReference<"action", "public", {
|
|
27
|
+
sessionSecret?: string;
|
|
28
|
+
}>;
|
|
29
|
+
interface MediaSession {
|
|
30
|
+
token: string | null;
|
|
31
|
+
/** Where the viewer token stands; `"error"` makes private previews fail fast with `token_unavailable`. */
|
|
32
|
+
tokenStatus: TokenStatus;
|
|
33
|
+
/** Why the last issuance failed (the action threw or returned no token), while `tokenStatus` is `"error"`. */
|
|
34
|
+
tokenError: Error | null;
|
|
35
|
+
/** Issues the viewer token again (after a failure, for example). Resolves to the new token, or `null`. */
|
|
36
|
+
retryToken: () => Promise<string | null>;
|
|
37
|
+
/**
|
|
38
|
+
* Asks for the viewer token where it's issued lazily (`<DevulturViewerProvider lazyToken>`): the first private media
|
|
39
|
+
* shown calls it, and so must code that reads `token` itself. A no-op once asked, and where the token is issued
|
|
40
|
+
* right away (the default).
|
|
41
|
+
*/
|
|
42
|
+
requestToken: () => void;
|
|
43
|
+
baseUrl: string | null;
|
|
44
|
+
/** Browser session secret → sent in `${SESSION_HEADER}` on every private delivery. */
|
|
45
|
+
sessionSecret: string;
|
|
46
|
+
/** Delivery URL from a reference (non-hook → usable in handlers and `.map`). Token-transparent. */
|
|
47
|
+
url: (media: DevulturMedia, options?: MediaRenderOptions) => string;
|
|
48
|
+
/** Downloads a video's subtitle text for a locale (auth-transparent). */
|
|
49
|
+
getCaptionsText: (media: DevulturMedia, locale: string) => Promise<string>;
|
|
50
|
+
/**
|
|
51
|
+
* Re-issues the viewer token (new session fingerprint with the current UA/IP) and updates the context.
|
|
52
|
+
* Single-flight: concurrent calls share the same issuance. Returns the new token (or null if it
|
|
53
|
+
* failed). The render uses it to transparently recover from a 401 `session_mismatch`.
|
|
54
|
+
*/
|
|
55
|
+
reissueToken: () => Promise<string | null>;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
type AnyAction = FunctionReference<"action">;
|
|
59
|
+
type AnyMutation = FunctionReference<"mutation">;
|
|
60
|
+
type AnyQuery = FunctionReference<"query">;
|
|
61
|
+
interface DevulturApi {
|
|
62
|
+
issueViewerToken: AnyAction;
|
|
63
|
+
createUploadUrl: AnyAction;
|
|
64
|
+
/** Records an upload in the component after its PUT, so it shows in `listMedia`. Optional (uploads still work). */
|
|
65
|
+
confirmUpload?: AnyAction;
|
|
66
|
+
deleteMedia: AnyAction;
|
|
67
|
+
/** Deletes the full folder of a video (raw/hls/thumbnails/captions). Required to delete videos. */
|
|
68
|
+
deleteVideo?: AnyAction;
|
|
69
|
+
/** Requests the transcode of an uploaded video. Required by `useMediaUpload` for auto-process. */
|
|
70
|
+
processVideo?: AnyAction;
|
|
71
|
+
getMedia?: AnyQuery;
|
|
72
|
+
/** The library, newest first. Required by `useMediaLibrary` and `<MediaLibrary>`. */
|
|
73
|
+
listMediaPage?: AnyQuery;
|
|
74
|
+
/** The library matching a search. Required by `useMediaLibrary` and `<MediaLibrary>` to search. */
|
|
75
|
+
searchMedia?: AnyQuery;
|
|
76
|
+
saveProgress?: AnyMutation;
|
|
77
|
+
loadProgress?: AnyQuery;
|
|
78
|
+
markComplete?: AnyMutation;
|
|
79
|
+
resetProgress?: AnyMutation;
|
|
80
|
+
}
|
|
81
|
+
interface UploadOptions {
|
|
82
|
+
/**
|
|
83
|
+
* Visibility override for this resource. Omitted → `config.upload.visibility` on your server, else the Worker's
|
|
84
|
+
* default by type. Your guard can still rewrite it.
|
|
85
|
+
*/
|
|
86
|
+
visibility?: Visibility;
|
|
87
|
+
/** Progress of the PUT: bytes sent, total bytes and their fraction (0 to 1). */
|
|
88
|
+
onProgress?: (progress: UploadProgress) => void;
|
|
89
|
+
/** Aborts the PUT → `upload()` rejects with an `AbortError`. */
|
|
90
|
+
signal?: AbortSignal;
|
|
91
|
+
/** A PDF's page count to record with it, a hint (`countPdfPages` from `@devultur/convex/pdfjs` counts it). */
|
|
92
|
+
pageCount?: number;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* The reference `upload()` resolves to: a `DevulturMedia` whose `key` is always set (the uploaded object's key; a
|
|
96
|
+
* video's raw file, what `processVideo` takes). It stores as-is in a `dv.media()` field.
|
|
97
|
+
*/
|
|
98
|
+
type UploadedMedia = DevulturMedia & {
|
|
99
|
+
readonly key: string;
|
|
100
|
+
};
|
|
101
|
+
/**
|
|
102
|
+
* What `useDevultur()` returns. Under a `<DevulturViewerProvider>`, `uploadUrl`, `upload` and `deleteMedia` throw a
|
|
103
|
+
* `DevulturCapabilityError` (`capability_unavailable`).
|
|
104
|
+
*/
|
|
105
|
+
interface DevulturContextValue extends MediaSession {
|
|
106
|
+
uploadUrl: (file: File) => Promise<{
|
|
107
|
+
url: string;
|
|
108
|
+
key: string;
|
|
109
|
+
}>;
|
|
110
|
+
/** Uploads a file (reporting progress), records it in the component, and returns the reference ready to save. */
|
|
111
|
+
upload: (file: File, options?: UploadOptions) => Promise<UploadedMedia>;
|
|
112
|
+
/** Deletes a media by reference: video → full folder; image/file → the object. */
|
|
113
|
+
deleteMedia: (media: DevulturMedia) => void;
|
|
114
|
+
}
|
|
115
|
+
declare const DevulturContext: React.Context<DevulturContextValue | null>;
|
|
116
|
+
declare const DevulturApiContext: React.Context<DevulturApi | null>;
|
|
117
|
+
declare function useDevultur(): DevulturContextValue;
|
|
118
|
+
/**
|
|
119
|
+
* `useDevultur()` for components that also render outside a provider: the same value under `<DevulturProvider>` or
|
|
120
|
+
* `<DevulturViewerProvider>`, and `null` outside both instead of throwing.
|
|
121
|
+
*/
|
|
122
|
+
declare function useDevulturOptional(): DevulturContextValue | null;
|
|
123
|
+
/**
|
|
124
|
+
* Whether the media hooks can build delivery URLs here: `true` under `<DevulturProvider>` or `<DevulturViewerProvider>`
|
|
125
|
+
* with a non-empty `baseUrl`; `false` outside both, or under a `<DevulturProvider>` without one, where `useMediaUrl`,
|
|
126
|
+
* `useVideoProps` and `url()` throw. That's all a public media needs. A private media also needs the viewer token,
|
|
127
|
+
* which the provider may not have yet (lazy issuance) or can't issue (no `issueViewerToken`): `tokenStatus` from
|
|
128
|
+
* `useDevultur()` tells, and the components handle it themselves.
|
|
129
|
+
*/
|
|
130
|
+
declare function useCanDeliver(): boolean;
|
|
131
|
+
/**
|
|
132
|
+
* The `api` passed to `<DevulturProvider>`: your app's exported Devultur functions. Under a `<DevulturViewerProvider>`
|
|
133
|
+
* it throws a `DevulturCapabilityError`.
|
|
134
|
+
*/
|
|
135
|
+
declare function useDevulturApi(): DevulturApi;
|
|
136
|
+
|
|
137
|
+
interface DevulturProgressContextValue {
|
|
138
|
+
save: (videoId: string, position: number) => Promise<void>;
|
|
139
|
+
load: (videoId: string) => Promise<{
|
|
140
|
+
lastPosition: number;
|
|
141
|
+
completedAt: Date | null;
|
|
142
|
+
} | null>;
|
|
143
|
+
markComplete: (videoId: string) => Promise<void>;
|
|
144
|
+
reset: (videoId: string) => Promise<void>;
|
|
145
|
+
}
|
|
146
|
+
declare const DevulturProgressContext: React.Context<DevulturProgressContextValue | null>;
|
|
147
|
+
declare function useDevulturProgress(): DevulturProgressContextValue;
|
|
148
|
+
|
|
149
|
+
export { type DevulturApi as D, type MediaRenderOptions as M, type TokenStatus as T, type UploadOptions as U, type ViewerTokenAction as V, type ViewerTokenArgs as a, DevulturApiContext as b, DevulturContext as c, type DevulturContextValue as d, DevulturProgressContext as e, type DevulturProgressContextValue as f, type UploadedMedia as g, type ViewerTokenResult as h, useDevultur as i, useDevulturApi as j, useDevulturOptional as k, useDevulturProgress as l, useCanDeliver as u };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { ObjectLock, LockView, SetLockOptions, HoldPage, DevulturMedia } from '@devultur/core';
|
|
1
|
+
import { WorkerUploadResult, DeleteVideoResult, TranscodeJobResult, ObjectLock, LockView, SetLockOptions, HoldPage, DevulturMedia } from '@devultur/core';
|
|
2
2
|
export { CaptionLocale, DevulturConfig, DevulturKind, DevulturMedia, ImageFit, ImageFormat, SecurityLevel, SpeechProvider, StorageProvider, TranscodePreset, TranscodeProvider, TranslationProvider, VideoModuleConfig, Visibility } from '@devultur/core';
|
|
3
|
-
import { FunctionReference, FunctionVisibility, DefaultFunctionArgs, GenericQueryCtx, GenericDataModel, GenericMutationCtx, GenericActionCtx, RegisteredQuery, RegisteredMutation, RegisteredAction, PaginationOptions
|
|
3
|
+
import { FunctionReference, PaginationResult, FunctionVisibility, DefaultFunctionArgs, GenericQueryCtx, GenericDataModel, GenericMutationCtx, GenericActionCtx, RegisteredQuery, RegisteredMutation, RegisteredAction, PaginationOptions } from 'convex/server';
|
|
4
4
|
import { PropertyValidators, Value, ConvexError } from 'convex/values';
|
|
5
|
-
import {
|
|
6
|
-
export { a as DevulturUploadConfig
|
|
5
|
+
import { M as MediaRecord, P as ProgressRecord, D as DevulturComponentConfig } from './types-C0ruHUcu.js';
|
|
6
|
+
export { a as DevulturUploadConfig } from './types-C0ruHUcu.js';
|
|
7
7
|
export { a as DEVULTUR_ERROR_CODES, b as DevulturErrorCode, c as DevulturErrorDetails, d as DevulturErrorLabel, D as DevulturErrorLabels, e as describeDevulturError, f as devulturErrorLabels, i as isDevulturErrorCode } from './error-labels-D0Vbp-qA.js';
|
|
8
8
|
export { dv } from './validators.js';
|
|
9
9
|
|
|
@@ -188,6 +188,42 @@ type ComponentApi<Name extends string | undefined = string | undefined> = {
|
|
|
188
188
|
};
|
|
189
189
|
};
|
|
190
190
|
|
|
191
|
+
/** Convex's system fields on a document a component returns. Its `_id` is a plain string in your app. */
|
|
192
|
+
interface ComponentSystemFields {
|
|
193
|
+
_id: string;
|
|
194
|
+
_creationTime: number;
|
|
195
|
+
}
|
|
196
|
+
/** A media record as the component's functions return it: the stored fields and the system fields. */
|
|
197
|
+
interface MediaDocument extends MediaRecord, ComponentSystemFields {
|
|
198
|
+
}
|
|
199
|
+
/** A watch-progress record as the component's functions return it. */
|
|
200
|
+
interface ProgressDocument extends ProgressRecord, ComponentSystemFields {
|
|
201
|
+
}
|
|
202
|
+
/** The result of each exposed function, by name. */
|
|
203
|
+
interface DevulturFunctionResults {
|
|
204
|
+
issueViewerToken: {
|
|
205
|
+
token: string;
|
|
206
|
+
expiresIn: number;
|
|
207
|
+
};
|
|
208
|
+
createUploadUrl: WorkerUploadResult;
|
|
209
|
+
/** The record, or `null` when nothing is stored at the key. */
|
|
210
|
+
confirmUpload: MediaDocument | null;
|
|
211
|
+
/** The updated record, or `null` when there is none. */
|
|
212
|
+
updateMediaMeta: MediaDocument | null;
|
|
213
|
+
deleteMedia: null;
|
|
214
|
+
deleteVideo: DeleteVideoResult;
|
|
215
|
+
processVideo: TranscodeJobResult;
|
|
216
|
+
getMedia: MediaDocument | null;
|
|
217
|
+
listMediaPage: PaginationResult<MediaDocument>;
|
|
218
|
+
searchMedia: PaginationResult<MediaDocument>;
|
|
219
|
+
/** The progress record's id. */
|
|
220
|
+
saveProgress: string;
|
|
221
|
+
loadProgress: ProgressDocument | null;
|
|
222
|
+
/** The progress record's id. */
|
|
223
|
+
markComplete: string;
|
|
224
|
+
resetProgress: null;
|
|
225
|
+
}
|
|
226
|
+
|
|
191
227
|
type FunctionKind = "query" | "mutation" | "action";
|
|
192
228
|
type ContextOf<Kind extends FunctionKind> = Kind extends "query" ? GenericQueryCtx<GenericDataModel> : Kind extends "mutation" ? GenericMutationCtx<GenericDataModel> : GenericActionCtx<GenericDataModel>;
|
|
193
229
|
/** What a guard tells the function about its caller. */
|
|
@@ -283,10 +319,10 @@ declare function exposedFunctions(component: ComponentApi, config?: DevulturComp
|
|
|
283
319
|
};
|
|
284
320
|
type ExposedFunctions = ReturnType<typeof exposedFunctions>;
|
|
285
321
|
type ExposedFunctionName = keyof ExposedFunctions;
|
|
286
|
-
type Registered<Fn, Visibility extends FunctionVisibility> = Fn extends ExposedFunction<"query", infer Args,
|
|
322
|
+
type Registered<Fn, Visibility extends FunctionVisibility, Result> = Fn extends ExposedFunction<"query", infer Args, unknown> ? RegisteredQuery<Visibility, Args, Promise<Result>> : Fn extends ExposedFunction<"mutation", infer Args, unknown> ? RegisteredMutation<Visibility, Args, Promise<Result>> : Fn extends ExposedFunction<"action", infer Args, unknown> ? RegisteredAction<Visibility, Args, Promise<Result>> : never;
|
|
287
323
|
/** The Convex functions registered from `ExposedFunctions`, each with its own args and result. */
|
|
288
324
|
type RegisteredFunctions<Visibility extends FunctionVisibility> = {
|
|
289
|
-
[Name in ExposedFunctionName]: Registered<ExposedFunctions[Name], Visibility>;
|
|
325
|
+
[Name in ExposedFunctionName]: Registered<ExposedFunctions[Name], Visibility, DevulturFunctionResults[Name]>;
|
|
290
326
|
};
|
|
291
327
|
|
|
292
328
|
type DevulturComponent = ComponentApi;
|
|
@@ -329,34 +365,35 @@ declare class Devultur {
|
|
|
329
365
|
/** The mounted component (`components.devultur`) this client calls. */
|
|
330
366
|
readonly component: DevulturComponent;
|
|
331
367
|
constructor(component: DevulturComponent);
|
|
332
|
-
getMedia(ctx: QueryCtx, key: string): Promise<
|
|
368
|
+
getMedia(ctx: QueryCtx, key: string): Promise<MediaDocument | null>;
|
|
369
|
+
/** Returns the progress record's id. */
|
|
333
370
|
saveProgress(ctx: MutationCtx, args: {
|
|
334
371
|
userId: string;
|
|
335
372
|
videoId: string;
|
|
336
373
|
position: number;
|
|
337
|
-
}): Promise<
|
|
374
|
+
}): Promise<string>;
|
|
338
375
|
loadProgress(ctx: QueryCtx, args: {
|
|
339
376
|
userId: string;
|
|
340
377
|
videoId: string;
|
|
341
|
-
}): Promise<
|
|
378
|
+
}): Promise<ProgressDocument | null>;
|
|
342
379
|
/** An upload URL from your server, optionally locked (retention and/or hold, written with the bytes). */
|
|
343
380
|
createUploadUrl(ctx: ActionCtx, args: UploadUrlArgs & {
|
|
344
381
|
lock?: ObjectLock;
|
|
345
382
|
hold?: boolean;
|
|
346
|
-
}): Promise<
|
|
383
|
+
}): Promise<DevulturFunctionResults["createUploadUrl"]>;
|
|
347
384
|
/**
|
|
348
385
|
* Records an upload in the component once its bytes are stored (after the PUT): it then shows in `listMedia`.
|
|
349
386
|
* Returns the record, or `null` when nothing is stored at `key`. Uploads nobody confirms are recorded a day later.
|
|
350
387
|
* `width`, `height` and `pageCount` are the client's hints (positive integers up to 100,000); an image size the
|
|
351
388
|
* Worker read from the bytes wins over them.
|
|
352
389
|
*/
|
|
353
|
-
confirmUpload(ctx: ActionCtx, args: ConfirmUploadArgs): Promise<
|
|
390
|
+
confirmUpload(ctx: ActionCtx, args: ConfirmUploadArgs): Promise<MediaDocument | null>;
|
|
354
391
|
/**
|
|
355
392
|
* Sets a record's `title`, `alt` and/or `tags` (only the ones passed; an empty string clears a title or alt, `[]`
|
|
356
393
|
* the tags). `key` is the media id. Trimmed and deduplicated; past title 200, alt 1000 or 20 tags of 50 characters
|
|
357
394
|
* it throws `invalid_media_metadata`. Returns the updated record, or `null` when there is none.
|
|
358
395
|
*/
|
|
359
|
-
updateMediaMeta(ctx: MutationCtx, args: MediaMetaArgs): Promise<
|
|
396
|
+
updateMediaMeta(ctx: MutationCtx, args: MediaMetaArgs): Promise<MediaDocument | null>;
|
|
360
397
|
/**
|
|
361
398
|
* The recorded media (every confirmed upload, and every video sent to `processVideo`), optionally of one type.
|
|
362
399
|
* `mediaFromRecord(record)` turns a row into the reference `<Image>` and `dv.media()` take. Unbounded: it reads every
|
|
@@ -364,15 +401,15 @@ declare class Devultur {
|
|
|
364
401
|
*/
|
|
365
402
|
listMedia(ctx: QueryCtx, args?: {
|
|
366
403
|
type?: MediaType;
|
|
367
|
-
}): Promise<
|
|
404
|
+
}): Promise<MediaDocument[]>;
|
|
368
405
|
/** One page of the recorded media, newest first, optionally of one type. Takes Convex's `paginationOpts`. */
|
|
369
|
-
listMediaPage(ctx: QueryCtx, args: MediaPageArgs): Promise<PaginationResult<
|
|
406
|
+
listMediaPage(ctx: QueryCtx, args: MediaPageArgs): Promise<PaginationResult<MediaDocument>>;
|
|
370
407
|
/**
|
|
371
408
|
* One page of the records whose title or filename matches `search` (its last word as a prefix), best match first,
|
|
372
409
|
* optionally of one type. A blank `search` lists newest first, like `listMediaPage`. Records from before 1.4.0
|
|
373
410
|
* match once `backfillSearchText` (or any write) gives them a `searchText`.
|
|
374
411
|
*/
|
|
375
|
-
searchMedia(ctx: QueryCtx, args: SearchMediaArgs): Promise<PaginationResult<
|
|
412
|
+
searchMedia(ctx: QueryCtx, args: SearchMediaArgs): Promise<PaginationResult<MediaDocument>>;
|
|
376
413
|
/**
|
|
377
414
|
* One-off for records written before 1.4.0: gives every record without a `searchText` one, `batchSize` records
|
|
378
415
|
* per mutation (default 100, at most 1000). Runs the first batch and schedules the rest. Safe to run again.
|
|
@@ -468,4 +505,4 @@ declare function defineDevulturFunctions<Visibility extends FunctionVisibility =
|
|
|
468
505
|
*/
|
|
469
506
|
declare function mediaFromRecord(record: MediaRecord): DevulturMedia | null;
|
|
470
507
|
|
|
471
|
-
export { type CallerContext, Devultur, type DevulturArgsRewrite, DevulturComponentConfig, type DevulturErrorData, type DevulturFunctionsOptions, type DevulturGuard, type DevulturGuardCtx, type DevulturGuardResult, type DevulturOperation, MediaRecord, type RegisteredFunctions, defineDevulturFunctions, devulturErrorOf, isDevulturError, mediaFromRecord };
|
|
508
|
+
export { type CallerContext, type ComponentSystemFields, Devultur, type DevulturArgsRewrite, DevulturComponentConfig, type DevulturErrorData, type DevulturFunctionResults, type DevulturFunctionsOptions, type DevulturGuard, type DevulturGuardCtx, type DevulturGuardResult, type DevulturOperation, type MediaDocument, MediaRecord, type ProgressDocument, ProgressRecord, type RegisteredFunctions, defineDevulturFunctions, devulturErrorOf, isDevulturError, mediaFromRecord };
|
package/dist/index.js
CHANGED
|
@@ -186,6 +186,7 @@ var Devultur = class {
|
|
|
186
186
|
async getMedia(ctx, key) {
|
|
187
187
|
return ctx.runQuery(this.component.public.getMediaByKey, { key });
|
|
188
188
|
}
|
|
189
|
+
/** Returns the progress record's id. */
|
|
189
190
|
async saveProgress(ctx, args) {
|
|
190
191
|
return ctx.runMutation(this.component.public.saveProgress, args);
|
|
191
192
|
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { D as DevulturApi, b as DevulturApiContext, c as DevulturContext, d as DevulturContextValue, e as DevulturProgressContext, f as DevulturProgressContextValue, U as UploadOptions, g as UploadedMedia, u as useCanDeliver, i as useDevultur, j as useDevulturApi, k as useDevulturOptional, l as useDevulturProgress } from '../contexts-6F8TJINX.js';
|
|
2
|
+
import 'react';
|
|
3
|
+
import '@devultur/core';
|
|
4
|
+
import '@devultur/react';
|
|
5
|
+
import 'convex/server';
|
|
6
|
+
import 'convex/values';
|