@devultur/convex 1.0.2 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/README.md +151 -5
  2. package/dist/bin/init.js +9 -2
  3. package/dist/{chunk-FHWT3GD6.js → chunk-GKLFRSGI.js} +2 -0
  4. package/dist/component/_generated/api.d.ts +12 -0
  5. package/dist/component/_generated/api.d.ts.map +1 -1
  6. package/dist/component/_generated/api.js.map +1 -1
  7. package/dist/component/_generated/component.d.ts +55 -1
  8. package/dist/component/_generated/component.d.ts.map +1 -1
  9. package/dist/component/_generated/server.d.ts +1 -1
  10. package/dist/component/_generated/server.d.ts.map +1 -1
  11. package/dist/component/lock.d.ts +33 -0
  12. package/dist/component/lock.d.ts.map +1 -0
  13. package/dist/component/lock.js +47 -0
  14. package/dist/component/lock.js.map +1 -0
  15. package/dist/component/lockValidators.d.ts +25 -0
  16. package/dist/component/lockValidators.d.ts.map +1 -0
  17. package/dist/component/lockValidators.js +14 -0
  18. package/dist/component/lockValidators.js.map +1 -0
  19. package/dist/component/mediaClient.d.ts +3 -0
  20. package/dist/component/mediaClient.d.ts.map +1 -0
  21. package/dist/component/mediaClient.js +7 -0
  22. package/dist/component/mediaClient.js.map +1 -0
  23. package/dist/component/public.d.ts +78 -0
  24. package/dist/component/public.d.ts.map +1 -1
  25. package/dist/component/public.js +83 -19
  26. package/dist/component/public.js.map +1 -1
  27. package/dist/component/schema.d.ts +12 -1
  28. package/dist/component/schema.d.ts.map +1 -1
  29. package/dist/component/schema.js +13 -1
  30. package/dist/component/schema.js.map +1 -1
  31. package/dist/component/uploadKeys.d.ts +14 -0
  32. package/dist/component/uploadKeys.d.ts.map +1 -0
  33. package/dist/component/uploadKeys.js +23 -0
  34. package/dist/component/uploadKeys.js.map +1 -0
  35. package/dist/component/uploads.d.ts +119 -0
  36. package/dist/component/uploads.d.ts.map +1 -0
  37. package/dist/component/uploads.js +106 -0
  38. package/dist/component/uploads.js.map +1 -0
  39. package/dist/component/workerErrors.d.ts +17 -0
  40. package/dist/component/workerErrors.d.ts.map +1 -0
  41. package/dist/component/workerErrors.js +41 -0
  42. package/dist/component/workerErrors.js.map +1 -0
  43. package/dist/index.d.ts +147 -6
  44. package/dist/index.js +102 -4
  45. package/dist/react.d.ts +18 -5
  46. package/dist/react.js +35 -13
  47. package/dist/validators.d.ts +5 -3
  48. package/dist/validators.js +1 -1
  49. package/package.json +8 -3
@@ -0,0 +1,119 @@
1
+ export declare const UPLOAD_CONFIRM_BACKSTOP_MS: number;
2
+ /**
3
+ * Records an upload in the component's `media` table once its bytes are stored (call it after the PUT). Returns the
4
+ * record, or `null` when nothing is stored at `key`. Idempotent: confirming twice updates the same record, and a
5
+ * video's transcode state is left as it is.
6
+ */
7
+ export declare const confirmUpload: import("convex/server").RegisteredAction<"public", {
8
+ width?: number | undefined;
9
+ height?: number | undefined;
10
+ filename?: string | undefined;
11
+ key: string;
12
+ }, Promise<{
13
+ _id: import("convex/values").GenericId<"media">;
14
+ _creationTime: number;
15
+ width?: number | undefined;
16
+ height?: number | undefined;
17
+ visibility?: "public" | "private" | undefined;
18
+ objectKey?: string | undefined;
19
+ transcodeStatus?: "queued" | "transcoding" | "captioning" | "ready" | "failed" | undefined;
20
+ transcodeProgress?: {
21
+ phase?: "captioning" | "downloading" | "encoding" | "uploading" | undefined;
22
+ current: number;
23
+ total: number;
24
+ } | undefined;
25
+ transcodeError?: string | undefined;
26
+ failureReason?: "invalid_input" | "unsupported_codec" | "timeout" | "capacity" | "internal" | undefined;
27
+ retriable?: boolean | undefined;
28
+ playlistUrl?: string | undefined;
29
+ thumbnailKey?: string | undefined;
30
+ captionLocales?: string[] | undefined;
31
+ captionsPending?: boolean | undefined;
32
+ duration?: number | undefined;
33
+ renditions?: {
34
+ width?: number | undefined;
35
+ height?: number | undefined;
36
+ variant: string;
37
+ kind: "hls" | "mp4";
38
+ rung: string;
39
+ key: string;
40
+ url: string;
41
+ }[] | undefined;
42
+ posterUrls?: {
43
+ jpg?: string | undefined;
44
+ avif?: string | undefined;
45
+ } | undefined;
46
+ hasAudio?: boolean | undefined;
47
+ storyboardKey?: string | undefined;
48
+ type: "video" | "image" | "file";
49
+ key: string;
50
+ filename: string;
51
+ contentType: string;
52
+ sizeBytes: number;
53
+ createdAt: number;
54
+ } | null>>;
55
+ /** The scheduled check `createUploadUrl` leaves behind: records the upload if it was stored and never confirmed. */
56
+ export declare const confirmUploadLater: import("convex/server").RegisteredAction<"internal", {
57
+ width?: number | undefined;
58
+ height?: number | undefined;
59
+ filename?: string | undefined;
60
+ key: string;
61
+ }, Promise<void>>;
62
+ export declare const recordUpload: import("convex/server").RegisteredMutation<"internal", {
63
+ width?: number | undefined;
64
+ height?: number | undefined;
65
+ type: "video" | "image" | "file";
66
+ key: string;
67
+ filename: string;
68
+ contentType: string;
69
+ sizeBytes: number;
70
+ visibility: "public" | "private";
71
+ objectKey: string;
72
+ }, Promise<{
73
+ _id: import("convex/values").GenericId<"media">;
74
+ _creationTime: number;
75
+ width?: number | undefined;
76
+ height?: number | undefined;
77
+ visibility?: "public" | "private" | undefined;
78
+ objectKey?: string | undefined;
79
+ transcodeStatus?: "queued" | "transcoding" | "captioning" | "ready" | "failed" | undefined;
80
+ transcodeProgress?: {
81
+ phase?: "captioning" | "downloading" | "encoding" | "uploading" | undefined;
82
+ current: number;
83
+ total: number;
84
+ } | undefined;
85
+ transcodeError?: string | undefined;
86
+ failureReason?: "invalid_input" | "unsupported_codec" | "timeout" | "capacity" | "internal" | undefined;
87
+ retriable?: boolean | undefined;
88
+ playlistUrl?: string | undefined;
89
+ thumbnailKey?: string | undefined;
90
+ captionLocales?: string[] | undefined;
91
+ captionsPending?: boolean | undefined;
92
+ duration?: number | undefined;
93
+ renditions?: {
94
+ width?: number | undefined;
95
+ height?: number | undefined;
96
+ variant: string;
97
+ kind: "hls" | "mp4";
98
+ rung: string;
99
+ key: string;
100
+ url: string;
101
+ }[] | undefined;
102
+ posterUrls?: {
103
+ jpg?: string | undefined;
104
+ avif?: string | undefined;
105
+ } | undefined;
106
+ hasAudio?: boolean | undefined;
107
+ storyboardKey?: string | undefined;
108
+ type: "video" | "image" | "file";
109
+ key: string;
110
+ filename: string;
111
+ contentType: string;
112
+ sizeBytes: number;
113
+ createdAt: number;
114
+ } | null>>;
115
+ /** Drops the record of a deleted image or file. A video's record goes with `deleteVideo`, not with one of its keys. */
116
+ export declare const forgetUpload: import("convex/server").RegisteredMutation<"internal", {
117
+ key: string;
118
+ }, Promise<void>>;
119
+ //# sourceMappingURL=uploads.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"uploads.d.ts","sourceRoot":"","sources":["../../src/component/uploads.ts"],"names":[],"mappings":"AAiBA,eAAO,MAAM,0BAA0B,QAAsB,CAAC;AAgD9D;;;;GAIG;AACH,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UAGxB,CAAC;AAEH,oHAAoH;AACpH,eAAO,MAAM,kBAAkB;;;;;iBAK7B,CAAC;AAEH,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UA0BvB,CAAC;AAEH,uHAAuH;AACvH,eAAO,MAAM,YAAY;;iBAYvB,CAAC"}
@@ -0,0 +1,106 @@
1
+ import { extractId, sanitizeFilename } from "@devultur/core";
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 { parseUploadKey } from "./uploadKeys";
7
+ import { workerAction } from "./workerErrors";
8
+ // Past the Worker's longest upload window (a multipart upload can run for 24 hours), so the scheduled check never runs
9
+ // while the bytes are still on their way.
10
+ export const UPLOAD_CONFIRM_BACKSTOP_MS = 25 * 60 * 60 * 1000;
11
+ /** The display name to store: the client's when it is the name in the key, else the key's own. */
12
+ function displayName(uploadKey, filename) {
13
+ if (filename && sanitizeFilename(filename) === uploadKey.storedName)
14
+ return filename;
15
+ return uploadKey.storedName;
16
+ }
17
+ const confirmArgs = {
18
+ key: v.string(),
19
+ /** The original filename (display and download name). Ignored unless it is the one in the key. */
20
+ filename: v.optional(v.string()),
21
+ /** Intrinsic size of an image, read in the browser. */
22
+ width: v.optional(v.number()),
23
+ height: v.optional(v.number()),
24
+ };
25
+ /** HEADs the object and records it; `null` when no object is stored at `key` (yet). */
26
+ // Annotated: the result of `internal.uploads.*` would otherwise type this module in terms of itself.
27
+ async function confirm(ctx, args) {
28
+ const uploadKey = parseUploadKey(args.key);
29
+ if (!uploadKey) {
30
+ throw new ConvexError({ code: "invalid_upload_key", message: `Not an upload key: ${args.key}`, status: 400 });
31
+ }
32
+ const head = await getMediaClient().files.head(args.key);
33
+ if (!head)
34
+ return null;
35
+ return ctx.runMutation(internal.uploads.recordUpload, {
36
+ key: uploadKey.id,
37
+ objectKey: args.key,
38
+ type: uploadKey.kind,
39
+ visibility: uploadKey.visibility,
40
+ filename: displayName(uploadKey, args.filename),
41
+ contentType: head.contentType,
42
+ sizeBytes: head.size,
43
+ ...(args.width !== undefined ? { width: args.width } : {}),
44
+ ...(args.height !== undefined ? { height: args.height } : {}),
45
+ });
46
+ }
47
+ /**
48
+ * Records an upload in the component's `media` table once its bytes are stored (call it after the PUT). Returns the
49
+ * record, or `null` when nothing is stored at `key`. Idempotent: confirming twice updates the same record, and a
50
+ * video's transcode state is left as it is.
51
+ */
52
+ export const confirmUpload = workerAction({
53
+ args: confirmArgs,
54
+ handler: async (ctx, args) => confirm(ctx, args),
55
+ });
56
+ /** The scheduled check `createUploadUrl` leaves behind: records the upload if it was stored and never confirmed. */
57
+ export const confirmUploadLater = internalAction({
58
+ args: confirmArgs,
59
+ handler: async (ctx, args) => {
60
+ await confirm(ctx, args);
61
+ },
62
+ });
63
+ export const recordUpload = internalMutation({
64
+ args: {
65
+ key: v.string(),
66
+ objectKey: v.string(),
67
+ type: v.union(v.literal("video"), v.literal("image"), v.literal("file")),
68
+ visibility: v.union(v.literal("public"), v.literal("private")),
69
+ filename: v.string(),
70
+ contentType: v.string(),
71
+ sizeBytes: v.number(),
72
+ width: v.optional(v.number()),
73
+ height: v.optional(v.number()),
74
+ },
75
+ handler: async (ctx, args) => {
76
+ const existing = await ctx.db
77
+ .query("media")
78
+ .withIndex("by_key", (q) => q.eq("key", args.key))
79
+ .first();
80
+ if (!existing) {
81
+ const id = await ctx.db.insert("media", { ...args, createdAt: Date.now() });
82
+ return ctx.db.get(id);
83
+ }
84
+ // A video `processVideo` recorded first: fill in what only the upload knows, keep its transcode state.
85
+ await ctx.db.patch(existing._id, args);
86
+ return ctx.db.get(existing._id);
87
+ },
88
+ });
89
+ /** Drops the record of a deleted image or file. A video's record goes with `deleteVideo`, not with one of its keys. */
90
+ export const forgetUpload = internalMutation({
91
+ args: { key: v.string() },
92
+ handler: async (ctx, args) => {
93
+ if (!parseUploadKey(args.key))
94
+ return;
95
+ const record = await ctx.db
96
+ .query("media")
97
+ .withIndex("by_key", (q) => q.eq("key", extractId(args.key)))
98
+ .first();
99
+ if (!record || record.type === "video")
100
+ return;
101
+ if (record.objectKey && record.objectKey !== args.key)
102
+ return;
103
+ await ctx.db.delete(record._id);
104
+ },
105
+ });
106
+ //# sourceMappingURL=uploads.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"uploads.js","sourceRoot":"","sources":["../../src/component/uploads.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ import { type Value } from "convex/values";
2
+ import { action } from "./_generated/server";
3
+ /** `data` of the `ConvexError` a component action throws when the Worker refuses a request. */
4
+ export interface DevulturErrorData {
5
+ /** The Worker's stable code (`retention_active`, `file_too_large`, ...), or `worker_error` when it sent none. */
6
+ code: string;
7
+ message: string;
8
+ /** HTTP status of the Worker's answer. */
9
+ status: number;
10
+ /** The rest of the Worker's error body, e.g. `{ key, mode, until, hold }` on `retention_active`. */
11
+ details?: Record<string, Value>;
12
+ }
13
+ /** The error to rethrow: a Worker refusal becomes a `ConvexError` with its code; anything else passes through. */
14
+ export declare function toComponentError(error: unknown): unknown;
15
+ /** `action` whose handler rethrows Worker refusals as `ConvexError<DevulturErrorData>`. */
16
+ export declare const workerAction: typeof action;
17
+ //# sourceMappingURL=workerErrors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workerErrors.d.ts","sourceRoot":"","sources":["../../src/component/workerErrors.ts"],"names":[],"mappings":"AAKA,OAAO,EAAe,KAAK,KAAK,EAAE,MAAM,eAAe,CAAC;AACxD,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAE7C,+FAA+F;AAC/F,MAAM,WAAW,iBAAiB;IACjC,iHAAiH;IACjH,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,0CAA0C;IAC1C,MAAM,EAAE,MAAM,CAAC;IACf,oGAAoG;IACpG,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;CAChC;AAYD,kHAAkH;AAClH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAUxD;AAED,2FAA2F;AAC3F,eAAO,MAAM,YAAY,EAUG,OAAO,MAAM,CAAC"}
@@ -0,0 +1,41 @@
1
+ // Worker errors across the component boundary. A plain `Error` thrown in a component function reaches the app as a
2
+ // message only; a `ConvexError` keeps its `data`. Every component action is built with `workerAction`, which rethrows
3
+ // the Worker's `DevulturApiError` as `ConvexError<DevulturErrorData>`, so the app reads `error.data.code`
4
+ // (`retention_active`, `lock_conflict`, `compliance_extend_only`, ...) and `error.data.details`.
5
+ import { DevulturApiError } from "@devultur/core";
6
+ import { ConvexError } from "convex/values";
7
+ import { action } from "./_generated/server";
8
+ function isApiError(error) {
9
+ if (error instanceof DevulturApiError)
10
+ return true;
11
+ // A second copy of `@devultur/core` (bundling) fails `instanceof`; its shape doesn't.
12
+ return (error instanceof Error &&
13
+ error.name === "DevulturApiError" &&
14
+ typeof error.status === "number");
15
+ }
16
+ /** The error to rethrow: a Worker refusal becomes a `ConvexError` with its code; anything else passes through. */
17
+ export function toComponentError(error) {
18
+ if (!isApiError(error))
19
+ return error;
20
+ const data = {
21
+ code: error.code ?? "worker_error",
22
+ message: error.message,
23
+ status: error.status,
24
+ // JSON from the Worker: valid Convex values once `undefined` is gone.
25
+ ...(error.details ? { details: JSON.parse(JSON.stringify(error.details)) } : {}),
26
+ };
27
+ return new ConvexError(data);
28
+ }
29
+ /** `action` whose handler rethrows Worker refusals as `ConvexError<DevulturErrorData>`. */
30
+ export const workerAction = ((definition) => action({
31
+ ...definition,
32
+ handler: async (ctx, args) => {
33
+ try {
34
+ return await definition.handler(ctx, args);
35
+ }
36
+ catch (error) {
37
+ throw toComponentError(error);
38
+ }
39
+ },
40
+ }));
41
+ //# sourceMappingURL=workerErrors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workerErrors.js","sourceRoot":"","sources":["../../src/component/workerErrors.ts"],"names":[],"mappings":"AAAA,mHAAmH;AACnH,sHAAsH;AACtH,0GAA0G;AAC1G,iGAAiG;AACjG,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,EAAE,WAAW,EAAc,MAAM,eAAe,CAAC;AACxD,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAa7C,SAAS,UAAU,CAAC,KAAc;IACjC,IAAI,KAAK,YAAY,gBAAgB;QAAE,OAAO,IAAI,CAAC;IACnD,sFAAsF;IACtF,OAAO,CACN,KAAK,YAAY,KAAK;QACtB,KAAK,CAAC,IAAI,KAAK,kBAAkB;QACjC,OAAQ,KAA8B,CAAC,MAAM,KAAK,QAAQ,CAC1D,CAAC;AACH,CAAC;AAED,kHAAkH;AAClH,MAAM,UAAU,gBAAgB,CAAC,KAAc;IAC9C,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IACrC,MAAM,IAAI,GAAsB;QAC/B,IAAI,EAAE,KAAK,CAAC,IAAI,IAAI,cAAc;QAClC,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,MAAM,EAAE,KAAK,CAAC,MAAM;QACpB,sEAAsE;QACtE,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,OAAO,CAAC,CAA0B,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACzG,CAAC;IACF,OAAO,IAAI,WAAW,CAAC,IAAwB,CAAC,CAAC;AAClD,CAAC;AAED,2FAA2F;AAC3F,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,CAAC,UAA0E,EAAE,EAAE,CAC3G,MAAM,CAAC;IACN,GAAG,UAAU;IACb,OAAO,EAAE,KAAK,EAAE,GAAY,EAAE,IAAa,EAAE,EAAE;QAC9C,IAAI,CAAC;YACJ,OAAO,MAAM,UAAU,CAAC,OAAO,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;QAC5C,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,MAAM,gBAAgB,CAAC,KAAK,CAAC,CAAC;QAC/B,CAAC;IACF,CAAC;CACQ,CAAC,CAA6B,CAAC"}
package/dist/index.d.ts CHANGED
@@ -1,9 +1,9 @@
1
- import { VideoModuleConfig } from '@devultur/core';
1
+ import { VideoModuleConfig, 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
3
  import * as convex_server from 'convex/server';
4
- import { FunctionReference, GenericQueryCtx, GenericDataModel, GenericMutationCtx } from 'convex/server';
4
+ import { FunctionReference, GenericQueryCtx, GenericDataModel, GenericMutationCtx, GenericActionCtx, PaginationOptions, PaginationResult } from 'convex/server';
5
+ import { Value, ConvexError } from 'convex/values';
5
6
  export { dv } from './validators.js';
6
- import 'convex/values';
7
7
 
8
8
  /**
9
9
  * Generated `ComponentApi` utility.
@@ -26,6 +26,26 @@ import 'convex/values';
26
26
  * ```
27
27
  */
28
28
  type ComponentApi<Name extends string | undefined = string | undefined> = {
29
+ lock: {
30
+ getLock: FunctionReference<"action", "internal", {
31
+ key: string;
32
+ }, any, Name>;
33
+ releaseHold: FunctionReference<"action", "internal", {
34
+ keys: Array<string>;
35
+ }, any, Name>;
36
+ setHold: FunctionReference<"action", "internal", {
37
+ keys: Array<string>;
38
+ }, any, Name>;
39
+ setLock: FunctionReference<"action", "internal", {
40
+ bypassGovernance?: boolean;
41
+ derivatives?: "inherit" | "original-only";
42
+ key: string;
43
+ mode?: "governance" | "compliance";
44
+ period?: number;
45
+ remove?: true;
46
+ until?: string;
47
+ }, any, Name>;
48
+ };
29
49
  public: {
30
50
  createMedia: FunctionReference<"mutation", "internal", {
31
51
  contentType: string;
@@ -37,12 +57,24 @@ type ComponentApi<Name extends string | undefined = string | undefined> = {
37
57
  createUploadUrl: FunctionReference<"action", "internal", {
38
58
  contentType: string;
39
59
  filename: string;
60
+ hold?: boolean;
61
+ lock?: {
62
+ hold?: boolean;
63
+ retention?: {
64
+ derivatives?: "inherit" | "original-only";
65
+ mode: "governance" | "compliance";
66
+ period?: number;
67
+ until?: string;
68
+ };
69
+ };
70
+ visibility?: "public" | "private";
40
71
  }, any, Name>;
41
72
  deleteMediaAction: FunctionReference<"action", "internal", {
42
73
  key: string;
43
74
  }, any, Name>;
44
75
  deleteVideo: FunctionReference<"action", "internal", {
45
76
  videoId: string;
77
+ visibility?: "public" | "private";
46
78
  }, any, Name>;
47
79
  getCourseProgress: FunctionReference<"query", "internal", {
48
80
  userId: string;
@@ -60,6 +92,17 @@ type ComponentApi<Name extends string | undefined = string | undefined> = {
60
92
  listMedia: FunctionReference<"query", "internal", {
61
93
  type?: "video" | "image" | "file";
62
94
  }, any, Name>;
95
+ listMediaPage: FunctionReference<"query", "internal", {
96
+ paginationOpts: {
97
+ cursor: string | null;
98
+ endCursor?: string | null;
99
+ id?: number;
100
+ maximumBytesRead?: number;
101
+ maximumRowsRead?: number;
102
+ numItems: number;
103
+ };
104
+ type?: "video" | "image" | "file";
105
+ }, any, Name>;
63
106
  loadProgress: FunctionReference<"query", "internal", {
64
107
  userId: string;
65
108
  videoId: string;
@@ -85,16 +128,19 @@ type ComponentApi<Name extends string | undefined = string | undefined> = {
85
128
  }, any, Name>;
86
129
  updateMedia: FunctionReference<"mutation", "internal", {
87
130
  captionLocales?: Array<string>;
131
+ captionsPending?: boolean;
88
132
  duration?: number;
133
+ failureReason?: "invalid_input" | "unsupported_codec" | "timeout" | "capacity" | "internal";
89
134
  key: string;
90
135
  playlistUrl?: string;
136
+ retriable?: boolean;
91
137
  thumbnailKey?: string;
92
138
  transcodeError?: string;
93
139
  transcodeProgress?: {
94
140
  current: number;
95
141
  total: number;
96
142
  };
97
- transcodeStatus?: "transcoding" | "captioning" | "ready" | "failed";
143
+ transcodeStatus?: "queued" | "transcoding" | "captioning" | "ready" | "failed";
98
144
  }, any, Name>;
99
145
  upsertCaption: FunctionReference<"mutation", "internal", {
100
146
  locale: string;
@@ -103,18 +149,41 @@ type ComponentApi<Name extends string | undefined = string | undefined> = {
103
149
  vttUrl: string;
104
150
  }, any, Name>;
105
151
  };
152
+ uploads: {
153
+ confirmUpload: FunctionReference<"action", "internal", {
154
+ filename?: string;
155
+ height?: number;
156
+ key: string;
157
+ width?: number;
158
+ }, any, Name>;
159
+ };
106
160
  };
107
161
 
108
162
  interface DevulturComponentConfig {
109
163
  video?: VideoModuleConfig;
164
+ /**
165
+ * Lock every upload through the exposed `createUploadUrl` gets (retention and/or hold). Set here, on your server:
166
+ * the browser never chooses a lock. To lock per upload, call `devultur.createUploadUrl(ctx, { lock })` from your
167
+ * own function.
168
+ */
169
+ upload?: {
170
+ lock?: ObjectLock;
171
+ };
110
172
  }
111
173
  interface MediaRecord {
174
+ /** The media id (the `id` of its `DevulturMedia`), for every kind. */
112
175
  key: string;
113
176
  type: "video" | "image" | "file";
114
177
  filename: string;
115
178
  contentType: string;
116
179
  sizeBytes: number;
117
- transcodeStatus?: "transcoding" | "captioning" | "ready" | "failed";
180
+ /** Set by a confirmed upload; unset on records from before 1.1.0. */
181
+ visibility?: "public" | "private";
182
+ /** Key of the uploaded object (a video's raw file). Set by a confirmed upload. */
183
+ objectKey?: string;
184
+ width?: number;
185
+ height?: number;
186
+ transcodeStatus?: "queued" | "transcoding" | "captioning" | "ready" | "failed";
118
187
  transcodeProgress?: {
119
188
  current: number;
120
189
  total: number;
@@ -125,6 +194,8 @@ interface MediaRecord {
125
194
  /** Key of the storyboard VTT (hover previews); URL via `buildStoryboardUrl` from `@devultur/core`. */
126
195
  storyboardKey?: string;
127
196
  captionLocales?: string[];
197
+ /** True while requested captions haven't arrived; the video waits in "captioning" only then. */
198
+ captionsPending?: boolean;
128
199
  duration?: number;
129
200
  createdAt: number;
130
201
  }
@@ -140,6 +211,23 @@ interface ProgressRecord {
140
211
  type DevulturComponent = ComponentApi;
141
212
  type QueryCtx = GenericQueryCtx<GenericDataModel>;
142
213
  type MutationCtx = GenericMutationCtx<GenericDataModel>;
214
+ type ActionCtx = GenericActionCtx<GenericDataModel>;
215
+ type UploadUrlArgs = {
216
+ filename: string;
217
+ contentType: string;
218
+ visibility?: "public" | "private";
219
+ };
220
+ type MediaType = "video" | "image" | "file";
221
+ type MediaPageArgs = {
222
+ type?: MediaType;
223
+ paginationOpts: PaginationOptions;
224
+ };
225
+ type ConfirmUploadArgs = {
226
+ key: string;
227
+ filename?: string;
228
+ width?: number;
229
+ height?: number;
230
+ };
143
231
  declare class Devultur {
144
232
  private component;
145
233
  constructor(component: DevulturComponent);
@@ -153,6 +241,34 @@ declare class Devultur {
153
241
  userId: string;
154
242
  videoId: string;
155
243
  }): Promise<any>;
244
+ /** An upload URL from your server, optionally locked (retention and/or hold, written with the bytes). */
245
+ createUploadUrl(ctx: ActionCtx, args: UploadUrlArgs & {
246
+ lock?: ObjectLock;
247
+ hold?: boolean;
248
+ }): Promise<any>;
249
+ /**
250
+ * Records an upload in the component once its bytes are stored (after the PUT): it then shows in `listMedia`.
251
+ * Returns the record, or `null` when nothing is stored at `key`. Uploads nobody confirms are recorded a day later.
252
+ */
253
+ confirmUpload(ctx: ActionCtx, args: ConfirmUploadArgs): Promise<MediaRecord | null>;
254
+ /**
255
+ * The recorded media (every confirmed upload, and every video sent to `processVideo`), optionally of one type.
256
+ * `mediaFromRecord(record)` turns a row into the reference `<Image>` and `dv.media()` take. Unbounded: it reads every
257
+ * record, so a media library pages with `listMediaPage`.
258
+ */
259
+ listMedia(ctx: QueryCtx, args?: {
260
+ type?: MediaType;
261
+ }): Promise<MediaRecord[]>;
262
+ /** One page of the recorded media, newest first, optionally of one type. Takes Convex's `paginationOpts`. */
263
+ listMediaPage(ctx: QueryCtx, args: MediaPageArgs): Promise<PaginationResult<MediaRecord>>;
264
+ /** The lock in force on `key`. */
265
+ getLock(ctx: ActionCtx, key: string): Promise<LockView>;
266
+ /** Sets or extends `key`'s retention (shortening a governance one needs `bypassGovernance` and the Worker's hook). */
267
+ setLock(ctx: ActionCtx, key: string, options: SetLockOptions): Promise<LockView>;
268
+ /** Holds `keys` (any number: sent in pages). A bulk hold by prefix uses `@devultur/core`'s client on your server. */
269
+ setHold(ctx: ActionCtx, keys: string[]): Promise<Pick<HoldPage, "updated" | "skipped">>;
270
+ /** Releases `keys`' holds: a retention `period` starts now; without a retention they're free. */
271
+ releaseHold(ctx: ActionCtx, keys: string[]): Promise<Pick<HoldPage, "updated" | "skipped">>;
156
272
  static exposeApi(component: DevulturComponent, config?: DevulturComponentConfig): {
157
273
  issueViewerToken: convex_server.RegisteredAction<"public", {
158
274
  sessionSecret?: string;
@@ -162,6 +278,7 @@ declare class Devultur {
162
278
  contentType: string;
163
279
  visibility?: "public" | "private";
164
280
  }, Promise<any>>;
281
+ confirmUpload: convex_server.RegisteredAction<"public", ConfirmUploadArgs, Promise<any>>;
165
282
  deleteMedia: convex_server.RegisteredAction<"public", {
166
283
  key: string;
167
284
  }, Promise<any>>;
@@ -175,6 +292,7 @@ declare class Devultur {
175
292
  getMedia: convex_server.RegisteredQuery<"public", {
176
293
  key: string;
177
294
  }, Promise<any>>;
295
+ listMediaPage: convex_server.RegisteredQuery<"public", MediaPageArgs, Promise<any>>;
178
296
  saveProgress: convex_server.RegisteredMutation<"public", {
179
297
  userId: string;
180
298
  videoId: string;
@@ -195,4 +313,27 @@ declare class Devultur {
195
313
  };
196
314
  }
197
315
 
198
- export { Devultur, type DevulturComponentConfig, type MediaRecord, type ProgressRecord };
316
+ /** `data` of the `ConvexError` a component action throws when the Worker refuses a request. */
317
+ interface DevulturErrorData {
318
+ /** The Worker's stable code (`retention_active`, `file_too_large`, ...), or `worker_error` when it sent none. */
319
+ code: string;
320
+ message: string;
321
+ /** HTTP status of the Worker's answer. */
322
+ status: number;
323
+ /** The rest of the Worker's error body, e.g. `{ key, mode, until, hold }` on `retention_active`. */
324
+ details?: Record<string, Value>;
325
+ }
326
+
327
+ /** True when `error` is a Worker refusal from a component action. */
328
+ declare function isDevulturError(error: unknown): error is ConvexError<DevulturErrorData & Record<string, never>>;
329
+ /** The refusal's `{ code, message, status, details? }`, or `null` when `error` isn't one. */
330
+ declare function devulturErrorOf(error: unknown): DevulturErrorData | null;
331
+
332
+ /**
333
+ * The `DevulturMedia` reference of a component record (a `listMedia` row), ready for `<Image>`, `<DownloadLink>` or a
334
+ * `dv.media()` field. `null` for a record without a visibility: one from before 1.1.0, or a video only `processVideo`
335
+ * recorded.
336
+ */
337
+ declare function mediaFromRecord(record: MediaRecord): DevulturMedia | null;
338
+
339
+ export { Devultur, type DevulturComponentConfig, type DevulturErrorData, type MediaRecord, type ProgressRecord, devulturErrorOf, isDevulturError, mediaFromRecord };