@pro-laico/payload-mux 0.0.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 (72) hide show
  1. package/LICENSE.md +52 -0
  2. package/README.md +11 -0
  3. package/dist/collections/MuxVideo.d.ts +7 -0
  4. package/dist/collections/MuxVideo.d.ts.map +1 -0
  5. package/dist/collections/MuxVideo.js +266 -0
  6. package/dist/collections/MuxVideo.js.map +1 -0
  7. package/dist/components/MuxUploaderField.d.ts +10 -0
  8. package/dist/components/MuxUploaderField.d.ts.map +1 -0
  9. package/dist/components/MuxUploaderField.js +143 -0
  10. package/dist/components/MuxUploaderField.js.map +1 -0
  11. package/dist/components/MuxVideoGifCell.d.ts +5 -0
  12. package/dist/components/MuxVideoGifCell.d.ts.map +1 -0
  13. package/dist/components/MuxVideoGifCell.js +21 -0
  14. package/dist/components/MuxVideoGifCell.js.map +1 -0
  15. package/dist/components/MuxVideoImageCell.d.ts +5 -0
  16. package/dist/components/MuxVideoImageCell.d.ts.map +1 -0
  17. package/dist/components/MuxVideoImageCell.js +21 -0
  18. package/dist/components/MuxVideoImageCell.js.map +1 -0
  19. package/dist/components/mux-uploader.css +23 -0
  20. package/dist/endpoints/upload.d.ts +10 -0
  21. package/dist/endpoints/upload.d.ts.map +1 -0
  22. package/dist/endpoints/upload.js +49 -0
  23. package/dist/endpoints/upload.js.map +1 -0
  24. package/dist/endpoints/webhook.d.ts +16 -0
  25. package/dist/endpoints/webhook.d.ts.map +1 -0
  26. package/dist/endpoints/webhook.js +148 -0
  27. package/dist/endpoints/webhook.js.map +1 -0
  28. package/dist/hooks/afterDelete.d.ts +6 -0
  29. package/dist/hooks/afterDelete.d.ts.map +1 -0
  30. package/dist/hooks/afterDelete.js +12 -0
  31. package/dist/hooks/afterDelete.js.map +1 -0
  32. package/dist/hooks/beforeChange.d.ts +11 -0
  33. package/dist/hooks/beforeChange.d.ts.map +1 -0
  34. package/dist/hooks/beforeChange.js +95 -0
  35. package/dist/hooks/beforeChange.js.map +1 -0
  36. package/dist/hooks/beforeValidate.d.ts +17 -0
  37. package/dist/hooks/beforeValidate.d.ts.map +1 -0
  38. package/dist/hooks/beforeValidate.js +61 -0
  39. package/dist/hooks/beforeValidate.js.map +1 -0
  40. package/dist/index.d.ts +6 -0
  41. package/dist/index.d.ts.map +1 -0
  42. package/dist/index.js +8 -0
  43. package/dist/index.js.map +1 -0
  44. package/dist/lib/defaultAccess.d.ts +5 -0
  45. package/dist/lib/defaultAccess.d.ts.map +1 -0
  46. package/dist/lib/defaultAccess.js +4 -0
  47. package/dist/lib/defaultAccess.js.map +1 -0
  48. package/dist/lib/delay.d.ts +3 -0
  49. package/dist/lib/delay.d.ts.map +1 -0
  50. package/dist/lib/delay.js +3 -0
  51. package/dist/lib/delay.js.map +1 -0
  52. package/dist/lib/getAssetMetadata.d.ts +34 -0
  53. package/dist/lib/getAssetMetadata.d.ts.map +1 -0
  54. package/dist/lib/getAssetMetadata.js +21 -0
  55. package/dist/lib/getAssetMetadata.js.map +1 -0
  56. package/dist/lib/ingest.d.ts +53 -0
  57. package/dist/lib/ingest.d.ts.map +1 -0
  58. package/dist/lib/ingest.js +109 -0
  59. package/dist/lib/ingest.js.map +1 -0
  60. package/dist/lib/isAllowed.d.ts +7 -0
  61. package/dist/lib/isAllowed.d.ts.map +1 -0
  62. package/dist/lib/isAllowed.js +6 -0
  63. package/dist/lib/isAllowed.js.map +1 -0
  64. package/dist/plugin.d.ts +13 -0
  65. package/dist/plugin.d.ts.map +1 -0
  66. package/dist/plugin.js +80 -0
  67. package/dist/plugin.js.map +1 -0
  68. package/dist/types.d.ts +78 -0
  69. package/dist/types.d.ts.map +1 -0
  70. package/dist/types.js +3 -0
  71. package/dist/types.js.map +1 -0
  72. package/package.json +89 -0
@@ -0,0 +1,34 @@
1
+ /** The fields `getAssetMetadata` reads, shared by the Mux `Asset` (from the assets API) and
2
+ * the asset webhook event `data` — both satisfy this structurally, so callers pass either
3
+ * without a cast. */
4
+ export interface AssetLike {
5
+ playback_ids?: Array<{
6
+ id: string;
7
+ policy: 'public' | 'signed' | 'drm';
8
+ }> | null;
9
+ aspect_ratio?: string | null;
10
+ duration?: number | null;
11
+ tracks?: ReadonlyArray<{
12
+ type?: string;
13
+ max_width?: number;
14
+ max_height?: number;
15
+ }> | null;
16
+ }
17
+ /** The subset of the video doc derived from a ready Mux asset. The plugin tracks only the two
18
+ * policies it can build URLs for; a `drm` playback id (rare) is treated as `signed`, since it
19
+ * too needs a token. */
20
+ export interface MuxAssetMetadata {
21
+ playbackOptions?: Array<{
22
+ playbackId: string;
23
+ playbackPolicy: 'public' | 'signed';
24
+ }>;
25
+ aspectRatio?: string;
26
+ duration?: number;
27
+ maxWidth?: number;
28
+ maxHeight?: number;
29
+ }
30
+ /** Map a ready Mux asset (or asset webhook payload) onto the video doc's fields: playback
31
+ * ids/policies, a CSS-friendly aspect ratio (`16:9` → `16/9`), duration, and the video
32
+ * track's max dimensions. */
33
+ export declare const getAssetMetadata: (asset: AssetLike) => MuxAssetMetadata;
34
+ //# sourceMappingURL=getAssetMetadata.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"getAssetMetadata.d.ts","sourceRoot":"","sources":["../../src/lib/getAssetMetadata.ts"],"names":[],"mappings":"AAAA;;sBAEsB;AACtB,MAAM,WAAW,SAAS;IACxB,YAAY,CAAC,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,QAAQ,GAAG,QAAQ,GAAG,KAAK,CAAA;KAAE,CAAC,GAAG,IAAI,CAAA;IAChF,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC5B,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,MAAM,CAAC,EAAE,aAAa,CAAC;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,GAAG,IAAI,CAAA;CAC1F;AAED;;yBAEyB;AACzB,MAAM,WAAW,gBAAgB;IAC/B,eAAe,CAAC,EAAE,KAAK,CAAC;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,cAAc,EAAE,QAAQ,GAAG,QAAQ,CAAA;KAAE,CAAC,CAAA;IACpF,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED;;8BAE8B;AAC9B,eAAO,MAAM,gBAAgB,GAAI,OAAO,SAAS,KAAG,gBAYnD,CAAA"}
@@ -0,0 +1,21 @@
1
+ /** The fields `getAssetMetadata` reads, shared by the Mux `Asset` (from the assets API) and
2
+ * the asset webhook event `data` — both satisfy this structurally, so callers pass either
3
+ * without a cast. */ /** Map a ready Mux asset (or asset webhook payload) onto the video doc's fields: playback
4
+ * ids/policies, a CSS-friendly aspect ratio (`16:9` → `16/9`), duration, and the video
5
+ * track's max dimensions. */ export const getAssetMetadata = (asset)=>{
6
+ const videoTrack = asset.tracks?.find((track)=>track.type === 'video');
7
+ return {
8
+ playbackOptions: asset.playback_ids?.map((value)=>({
9
+ playbackId: value.id,
10
+ playbackPolicy: value.policy === 'public' ? 'public' : 'signed'
11
+ })),
12
+ aspectRatio: asset.aspect_ratio?.replace(':', '/'),
13
+ duration: asset.duration ?? undefined,
14
+ ...videoTrack ? {
15
+ maxWidth: videoTrack.max_width,
16
+ maxHeight: videoTrack.max_height
17
+ } : {}
18
+ };
19
+ };
20
+
21
+ //# sourceMappingURL=getAssetMetadata.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/lib/getAssetMetadata.ts"],"sourcesContent":["/** The fields `getAssetMetadata` reads, shared by the Mux `Asset` (from the assets API) and\n * the asset webhook event `data` — both satisfy this structurally, so callers pass either\n * without a cast. */\nexport interface AssetLike {\n playback_ids?: Array<{ id: string; policy: 'public' | 'signed' | 'drm' }> | null\n aspect_ratio?: string | null\n duration?: number | null\n tracks?: ReadonlyArray<{ type?: string; max_width?: number; max_height?: number }> | null\n}\n\n/** The subset of the video doc derived from a ready Mux asset. The plugin tracks only the two\n * policies it can build URLs for; a `drm` playback id (rare) is treated as `signed`, since it\n * too needs a token. */\nexport interface MuxAssetMetadata {\n playbackOptions?: Array<{ playbackId: string; playbackPolicy: 'public' | 'signed' }>\n aspectRatio?: string\n duration?: number\n maxWidth?: number\n maxHeight?: number\n}\n\n/** Map a ready Mux asset (or asset webhook payload) onto the video doc's fields: playback\n * ids/policies, a CSS-friendly aspect ratio (`16:9` → `16/9`), duration, and the video\n * track's max dimensions. */\nexport const getAssetMetadata = (asset: AssetLike): MuxAssetMetadata => {\n const videoTrack = asset.tracks?.find((track) => track.type === 'video')\n\n return {\n playbackOptions: asset.playback_ids?.map((value) => ({\n playbackId: value.id,\n playbackPolicy: value.policy === 'public' ? 'public' : 'signed',\n })),\n aspectRatio: asset.aspect_ratio?.replace(':', '/'),\n duration: asset.duration ?? undefined,\n ...(videoTrack ? { maxWidth: videoTrack.max_width, maxHeight: videoTrack.max_height } : {}),\n }\n}\n"],"names":["getAssetMetadata","asset","videoTrack","tracks","find","track","type","playbackOptions","playback_ids","map","value","playbackId","id","playbackPolicy","policy","aspectRatio","aspect_ratio","replace","duration","undefined","maxWidth","max_width","maxHeight","max_height"],"mappings":"AAAA;;oBAEoB,GAmBpB;;4BAE4B,GAC5B,OAAO,MAAMA,mBAAmB,CAACC;IAC/B,MAAMC,aAAaD,MAAME,MAAM,EAAEC,KAAK,CAACC,QAAUA,MAAMC,IAAI,KAAK;IAEhE,OAAO;QACLC,iBAAiBN,MAAMO,YAAY,EAAEC,IAAI,CAACC,QAAW,CAAA;gBACnDC,YAAYD,MAAME,EAAE;gBACpBC,gBAAgBH,MAAMI,MAAM,KAAK,WAAW,WAAW;YACzD,CAAA;QACAC,aAAad,MAAMe,YAAY,EAAEC,QAAQ,KAAK;QAC9CC,UAAUjB,MAAMiB,QAAQ,IAAIC;QAC5B,GAAIjB,aAAa;YAAEkB,UAAUlB,WAAWmB,SAAS;YAAEC,WAAWpB,WAAWqB,UAAU;QAAC,IAAI,CAAC,CAAC;IAC5F;AACF,EAAC"}
@@ -0,0 +1,53 @@
1
+ import type Mux from '@mux/mux-node';
2
+ import type { Payload } from 'payload';
3
+ import type { MuxVideoNewAssetSettings } from '../types';
4
+ /** A server-side video source: a local file path or an `http(s)` URL. */
5
+ export type MuxSource = string;
6
+ export interface IngestMuxAssetOptions {
7
+ /** Base new-asset settings — normally the plugin's `uploadSettings.new_asset_settings`, so a
8
+ * server-side ingest produces the same asset config as the admin direct-upload path. */
9
+ newAssetSettings?: MuxVideoNewAssetSettings;
10
+ /** Playback policy override for this asset. When set, wins over `newAssetSettings`'s policy;
11
+ * otherwise the configured policy applies (falling back to `'public'`). */
12
+ playbackPolicy?: 'public' | 'signed';
13
+ /** CORS origin for the direct upload. @default '*' */
14
+ corsOrigin?: string;
15
+ }
16
+ /**
17
+ * Resolve the `new_asset_settings` for an ingest, mirroring the admin path (`upload.ts`): a
18
+ * `['public']` default, overlaid with the plugin's configured `newAssetSettings`, with a per-call
19
+ * `playbackPolicy` winning last. Keeps seeded/ingested videos in sync with admin uploads.
20
+ */
21
+ export declare function resolveNewAssetSettings(newAssetSettings?: MuxVideoNewAssetSettings, playbackPolicy?: 'public' | 'signed'): MuxVideoNewAssetSettings;
22
+ /**
23
+ * Ingest a video into Mux server-side: create a direct upload, PUT the source bytes (from a
24
+ * local path or an `http(s)` URL), and poll until the asset is `ready`. The programmatic
25
+ * counterpart to the admin uploader's client-side direct upload — used for seeding, imports,
26
+ * and migrations. Returns the ready asset.
27
+ *
28
+ * `new_asset_settings` are resolved by {@link resolveNewAssetSettings} — the plugin's configured
29
+ * `newAssetSettings` over a `['public']` default, with `playbackPolicy` as a per-call override —
30
+ * so seeded videos honor the same playback policy as admin uploads.
31
+ */
32
+ export declare function ingestMuxAsset(mux: Mux, source: MuxSource, opts?: IngestMuxAssetOptions): Promise<Mux.Video.Assets.Asset>;
33
+ export interface IngestMuxVideoOptions {
34
+ /** Local file path or `http(s)` URL of the video to ingest. */
35
+ source: MuxSource;
36
+ /** Title for the created `mux-video` doc (must be unique within the collection). */
37
+ title: string;
38
+ /** Playback policy override for the uploaded asset. @default the plugin's configured policy */
39
+ playbackPolicy?: 'public' | 'signed';
40
+ /** Optional poster timestamp (seconds). */
41
+ posterTimestamp?: number;
42
+ /** The `mux-video` collection slug. @default 'mux-video' */
43
+ collection?: string;
44
+ }
45
+ /**
46
+ * Create a `mux-video` doc from a local file or URL — the programmatic equivalent of uploading
47
+ * through the admin uploader. Hands `source` to the collection, whose `beforeValidate` hook
48
+ * uploads it to Mux and folds in the ready asset's metadata.
49
+ */
50
+ export declare function ingestMuxVideo(payload: Payload, opts: IngestMuxVideoOptions): Promise<{
51
+ id: string | number;
52
+ }>;
53
+ //# sourceMappingURL=ingest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ingest.d.ts","sourceRoot":"","sources":["../../src/lib/ingest.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,GAAG,MAAM,eAAe,CAAA;AACpC,OAAO,KAAK,EAAkB,OAAO,EAAE,MAAM,SAAS,CAAA;AACtD,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,UAAU,CAAA;AAGxD,yEAAyE;AACzE,MAAM,MAAM,SAAS,GAAG,MAAM,CAAA;AA6B9B,MAAM,WAAW,qBAAqB;IACpC;6FACyF;IACzF,gBAAgB,CAAC,EAAE,wBAAwB,CAAA;IAC3C;gFAC4E;IAC5E,cAAc,CAAC,EAAE,QAAQ,GAAG,QAAQ,CAAA;IACpC,sDAAsD;IACtD,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,CACrC,gBAAgB,CAAC,EAAE,wBAAwB,EAC3C,cAAc,CAAC,EAAE,QAAQ,GAAG,QAAQ,GACnC,wBAAwB,CAM1B;AAED;;;;;;;;;GASG;AACH,wBAAsB,cAAc,CAAC,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,IAAI,GAAE,qBAA0B,GAAG,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAgCnI;AAED,MAAM,WAAW,qBAAqB;IACpC,+DAA+D;IAC/D,MAAM,EAAE,SAAS,CAAA;IACjB,oFAAoF;IACpF,KAAK,EAAE,MAAM,CAAA;IACb,+FAA+F;IAC/F,cAAc,CAAC,EAAE,QAAQ,GAAG,QAAQ,CAAA;IACpC,2CAA2C;IAC3C,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,4DAA4D;IAC5D,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB;AAED;;;;GAIG;AACH,wBAAsB,cAAc,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,qBAAqB,GAAG,OAAO,CAAC;IAAE,EAAE,EAAE,MAAM,GAAG,MAAM,CAAA;CAAE,CAAC,CAWpH"}
@@ -0,0 +1,109 @@
1
+ import { createReadStream } from "node:fs";
2
+ import { stat } from "node:fs/promises";
3
+ import { delay } from "./delay.js";
4
+ /** Poll `fetch` until `done(value)` is true or the deadline passes; returns the last value. */ async function pollUntil(fetch1, done, intervalMs, timeoutMs = 120_000) {
5
+ const deadline = Date.now() + timeoutMs;
6
+ let value = await fetch1();
7
+ while(!done(value) && Date.now() < deadline){
8
+ await delay(intervalMs);
9
+ value = await fetch1();
10
+ }
11
+ return value;
12
+ }
13
+ /** Build the PUT body for the Mux upload. A local path streams from disk with a Content-Length
14
+ * (so a large video never sits fully in memory); an `http(s)` URL is fetched and forwarded.
15
+ * Note: a URL source is buffered fully in memory before the PUT (Mux's upload endpoint needs a
16
+ * known length) — for very large remote files, download to disk and ingest the local path. */ async function buildUploadBody(source) {
17
+ if (/^https?:\/\//i.test(source)) {
18
+ const res = await fetch(source);
19
+ if (!res.ok) throw new Error(`[payload-mux] failed to fetch source '${source}': ${res.status} ${res.statusText}`);
20
+ return {
21
+ body: new Blob([
22
+ await res.arrayBuffer()
23
+ ])
24
+ };
25
+ }
26
+ const { size } = await stat(source).catch(()=>{
27
+ throw new Error(`[payload-mux] source file not found: ${source}`);
28
+ });
29
+ return {
30
+ body: createReadStream(source),
31
+ headers: {
32
+ 'Content-Length': String(size)
33
+ }
34
+ };
35
+ }
36
+ /**
37
+ * Resolve the `new_asset_settings` for an ingest, mirroring the admin path (`upload.ts`): a
38
+ * `['public']` default, overlaid with the plugin's configured `newAssetSettings`, with a per-call
39
+ * `playbackPolicy` winning last. Keeps seeded/ingested videos in sync with admin uploads.
40
+ */ export function resolveNewAssetSettings(newAssetSettings, playbackPolicy) {
41
+ return {
42
+ playback_policy: [
43
+ 'public'
44
+ ],
45
+ ...newAssetSettings,
46
+ ...playbackPolicy ? {
47
+ playback_policy: [
48
+ playbackPolicy
49
+ ]
50
+ } : {}
51
+ };
52
+ }
53
+ /**
54
+ * Ingest a video into Mux server-side: create a direct upload, PUT the source bytes (from a
55
+ * local path or an `http(s)` URL), and poll until the asset is `ready`. The programmatic
56
+ * counterpart to the admin uploader's client-side direct upload — used for seeding, imports,
57
+ * and migrations. Returns the ready asset.
58
+ *
59
+ * `new_asset_settings` are resolved by {@link resolveNewAssetSettings} — the plugin's configured
60
+ * `newAssetSettings` over a `['public']` default, with `playbackPolicy` as a per-call override —
61
+ * so seeded videos honor the same playback policy as admin uploads.
62
+ */ export async function ingestMuxAsset(mux, source, opts = {}) {
63
+ const upload = await mux.video.uploads.create({
64
+ cors_origin: opts.corsOrigin ?? '*',
65
+ new_asset_settings: resolveNewAssetSettings(opts.newAssetSettings, opts.playbackPolicy)
66
+ });
67
+ if (!upload.url) throw new Error(`[payload-mux] Mux did not return an upload URL for '${source}'`);
68
+ const { body, headers } = await buildUploadBody(source);
69
+ // `duplex: 'half'` is required when streaming a request body (the local-file path).
70
+ const res = await fetch(upload.url, {
71
+ method: 'PUT',
72
+ body,
73
+ headers,
74
+ duplex: 'half'
75
+ });
76
+ if (!res.ok) throw new Error(`[payload-mux] upload PUT failed for '${source}': ${res.status} ${res.statusText}`);
77
+ const ready = await pollUntil(()=>mux.video.uploads.retrieve(upload.id), (u)=>Boolean(u.asset_id), 1000);
78
+ if (!ready.asset_id) throw new Error(`[payload-mux] Mux upload '${upload.id}' never produced an asset`);
79
+ const asset = await pollUntil(()=>mux.video.assets.retrieve(ready.asset_id), (a)=>a.status !== 'preparing', 2000);
80
+ // Still `preparing` means the 120s pollUntil deadline passed, not a Mux verdict.
81
+ if (asset.status === 'preparing') throw new Error(`[payload-mux] timed out after 120s waiting for asset '${asset.id}' (status: preparing)`);
82
+ if (asset.status !== 'ready') {
83
+ const detail = asset.errors?.messages?.length ? `: ${asset.errors.messages.join('; ')}` : '';
84
+ throw new Error(`[payload-mux] asset '${asset.id}' did not become ready (status: ${asset.status})${detail}`);
85
+ }
86
+ return asset;
87
+ }
88
+ /**
89
+ * Create a `mux-video` doc from a local file or URL — the programmatic equivalent of uploading
90
+ * through the admin uploader. Hands `source` to the collection, whose `beforeValidate` hook
91
+ * uploads it to Mux and folds in the ready asset's metadata.
92
+ */ export async function ingestMuxVideo(payload, opts) {
93
+ const collection = opts.collection ?? 'mux-video';
94
+ const doc = await payload.create({
95
+ collection,
96
+ data: {
97
+ title: opts.title,
98
+ source: {
99
+ file: opts.source,
100
+ playbackPolicy: opts.playbackPolicy,
101
+ posterTimestamp: opts.posterTimestamp
102
+ }
103
+ },
104
+ overrideAccess: true
105
+ });
106
+ return doc;
107
+ }
108
+
109
+ //# sourceMappingURL=ingest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/lib/ingest.ts"],"sourcesContent":["import { createReadStream } from 'node:fs'\nimport { stat } from 'node:fs/promises'\nimport type Mux from '@mux/mux-node'\nimport type { CollectionSlug, Payload } from 'payload'\nimport type { MuxVideoNewAssetSettings } from '../types'\nimport { delay } from './delay'\n\n/** A server-side video source: a local file path or an `http(s)` URL. */\nexport type MuxSource = string\n\n/** Poll `fetch` until `done(value)` is true or the deadline passes; returns the last value. */\nasync function pollUntil<T>(fetch: () => Promise<T>, done: (value: T) => boolean, intervalMs: number, timeoutMs = 120_000): Promise<T> {\n const deadline = Date.now() + timeoutMs\n let value = await fetch()\n while (!done(value) && Date.now() < deadline) {\n await delay(intervalMs)\n value = await fetch()\n }\n return value\n}\n\n/** Build the PUT body for the Mux upload. A local path streams from disk with a Content-Length\n * (so a large video never sits fully in memory); an `http(s)` URL is fetched and forwarded.\n * Note: a URL source is buffered fully in memory before the PUT (Mux's upload endpoint needs a\n * known length) — for very large remote files, download to disk and ingest the local path. */\nasync function buildUploadBody(source: MuxSource): Promise<{ body: BodyInit; headers?: Record<string, string> }> {\n if (/^https?:\\/\\//i.test(source)) {\n const res = await fetch(source)\n if (!res.ok) throw new Error(`[payload-mux] failed to fetch source '${source}': ${res.status} ${res.statusText}`)\n return { body: new Blob([await res.arrayBuffer()]) }\n }\n const { size } = await stat(source).catch(() => {\n throw new Error(`[payload-mux] source file not found: ${source}`)\n })\n return { body: createReadStream(source) as unknown as BodyInit, headers: { 'Content-Length': String(size) } }\n}\n\nexport interface IngestMuxAssetOptions {\n /** Base new-asset settings — normally the plugin's `uploadSettings.new_asset_settings`, so a\n * server-side ingest produces the same asset config as the admin direct-upload path. */\n newAssetSettings?: MuxVideoNewAssetSettings\n /** Playback policy override for this asset. When set, wins over `newAssetSettings`'s policy;\n * otherwise the configured policy applies (falling back to `'public'`). */\n playbackPolicy?: 'public' | 'signed'\n /** CORS origin for the direct upload. @default '*' */\n corsOrigin?: string\n}\n\n/**\n * Resolve the `new_asset_settings` for an ingest, mirroring the admin path (`upload.ts`): a\n * `['public']` default, overlaid with the plugin's configured `newAssetSettings`, with a per-call\n * `playbackPolicy` winning last. Keeps seeded/ingested videos in sync with admin uploads.\n */\nexport function resolveNewAssetSettings(\n newAssetSettings?: MuxVideoNewAssetSettings,\n playbackPolicy?: 'public' | 'signed',\n): MuxVideoNewAssetSettings {\n return {\n playback_policy: ['public'],\n ...newAssetSettings,\n ...(playbackPolicy ? { playback_policy: [playbackPolicy] } : {}),\n }\n}\n\n/**\n * Ingest a video into Mux server-side: create a direct upload, PUT the source bytes (from a\n * local path or an `http(s)` URL), and poll until the asset is `ready`. The programmatic\n * counterpart to the admin uploader's client-side direct upload — used for seeding, imports,\n * and migrations. Returns the ready asset.\n *\n * `new_asset_settings` are resolved by {@link resolveNewAssetSettings} — the plugin's configured\n * `newAssetSettings` over a `['public']` default, with `playbackPolicy` as a per-call override —\n * so seeded videos honor the same playback policy as admin uploads.\n */\nexport async function ingestMuxAsset(mux: Mux, source: MuxSource, opts: IngestMuxAssetOptions = {}): Promise<Mux.Video.Assets.Asset> {\n const upload = await mux.video.uploads.create({\n cors_origin: opts.corsOrigin ?? '*',\n new_asset_settings: resolveNewAssetSettings(opts.newAssetSettings, opts.playbackPolicy),\n })\n if (!upload.url) throw new Error(`[payload-mux] Mux did not return an upload URL for '${source}'`)\n\n const { body, headers } = await buildUploadBody(source)\n // `duplex: 'half'` is required when streaming a request body (the local-file path).\n const res = await fetch(upload.url, { method: 'PUT', body, headers, duplex: 'half' } as RequestInit & { duplex: 'half' })\n if (!res.ok) throw new Error(`[payload-mux] upload PUT failed for '${source}': ${res.status} ${res.statusText}`)\n\n const ready = await pollUntil(\n () => mux.video.uploads.retrieve(upload.id),\n (u) => Boolean(u.asset_id),\n 1000,\n )\n if (!ready.asset_id) throw new Error(`[payload-mux] Mux upload '${upload.id}' never produced an asset`)\n\n const asset = await pollUntil(\n () => mux.video.assets.retrieve(ready.asset_id as string),\n (a) => a.status !== 'preparing',\n 2000,\n )\n // Still `preparing` means the 120s pollUntil deadline passed, not a Mux verdict.\n if (asset.status === 'preparing') throw new Error(`[payload-mux] timed out after 120s waiting for asset '${asset.id}' (status: preparing)`)\n if (asset.status !== 'ready') {\n const detail = asset.errors?.messages?.length ? `: ${asset.errors.messages.join('; ')}` : ''\n throw new Error(`[payload-mux] asset '${asset.id}' did not become ready (status: ${asset.status})${detail}`)\n }\n\n return asset\n}\n\nexport interface IngestMuxVideoOptions {\n /** Local file path or `http(s)` URL of the video to ingest. */\n source: MuxSource\n /** Title for the created `mux-video` doc (must be unique within the collection). */\n title: string\n /** Playback policy override for the uploaded asset. @default the plugin's configured policy */\n playbackPolicy?: 'public' | 'signed'\n /** Optional poster timestamp (seconds). */\n posterTimestamp?: number\n /** The `mux-video` collection slug. @default 'mux-video' */\n collection?: string\n}\n\n/**\n * Create a `mux-video` doc from a local file or URL — the programmatic equivalent of uploading\n * through the admin uploader. Hands `source` to the collection, whose `beforeValidate` hook\n * uploads it to Mux and folds in the ready asset's metadata.\n */\nexport async function ingestMuxVideo(payload: Payload, opts: IngestMuxVideoOptions): Promise<{ id: string | number }> {\n const collection = (opts.collection ?? 'mux-video') as CollectionSlug\n const doc = await payload.create({\n collection,\n data: {\n title: opts.title,\n source: { file: opts.source, playbackPolicy: opts.playbackPolicy, posterTimestamp: opts.posterTimestamp },\n } as never,\n overrideAccess: true,\n })\n return doc as { id: string | number }\n}\n"],"names":["createReadStream","stat","delay","pollUntil","fetch","done","intervalMs","timeoutMs","deadline","Date","now","value","buildUploadBody","source","test","res","ok","Error","status","statusText","body","Blob","arrayBuffer","size","catch","headers","String","resolveNewAssetSettings","newAssetSettings","playbackPolicy","playback_policy","ingestMuxAsset","mux","opts","upload","video","uploads","create","cors_origin","corsOrigin","new_asset_settings","url","method","duplex","ready","retrieve","id","u","Boolean","asset_id","asset","assets","a","detail","errors","messages","length","join","ingestMuxVideo","payload","collection","doc","data","title","file","posterTimestamp","overrideAccess"],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ import type { PayloadRequest } from 'payload';
2
+ import type { MuxVideoPluginOptions } from '../types';
3
+ /** Resolve the plugin's access gate for a request: the configured `access` option if given,
4
+ * else the default logged-in-admin check. Shared by the upload endpoints and the collection
5
+ * `read` access so the gate stays defined in one place. */
6
+ export declare const isAllowed: (options: MuxVideoPluginOptions, req: PayloadRequest) => Promise<boolean>;
7
+ //# sourceMappingURL=isAllowed.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"isAllowed.d.ts","sourceRoot":"","sources":["../../src/lib/isAllowed.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAC7C,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,UAAU,CAAA;AAGrD;;4DAE4D;AAC5D,eAAO,MAAM,SAAS,GAAU,SAAS,qBAAqB,EAAE,KAAK,cAAc,KAAG,OAAO,CAAC,OAAO,CAChD,CAAA"}
@@ -0,0 +1,6 @@
1
+ import { defaultAccess } from "./defaultAccess.js";
2
+ /** Resolve the plugin's access gate for a request: the configured `access` option if given,
3
+ * else the default logged-in-admin check. Shared by the upload endpoints and the collection
4
+ * `read` access so the gate stays defined in one place. */ export const isAllowed = async (options, req)=>await options.access?.(req) ?? defaultAccess(req);
5
+
6
+ //# sourceMappingURL=isAllowed.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/lib/isAllowed.ts"],"sourcesContent":["import type { PayloadRequest } from 'payload'\nimport type { MuxVideoPluginOptions } from '../types'\nimport { defaultAccess } from './defaultAccess'\n\n/** Resolve the plugin's access gate for a request: the configured `access` option if given,\n * else the default logged-in-admin check. Shared by the upload endpoints and the collection\n * `read` access so the gate stays defined in one place. */\nexport const isAllowed = async (options: MuxVideoPluginOptions, req: PayloadRequest): Promise<boolean> =>\n (await options.access?.(req)) ?? defaultAccess(req)\n"],"names":["defaultAccess","isAllowed","options","req","access"],"mappings":"AAEA,SAASA,aAAa,QAAQ,qBAAiB;AAE/C;;0DAE0D,GAC1D,OAAO,MAAMC,YAAY,OAAOC,SAAgCC,MAC9D,AAAC,MAAMD,QAAQE,MAAM,GAAGD,QAASH,cAAcG,KAAI"}
@@ -0,0 +1,13 @@
1
+ import type { Plugin } from 'payload';
2
+ import type { MuxVideoPluginOptions } from './types';
3
+ /**
4
+ * Brings Mux Video to Payload. Adds a `mux-video` collection (or extends one you name),
5
+ * registers the upload + webhook endpoints, and wires the admin uploader. Direct uploads go
6
+ * straight to Mux; a webhook keeps Payload in sync and deletes cascade both ways.
7
+ *
8
+ * Credentials default to the standard `MUX_*` env vars, so `muxVideoPlugin()` is enough — pass
9
+ * options only to override (custom env var name, playback policy, etc).
10
+ */
11
+ export declare const muxVideoPlugin: (pluginOptions?: MuxVideoPluginOptions) => Plugin;
12
+ export default muxVideoPlugin;
13
+ //# sourceMappingURL=plugin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAU,MAAM,EAAE,MAAM,SAAS,CAAA;AAK7C,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,SAAS,CAAA;AAEpD;;;;;;;GAOG;AACH,eAAO,MAAM,cAAc,GACxB,gBAAe,qBAA0B,KAAG,MAqD5C,CAAA;AAEH,eAAe,cAAc,CAAA"}
package/dist/plugin.js ADDED
@@ -0,0 +1,80 @@
1
+ import Mux from "@mux/mux-node";
2
+ import { deepMerge } from "payload";
3
+ import { MuxVideo } from "./collections/MuxVideo.js";
4
+ import { createMuxUploadHandler, getMuxUploadHandler } from "./endpoints/upload.js";
5
+ import { muxWebhookHandler } from "./endpoints/webhook.js";
6
+ /**
7
+ * Brings Mux Video to Payload. Adds a `mux-video` collection (or extends one you name),
8
+ * registers the upload + webhook endpoints, and wires the admin uploader. Direct uploads go
9
+ * straight to Mux; a webhook keeps Payload in sync and deletes cascade both ways.
10
+ *
11
+ * Credentials default to the standard `MUX_*` env vars, so `muxVideoPlugin()` is enough — pass
12
+ * options only to override (custom env var name, playback policy, etc).
13
+ */ export const muxVideoPlugin = (pluginOptions = {})=>(incomingConfig)=>{
14
+ if (pluginOptions.enabled === false) return incomingConfig;
15
+ const mux = new Mux(pluginOptions.initSettings);
16
+ // Expose the options on config.custom so external tooling (e.g. a seeder) can build a Mux
17
+ // client from the already-configured credentials, given just `payload` — read by string
18
+ // key, no import, so other packages stay decoupled from this one.
19
+ const config = {
20
+ ...incomingConfig,
21
+ custom: {
22
+ ...incomingConfig.custom,
23
+ payloadMux: {
24
+ options: pluginOptions
25
+ }
26
+ }
27
+ };
28
+ // Without credentials the collection still registers (so generated types stay stable), but
29
+ // ingest can't reach Mux — mark it seed-disabled so a seeder skips its definition with this
30
+ // reason instead of failing mid-run. Set the env vars and the next seed picks it up.
31
+ const hasCreds = Boolean((pluginOptions.initSettings?.tokenId ?? process.env.MUX_TOKEN_ID) && (pluginOptions.initSettings?.tokenSecret ?? process.env.MUX_TOKEN_SECRET));
32
+ const collection = MuxVideo(mux, pluginOptions);
33
+ if (!hasCreds) {
34
+ collection.custom = {
35
+ ...collection.custom,
36
+ seedDisabled: 'Mux credentials not set (MUX_TOKEN_ID / MUX_TOKEN_SECRET)'
37
+ };
38
+ console.warn('[payload-mux] MUX_TOKEN_ID / MUX_TOKEN_SECRET not set — uploads, server-side ingest, and webhook handling will fail');
39
+ }
40
+ // Signed playback needs the JWT key pair — without it every read of a signed video throws
41
+ // in the virtual URL fields, so warn at boot instead of failing per-request.
42
+ const signed = pluginOptions.playbackPolicy === 'signed' || pluginOptions.uploadSettings?.new_asset_settings?.playback_policy?.includes('signed');
43
+ const hasSigningKeys = Boolean((pluginOptions.initSettings?.jwtSigningKey ?? process.env.MUX_SIGNING_KEY) && (pluginOptions.initSettings?.jwtPrivateKey ?? process.env.MUX_PRIVATE_KEY));
44
+ if (signed && !hasSigningKeys) console.warn('[payload-mux] signed playback is configured but MUX_SIGNING_KEY / MUX_PRIVATE_KEY not set — signed video reads will fail');
45
+ if (pluginOptions.extendCollection) {
46
+ const target = config.collections?.find((c)=>c.slug === pluginOptions.extendCollection);
47
+ if (!target) throw new Error(`[payload-mux] extendCollection: collection '${pluginOptions.extendCollection}' not found`);
48
+ config.collections = [
49
+ ...(config.collections ?? []).filter((c)=>c.slug !== pluginOptions.extendCollection),
50
+ deepMerge(collection, target)
51
+ ];
52
+ } else {
53
+ config.collections = [
54
+ ...config.collections ?? [],
55
+ collection
56
+ ];
57
+ }
58
+ config.endpoints = [
59
+ ...config.endpoints ?? [],
60
+ {
61
+ method: 'post',
62
+ path: '/mux/upload',
63
+ handler: createMuxUploadHandler(mux, pluginOptions)
64
+ },
65
+ {
66
+ method: 'get',
67
+ path: '/mux/upload',
68
+ handler: getMuxUploadHandler(mux, pluginOptions)
69
+ },
70
+ {
71
+ method: 'post',
72
+ path: '/mux/webhook',
73
+ handler: muxWebhookHandler(mux, pluginOptions)
74
+ }
75
+ ];
76
+ return config;
77
+ };
78
+ export default muxVideoPlugin;
79
+
80
+ //# sourceMappingURL=plugin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/plugin.ts"],"sourcesContent":["import Mux from '@mux/mux-node'\nimport type { Config, Plugin } from 'payload'\nimport { deepMerge } from 'payload'\nimport { MuxVideo } from './collections/MuxVideo'\nimport { createMuxUploadHandler, getMuxUploadHandler } from './endpoints/upload'\nimport { muxWebhookHandler } from './endpoints/webhook'\nimport type { MuxVideoPluginOptions } from './types'\n\n/**\n * Brings Mux Video to Payload. Adds a `mux-video` collection (or extends one you name),\n * registers the upload + webhook endpoints, and wires the admin uploader. Direct uploads go\n * straight to Mux; a webhook keeps Payload in sync and deletes cascade both ways.\n *\n * Credentials default to the standard `MUX_*` env vars, so `muxVideoPlugin()` is enough — pass\n * options only to override (custom env var name, playback policy, etc).\n */\nexport const muxVideoPlugin =\n (pluginOptions: MuxVideoPluginOptions = {}): Plugin =>\n (incomingConfig: Config): Config => {\n if (pluginOptions.enabled === false) return incomingConfig\n\n const mux = new Mux(pluginOptions.initSettings)\n // Expose the options on config.custom so external tooling (e.g. a seeder) can build a Mux\n // client from the already-configured credentials, given just `payload` — read by string\n // key, no import, so other packages stay decoupled from this one.\n const config: Config = { ...incomingConfig, custom: { ...incomingConfig.custom, payloadMux: { options: pluginOptions } } }\n\n // Without credentials the collection still registers (so generated types stay stable), but\n // ingest can't reach Mux — mark it seed-disabled so a seeder skips its definition with this\n // reason instead of failing mid-run. Set the env vars and the next seed picks it up.\n const hasCreds = Boolean(\n (pluginOptions.initSettings?.tokenId ?? process.env.MUX_TOKEN_ID) &&\n (pluginOptions.initSettings?.tokenSecret ?? process.env.MUX_TOKEN_SECRET),\n )\n const collection = MuxVideo(mux, pluginOptions)\n if (!hasCreds) {\n collection.custom = { ...collection.custom, seedDisabled: 'Mux credentials not set (MUX_TOKEN_ID / MUX_TOKEN_SECRET)' }\n console.warn('[payload-mux] MUX_TOKEN_ID / MUX_TOKEN_SECRET not set — uploads, server-side ingest, and webhook handling will fail')\n }\n\n // Signed playback needs the JWT key pair — without it every read of a signed video throws\n // in the virtual URL fields, so warn at boot instead of failing per-request.\n const signed =\n pluginOptions.playbackPolicy === 'signed' || pluginOptions.uploadSettings?.new_asset_settings?.playback_policy?.includes('signed')\n const hasSigningKeys = Boolean(\n (pluginOptions.initSettings?.jwtSigningKey ?? process.env.MUX_SIGNING_KEY) &&\n (pluginOptions.initSettings?.jwtPrivateKey ?? process.env.MUX_PRIVATE_KEY),\n )\n if (signed && !hasSigningKeys)\n console.warn('[payload-mux] signed playback is configured but MUX_SIGNING_KEY / MUX_PRIVATE_KEY not set — signed video reads will fail')\n\n if (pluginOptions.extendCollection) {\n const target = config.collections?.find((c) => c.slug === pluginOptions.extendCollection)\n if (!target) throw new Error(`[payload-mux] extendCollection: collection '${pluginOptions.extendCollection}' not found`)\n config.collections = [\n ...(config.collections ?? []).filter((c) => c.slug !== pluginOptions.extendCollection),\n deepMerge(collection, target),\n ]\n } else {\n config.collections = [...(config.collections ?? []), collection]\n }\n\n config.endpoints = [\n ...(config.endpoints ?? []),\n { method: 'post', path: '/mux/upload', handler: createMuxUploadHandler(mux, pluginOptions) },\n { method: 'get', path: '/mux/upload', handler: getMuxUploadHandler(mux, pluginOptions) },\n { method: 'post', path: '/mux/webhook', handler: muxWebhookHandler(mux, pluginOptions) },\n ]\n\n return config\n }\n\nexport default muxVideoPlugin\n"],"names":["Mux","deepMerge","MuxVideo","createMuxUploadHandler","getMuxUploadHandler","muxWebhookHandler","muxVideoPlugin","pluginOptions","incomingConfig","enabled","mux","initSettings","config","custom","payloadMux","options","hasCreds","Boolean","tokenId","process","env","MUX_TOKEN_ID","tokenSecret","MUX_TOKEN_SECRET","collection","seedDisabled","console","warn","signed","playbackPolicy","uploadSettings","new_asset_settings","playback_policy","includes","hasSigningKeys","jwtSigningKey","MUX_SIGNING_KEY","jwtPrivateKey","MUX_PRIVATE_KEY","extendCollection","target","collections","find","c","slug","Error","filter","endpoints","method","path","handler"],"mappings":"AAAA,OAAOA,SAAS,gBAAe;AAE/B,SAASC,SAAS,QAAQ,UAAS;AACnC,SAASC,QAAQ,QAAQ,4BAAwB;AACjD,SAASC,sBAAsB,EAAEC,mBAAmB,QAAQ,wBAAoB;AAChF,SAASC,iBAAiB,QAAQ,yBAAqB;AAGvD;;;;;;;CAOC,GACD,OAAO,MAAMC,iBACX,CAACC,gBAAuC,CAAC,CAAC,GAC1C,CAACC;QACC,IAAID,cAAcE,OAAO,KAAK,OAAO,OAAOD;QAE5C,MAAME,MAAM,IAAIV,IAAIO,cAAcI,YAAY;QAC9C,0FAA0F;QAC1F,wFAAwF;QACxF,kEAAkE;QAClE,MAAMC,SAAiB;YAAE,GAAGJ,cAAc;YAAEK,QAAQ;gBAAE,GAAGL,eAAeK,MAAM;gBAAEC,YAAY;oBAAEC,SAASR;gBAAc;YAAE;QAAE;QAEzH,2FAA2F;QAC3F,4FAA4F;QAC5F,qFAAqF;QACrF,MAAMS,WAAWC,QACf,AAACV,CAAAA,cAAcI,YAAY,EAAEO,WAAWC,QAAQC,GAAG,CAACC,YAAY,AAAD,KAC5Dd,CAAAA,cAAcI,YAAY,EAAEW,eAAeH,QAAQC,GAAG,CAACG,gBAAgB,AAAD;QAE3E,MAAMC,aAAatB,SAASQ,KAAKH;QACjC,IAAI,CAACS,UAAU;YACbQ,WAAWX,MAAM,GAAG;gBAAE,GAAGW,WAAWX,MAAM;gBAAEY,cAAc;YAA4D;YACtHC,QAAQC,IAAI,CAAC;QACf;QAEA,0FAA0F;QAC1F,6EAA6E;QAC7E,MAAMC,SACJrB,cAAcsB,cAAc,KAAK,YAAYtB,cAAcuB,cAAc,EAAEC,oBAAoBC,iBAAiBC,SAAS;QAC3H,MAAMC,iBAAiBjB,QACrB,AAACV,CAAAA,cAAcI,YAAY,EAAEwB,iBAAiBhB,QAAQC,GAAG,CAACgB,eAAe,AAAD,KACrE7B,CAAAA,cAAcI,YAAY,EAAE0B,iBAAiBlB,QAAQC,GAAG,CAACkB,eAAe,AAAD;QAE5E,IAAIV,UAAU,CAACM,gBACbR,QAAQC,IAAI,CAAC;QAEf,IAAIpB,cAAcgC,gBAAgB,EAAE;YAClC,MAAMC,SAAS5B,OAAO6B,WAAW,EAAEC,KAAK,CAACC,IAAMA,EAAEC,IAAI,KAAKrC,cAAcgC,gBAAgB;YACxF,IAAI,CAACC,QAAQ,MAAM,IAAIK,MAAM,CAAC,4CAA4C,EAAEtC,cAAcgC,gBAAgB,CAAC,WAAW,CAAC;YACvH3B,OAAO6B,WAAW,GAAG;mBAChB,AAAC7B,CAAAA,OAAO6B,WAAW,IAAI,EAAE,AAAD,EAAGK,MAAM,CAAC,CAACH,IAAMA,EAAEC,IAAI,KAAKrC,cAAcgC,gBAAgB;gBACrFtC,UAAUuB,YAAYgB;aACvB;QACH,OAAO;YACL5B,OAAO6B,WAAW,GAAG;mBAAK7B,OAAO6B,WAAW,IAAI,EAAE;gBAAGjB;aAAW;QAClE;QAEAZ,OAAOmC,SAAS,GAAG;eACbnC,OAAOmC,SAAS,IAAI,EAAE;YAC1B;gBAAEC,QAAQ;gBAAQC,MAAM;gBAAeC,SAAS/C,uBAAuBO,KAAKH;YAAe;YAC3F;gBAAEyC,QAAQ;gBAAOC,MAAM;gBAAeC,SAAS9C,oBAAoBM,KAAKH;YAAe;YACvF;gBAAEyC,QAAQ;gBAAQC,MAAM;gBAAgBC,SAAS7C,kBAAkBK,KAAKH;YAAe;SACxF;QAED,OAAOK;IACT,EAAC;AAEH,eAAeN,eAAc"}
@@ -0,0 +1,78 @@
1
+ import type Mux from '@mux/mux-node';
2
+ import type { PayloadRequest, TypedCollection } from 'payload';
3
+ /** Mux's asset-creation params, via the SDK namespace (avoids a deep subpath import). */
4
+ type AssetOptions = Mux.Video.Assets.AssetOptions;
5
+ /**
6
+ * Mux API credentials + webhook/signing secrets. Every field is optional: anything you don't
7
+ * pass is read from the standard Mux environment variables by the SDK —
8
+ * `MUX_TOKEN_ID`, `MUX_TOKEN_SECRET`, `MUX_WEBHOOK_SECRET`, `MUX_SIGNING_KEY`,
9
+ * `MUX_PRIVATE_KEY`. Only pass a field to override it (e.g. a non-standard env var name).
10
+ */
11
+ export interface MuxVideoInitSettings {
12
+ /** The Mux token ID. @default process.env.MUX_TOKEN_ID */
13
+ tokenId?: string;
14
+ /** The Mux token secret. @default process.env.MUX_TOKEN_SECRET */
15
+ tokenSecret?: string;
16
+ /** The secret used to verify incoming Mux webhooks. @default process.env.MUX_WEBHOOK_SECRET */
17
+ webhookSecret?: string;
18
+ /** JWT signing key ID (signed playback). @default process.env.MUX_SIGNING_KEY */
19
+ jwtSigningKey?: string;
20
+ /** JWT private key (signed playback). @default process.env.MUX_PRIVATE_KEY */
21
+ jwtPrivateKey?: string;
22
+ }
23
+ /** `new_asset_settings` passed to Mux when creating an asset, plus the playback policy. */
24
+ export type MuxVideoNewAssetSettings = AssetOptions & {
25
+ /** Playback policy for uploaded videos. `['public']` by default; use `['signed']` for
26
+ * the signed-URL setup. */
27
+ playback_policy?: Array<'public' | 'signed'>;
28
+ };
29
+ /** Settings applied to every upload. */
30
+ export interface MuxVideoUploadSettings {
31
+ /** CORS origin for the direct-upload URL (usually your site URL).
32
+ * @default process.env.NEXT_PUBLIC_SERVER_URL, falling back to '*' */
33
+ cors_origin?: string;
34
+ /** Extra settings forwarded to Mux when the asset is created. */
35
+ new_asset_settings?: MuxVideoNewAssetSettings;
36
+ }
37
+ /** Signed-URL generation options. */
38
+ export interface MuxVideoSignedUrlOptions {
39
+ /** Expiration window for signed playback/poster/gif URLs. @default "1d" */
40
+ expiration?: string;
41
+ }
42
+ /** Configuration for the Mux Video plugin. */
43
+ export interface MuxVideoPluginOptions {
44
+ /** Set to `false` to disable the plugin (collection, endpoints, and hooks are skipped).
45
+ * @default true */
46
+ enabled?: boolean;
47
+ /** Mux credentials + webhook secret. Optional — fields default to the standard `MUX_*`
48
+ * environment variables, so you only pass this to override an env var. */
49
+ initSettings?: MuxVideoInitSettings;
50
+ /** Upload settings (CORS origin + new-asset settings). Optional — `cors_origin` defaults
51
+ * to `NEXT_PUBLIC_SERVER_URL`. */
52
+ uploadSettings?: MuxVideoUploadSettings;
53
+ /** Playback policy for newly uploaded videos: `'public'` URLs, or `'signed'` JWT-signed
54
+ * URLs (also set `MUX_SIGNING_KEY` + `MUX_PRIVATE_KEY`). Shorthand for
55
+ * `uploadSettings.new_asset_settings.playback_policy`; an explicit value there wins.
56
+ * @default 'public' */
57
+ playbackPolicy?: 'public' | 'signed';
58
+ /** Slug of an existing collection to extend with Mux fields instead of creating the
59
+ * default `mux-video` collection. */
60
+ extendCollection?: keyof TypedCollection;
61
+ /** Gate who may request an upload / read videos. Defaults to logged-in admins. */
62
+ access?: (request: PayloadRequest) => Promise<boolean> | boolean;
63
+ /** Signed-URL generation options. */
64
+ signedUrlOptions?: MuxVideoSignedUrlOptions;
65
+ /** Image format for video posters. @default "png" */
66
+ posterExtension?: 'webp' | 'jpg' | 'png';
67
+ /** Image format for animated preview thumbnails. @default "gif" */
68
+ animatedGifExtension?: 'gif' | 'webp';
69
+ /** Thumbnail shown in the collection list view: animated `gif`, static `image`, or
70
+ * `none`. @default "gif" */
71
+ adminThumbnail?: 'gif' | 'image' | 'none';
72
+ /** When set, the webhook creates a Payload doc for a Mux asset that doesn't exist yet
73
+ * (on `video.asset.created` / `ready` / `updated`) — backfills videos uploaded directly
74
+ * in Mux. @default false */
75
+ autoCreateOnWebhook?: boolean;
76
+ }
77
+ export {};
78
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,GAAG,MAAM,eAAe,CAAA;AACpC,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AAE9D,yFAAyF;AACzF,KAAK,YAAY,GAAG,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,YAAY,CAAA;AAEjD;;;;;GAKG;AACH,MAAM,WAAW,oBAAoB;IACnC,0DAA0D;IAC1D,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,kEAAkE;IAClE,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,+FAA+F;IAC/F,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,iFAAiF;IACjF,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,8EAA8E;IAC9E,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB;AAED,2FAA2F;AAC3F,MAAM,MAAM,wBAAwB,GAAG,YAAY,GAAG;IACpD;gCAC4B;IAC5B,eAAe,CAAC,EAAE,KAAK,CAAC,QAAQ,GAAG,QAAQ,CAAC,CAAA;CAC7C,CAAA;AAED,wCAAwC;AACxC,MAAM,WAAW,sBAAsB;IACrC;2EACuE;IACvE,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,iEAAiE;IACjE,kBAAkB,CAAC,EAAE,wBAAwB,CAAA;CAC9C;AAED,qCAAqC;AACrC,MAAM,WAAW,wBAAwB;IACvC,2EAA2E;IAC3E,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB;AAED,8CAA8C;AAC9C,MAAM,WAAW,qBAAqB;IACpC;wBACoB;IACpB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB;+EAC2E;IAC3E,YAAY,CAAC,EAAE,oBAAoB,CAAA;IACnC;uCACmC;IACnC,cAAc,CAAC,EAAE,sBAAsB,CAAA;IACvC;;;4BAGwB;IACxB,cAAc,CAAC,EAAE,QAAQ,GAAG,QAAQ,CAAA;IACpC;0CACsC;IACtC,gBAAgB,CAAC,EAAE,MAAM,eAAe,CAAA;IACxC,kFAAkF;IAClF,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,cAAc,KAAK,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAA;IAChE,qCAAqC;IACrC,gBAAgB,CAAC,EAAE,wBAAwB,CAAA;IAC3C,qDAAqD;IACrD,eAAe,CAAC,EAAE,MAAM,GAAG,KAAK,GAAG,KAAK,CAAA;IACxC,mEAAmE;IACnE,oBAAoB,CAAC,EAAE,KAAK,GAAG,MAAM,CAAA;IACrC;iCAC6B;IAC7B,cAAc,CAAC,EAAE,KAAK,GAAG,OAAO,GAAG,MAAM,CAAA;IACzC;;iCAE6B;IAC7B,mBAAmB,CAAC,EAAE,OAAO,CAAA;CAC9B"}
package/dist/types.js ADDED
@@ -0,0 +1,3 @@
1
+ /** Configuration for the Mux Video plugin. */ export { };
2
+
3
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/types.ts"],"sourcesContent":["import type Mux from '@mux/mux-node'\nimport type { PayloadRequest, TypedCollection } from 'payload'\n\n/** Mux's asset-creation params, via the SDK namespace (avoids a deep subpath import). */\ntype AssetOptions = Mux.Video.Assets.AssetOptions\n\n/**\n * Mux API credentials + webhook/signing secrets. Every field is optional: anything you don't\n * pass is read from the standard Mux environment variables by the SDK —\n * `MUX_TOKEN_ID`, `MUX_TOKEN_SECRET`, `MUX_WEBHOOK_SECRET`, `MUX_SIGNING_KEY`,\n * `MUX_PRIVATE_KEY`. Only pass a field to override it (e.g. a non-standard env var name).\n */\nexport interface MuxVideoInitSettings {\n /** The Mux token ID. @default process.env.MUX_TOKEN_ID */\n tokenId?: string\n /** The Mux token secret. @default process.env.MUX_TOKEN_SECRET */\n tokenSecret?: string\n /** The secret used to verify incoming Mux webhooks. @default process.env.MUX_WEBHOOK_SECRET */\n webhookSecret?: string\n /** JWT signing key ID (signed playback). @default process.env.MUX_SIGNING_KEY */\n jwtSigningKey?: string\n /** JWT private key (signed playback). @default process.env.MUX_PRIVATE_KEY */\n jwtPrivateKey?: string\n}\n\n/** `new_asset_settings` passed to Mux when creating an asset, plus the playback policy. */\nexport type MuxVideoNewAssetSettings = AssetOptions & {\n /** Playback policy for uploaded videos. `['public']` by default; use `['signed']` for\n * the signed-URL setup. */\n playback_policy?: Array<'public' | 'signed'>\n}\n\n/** Settings applied to every upload. */\nexport interface MuxVideoUploadSettings {\n /** CORS origin for the direct-upload URL (usually your site URL).\n * @default process.env.NEXT_PUBLIC_SERVER_URL, falling back to '*' */\n cors_origin?: string\n /** Extra settings forwarded to Mux when the asset is created. */\n new_asset_settings?: MuxVideoNewAssetSettings\n}\n\n/** Signed-URL generation options. */\nexport interface MuxVideoSignedUrlOptions {\n /** Expiration window for signed playback/poster/gif URLs. @default \"1d\" */\n expiration?: string\n}\n\n/** Configuration for the Mux Video plugin. */\nexport interface MuxVideoPluginOptions {\n /** Set to `false` to disable the plugin (collection, endpoints, and hooks are skipped).\n * @default true */\n enabled?: boolean\n /** Mux credentials + webhook secret. Optional — fields default to the standard `MUX_*`\n * environment variables, so you only pass this to override an env var. */\n initSettings?: MuxVideoInitSettings\n /** Upload settings (CORS origin + new-asset settings). Optional — `cors_origin` defaults\n * to `NEXT_PUBLIC_SERVER_URL`. */\n uploadSettings?: MuxVideoUploadSettings\n /** Playback policy for newly uploaded videos: `'public'` URLs, or `'signed'` JWT-signed\n * URLs (also set `MUX_SIGNING_KEY` + `MUX_PRIVATE_KEY`). Shorthand for\n * `uploadSettings.new_asset_settings.playback_policy`; an explicit value there wins.\n * @default 'public' */\n playbackPolicy?: 'public' | 'signed'\n /** Slug of an existing collection to extend with Mux fields instead of creating the\n * default `mux-video` collection. */\n extendCollection?: keyof TypedCollection\n /** Gate who may request an upload / read videos. Defaults to logged-in admins. */\n access?: (request: PayloadRequest) => Promise<boolean> | boolean\n /** Signed-URL generation options. */\n signedUrlOptions?: MuxVideoSignedUrlOptions\n /** Image format for video posters. @default \"png\" */\n posterExtension?: 'webp' | 'jpg' | 'png'\n /** Image format for animated preview thumbnails. @default \"gif\" */\n animatedGifExtension?: 'gif' | 'webp'\n /** Thumbnail shown in the collection list view: animated `gif`, static `image`, or\n * `none`. @default \"gif\" */\n adminThumbnail?: 'gif' | 'image' | 'none'\n /** When set, the webhook creates a Payload doc for a Mux asset that doesn't exist yet\n * (on `video.asset.created` / `ready` / `updated`) — backfills videos uploaded directly\n * in Mux. @default false */\n autoCreateOnWebhook?: boolean\n}\n"],"names":[],"mappings":"AA+CA,4CAA4C,GAC5C,WAiCC"}
package/package.json ADDED
@@ -0,0 +1,89 @@
1
+ {
2
+ "name": "@pro-laico/payload-mux",
3
+ "version": "0.0.0",
4
+ "description": "Mux Video for Payload CMS: a Videos collection that uploads straight to Mux, signed or public playback, virtual playback/poster/gif URLs, two-way delete, and an admin uploader + list-view previews.",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/pro-laico/payload-plugins.git",
9
+ "directory": "packages/payload-mux"
10
+ },
11
+ "keywords": [
12
+ "payload",
13
+ "payloadcms",
14
+ "plugin",
15
+ "mux",
16
+ "video",
17
+ "upload"
18
+ ],
19
+ "sideEffects": [
20
+ "*.scss",
21
+ "*.css"
22
+ ],
23
+ "type": "module",
24
+ "publishConfig": {
25
+ "access": "public"
26
+ },
27
+ "main": "./dist/index.js",
28
+ "types": "./dist/index.d.ts",
29
+ "exports": {
30
+ ".": {
31
+ "types": "./dist/index.d.ts",
32
+ "import": "./dist/index.js",
33
+ "default": "./dist/index.js"
34
+ },
35
+ "./components/MuxUploaderField": {
36
+ "types": "./dist/components/MuxUploaderField.d.ts",
37
+ "import": "./dist/components/MuxUploaderField.js",
38
+ "default": "./dist/components/MuxUploaderField.js"
39
+ },
40
+ "./components/MuxVideoGifCell": {
41
+ "types": "./dist/components/MuxVideoGifCell.d.ts",
42
+ "import": "./dist/components/MuxVideoGifCell.js",
43
+ "default": "./dist/components/MuxVideoGifCell.js"
44
+ },
45
+ "./components/MuxVideoImageCell": {
46
+ "types": "./dist/components/MuxVideoImageCell.d.ts",
47
+ "import": "./dist/components/MuxVideoImageCell.js",
48
+ "default": "./dist/components/MuxVideoImageCell.js"
49
+ }
50
+ },
51
+ "files": [
52
+ "dist"
53
+ ],
54
+ "dependencies": {
55
+ "@mux/mux-node": "^14.1.1",
56
+ "@mux/mux-player-react": "^3.13.0",
57
+ "@mux/mux-uploader-react": "^1.5.0"
58
+ },
59
+ "peerDependencies": {
60
+ "@payloadcms/ui": "^3.0.0",
61
+ "payload": "^3.0.0",
62
+ "react": "^19.0.0",
63
+ "react-dom": "^19.0.0"
64
+ },
65
+ "devDependencies": {
66
+ "@payloadcms/ui": "^3.85.1",
67
+ "@types/node": "^26.0.1",
68
+ "@types/react": "^19.2.17",
69
+ "@types/react-dom": "^19.2.3",
70
+ "payload": "^3.85.1",
71
+ "react": "^19.2.7",
72
+ "react-dom": "^19.2.7",
73
+ "typescript": "^6.0.3",
74
+ "vitest": "^4.1.9"
75
+ },
76
+ "engines": {
77
+ "node": ">=18"
78
+ },
79
+ "scripts": {
80
+ "build": "pnpm clean && pnpm build:types && pnpm build:swc && pnpm build:assets",
81
+ "clean": "rimraf dist tsconfig.tsbuildinfo",
82
+ "typecheck": "tsc -p tsconfig.json --noEmit",
83
+ "test": "vitest run",
84
+ "test:watch": "vitest",
85
+ "build:swc": "swc ./src -d ./dist --config-file ../../.swcrc --strip-leading-paths",
86
+ "build:types": "tsc -p tsconfig.build.json --emitDeclarationOnly --noCheck",
87
+ "build:assets": "copyfiles -u 1 \"src/**/*.css\" dist"
88
+ }
89
+ }