@devultur/convex 1.5.2 → 1.6.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.
@@ -39,7 +39,21 @@ var DEVULTUR_ERROR_CODES = [
39
39
  "link_not_found",
40
40
  "link_expired",
41
41
  "link_used",
42
+ "file_gone",
42
43
  "not_configured",
44
+ // Download grants
45
+ "invalid_grant",
46
+ "grant_gone",
47
+ "grant_exhausted",
48
+ "grant_exists",
49
+ "grant_in_query",
50
+ "grant_not_found",
51
+ "grant_cannot_delete",
52
+ "link_exceeds_grant",
53
+ "invalid_link_caps",
54
+ "invalid_max_links",
55
+ "invalid_can_delete",
56
+ "invalid_metadata",
43
57
  // Retention and holds
44
58
  "retention_active",
45
59
  "lock_conflict",
@@ -169,7 +183,20 @@ var devulturErrorLabels = {
169
183
  link_not_found: "This download link doesn't exist.",
170
184
  link_expired: "This download link has expired.",
171
185
  link_used: "This download link was already used.",
186
+ file_gone: "This download link's file was deleted.",
172
187
  not_configured: "This feature isn't set up.",
188
+ invalid_grant: "This download grant isn't valid.",
189
+ grant_gone: "This download grant is no longer valid.",
190
+ grant_exhausted: "This download grant has issued all its links.",
191
+ grant_exists: "This download grant already exists.",
192
+ grant_in_query: "Send the download grant in the Authorization header, never in a URL.",
193
+ grant_not_found: "This download grant doesn't exist.",
194
+ grant_cannot_delete: "This download grant can't delete its file.",
195
+ link_exceeds_grant: "The download link exceeds what the grant allows.",
196
+ invalid_link_caps: "The download link limits aren't valid.",
197
+ invalid_max_links: "The number of links isn't valid.",
198
+ invalid_can_delete: "Whether the grant can delete its file must be true or false.",
199
+ invalid_metadata: "The metadata isn't valid.",
173
200
  retention_active: (details) => {
174
201
  if (details.hold === true) return "This file is on hold and can't be changed or deleted.";
175
202
  const until = formatDate(details.until, "en");
@@ -7,7 +7,7 @@ import {
7
7
  formatBytes,
8
8
  formatDate,
9
9
  labelText
10
- } from "./chunk-YYOZ3PDH.js";
10
+ } from "./chunk-5E5O6VTK.js";
11
11
 
12
12
  // src/locales/es.ts
13
13
  var LOCALE = "es";
@@ -56,7 +56,20 @@ var devulturErrorLabels = {
56
56
  link_not_found: "Este enlace de descarga no existe.",
57
57
  link_expired: "Este enlace de descarga ya venci\xF3.",
58
58
  link_used: "Este enlace de descarga ya se us\xF3.",
59
+ file_gone: "El archivo de este enlace de descarga se elimin\xF3.",
59
60
  not_configured: "Esta funci\xF3n no est\xE1 configurada.",
61
+ invalid_grant: "Este permiso de descarga no es v\xE1lido.",
62
+ grant_gone: "Este permiso de descarga ya no es v\xE1lido.",
63
+ grant_exhausted: "Este permiso de descarga ya emiti\xF3 todos sus enlaces.",
64
+ grant_exists: "Este permiso de descarga ya existe.",
65
+ grant_in_query: "Env\xEDa el permiso de descarga en el encabezado Authorization, nunca en una URL.",
66
+ grant_not_found: "Este permiso de descarga no existe.",
67
+ grant_cannot_delete: "Este permiso de descarga no puede eliminar su archivo.",
68
+ link_exceeds_grant: "El enlace de descarga supera lo que permite el permiso.",
69
+ invalid_link_caps: "Los l\xEDmites del enlace de descarga no son v\xE1lidos.",
70
+ invalid_max_links: "El n\xFAmero de enlaces no es v\xE1lido.",
71
+ invalid_can_delete: "Si el permiso puede eliminar su archivo debe ser verdadero o falso.",
72
+ invalid_metadata: "Los metadatos no son v\xE1lidos.",
60
73
  retention_active: (details) => {
61
74
  if (details.hold === true) return "Este archivo tiene una retenci\xF3n legal y no se puede modificar ni eliminar.";
62
75
  const until = formatDate(details.until, LOCALE);
@@ -1,4 +1,4 @@
1
- declare const DEVULTUR_ERROR_CODES: readonly ["file_too_large", "content_type_not_allowed", "content_type_mismatch", "content_unverified", "unsupported_content_type", "media_type_mismatch", "already_exists", "write_conflict", "upload_not_found", "invalid_upload_key", "invalid_path", "path_required", "invalid_expires_in", "invalid_file_expires_in", "invalid_cache_control", "invalid_limits", "invalid_prefix", "invalid_visibility", "invalid_cursor", "invalid_sha256", "sha256_mismatch", "max_files_reached", "invalid_multipart", "invalid_size", "invalid_part", "invalid_parts", "part_size_mismatch", "part_sha256_required", "invalid_media_metadata", "expired", "invalid_key", "invalid_max_uses", "link_exists", "link_not_found", "link_expired", "link_used", "not_configured", "retention_active", "lock_conflict", "compliance_extend_only", "lock_privilege_required", "lock_record_unreadable", "lock_below_minimum", "invalid_lock", "invalid_hold", "reserved_key", "keys_bucket_missing", "encryption_key_missing", "unauthorized", "session_mismatch", "token_rejected", "reserved_user_id", "invalid_scopes", "user_mismatch", "transcode_not_configured", "transcode_runner_unavailable", "transcode_launch_failed", "original_not_retained", "locale_not_allowed", "captions_provider_rate_limited", "captions_provider_error", "captions_failed", "webhook_secret_missing", "worker_auth_secret_missing", "worker_error", "http_error", "too_large", "unsupported_type", "type_mismatch", "token_unavailable", "page_cap", "render_failed", "capability_unavailable"];
1
+ declare const DEVULTUR_ERROR_CODES: readonly ["file_too_large", "content_type_not_allowed", "content_type_mismatch", "content_unverified", "unsupported_content_type", "media_type_mismatch", "already_exists", "write_conflict", "upload_not_found", "invalid_upload_key", "invalid_path", "path_required", "invalid_expires_in", "invalid_file_expires_in", "invalid_cache_control", "invalid_limits", "invalid_prefix", "invalid_visibility", "invalid_cursor", "invalid_sha256", "sha256_mismatch", "max_files_reached", "invalid_multipart", "invalid_size", "invalid_part", "invalid_parts", "part_size_mismatch", "part_sha256_required", "invalid_media_metadata", "expired", "invalid_key", "invalid_max_uses", "link_exists", "link_not_found", "link_expired", "link_used", "file_gone", "not_configured", "invalid_grant", "grant_gone", "grant_exhausted", "grant_exists", "grant_in_query", "grant_not_found", "grant_cannot_delete", "link_exceeds_grant", "invalid_link_caps", "invalid_max_links", "invalid_can_delete", "invalid_metadata", "retention_active", "lock_conflict", "compliance_extend_only", "lock_privilege_required", "lock_record_unreadable", "lock_below_minimum", "invalid_lock", "invalid_hold", "reserved_key", "keys_bucket_missing", "encryption_key_missing", "unauthorized", "session_mismatch", "token_rejected", "reserved_user_id", "invalid_scopes", "user_mismatch", "transcode_not_configured", "transcode_runner_unavailable", "transcode_launch_failed", "original_not_retained", "locale_not_allowed", "captions_provider_rate_limited", "captions_provider_error", "captions_failed", "webhook_secret_missing", "worker_auth_secret_missing", "worker_error", "http_error", "too_large", "unsupported_type", "type_mismatch", "token_unavailable", "page_cap", "render_failed", "capability_unavailable"];
2
2
  /** A stable error code: match on it, not on `message`. */
3
3
  type DevulturErrorCode = (typeof DEVULTUR_ERROR_CODES)[number];
4
4
  declare function isDevulturErrorCode(code: unknown): code is DevulturErrorCode;
package/dist/index.d.ts CHANGED
@@ -2,9 +2,9 @@ import { WorkerUploadResult, DeleteVideoResult, TranscodeJobResult, ObjectLock,
2
2
  export { CaptionLocale, DevulturConfig, DevulturKind, DevulturMedia, ImageFit, ImageFormat, SecurityLevel, SpeechProvider, StorageProvider, TranscodePreset, TranscodeProvider, TranslationProvider, VideoModuleConfig, Visibility } from '@devultur/core';
3
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 { M as MediaRecord, P as ProgressRecord, D as DevulturComponentConfig } from './types-C0ruHUcu.js';
6
- export { a as DevulturUploadConfig } from './types-C0ruHUcu.js';
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';
5
+ import { M as MediaRecord, P as ProgressRecord, D as DevulturComponentConfig } from './types-VIMqtvkA.js';
6
+ export { a as DevulturUploadConfig } from './types-VIMqtvkA.js';
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-DRvjG-Vd.js';
8
8
  export { dv } from './validators.js';
9
9
 
10
10
  /**
@@ -247,20 +247,20 @@ declare function exposedFunctions(component: ComponentApi, config?: DevulturComp
247
247
  }, any>;
248
248
  createUploadUrl: ExposedFunction<"action", {
249
249
  visibility?: "public" | "private" | undefined;
250
- contentType: string;
251
250
  filename: string;
251
+ contentType: string;
252
252
  }, any>;
253
253
  confirmUpload: ExposedFunction<"action", {
254
254
  filename?: string | undefined;
255
+ width?: number | undefined;
255
256
  height?: number | undefined;
256
257
  pageCount?: number | undefined;
257
- width?: number | undefined;
258
258
  key: string;
259
259
  }, any>;
260
260
  updateMediaMeta: ExposedFunction<"mutation", {
261
+ title?: string | undefined;
261
262
  alt?: string | undefined;
262
263
  tags?: string[] | undefined;
263
- title?: string | undefined;
264
264
  key: string;
265
265
  }, any>;
266
266
  deleteMedia: ExposedFunction<"action", {
@@ -277,7 +277,7 @@ declare function exposedFunctions(component: ComponentApi, config?: DevulturComp
277
277
  key: string;
278
278
  }, any>;
279
279
  listMediaPage: ExposedFunction<"query", {
280
- type?: "video" | "image" | "file" | undefined;
280
+ type?: "image" | "video" | "file" | undefined;
281
281
  paginationOpts: {
282
282
  id?: number;
283
283
  endCursor?: string | null;
@@ -288,7 +288,7 @@ declare function exposedFunctions(component: ComponentApi, config?: DevulturComp
288
288
  };
289
289
  }, any>;
290
290
  searchMedia: ExposedFunction<"query", {
291
- type?: "video" | "image" | "file" | undefined;
291
+ type?: "image" | "video" | "file" | undefined;
292
292
  paginationOpts: {
293
293
  id?: number;
294
294
  endCursor?: string | null;
package/dist/index.js CHANGED
@@ -11,7 +11,7 @@ import {
11
11
  devulturErrorOf,
12
12
  isDevulturError,
13
13
  isDevulturErrorCode
14
- } from "./chunk-YYOZ3PDH.js";
14
+ } from "./chunk-5E5O6VTK.js";
15
15
 
16
16
  // src/index.ts
17
17
  import {
@@ -1,7 +1,7 @@
1
1
  import { VideoPlayerLabels } from '@devultur/react';
2
- import { A as AttachmentLabels, M as MediaLibraryLabels, a as MediaPickerLabels, P as PreviewLabels } from '../preview-labels-C2nUUY_f.js';
3
- import { D as DevulturErrorLabels } from '../error-labels-D0Vbp-qA.js';
4
- import '../types-C0ruHUcu.js';
2
+ import { A as AttachmentLabels, M as MediaLibraryLabels, a as MediaPickerLabels, P as PreviewLabels } from '../preview-labels-CnFuWb4Z.js';
3
+ import { D as DevulturErrorLabels } from '../error-labels-DRvjG-Vd.js';
4
+ import '../types-VIMqtvkA.js';
5
5
  import '@devultur/core';
6
6
  import 'convex/values';
7
7
  import 'convex/server';
@@ -5,12 +5,12 @@ import {
5
5
  mediaPickerLabels,
6
6
  previewLabels,
7
7
  videoPlayerLabels
8
- } from "../chunk-RDVBE3G3.js";
8
+ } from "../chunk-6B56MANA.js";
9
9
  import "../chunk-QBHIZBOQ.js";
10
10
  import {
11
11
  describeDevulturError,
12
12
  labelText
13
- } from "../chunk-YYOZ3PDH.js";
13
+ } from "../chunk-5E5O6VTK.js";
14
14
 
15
15
  // src/locales/es-formal.ts
16
16
  var TRY_AGAIN = "Vuelva a intentarlo.";
@@ -30,6 +30,7 @@ var devulturErrorLabels2 = {
30
30
  if (details.field === "tags") return "Use como m\xE1ximo 20 etiquetas, de hasta 50 caracteres cada una.";
31
31
  return labelText(devulturErrorLabels.invalid_media_metadata, details);
32
32
  },
33
+ grant_in_query: "Env\xEDe el permiso de descarga en el encabezado Authorization, nunca en una URL.",
33
34
  lock_conflict: `La protecci\xF3n del archivo cambi\xF3 mientras tanto. ${TRY_AGAIN}`,
34
35
  lock_privilege_required: "No tiene permiso para acortar ni quitar la protecci\xF3n de este archivo.",
35
36
  encryption_key_missing: "Este archivo ya no se puede leer. Vuelva a subirlo.",
@@ -1,7 +1,7 @@
1
1
  import { VideoPlayerLabels } from '@devultur/react';
2
- import { A as AttachmentLabels, M as MediaLibraryLabels, a as MediaPickerLabels, P as PreviewLabels } from '../preview-labels-C2nUUY_f.js';
3
- import { D as DevulturErrorLabels } from '../error-labels-D0Vbp-qA.js';
4
- import '../types-C0ruHUcu.js';
2
+ import { A as AttachmentLabels, M as MediaLibraryLabels, a as MediaPickerLabels, P as PreviewLabels } from '../preview-labels-CnFuWb4Z.js';
3
+ import { D as DevulturErrorLabels } from '../error-labels-DRvjG-Vd.js';
4
+ import '../types-VIMqtvkA.js';
5
5
  import '@devultur/core';
6
6
  import 'convex/values';
7
7
  import 'convex/server';
@@ -5,9 +5,9 @@ import {
5
5
  mediaPickerLabels,
6
6
  previewLabels,
7
7
  videoPlayerLabels
8
- } from "../chunk-RDVBE3G3.js";
8
+ } from "../chunk-6B56MANA.js";
9
9
  import "../chunk-QBHIZBOQ.js";
10
- import "../chunk-YYOZ3PDH.js";
10
+ import "../chunk-5E5O6VTK.js";
11
11
  export {
12
12
  attachmentLabels,
13
13
  devulturErrorLabels,
@@ -1,4 +1,4 @@
1
- import { M as MediaRecord } from './types-C0ruHUcu.js';
1
+ import { M as MediaRecord } from './types-VIMqtvkA.js';
2
2
 
3
3
  /** What an attachment is, for its icon and type label. */
4
4
  type AttachmentKind = "image" | "video" | "pdf" | "audio" | "text" | "file";
package/dist/react.d.ts CHANGED
@@ -3,12 +3,12 @@ import { V as ViewerTokenAction, a as ViewerTokenArgs, D as DevulturApi } from '
3
3
  export { b as DevulturApiContext, c as DevulturContext, d as DevulturContextValue, e as DevulturProgressContext, f as DevulturProgressContextValue, T as TokenStatus, U as UploadOptions, g as UploadedMedia, h as ViewerTokenResult, u as useCanDeliver, i as useDevultur, j as useDevulturApi, k as useDevulturOptional, l as useDevulturProgress } from './contexts-6F8TJINX.js';
4
4
  import { BakeOutputFormat, MediaUploadState, MediaUploadError } from '@devultur/react';
5
5
  export { MediaUploadError, MediaUploadState, UploadProgress } from '@devultur/react';
6
- import { A as AttachmentLabels, b as MediaFetchError, M as MediaLibraryLabels, P as PreviewLabels, a as MediaPickerLabels } from './preview-labels-C2nUUY_f.js';
7
- export { c as AttachmentKind, d as MediaFetchErrorCode, e as defaultAttachmentLabels, f as defaultMediaLibraryLabels, g as defaultMediaPickerLabels, h as defaultPreviewLabels, i as formatDuration, j as formatFileSize } from './preview-labels-C2nUUY_f.js';
6
+ import { A as AttachmentLabels, b as MediaFetchError, M as MediaLibraryLabels, P as PreviewLabels, a as MediaPickerLabels } from './preview-labels-CnFuWb4Z.js';
7
+ export { c as AttachmentKind, d as MediaFetchErrorCode, e as defaultAttachmentLabels, f as defaultMediaLibraryLabels, g as defaultMediaPickerLabels, h as defaultPreviewLabels, i as formatDuration, j as formatFileSize } from './preview-labels-CnFuWb4Z.js';
8
8
  import { Visibility, DevulturMedia, MediaUrlOptions, MediaStatusValue, ProcessingPhase, FailureReason } from '@devultur/core';
9
9
  import { VideoProps } from './react/hooks.js';
10
10
  export { useMediaUrl, useVideoProps } from './react/hooks.js';
11
- import { M as MediaRecord } from './types-C0ruHUcu.js';
11
+ import { M as MediaRecord } from './types-VIMqtvkA.js';
12
12
  import { P as PdfjsLoader } from './pdf-render-BwJGagvz.js';
13
13
  export { a as PdfjsModule } from './pdf-render-BwJGagvz.js';
14
14
  import 'convex/server';
package/dist/react.js CHANGED
@@ -28,7 +28,7 @@ import {
28
28
  } from "./chunk-QBHIZBOQ.js";
29
29
  import {
30
30
  describeDevulturError
31
- } from "./chunk-YYOZ3PDH.js";
31
+ } from "./chunk-5E5O6VTK.js";
32
32
  import {
33
33
  DevulturApiContext,
34
34
  DevulturCapabilityError,
@@ -5,13 +5,16 @@ import * as convex_server from 'convex/server';
5
5
 
6
6
  declare const _default: convex_server.SchemaDefinition<{
7
7
  media: convex_server.TableDefinition<convex_values.VObject<{
8
+ visibility?: "public" | "private" | undefined;
9
+ width?: number | undefined;
10
+ height?: number | undefined;
11
+ title?: string | undefined;
8
12
  alt?: string | undefined;
9
13
  tags?: string[] | undefined;
10
- title?: string | undefined;
11
- visibility?: "public" | "private" | undefined;
14
+ duration?: number | undefined;
15
+ pageCount?: number | undefined;
12
16
  captionLocales?: string[] | undefined;
13
17
  captionsPending?: boolean | undefined;
14
- duration?: number | undefined;
15
18
  failureReason?: "internal" | "invalid_input" | "unsupported_codec" | "timeout" | "capacity" | undefined;
16
19
  playlistUrl?: string | undefined;
17
20
  retriable?: boolean | undefined;
@@ -23,17 +26,14 @@ declare const _default: convex_server.SchemaDefinition<{
23
26
  total: number;
24
27
  } | undefined;
25
28
  transcodeStatus?: "queued" | "transcoding" | "captioning" | "ready" | "failed" | undefined;
26
- height?: number | undefined;
27
- pageCount?: number | undefined;
28
- width?: number | undefined;
29
29
  objectKey?: string | undefined;
30
30
  searchText?: string | undefined;
31
31
  renditions?: {
32
- height?: number | undefined;
33
32
  width?: number | undefined;
33
+ height?: number | undefined;
34
+ kind: "hls" | "mp4";
34
35
  key: string;
35
36
  variant: string;
36
- kind: "hls" | "mp4";
37
37
  rung: string;
38
38
  url: string;
39
39
  }[] | undefined;
@@ -43,11 +43,11 @@ declare const _default: convex_server.SchemaDefinition<{
43
43
  } | undefined;
44
44
  hasAudio?: boolean | undefined;
45
45
  storyboardKey?: string | undefined;
46
- type: "video" | "image" | "file";
47
- key: string;
48
- contentType: string;
46
+ type: "image" | "video" | "file";
49
47
  filename: string;
48
+ key: string;
50
49
  sizeBytes: number;
50
+ contentType: string;
51
51
  createdAt: number;
52
52
  }, {
53
53
  captionLocales: convex_values.VArray<string[] | undefined, convex_values.VString<string, "required">, "optional">;
@@ -55,19 +55,19 @@ declare const _default: convex_server.SchemaDefinition<{
55
55
  duration: convex_values.VFloat64<number | undefined, "optional">;
56
56
  createdAt: convex_values.VFloat64<number, "required">;
57
57
  renditions: convex_values.VArray<{
58
- height?: number | undefined;
59
58
  width?: number | undefined;
59
+ height?: number | undefined;
60
+ kind: "hls" | "mp4";
60
61
  key: string;
61
62
  variant: string;
62
- kind: "hls" | "mp4";
63
63
  rung: string;
64
64
  url: string;
65
65
  }[] | undefined, convex_values.VObject<{
66
- height?: number | undefined;
67
66
  width?: number | undefined;
67
+ height?: number | undefined;
68
+ kind: "hls" | "mp4";
68
69
  key: string;
69
70
  variant: string;
70
- kind: "hls" | "mp4";
71
71
  rung: string;
72
72
  url: string;
73
73
  }, {
@@ -78,7 +78,7 @@ declare const _default: convex_server.SchemaDefinition<{
78
78
  width: convex_values.VFloat64<number | undefined, "optional">;
79
79
  height: convex_values.VFloat64<number | undefined, "optional">;
80
80
  url: convex_values.VString<string, "required">;
81
- }, "required", "key" | "height" | "width" | "variant" | "kind" | "rung" | "url">, "optional">;
81
+ }, "required", "kind" | "key" | "width" | "height" | "variant" | "rung" | "url">, "optional">;
82
82
  posterUrls: convex_values.VObject<{
83
83
  jpg?: string | undefined;
84
84
  avif?: string | undefined;
@@ -89,7 +89,7 @@ declare const _default: convex_server.SchemaDefinition<{
89
89
  hasAudio: convex_values.VBoolean<boolean | undefined, "optional">;
90
90
  storyboardKey: convex_values.VString<string | undefined, "optional">;
91
91
  key: convex_values.VString<string, "required">;
92
- type: convex_values.VUnion<"video" | "image" | "file", [convex_values.VLiteral<"video", "required">, convex_values.VLiteral<"image", "required">, convex_values.VLiteral<"file", "required">], "required", never>;
92
+ type: convex_values.VUnion<"image" | "video" | "file", [convex_values.VLiteral<"video", "required">, convex_values.VLiteral<"image", "required">, convex_values.VLiteral<"file", "required">], "required", never>;
93
93
  filename: convex_values.VString<string, "required">;
94
94
  contentType: convex_values.VString<string, "required">;
95
95
  sizeBytes: convex_values.VFloat64<number, "required">;
@@ -117,7 +117,7 @@ declare const _default: convex_server.SchemaDefinition<{
117
117
  retriable: convex_values.VBoolean<boolean | undefined, "optional">;
118
118
  playlistUrl: convex_values.VString<string | undefined, "optional">;
119
119
  thumbnailKey: convex_values.VString<string | undefined, "optional">;
120
- }, "required", "type" | "key" | "alt" | "tags" | "title" | "contentType" | "filename" | "sizeBytes" | "visibility" | "captionLocales" | "captionsPending" | "duration" | "failureReason" | "playlistUrl" | "retriable" | "thumbnailKey" | "transcodeError" | "transcodeProgress" | "transcodeStatus" | "height" | "pageCount" | "width" | "objectKey" | "searchText" | "createdAt" | "renditions" | "posterUrls" | "hasAudio" | "storyboardKey" | "transcodeProgress.current" | "transcodeProgress.total" | "transcodeProgress.phase" | "posterUrls.jpg" | "posterUrls.avif">, {
120
+ }, "required", "type" | "visibility" | "filename" | "key" | "width" | "height" | "title" | "alt" | "tags" | "sizeBytes" | "duration" | "pageCount" | "contentType" | "captionLocales" | "captionsPending" | "failureReason" | "playlistUrl" | "retriable" | "thumbnailKey" | "transcodeError" | "transcodeProgress" | "transcodeStatus" | "objectKey" | "searchText" | "createdAt" | "renditions" | "posterUrls" | "hasAudio" | "storyboardKey" | "transcodeProgress.current" | "transcodeProgress.total" | "transcodeProgress.phase" | "posterUrls.jpg" | "posterUrls.avif">, {
121
121
  by_key: ["key", "_creationTime"];
122
122
  by_type: ["type", "_creationTime"];
123
123
  }, {
@@ -12,21 +12,21 @@ import { DevulturKind } from '@devultur/core';
12
12
  */
13
13
  declare const dv: {
14
14
  media(kind?: DevulturKind): convex_values.VObject<{
15
+ filename?: string | undefined;
15
16
  key?: string | undefined;
17
+ width?: number | undefined;
18
+ height?: number | undefined;
19
+ title?: string | undefined;
16
20
  alt?: string | undefined;
17
21
  tags?: string[] | undefined;
18
- title?: string | undefined;
19
- filename?: string | undefined;
20
22
  sizeBytes?: number | undefined;
21
23
  duration?: number | undefined;
22
- height?: number | undefined;
23
24
  pageCount?: number | undefined;
24
- width?: number | undefined;
25
- visibility: "public" | "private";
26
25
  id: string;
27
26
  kind: DevulturKind;
27
+ visibility: "public" | "private";
28
28
  }, {
29
- kind: convex_values.VLiteral<DevulturKind, "required"> | convex_values.VUnion<"video" | "image" | "file", [convex_values.VLiteral<"image", "required">, convex_values.VLiteral<"video", "required">, convex_values.VLiteral<"file", "required">], "required", never>;
29
+ kind: convex_values.VLiteral<DevulturKind, "required"> | convex_values.VUnion<"image" | "video" | "file", [convex_values.VLiteral<"image", "required">, convex_values.VLiteral<"video", "required">, convex_values.VLiteral<"file", "required">], "required", never>;
30
30
  id: convex_values.VString<string, "required">;
31
31
  visibility: convex_values.VUnion<"public" | "private", [convex_values.VLiteral<"public", "required">, convex_values.VLiteral<"private", "required">], "required", never>;
32
32
  filename: convex_values.VString<string | undefined, "optional">;
@@ -39,7 +39,7 @@ declare const dv: {
39
39
  sizeBytes: convex_values.VFloat64<number | undefined, "optional">;
40
40
  duration: convex_values.VFloat64<number | undefined, "optional">;
41
41
  pageCount: convex_values.VFloat64<number | undefined, "optional">;
42
- }, "required", "key" | "alt" | "tags" | "title" | "filename" | "sizeBytes" | "visibility" | "duration" | "height" | "pageCount" | "width" | "id" | "kind">;
42
+ }, "required", "id" | "kind" | "visibility" | "filename" | "key" | "width" | "height" | "title" | "alt" | "tags" | "sizeBytes" | "duration" | "pageCount">;
43
43
  };
44
44
 
45
45
  export { dv };
@@ -8,7 +8,7 @@ same API as yours. Add plugins to extend the Worker, or replace a default one.
8
8
  import { createWorker, defaultPlugins } from "@devultur/self-host";
9
9
  import { usageLog } from "./plugins/usage-log";
10
10
 
11
- export { DownloadLink } from "@devultur/self-host";
11
+ export { DownloadGrants, DownloadLink } from "@devultur/self-host";
12
12
 
13
13
  export default createWorker({
14
14
  plugins: [...defaultPlugins({ transcode: { maxResolution: "1080p" } }), usageLog()],
@@ -22,7 +22,7 @@ export default createWorker({
22
22
  | `staticConfig(config)` | Applies the [rules](self-host.md#rules) to every request. Put it first: later plugins narrow it. |
23
23
  | `workerSecretAuth()` | Control routes require `Authorization: Bearer <WORKER_AUTH_SECRET>`; the transcode runner uploads with the same secret. |
24
24
  | `credentialPolicy(options?)` | Decides before a credential is issued. Without options it issues what is asked; `maxTtlSeconds: { viewer: 3600 }` caps a kind. |
25
- | `downloadLinks()` | `POST /v1/token/download` and `GET /v1/download/<id>` (needs the `DOWNLOAD_LINKS` Durable Object). Without it both answer `404`. |
25
+ | `downloadLinks()` | `POST /v1/token/download` and `GET /v1/download/<id>` (needs the `DOWNLOAD_LINKS` Durable Object), and download grants: `/v1/token/grant` and `/v1/grant/*` (also need `DOWNLOAD_GRANTS`). Without it they answer `404`. |
26
26
 
27
27
  `createWorker()` refuses to start when no plugin authenticates requests, so dropping `workerSecretAuth()` without
28
28
  an auth plugin of yours fails at deploy, not in production.
@@ -42,6 +42,7 @@ interface WorkerPlugin {
42
42
  routes?: Array<{ path: string; app: Hono }>;
43
43
  issueCredential?: (context: Context, request: CredentialRequest) => CredentialDecision | Promise<CredentialDecision>;
44
44
  beforeLinkRedeem?: (context: Context, link: DownloadLinkInfo) => Response | null | Promise<Response | null>;
45
+ authorizeGrant?: (context: Context, request: GrantAccessRequest) => Response | null | Promise<Response | null>;
45
46
  authorizeLockChange?: (context: Context, request: LockChangeRequest) => LockChangeDecision | Promise<LockChangeDecision>;
46
47
  onLockAudit?: (context: Context, record: LockAuditRecord) => void | Promise<void>;
47
48
  }
@@ -94,8 +95,8 @@ updateRequestState(context, { context: { appId: "web" } });
94
95
 
95
96
  ## Hooks
96
97
 
97
- **`issueCredential`** runs before the Worker issues a viewer token, an upload token, a download link or the runner's
98
- service token. It gets the `kind`, the `scopes`, the `path` it is bound to and the requested `expiresAt` (epoch ms).
98
+ **`issueCredential`** runs before the Worker issues a viewer token, an upload token, a download link, a download grant
99
+ or the runner's service token. It gets the `kind`, the `scopes`, the `path` it is bound to and the requested `expiresAt` (epoch ms).
99
100
  Return `{ allow: false, response }` to refuse, or `{ allow: true, expiresAt?, claims? }` to issue it: `expiresAt`
100
101
  caps the expiry, and `claims` are added to a token. The hooks of every plugin run in order: the first refusal wins,
101
102
  the earliest `expiresAt` applies, and the claims of all of them are added.
@@ -120,6 +121,12 @@ allow gets `403 { "code": "token_rejected" }`; which claim failed is only logged
120
121
  **`beforeLinkRedeem`** runs before `GET /v1/download/<id>` spends a use of a link. Return a `Response` to refuse it
121
122
  (the use isn't spent), or `null` to let it go on. It gets the link's `storagePrefix`, `key` and plugin `context`.
122
123
 
124
+ **`authorizeGrant`** runs before a download grant is used, once the Worker checked the grant is live and belongs to the
125
+ request's storage prefix: `action` is `head`, `issue-link`, `delete-file` or `revoke` (its bearer), or `owner-revoke`
126
+ (the owner, by its public id). It also gets the `grant` (its key, caps, `linksIssued`, metadata and the plugin `context`
127
+ it was issued with, never its secret). Return a `Response` to refuse (a refused link spends nothing), or `null`. Use it
128
+ to tie a grant to the credential that issued it, for example to refuse it once that credential is revoked.
129
+
123
130
  **`authorizeLockChange`** runs before the Worker changes a lock ([Retention and holds](object-lock.md)):
124
131
  `PUT /v1/lock/<key>` once per change, and `POST /v1/lock/hold` once per page. It gets:
125
132
 
@@ -173,7 +180,8 @@ const auditToD1: WorkerPlugin = {
173
180
  ## After the route
174
181
 
175
182
  - `getRouteResult(context)` tells a middleware that runs after `await next()` what the route did: `redeemedLink`
176
- (the download link just redeemed), `originalDeleted` (`transcode.complete` deleted the original),
183
+ (the download link just redeemed), `issuedGrant` and `revokedGrant` (a download grant, with its public id; a revoke
184
+ says `by: "bearer"` or `"owner"`), `grantDeletedKey` (a grant deleted its file), `originalDeleted` (`transcode.complete` deleted the original),
177
185
  `originalRetained` (it kept a locked original instead) and
178
186
  `transcodeJob` (the job a runner callback belongs to, with its plugin `context`).
179
187
  - `getPendingUpload(context)`, before the route, returns the upload an upload URL points to: its key, storage key,
@@ -44,7 +44,7 @@ media Worker from `@devultur/self-host`:
44
44
 
45
45
  ```typescript
46
46
  import { createWorker } from "@devultur/self-host";
47
- export { DownloadLink } from "@devultur/self-host";
47
+ export { DownloadGrants, DownloadLink } from "@devultur/self-host";
48
48
 
49
49
  export default createWorker();
50
50
  ```
@@ -98,7 +98,7 @@ It prints a config snippet with the new namespace's `id`. Paste that id into the
98
98
 
99
99
  #### 4. Write `wrangler.toml`
100
100
 
101
- Bind both buckets, the KV namespace and the `DownloadLink` Durable Object:
101
+ Bind both buckets, the KV namespace and the `DownloadLink` and `DownloadGrants` Durable Objects:
102
102
 
103
103
  ```toml
104
104
  name = "media"
@@ -123,13 +123,22 @@ id = "<kv-namespace-id>"
123
123
  name = "DOWNLOAD_LINKS"
124
124
  class_name = "DownloadLink"
125
125
 
126
+ [[durable_objects.bindings]]
127
+ name = "DOWNLOAD_GRANTS"
128
+ class_name = "DownloadGrants"
129
+
126
130
  [[migrations]]
127
131
  tag = "v1"
128
132
  new_sqlite_classes = ["DownloadLink"]
133
+
134
+ [[migrations]]
135
+ tag = "v2"
136
+ new_sqlite_classes = ["DownloadGrants"]
129
137
  ```
130
138
 
131
- Without the `DOWNLOAD_LINKS` binding, `POST /v1/token/download` answers `501`. For staging and production in one
132
- file, see [Per-environment config](#per-environment-config).
139
+ Without the `DOWNLOAD_LINKS` binding, `POST /v1/token/download` answers `501`; without `DOWNLOAD_GRANTS`, the download
140
+ grant routes do. A Worker deployed before download grants keeps its `v1` migration and adds `v2`. For staging and
141
+ production in one file, see [Per-environment config](#per-environment-config).
133
142
 
134
143
  #### 5. Set the secrets
135
144
 
@@ -189,6 +198,10 @@ account_id = "<your-account-id>"
189
198
  tag = "v1"
190
199
  new_sqlite_classes = ["DownloadLink"]
191
200
 
201
+ [[migrations]]
202
+ tag = "v2"
203
+ new_sqlite_classes = ["DownloadGrants"]
204
+
192
205
  # Staging
193
206
  [env.staging]
194
207
  name = "media-staging"
@@ -209,6 +222,10 @@ id = "<staging-kv-namespace-id>"
209
222
  name = "DOWNLOAD_LINKS"
210
223
  class_name = "DownloadLink"
211
224
 
225
+ [[env.staging.durable_objects.bindings]]
226
+ name = "DOWNLOAD_GRANTS"
227
+ class_name = "DownloadGrants"
228
+
212
229
  # Production
213
230
  [env.production]
214
231
  name = "media-production"
@@ -228,6 +245,10 @@ id = "<production-kv-namespace-id>"
228
245
  [[env.production.durable_objects.bindings]]
229
246
  name = "DOWNLOAD_LINKS"
230
247
  class_name = "DownloadLink"
248
+
249
+ [[env.production.durable_objects.bindings]]
250
+ name = "DOWNLOAD_GRANTS"
251
+ class_name = "DownloadGrants"
231
252
  ```
232
253
 
233
254
  `vars` aren't inherited either: repeat `[env.<name>.vars]` (for example `TRANSCODE_RUNNER`) in each one. Then set
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@devultur/convex",
3
- "version": "1.5.2",
3
+ "version": "1.6.1",
4
4
  "description": "Convex component for Devultur media: media records, uploads, transcodes, watch progress and React bindings",
5
5
  "keywords": [
6
6
  "convex",
@@ -69,8 +69,8 @@
69
69
  "docs"
70
70
  ],
71
71
  "dependencies": {
72
- "@devultur/core": "1.5.2",
73
- "@devultur/react": "1.5.2"
72
+ "@devultur/core": "1.6.1",
73
+ "@devultur/react": "1.6.1"
74
74
  },
75
75
  "peerDependencies": {
76
76
  "convex": ">=1.25.0",