@devultur/convex 0.0.0-stage → 1.0.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.
Files changed (44) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +57 -2
  3. package/dist/bin/init.d.ts +1 -0
  4. package/dist/bin/init.js +112 -0
  5. package/dist/chunk-FHWT3GD6.js +21 -0
  6. package/dist/component/_generated/api.d.ts +36 -0
  7. package/dist/component/_generated/api.d.ts.map +1 -0
  8. package/dist/component/_generated/api.js +31 -0
  9. package/dist/component/_generated/api.js.map +1 -0
  10. package/dist/component/_generated/component.d.ts +100 -0
  11. package/dist/component/_generated/component.d.ts.map +1 -0
  12. package/dist/component/_generated/component.js +11 -0
  13. package/dist/component/_generated/component.js.map +1 -0
  14. package/dist/component/_generated/dataModel.d.ts +46 -0
  15. package/dist/component/_generated/dataModel.d.ts.map +1 -0
  16. package/dist/component/_generated/dataModel.js +11 -0
  17. package/dist/component/_generated/dataModel.js.map +1 -0
  18. package/dist/component/_generated/server.d.ts +130 -0
  19. package/dist/component/_generated/server.d.ts.map +1 -0
  20. package/dist/component/_generated/server.js +79 -0
  21. package/dist/component/_generated/server.js.map +1 -0
  22. package/dist/component/convex.config.d.ts +6 -0
  23. package/dist/component/convex.config.d.ts.map +1 -0
  24. package/dist/component/convex.config.js +10 -0
  25. package/dist/component/convex.config.js.map +1 -0
  26. package/dist/component/http.d.ts +3 -0
  27. package/dist/component/http.d.ts.map +1 -0
  28. package/dist/component/http.js +25 -0
  29. package/dist/component/http.js.map +1 -0
  30. package/dist/component/public.d.ts +221 -0
  31. package/dist/component/public.d.ts.map +1 -0
  32. package/dist/component/public.js +429 -0
  33. package/dist/component/public.js.map +1 -0
  34. package/dist/component/schema.d.ts +169 -0
  35. package/dist/component/schema.d.ts.map +1 -0
  36. package/dist/component/schema.js +63 -0
  37. package/dist/component/schema.js.map +1 -0
  38. package/dist/index.d.ts +198 -0
  39. package/dist/index.js +121 -0
  40. package/dist/react.d.ts +281 -0
  41. package/dist/react.js +756 -0
  42. package/dist/validators.d.ts +31 -0
  43. package/dist/validators.js +6 -0
  44. package/package.json +79 -3
@@ -0,0 +1,63 @@
1
+ import { defineSchema, defineTable } from "convex/server";
2
+ import { v } from "convex/values";
3
+ // Outputs of a preset transcode (carried by `transcode.complete`): each MP4/ladder with its stable URL, the
4
+ // poster (JPG + AVIF per transform), and whether the video ended up with audio.
5
+ export const transcodeOutputFields = {
6
+ renditions: v.optional(v.array(v.object({
7
+ variant: v.string(),
8
+ kind: v.union(v.literal("hls"), v.literal("mp4")),
9
+ rung: v.string(),
10
+ key: v.string(),
11
+ width: v.optional(v.number()),
12
+ height: v.optional(v.number()),
13
+ url: v.string(),
14
+ }))),
15
+ posterUrls: v.optional(v.object({ jpg: v.optional(v.string()), avif: v.optional(v.string()) })),
16
+ hasAudio: v.optional(v.boolean()),
17
+ // Key of the storyboard VTT (scrub-bar previews). URL: `buildStoryboardUrl` / `buildMediaUrl` from core.
18
+ storyboardKey: v.optional(v.string()),
19
+ };
20
+ export default defineSchema({
21
+ media: defineTable({
22
+ key: v.string(),
23
+ type: v.union(v.literal("video"), v.literal("image"), v.literal("file")),
24
+ filename: v.string(),
25
+ contentType: v.string(),
26
+ sizeBytes: v.number(),
27
+ transcodeStatus: v.optional(v.union(v.literal("queued"), v.literal("transcoding"), v.literal("captioning"), v.literal("ready"), v.literal("failed"))),
28
+ transcodeProgress: v.optional(v.object({
29
+ current: v.number(),
30
+ total: v.number(),
31
+ phase: v.optional(v.union(v.literal("downloading"), v.literal("encoding"), v.literal("uploading"), v.literal("captioning"))),
32
+ })),
33
+ transcodeError: v.optional(v.string()),
34
+ // Categorized failure + whether it's retriable (webhook / accept-timeout). See FailureReason in core.
35
+ failureReason: v.optional(v.union(v.literal("invalid_input"), v.literal("unsupported_codec"), v.literal("timeout"), v.literal("capacity"), v.literal("internal"))),
36
+ retriable: v.optional(v.boolean()),
37
+ playlistUrl: v.optional(v.string()),
38
+ thumbnailKey: v.optional(v.string()),
39
+ // Outputs of a preset transcode: each MP4/ladder with its stable URL, poster, and whether it has audio.
40
+ ...transcodeOutputFields,
41
+ captionLocales: v.optional(v.array(v.string())),
42
+ duration: v.optional(v.number()),
43
+ createdAt: v.number(),
44
+ }).index("by_key", ["key"]),
45
+ captions: defineTable({
46
+ mediaKey: v.string(),
47
+ locale: v.string(),
48
+ vttUrl: v.string(),
49
+ srtUrl: v.optional(v.string()),
50
+ createdAt: v.number(),
51
+ })
52
+ .index("by_media", ["mediaKey"])
53
+ .index("by_media_locale", ["mediaKey", "locale"]),
54
+ progress: defineTable({
55
+ userId: v.string(),
56
+ videoId: v.string(),
57
+ lastPosition: v.number(),
58
+ completedAt: v.optional(v.number()),
59
+ watchedPercent: v.optional(v.number()),
60
+ updatedAt: v.number(),
61
+ }).index("by_user_video", ["userId", "videoId"]),
62
+ });
63
+ //# sourceMappingURL=schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/component/schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC1D,OAAO,EAAE,CAAC,EAAE,MAAM,eAAe,CAAC;AAElC,4GAA4G;AAC5G,gFAAgF;AAChF,MAAM,CAAC,MAAM,qBAAqB,GAAG;IACpC,UAAU,EAAE,CAAC,CAAC,QAAQ,CACrB,CAAC,CAAC,KAAK,CACN,CAAC,CAAC,MAAM,CAAC;QACR,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE;QACnB,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QACjD,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;QAChB,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE;QACf,KAAK,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;QAC7B,MAAM,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;QAC9B,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE;KACf,CAAC,CACF,CACD;IACD,UAAU,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC;IAC/F,QAAQ,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC;IACjC,yGAAyG;IACzG,aAAa,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;CACrC,CAAC;AAEF,eAAe,YAAY,CAAC;IAC3B,KAAK,EAAE,WAAW,CAAC;QAClB,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE;QACf,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QACxE,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE;QACpB,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE;QACvB,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE;QACrB,eAAe,EAAE,CAAC,CAAC,QAAQ,CAC1B,CAAC,CAAC,KAAK,CACN,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,EACnB,CAAC,CAAC,OAAO,CAAC,aAAa,CAAC,EACxB,CAAC,CAAC,OAAO,CAAC,YAAY,CAAC,EACvB,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,EAClB,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,CACnB,CACD;QACD,iBAAiB,EAAE,CAAC,CAAC,QAAQ,CAC5B,CAAC,CAAC,MAAM,CAAC;YACR,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE;YACnB,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE;YACjB,KAAK,EAAE,CAAC,CAAC,QAAQ,CAChB,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,aAAa,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC,CACzG;SACD,CAAC,CACF;QACD,cAAc,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;QACtC,sGAAsG;QACtG,aAAa,EAAE,CAAC,CAAC,QAAQ,CACxB,CAAC,CAAC,KAAK,CACN,CAAC,CAAC,OAAO,CAAC,eAAe,CAAC,EAC1B,CAAC,CAAC,OAAO,CAAC,mBAAmB,CAAC,EAC9B,CAAC,CAAC,OAAO,CAAC,SAAS,CAAC,EACpB,CAAC,CAAC,OAAO,CAAC,UAAU,CAAC,EACrB,CAAC,CAAC,OAAO,CAAC,UAAU,CAAC,CACrB,CACD;QACD,SAAS,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC;QAClC,WAAW,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;QACnC,YAAY,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;QACpC,wGAAwG;QACxG,GAAG,qBAAqB;QACxB,cAAc,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC;QAC/C,QAAQ,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;QAChC,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE;KACrB,CAAC,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC,KAAK,CAAC,CAAC;IAE3B,QAAQ,EAAE,WAAW,CAAC;QACrB,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE;QACpB,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE;QAClB,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE;QAClB,MAAM,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;QAC9B,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE;KACrB,CAAC;SACA,KAAK,CAAC,UAAU,EAAE,CAAC,UAAU,CAAC,CAAC;SAC/B,KAAK,CAAC,iBAAiB,EAAE,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;IAElD,QAAQ,EAAE,WAAW,CAAC;QACrB,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE;QAClB,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE;QACnB,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE;QACxB,WAAW,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;QACnC,cAAc,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;QACtC,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE;KACrB,CAAC,CAAC,KAAK,CAAC,eAAe,EAAE,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;CAChD,CAAC,CAAC"}
@@ -0,0 +1,198 @@
1
+ import { VideoModuleConfig } from '@devultur/core';
2
+ export { CaptionLocale, DevulturConfig, DevulturKind, DevulturMedia, ImageFit, ImageFormat, SecurityLevel, SpeechProvider, StorageProvider, TranscodePreset, TranscodeProvider, TranslationProvider, VideoModuleConfig, Visibility } from '@devultur/core';
3
+ import * as convex_server from 'convex/server';
4
+ import { FunctionReference, GenericQueryCtx, GenericDataModel, GenericMutationCtx } from 'convex/server';
5
+ export { dv } from './validators.js';
6
+ import 'convex/values';
7
+
8
+ /**
9
+ * Generated `ComponentApi` utility.
10
+ *
11
+ * THIS CODE IS AUTOMATICALLY GENERATED.
12
+ *
13
+ * To regenerate, run `npx convex dev`.
14
+ * @module
15
+ */
16
+
17
+ /**
18
+ * A utility for referencing a Convex component's exposed API.
19
+ *
20
+ * Useful when expecting a parameter like `components.myComponent`.
21
+ * Usage:
22
+ * ```ts
23
+ * async function myFunction(ctx: QueryCtx, component: ComponentApi) {
24
+ * return ctx.runQuery(component.someFile.someQuery, { ...args });
25
+ * }
26
+ * ```
27
+ */
28
+ type ComponentApi<Name extends string | undefined = string | undefined> = {
29
+ public: {
30
+ createMedia: FunctionReference<"mutation", "internal", {
31
+ contentType: string;
32
+ filename: string;
33
+ key: string;
34
+ sizeBytes: number;
35
+ type: "video" | "image" | "file";
36
+ }, any, Name>;
37
+ createUploadUrl: FunctionReference<"action", "internal", {
38
+ contentType: string;
39
+ filename: string;
40
+ }, any, Name>;
41
+ deleteMediaAction: FunctionReference<"action", "internal", {
42
+ key: string;
43
+ }, any, Name>;
44
+ deleteVideo: FunctionReference<"action", "internal", {
45
+ videoId: string;
46
+ }, any, Name>;
47
+ getCourseProgress: FunctionReference<"query", "internal", {
48
+ userId: string;
49
+ videoIds: Array<string>;
50
+ }, any, Name>;
51
+ getMediaByKey: FunctionReference<"query", "internal", {
52
+ key: string;
53
+ }, any, Name>;
54
+ issueViewerToken: FunctionReference<"action", "internal", {
55
+ sessionSecret?: string;
56
+ }, any, Name>;
57
+ listCaptionsByMedia: FunctionReference<"query", "internal", {
58
+ mediaKey: string;
59
+ }, any, Name>;
60
+ listMedia: FunctionReference<"query", "internal", {
61
+ type?: "video" | "image" | "file";
62
+ }, any, Name>;
63
+ loadProgress: FunctionReference<"query", "internal", {
64
+ userId: string;
65
+ videoId: string;
66
+ }, any, Name>;
67
+ markComplete: FunctionReference<"mutation", "internal", {
68
+ userId: string;
69
+ videoId: string;
70
+ }, any, Name>;
71
+ processVideo: FunctionReference<"action", "internal", {
72
+ callbackUrl?: string;
73
+ key: string;
74
+ locales?: Array<string>;
75
+ preset?: string;
76
+ }, any, Name>;
77
+ resetProgress: FunctionReference<"mutation", "internal", {
78
+ userId: string;
79
+ videoId: string;
80
+ }, any, Name>;
81
+ saveProgress: FunctionReference<"mutation", "internal", {
82
+ position: number;
83
+ userId: string;
84
+ videoId: string;
85
+ }, any, Name>;
86
+ updateMedia: FunctionReference<"mutation", "internal", {
87
+ captionLocales?: Array<string>;
88
+ duration?: number;
89
+ key: string;
90
+ playlistUrl?: string;
91
+ thumbnailKey?: string;
92
+ transcodeError?: string;
93
+ transcodeProgress?: {
94
+ current: number;
95
+ total: number;
96
+ };
97
+ transcodeStatus?: "transcoding" | "captioning" | "ready" | "failed";
98
+ }, any, Name>;
99
+ upsertCaption: FunctionReference<"mutation", "internal", {
100
+ locale: string;
101
+ mediaKey: string;
102
+ srtUrl?: string;
103
+ vttUrl: string;
104
+ }, any, Name>;
105
+ };
106
+ };
107
+
108
+ interface DevulturComponentConfig {
109
+ video?: VideoModuleConfig;
110
+ }
111
+ interface MediaRecord {
112
+ key: string;
113
+ type: "video" | "image" | "file";
114
+ filename: string;
115
+ contentType: string;
116
+ sizeBytes: number;
117
+ transcodeStatus?: "transcoding" | "captioning" | "ready" | "failed";
118
+ transcodeProgress?: {
119
+ current: number;
120
+ total: number;
121
+ };
122
+ transcodeError?: string;
123
+ playlistUrl?: string;
124
+ thumbnailKey?: string;
125
+ /** Key of the storyboard VTT (hover previews); URL via `buildStoryboardUrl` from `@devultur/core`. */
126
+ storyboardKey?: string;
127
+ captionLocales?: string[];
128
+ duration?: number;
129
+ createdAt: number;
130
+ }
131
+ interface ProgressRecord {
132
+ userId: string;
133
+ videoId: string;
134
+ lastPosition: number;
135
+ completedAt?: number;
136
+ watchedPercent?: number;
137
+ updatedAt: number;
138
+ }
139
+
140
+ type DevulturComponent = ComponentApi;
141
+ type QueryCtx = GenericQueryCtx<GenericDataModel>;
142
+ type MutationCtx = GenericMutationCtx<GenericDataModel>;
143
+ declare class Devultur {
144
+ private component;
145
+ constructor(component: DevulturComponent);
146
+ getMedia(ctx: QueryCtx, key: string): Promise<any>;
147
+ saveProgress(ctx: MutationCtx, args: {
148
+ userId: string;
149
+ videoId: string;
150
+ position: number;
151
+ }): Promise<any>;
152
+ loadProgress(ctx: QueryCtx, args: {
153
+ userId: string;
154
+ videoId: string;
155
+ }): Promise<any>;
156
+ static exposeApi(component: DevulturComponent, config?: DevulturComponentConfig): {
157
+ issueViewerToken: convex_server.RegisteredAction<"public", {
158
+ sessionSecret?: string;
159
+ }, Promise<any>>;
160
+ createUploadUrl: convex_server.RegisteredAction<"public", {
161
+ filename: string;
162
+ contentType: string;
163
+ visibility?: "public" | "private";
164
+ }, Promise<any>>;
165
+ deleteMedia: convex_server.RegisteredAction<"public", {
166
+ key: string;
167
+ }, Promise<any>>;
168
+ deleteVideo: convex_server.RegisteredAction<"public", {
169
+ videoId: string;
170
+ visibility?: "public" | "private";
171
+ }, Promise<any>>;
172
+ processVideo: convex_server.RegisteredAction<"public", {
173
+ key: string;
174
+ }, Promise<any>>;
175
+ getMedia: convex_server.RegisteredQuery<"public", {
176
+ key: string;
177
+ }, Promise<any>>;
178
+ saveProgress: convex_server.RegisteredMutation<"public", {
179
+ userId: string;
180
+ videoId: string;
181
+ position: number;
182
+ }, Promise<any>>;
183
+ loadProgress: convex_server.RegisteredQuery<"public", {
184
+ userId: string;
185
+ videoId: string;
186
+ }, Promise<any>>;
187
+ markComplete: convex_server.RegisteredMutation<"public", {
188
+ userId: string;
189
+ videoId: string;
190
+ }, Promise<any>>;
191
+ resetProgress: convex_server.RegisteredMutation<"public", {
192
+ userId: string;
193
+ videoId: string;
194
+ }, Promise<any>>;
195
+ };
196
+ }
197
+
198
+ export { Devultur, type DevulturComponentConfig, type MediaRecord, type ProgressRecord };
package/dist/index.js ADDED
@@ -0,0 +1,121 @@
1
+ import {
2
+ dv
3
+ } from "./chunk-FHWT3GD6.js";
4
+
5
+ // src/index.ts
6
+ import {
7
+ CaptionLocale,
8
+ ImageFit,
9
+ ImageFormat,
10
+ SecurityLevel,
11
+ SpeechProvider,
12
+ StorageProvider,
13
+ TranscodePreset,
14
+ TranscodeProvider,
15
+ TranslationProvider
16
+ } from "@devultur/core";
17
+
18
+ // src/client.ts
19
+ import { extractId } from "@devultur/core";
20
+ import { actionGeneric, mutationGeneric, queryGeneric } from "convex/server";
21
+ import { v } from "convex/values";
22
+ var Devultur = class {
23
+ component;
24
+ constructor(component) {
25
+ this.component = component;
26
+ }
27
+ async getMedia(ctx, key) {
28
+ return ctx.runQuery(this.component.public.getMediaByKey, { key });
29
+ }
30
+ async saveProgress(ctx, args) {
31
+ return ctx.runMutation(this.component.public.saveProgress, args);
32
+ }
33
+ async loadProgress(ctx, args) {
34
+ return ctx.runQuery(this.component.public.loadProgress, args);
35
+ }
36
+ static exposeApi(component, config) {
37
+ const preset = config?.video?.transcodePreset;
38
+ const locales = config?.video?.captionLocales ?? [];
39
+ const visibilityArg = v.optional(v.union(v.literal("public"), v.literal("private")));
40
+ return {
41
+ issueViewerToken: actionGeneric({
42
+ args: { sessionSecret: v.optional(v.string()) },
43
+ handler: async (ctx, args) => {
44
+ return ctx.runAction(component.public.issueViewerToken, args);
45
+ }
46
+ }),
47
+ createUploadUrl: actionGeneric({
48
+ args: { filename: v.string(), contentType: v.string(), visibility: visibilityArg },
49
+ handler: async (ctx, args) => {
50
+ return ctx.runAction(component.public.createUploadUrl, args);
51
+ }
52
+ }),
53
+ deleteMedia: actionGeneric({
54
+ args: { key: v.string() },
55
+ handler: async (ctx, args) => {
56
+ return ctx.runAction(component.public.deleteMediaAction, args);
57
+ }
58
+ }),
59
+ deleteVideo: actionGeneric({
60
+ args: { videoId: v.string(), visibility: visibilityArg },
61
+ handler: async (ctx, args) => {
62
+ return ctx.runAction(component.public.deleteVideo, args);
63
+ }
64
+ }),
65
+ processVideo: actionGeneric({
66
+ args: { key: v.string() },
67
+ handler: async (ctx, args) => {
68
+ return ctx.runAction(component.public.processVideo, {
69
+ key: args.key,
70
+ preset,
71
+ locales
72
+ });
73
+ }
74
+ }),
75
+ getMedia: queryGeneric({
76
+ args: { key: v.string() },
77
+ handler: async (ctx, args) => {
78
+ const mediaKey = args.key.includes("/") ? extractId(args.key) : args.key;
79
+ return ctx.runQuery(component.public.getMediaByKey, { key: mediaKey });
80
+ }
81
+ }),
82
+ saveProgress: mutationGeneric({
83
+ args: { userId: v.string(), videoId: v.string(), position: v.number() },
84
+ handler: async (ctx, args) => {
85
+ return ctx.runMutation(component.public.saveProgress, args);
86
+ }
87
+ }),
88
+ loadProgress: queryGeneric({
89
+ args: { userId: v.string(), videoId: v.string() },
90
+ handler: async (ctx, args) => {
91
+ return ctx.runQuery(component.public.loadProgress, args);
92
+ }
93
+ }),
94
+ markComplete: mutationGeneric({
95
+ args: { userId: v.string(), videoId: v.string() },
96
+ handler: async (ctx, args) => {
97
+ return ctx.runMutation(component.public.markComplete, args);
98
+ }
99
+ }),
100
+ resetProgress: mutationGeneric({
101
+ args: { userId: v.string(), videoId: v.string() },
102
+ handler: async (ctx, args) => {
103
+ return ctx.runMutation(component.public.resetProgress, args);
104
+ }
105
+ })
106
+ };
107
+ }
108
+ };
109
+ export {
110
+ CaptionLocale,
111
+ Devultur,
112
+ ImageFit,
113
+ ImageFormat,
114
+ SecurityLevel,
115
+ SpeechProvider,
116
+ StorageProvider,
117
+ TranscodePreset,
118
+ TranscodeProvider,
119
+ TranslationProvider,
120
+ dv
121
+ };
@@ -0,0 +1,281 @@
1
+ import React from 'react';
2
+ import { Visibility, DevulturMedia, MediaUrlOptions, ProcessingPhase, FailureReason, MediaStatusValue } from '@devultur/core';
3
+ import { BakeOutputFormat, MediaUploadState, MediaUploadError } from '@devultur/react';
4
+ export { MediaUploadError, MediaUploadState } from '@devultur/react';
5
+ import { FunctionReference } from 'convex/server';
6
+
7
+ interface CropUploadRenderProps {
8
+ /** The state machine of the full cycle (idle → uploading → ready / failed). */
9
+ state: MediaUploadState;
10
+ /** Opens the file picker. */
11
+ pickFile: () => void;
12
+ /** Selected file waiting to be cropped (`null` if none). */
13
+ pendingFile: File | null;
14
+ /** Cancels the in-progress upload. */
15
+ cancel: () => void;
16
+ /** Returns to idle and clears state (including discarding the pending file). */
17
+ reset: () => void;
18
+ }
19
+ /** Text of the crop controls; pass your own to localize them. */
20
+ interface CropUploadLabels {
21
+ zoom: string;
22
+ confirm: string;
23
+ processing: string;
24
+ cancel: string;
25
+ }
26
+ interface CropUploadProps {
27
+ /** Crop aspect ratio (width/height), e.g. `16/9`. */
28
+ aspectRatio: number;
29
+ visibility?: Visibility;
30
+ outputFormat?: BakeOutputFormat;
31
+ maxWidth?: number;
32
+ minSourceWidth?: number;
33
+ /** Deletes this old asset after a successful upload. */
34
+ replaces?: DevulturMedia | null;
35
+ /** Fires with the uploaded `DevulturMedia` (persistence point). */
36
+ onComplete?: (media: DevulturMedia) => void;
37
+ onReject?: (reason: "too_small") => void;
38
+ /** Accepted types in the file picker. Default `["image/*"]`. */
39
+ accept?: string[];
40
+ /** Class for the cropper's box — only needs a **height** (`position: relative` is automatic). */
41
+ cropperClassName?: string;
42
+ /** Overrides any of the control labels (English by default). */
43
+ labels?: Partial<CropUploadLabels>;
44
+ /** External UI (trigger + state). Receives `pickFile`/`state`. Without this there's no way to start. */
45
+ children?: (api: CropUploadRenderProps) => React.ReactNode;
46
+ }
47
+ /**
48
+ * Upload with aspect-ratio crop: file picker → `<ImageCropper>` (crop + bake to WebP) →
49
+ * `useMediaUpload` (uploads the cropped result, returns `DevulturMedia`). Batteries-included for the crop
50
+ * phase (zoom + confirm); the trigger and state rendering are left to the consumer via `children`.
51
+ */
52
+ declare function CropUpload({ aspectRatio, visibility, outputFormat, maxWidth, minSourceWidth, replaces, onComplete, onReject, accept, cropperClassName, labels, children, }: CropUploadProps): React.JSX.Element;
53
+
54
+ /** Transform/delivery options for media URLs (without `token`, which the context injects). */
55
+ type MediaRenderOptions = Omit<MediaUrlOptions, "token">;
56
+ type AnyAction = FunctionReference<"action">;
57
+ type AnyMutation = FunctionReference<"mutation">;
58
+ type AnyQuery = FunctionReference<"query">;
59
+ interface DevulturApi {
60
+ issueViewerToken: AnyAction;
61
+ createUploadUrl: AnyAction;
62
+ deleteMedia: AnyAction;
63
+ /** Deletes the full folder of a video (raw/hls/thumbnails/captions). Required to delete videos. */
64
+ deleteVideo?: AnyAction;
65
+ /** Requests the transcode of an uploaded video. Required by `useMediaUpload` for auto-process. */
66
+ processVideo?: AnyAction;
67
+ getMedia?: AnyQuery;
68
+ saveProgress?: AnyMutation;
69
+ loadProgress?: AnyQuery;
70
+ markComplete?: AnyMutation;
71
+ resetProgress?: AnyMutation;
72
+ }
73
+ interface UploadOptions {
74
+ /** Visibility override for this resource. Omitted → inherits the router's default by type. */
75
+ visibility?: Visibility;
76
+ }
77
+ interface DevulturContextValue {
78
+ token: string | null;
79
+ baseUrl: string | null;
80
+ /** Browser session secret → sent in `${SESSION_HEADER}` on every private delivery. */
81
+ sessionSecret: string;
82
+ uploadUrl: (file: File) => Promise<{
83
+ url: string;
84
+ key: string;
85
+ }>;
86
+ /** Uploads a file and returns the typed reference ready to save. */
87
+ upload: (file: File, options?: UploadOptions) => Promise<DevulturMedia>;
88
+ /** Delivery URL from a reference (non-hook → usable in handlers and `.map`). Token-transparent. */
89
+ url: (media: DevulturMedia, options?: MediaRenderOptions) => string;
90
+ /** Downloads a video's subtitle text for a locale (auth-transparent). */
91
+ getCaptionsText: (media: DevulturMedia, locale: string) => Promise<string>;
92
+ /** Deletes a media by reference: video → full folder; image/file → the object. */
93
+ deleteMedia: (media: DevulturMedia) => void;
94
+ /**
95
+ * Re-issues the viewer token (new session fingerprint with the current UA/IP) and updates the context.
96
+ * Single-flight: concurrent calls share the same issuance. Returns the new token (or null if it
97
+ * failed). The render uses it to transparently recover from a 401 `session_mismatch`.
98
+ */
99
+ reissueToken: () => Promise<string | null>;
100
+ }
101
+ interface ProgressContextValue {
102
+ save: (videoId: string, position: number) => Promise<void>;
103
+ load: (videoId: string) => Promise<{
104
+ lastPosition: number;
105
+ completedAt: Date | null;
106
+ } | null>;
107
+ markComplete: (videoId: string) => Promise<void>;
108
+ reset: (videoId: string) => Promise<void>;
109
+ }
110
+ interface DevulturProviderProps {
111
+ api: DevulturApi;
112
+ /** Media worker base (e.g. `https://media.your-domain.com`). Required for `useMediaUrl`. */
113
+ baseUrl?: string;
114
+ children: React.ReactNode;
115
+ }
116
+ declare function DevulturProvider({ api, baseUrl, children }: DevulturProviderProps): React.JSX.Element;
117
+ declare function useDevultur(): DevulturContextValue;
118
+ declare function useDevulturProgress(): ProgressContextValue;
119
+ /**
120
+ * Ready-to-use delivery URL for a key, with the context's viewer token already injected. The consumer
121
+ * never touches the token or the query — it only declares what it wants (download, resize, etc.).
122
+ *
123
+ * @example
124
+ * <img src={useMediaUrl(key)} />
125
+ * <a href={useMediaUrl(key, { download: true })} download>Download</a>
126
+ * <img src={useMediaUrl(key, { width: 512, format: "webp" })} />
127
+ */
128
+ declare function useMediaUrl(key: string | null | undefined, options?: MediaRenderOptions): string | null;
129
+ interface VideoProps {
130
+ src: string;
131
+ token: string | undefined;
132
+ captions: {
133
+ locale: string;
134
+ label: string;
135
+ src: string;
136
+ }[];
137
+ /** Session secret → `<VideoPlayer>` sends it in `${SESSION_HEADER}` on hls.js/captions requests. */
138
+ sessionSecret?: string;
139
+ }
140
+ /**
141
+ * Token-transparent props for `<VideoPlayer>` from a video reference: builds the HLS playlist `src`,
142
+ * the viewer `token` (only if private), and the `captions` for the requested locales.
143
+ * The consumer just does `<VideoPlayer {...useVideoProps(lesson.video, { locales })} />`.
144
+ */
145
+ declare function useVideoProps(media: DevulturMedia | null | undefined, options?: {
146
+ locales?: string[];
147
+ }): VideoProps | null;
148
+ interface ImageProps extends Omit<React.ImgHTMLAttributes<HTMLImageElement>, "src" | "width" | "height"> {
149
+ /** Typed reference. `null`/`undefined` → renders `fallback` (or `externalFallback`). */
150
+ media: DevulturMedia | null | undefined;
151
+ width?: number;
152
+ height?: number;
153
+ fit?: MediaUrlOptions["fit"];
154
+ format?: MediaUrlOptions["format"];
155
+ quality?: number;
156
+ /** What to show when there's no media (or it's loading). */
157
+ fallback?: React.ReactNode;
158
+ /** Explicit external URL (e.g. Unsplash) when `media` is null or the load fails. */
159
+ externalFallback?: string;
160
+ /** background-image mode: composes the URL as this element's `background-image` (doesn't force `<img>`). */
161
+ render?: React.ReactElement<{
162
+ style?: React.CSSProperties;
163
+ }>;
164
+ }
165
+ /**
166
+ * Renders any media declaratively: token-transparent, with typed transforms, and works inside a
167
+ * `.map()` (no wrappers or hooks rules on the consumer's side).
168
+ *
169
+ * @example
170
+ * <Image media={course.thumbnail} alt={course.title} width={512} format="webp" fallback={<Placeholder />} />
171
+ */
172
+ declare function Image({ media, width, height, fit, format, quality, fallback, externalFallback, render, alt, ...imgProps }: ImageProps): React.JSX.Element;
173
+ interface DownloadLinkProps extends Omit<React.AnchorHTMLAttributes<HTMLAnchorElement>, "href" | "download" | "media"> {
174
+ media: DevulturMedia | null | undefined;
175
+ /** Name it downloads as. Without this it uses the upload's original name. */
176
+ filename?: string;
177
+ children: React.ReactNode;
178
+ }
179
+ /**
180
+ * `<a>` that downloads a media with its original name (token-transparent). The worker serves it with
181
+ * `Content-Disposition: attachment`, so a click downloads the file instead of opening it in a tab.
182
+ *
183
+ * Private: an `<a download>` NAVIGATES, and a navigation can't send `${SESSION_HEADER}` → with session
184
+ * binding the gate gives a 401. So, for private media, we intercept the click, blob-fetch with the header
185
+ * (like private `<Image>`) and trigger the download from the object URL. Public keeps native navigation.
186
+ */
187
+ declare function DownloadLink({ media, filename, children, ...anchorProps }: DownloadLinkProps): React.JSX.Element | null;
188
+ interface MediaStatusReturn {
189
+ status: MediaStatusValue;
190
+ /** Typed progress: `phase` is the pipeline step (no `as any`). */
191
+ progress: {
192
+ current: number;
193
+ total: number;
194
+ percent: number;
195
+ phase?: ProcessingPhase;
196
+ } | null;
197
+ error: string | null;
198
+ /** Categorized failure reason (null if it didn't fail). */
199
+ failureReason: FailureReason | null;
200
+ /** Whether the failure is retriable (null if it didn't fail). */
201
+ retriable: boolean | null;
202
+ isQueued: boolean;
203
+ isTranscoding: boolean;
204
+ isCaptioning: boolean;
205
+ isReady: boolean;
206
+ isFailed: boolean;
207
+ playlistUrl: string | null;
208
+ thumbnailKey: string | null;
209
+ captionLocales: string[];
210
+ duration: number | null;
211
+ isLoading: boolean;
212
+ }
213
+ declare function useDevulturMedia(key: string | null | undefined): MediaStatusReturn;
214
+ interface CaptionTrack {
215
+ locale: string;
216
+ label: string;
217
+ src: string;
218
+ }
219
+ /**
220
+ * A video's pipeline state as a **discriminated union** on `phase`: the render is an exhaustive
221
+ * `switch` (no boolean ordering or `as any`), and adding a new phase is a compile error until it's
222
+ * handled. `processing` carries typed `step`/`percent`; `ready` carries the ref + props ready for
223
+ * `<VideoPlayer>` (token-transparent); `failed` carries a categorized `reason` + `retriable`.
224
+ */
225
+ type VideoState = {
226
+ phase: "idle";
227
+ } | {
228
+ phase: "queued";
229
+ } | {
230
+ phase: "processing";
231
+ step: ProcessingPhase;
232
+ percent: number;
233
+ } | {
234
+ phase: "ready";
235
+ media: DevulturMedia;
236
+ playlist: string;
237
+ duration: number;
238
+ captions: CaptionTrack[];
239
+ playerProps: VideoProps;
240
+ } | {
241
+ phase: "failed";
242
+ reason: FailureReason;
243
+ message: string;
244
+ retriable: boolean;
245
+ };
246
+ declare function useVideoState(media: DevulturMedia | null | undefined, options?: {
247
+ locales?: string[];
248
+ }): VideoState;
249
+ interface UseMediaUploadOptions {
250
+ /** Resource visibility. Omitted → config default → private (secure-by-default). */
251
+ visibility?: Visibility;
252
+ /** Fires ONCE, at the persistence point (PUT done + transcode requested). */
253
+ onComplete?: (media: DevulturMedia) => void;
254
+ onError?: (error: MediaUploadError) => void;
255
+ /** Deletes this old video/asset after a successful PUT (best-effort). */
256
+ replaces?: DevulturMedia | null;
257
+ /** Auto-request the transcode for videos. Default `true`; `false` = upload the raw file unprocessed. */
258
+ process?: boolean;
259
+ /** Caption locales to request when transcoding. */
260
+ locales?: string[];
261
+ }
262
+ interface UseMediaUploadReturn {
263
+ /** Triggers the upload; resolves with the `DevulturMedia` at the persistence point, or `null` if cancelled/failed. */
264
+ upload: (file: File) => Promise<DevulturMedia | null>;
265
+ /** The reactive state machine for the full cycle. */
266
+ state: MediaUploadState;
267
+ /** Aborts the in-progress PUT (`uploading` phase). */
268
+ cancel: () => void;
269
+ /** Returns to `idle` and clears everything. */
270
+ reset: () => void;
271
+ }
272
+ /**
273
+ * Unified upload primitive: hides the seam between the (imperative) upload and the (reactive)
274
+ * transcode behind ONE state machine. The consumer calls `upload(file)`, persists the ref in
275
+ * `onComplete`, and renders a `switch` over `state.phase`. Video triggers the transcode on its own;
276
+ * image/file go straight to `ready`. Requires `createUploadUrl` + `getMedia` in the provider (and
277
+ * `processVideo` for video).
278
+ */
279
+ declare function useMediaUpload(options?: UseMediaUploadOptions): UseMediaUploadReturn;
280
+
281
+ export { type CaptionTrack, CropUpload, type CropUploadLabels, type CropUploadProps, type CropUploadRenderProps, DevulturProvider, DownloadLink, Image, type UseMediaUploadOptions, type UseMediaUploadReturn, type VideoProps, type VideoState, useDevultur, useDevulturMedia, useDevulturProgress, useMediaUpload, useMediaUrl, useVideoProps, useVideoState };