@contember/bindx-uploader 0.1.49 → 0.1.50

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.
@@ -1,8 +1,8 @@
1
1
  import { type ReactNode } from 'react';
2
2
  import type { HasManyRef } from '@contember/bindx';
3
3
  import { BINDX_COMPONENT, type SelectionProvider } from '@contember/bindx-react';
4
- import type { FileType } from '../types.js';
5
- export interface MultiUploaderProps<TEntity = Record<string, unknown>> {
4
+ import type { FileType, UploaderEvents } from '../types.js';
5
+ export interface MultiUploaderProps<TEntity = Record<string, unknown>> extends Partial<UploaderEvents> {
6
6
  /**
7
7
  * The has-many relation to add uploaded files to.
8
8
  */
@@ -35,7 +35,7 @@ export interface MultiUploaderProps<TEntity = Record<string, unknown>> {
35
35
  * </MultiUploader>
36
36
  * ```
37
37
  */
38
- export declare function MultiUploader<TEntity extends Record<string, unknown>>({ field, fileType, children, }: MultiUploaderProps<TEntity>): ReactNode;
38
+ export declare function MultiUploader<TEntity extends Record<string, unknown>>({ field, fileType, children, ...events }: MultiUploaderProps<TEntity>): ReactNode;
39
39
  declare const multiUploaderWithSelection: typeof MultiUploader & SelectionProvider & {
40
40
  [BINDX_COMPONENT]: true;
41
41
  };
@@ -1 +1 @@
1
- {"version":3,"file":"MultiUploader.d.ts","sourceRoot":"","sources":["../../src/components/MultiUploader.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAgC,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AACpE,OAAO,KAAK,EAAa,UAAU,EAAqC,MAAM,kBAAkB,CAAA;AAEhG,OAAO,EAAE,eAAe,EAAE,KAAK,iBAAiB,EAAoC,MAAM,wBAAwB,CAAA;AAClH,OAAO,KAAK,EAAE,QAAQ,EAAkB,MAAM,aAAa,CAAA;AAa3D,MAAM,WAAW,kBAAkB,CAAC,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IACpE;;OAEG;IACH,KAAK,EAAE,UAAU,CAAC,OAAO,CAAC,CAAA;IAC1B;;;OAGG;IACH,QAAQ,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAA;IAC3B;;OAEG;IACH,QAAQ,CAAC,EAAE,SAAS,CAAA;CACpB;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,aAAa,CAAC,OAAO,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,EACtE,KAAK,EACL,QAAQ,EACR,QAAQ,GACR,EAAE,kBAAkB,CAAC,OAAO,CAAC,GAAG,SAAS,CAiGzC;AA+BD,QAAA,MAAM,0BAA0B,EAAoB,OAAO,aAAa,GAAG,iBAAiB,GAAG;IAAE,CAAC,eAAe,CAAC,EAAE,IAAI,CAAA;CAAE,CAAA;AA2B1H,OAAO,EAAE,0BAA0B,IAAI,qBAAqB,EAAE,CAAA"}
1
+ {"version":3,"file":"MultiUploader.d.ts","sourceRoot":"","sources":["../../src/components/MultiUploader.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAgC,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AACpE,OAAO,KAAK,EAAa,UAAU,EAAqC,MAAM,kBAAkB,CAAA;AAEhG,OAAO,EAAE,eAAe,EAAE,KAAK,iBAAiB,EAAoC,MAAM,wBAAwB,CAAA;AAClH,OAAO,KAAK,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAa3D,MAAM,WAAW,kBAAkB,CAAC,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAE,SAAQ,OAAO,CAAC,cAAc,CAAC;IACrG;;OAEG;IACH,KAAK,EAAE,UAAU,CAAC,OAAO,CAAC,CAAA;IAC1B;;;OAGG;IACH,QAAQ,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAA;IAC3B;;OAEG;IACH,QAAQ,CAAC,EAAE,SAAS,CAAA;CACpB;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,aAAa,CAAC,OAAO,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,EACtE,KAAK,EACL,QAAQ,EACR,QAAQ,EACR,GAAG,MAAM,EACT,EAAE,kBAAkB,CAAC,OAAO,CAAC,GAAG,SAAS,CAwFzC;AA+BD,QAAA,MAAM,0BAA0B,EAAoB,OAAO,aAAa,GAAG,iBAAiB,GAAG;IAAE,CAAC,eAAe,CAAC,EAAE,IAAI,CAAA;CAAE,CAAA;AA2B1H,OAAO,EAAE,0BAA0B,IAAI,qBAAqB,EAAE,CAAA"}
@@ -1,18 +1,24 @@
1
1
  import { type ReactNode } from 'react';
2
- import type { EntityRef, HasOneRef } from '@contember/bindx';
3
2
  import { BINDX_COMPONENT, type SelectionProvider } from '@contember/bindx-react';
4
- import type { FileType } from '../types.js';
5
- export interface UploaderProps<TEntity = Record<string, unknown>> {
3
+ import type { FileType, PrepareUploadTarget, UploaderEvents, UploaderFillTarget } from '../types.js';
4
+ export interface UploaderProps<TEntity = Record<string, unknown>> extends Partial<UploaderEvents> {
6
5
  /**
7
6
  * The entity to fill with uploaded file data.
8
7
  * Can be an EntityRef or HasOneRef.
9
8
  */
10
- entity: EntityRef<TEntity> | HasOneRef<TEntity>;
9
+ entity: UploaderFillTarget<TEntity>;
11
10
  /**
12
11
  * File type configuration defining accepted files and extractors.
13
12
  * Must be created with the same entity type as the entity prop.
14
13
  */
15
14
  fileType: FileType<TEntity>;
15
+ /**
16
+ * Resolves the target of an upload batch: it runs once, after the files pass the accept
17
+ * check and before the uploader disconnects or fills anything, and receives the accepted
18
+ * files. A rejected batch never reaches it. Return another target to fork first
19
+ * (copy-on-write); returning nothing keeps the entity prop.
20
+ */
21
+ prepareTarget?: PrepareUploadTarget<UploaderFillTarget<TEntity>>;
16
22
  /**
17
23
  * Children to render within the uploader context.
18
24
  */
@@ -49,8 +55,23 @@ export interface UploaderProps<TEntity = Record<string, unknown>> {
49
55
  * </UploaderEachFile>
50
56
  * </Uploader>
51
57
  * ```
58
+ *
59
+ * @example Copy-on-write: fork a shared asset so the upload lands on a fresh one
60
+ * ```tsx
61
+ * <Uploader
62
+ * entity={block.asset.image}
63
+ * fileType={imageFileType}
64
+ * prepareTarget={() => {
65
+ * block.asset.$disconnect()
66
+ * block.asset.$create()
67
+ * return block.asset.image
68
+ * }}
69
+ * >
70
+ * <DropZone />
71
+ * </Uploader>
72
+ * ```
52
73
  */
53
- export declare function Uploader<TEntity extends Record<string, unknown>>({ entity, fileType, children, }: UploaderProps<TEntity>): ReactNode;
74
+ export declare function Uploader<TEntity extends Record<string, unknown>>({ entity, fileType, prepareTarget, children, ...events }: UploaderProps<TEntity>): ReactNode;
54
75
  declare const uploaderWithSelection: typeof Uploader & SelectionProvider & {
55
76
  [BINDX_COMPONENT]: true;
56
77
  };
@@ -1 +1 @@
1
- {"version":3,"file":"Uploader.d.ts","sourceRoot":"","sources":["../../src/components/Uploader.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAW,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AAC/C,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAqC,MAAM,kBAAkB,CAAA;AAE/F,OAAO,EAAE,eAAe,EAAE,KAAK,iBAAiB,EAAwB,MAAM,wBAAwB,CAAA;AACtG,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AAW3C,MAAM,WAAW,aAAa,CAAC,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAC/D;;;OAGG;IACH,MAAM,EAAE,SAAS,CAAC,OAAO,CAAC,GAAG,SAAS,CAAC,OAAO,CAAC,CAAA;IAC/C;;;OAGG;IACH,QAAQ,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAA;IAC3B;;OAEG;IACH,QAAQ,CAAC,EAAE,SAAS,CAAA;CACpB;AAID;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAgB,QAAQ,CAAC,OAAO,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,EACjE,MAAM,EACN,QAAQ,EACR,QAAQ,GACR,EAAE,aAAa,CAAC,OAAO,CAAC,GAAG,SAAS,CAgCpC;AAsCD,QAAA,MAAM,qBAAqB,EAAe,OAAO,QAAQ,GAAG,iBAAiB,GAAG;IAAE,CAAC,eAAe,CAAC,EAAE,IAAI,CAAA;CAAE,CAAA;AAiC3G,OAAO,EAAE,qBAAqB,IAAI,gBAAgB,EAAE,CAAA"}
1
+ {"version":3,"file":"Uploader.d.ts","sourceRoot":"","sources":["../../src/components/Uploader.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAW,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AAG/C,OAAO,EAAE,eAAe,EAAE,KAAK,iBAAiB,EAAwB,MAAM,wBAAwB,CAAA;AACtG,OAAO,KAAK,EAAE,QAAQ,EAAE,mBAAmB,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAA;AAWpG,MAAM,WAAW,aAAa,CAAC,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAE,SAAQ,OAAO,CAAC,cAAc,CAAC;IAChG;;;OAGG;IACH,MAAM,EAAE,kBAAkB,CAAC,OAAO,CAAC,CAAA;IACnC;;;OAGG;IACH,QAAQ,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAA;IAC3B;;;;;OAKG;IACH,aAAa,CAAC,EAAE,mBAAmB,CAAC,kBAAkB,CAAC,OAAO,CAAC,CAAC,CAAA;IAChE;;OAEG;IACH,QAAQ,CAAC,EAAE,SAAS,CAAA;CACpB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AACH,wBAAgB,QAAQ,CAAC,OAAO,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,EACjE,MAAM,EACN,QAAQ,EACR,aAAa,EACb,QAAQ,EACR,GAAG,MAAM,EACT,EAAE,aAAa,CAAC,OAAO,CAAC,GAAG,SAAS,CA6BpC;AAsCD,QAAA,MAAM,qBAAqB,EAAe,OAAO,QAAQ,GAAG,iBAAiB,GAAG;IAAE,CAAC,eAAe,CAAC,EAAE,IAAI,CAAA;CAAE,CAAA;AAiC3G,OAAO,EAAE,qBAAqB,IAAI,gBAAgB,EAAE,CAAA"}
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export type { UploadClient, FileUploadResult, FileUploadProgress, UploadClientUploadArgs, FileWithMeta, UploaderFileState, UploaderFileStateInitial, UploaderFileStateUploading, UploaderFileStateFinalizing, UploaderFileStateSuccess, UploaderFileStateError, UploaderState, BeforeUploadEvent, StartUploadEvent, ProgressEvent, SuccessEvent, AfterUploadEvent, ErrorEvent, UploaderEvents, FileDataExtractor, FileDataExtractorPopulator, FileType, DiscriminatedFileType, DiscriminatedFileTypeMap, FieldName, UploaderOptions, UploaderErrorType, UploaderErrorOptions, } from './types.js';
1
+ export type { UploadClient, FileUploadResult, FileUploadProgress, UploadClientUploadArgs, FileWithMeta, UploaderFileState, UploaderFileStateInitial, UploaderFileStateUploading, UploaderFileStateFinalizing, UploaderFileStateSuccess, UploaderFileStateError, UploaderState, BeforeUploadEvent, StartUploadEvent, ProgressEvent, SuccessEvent, AfterUploadEvent, ErrorEvent, UploaderEvents, UploaderFillTarget, PrepareUploadTarget, FileDataExtractor, FileDataExtractorPopulator, FileType, DiscriminatedFileType, DiscriminatedFileTypeMap, FieldName, UploaderOptions, UploaderErrorType, UploaderErrorOptions, } from './types.js';
2
2
  export { UploaderError } from './UploaderError.js';
3
3
  export { UploaderUploadFilesContext, useUploaderUploadFiles, UploaderStateContext, useUploaderState, UploaderOptionsContext, useUploaderOptions, UploaderFileStateContext, useUploaderFileState, UploaderClientContext, useUploaderClient, UploaderDropzoneStateContext, useUploaderDropzoneState, } from './contexts.js';
4
4
  export { S3UploadClient, type S3UploadClientOptions, type S3FileOptions, type S3Acl, type S3FileParameters, type S3SignedUrlResponse, type S3UrlSigner, } from './uploadClient/index.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,YAAY,EAEX,YAAY,EACZ,gBAAgB,EAChB,kBAAkB,EAClB,sBAAsB,EAEtB,YAAY,EAEZ,iBAAiB,EACjB,wBAAwB,EACxB,0BAA0B,EAC1B,2BAA2B,EAC3B,wBAAwB,EACxB,sBAAsB,EACtB,aAAa,EAEb,iBAAiB,EACjB,gBAAgB,EAChB,aAAa,EACb,YAAY,EACZ,gBAAgB,EAChB,UAAU,EACV,cAAc,EAEd,iBAAiB,EACjB,0BAA0B,EAE1B,QAAQ,EACR,qBAAqB,EACrB,wBAAwB,EAExB,SAAS,EAET,eAAe,EAEf,iBAAiB,EACjB,oBAAoB,GACpB,MAAM,YAAY,CAAA;AAGnB,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAGlD,OAAO,EACN,0BAA0B,EAC1B,sBAAsB,EACtB,oBAAoB,EACpB,gBAAgB,EAChB,sBAAsB,EACtB,kBAAkB,EAClB,wBAAwB,EACxB,oBAAoB,EACpB,qBAAqB,EACrB,iBAAiB,EACjB,4BAA4B,EAC5B,wBAAwB,GACxB,MAAM,eAAe,CAAA;AAGtB,OAAO,EACN,cAAc,EACd,KAAK,qBAAqB,EAC1B,KAAK,aAAa,EAClB,KAAK,KAAK,EACV,KAAK,gBAAgB,EACrB,KAAK,mBAAmB,EACxB,KAAK,WAAW,GAChB,MAAM,yBAAyB,CAAA;AAGhC,OAAO,EAAE,wBAAwB,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAA;AAGvF,OAAO,EACN,uBAAuB,EACvB,KAAK,yBAAyB,EAC9B,+BAA+B,EAC/B,KAAK,iCAAiC,EACtC,yBAAyB,EACzB,KAAK,2BAA2B,EAChC,yBAAyB,EACzB,KAAK,2BAA2B,EAChC,yBAAyB,EACzB,KAAK,2BAA2B,GAChC,MAAM,uBAAuB,CAAA;AAG9B,OAAO,EACN,mBAAmB,EACnB,KAAK,wBAAwB,EAC7B,mBAAmB,EACnB,KAAK,wBAAwB,EAC7B,mBAAmB,EACnB,KAAK,wBAAwB,EAC7B,iBAAiB,EACjB,KAAK,sBAAsB,GAC3B,MAAM,sBAAsB,CAAA;AAG7B,OAAO,EACN,qBAAqB,EACrB,KAAK,WAAW,EAChB,WAAW,GACX,MAAM,kBAAkB,CAAA;AAGzB,OAAO,EACN,QAAQ,EACR,gBAAgB,EAChB,KAAK,aAAa,EAClB,aAAa,EACb,qBAAqB,EACrB,KAAK,kBAAkB,EACvB,gBAAgB,EAChB,KAAK,qBAAqB,EAC1B,eAAe,EACf,KAAK,oBAAoB,EACzB,uBAAuB,EACvB,KAAK,4BAA4B,EACjC,oBAAoB,EACpB,KAAK,yBAAyB,EAC9B,oBAAoB,EACpB,KAAK,yBAAyB,GAC9B,MAAM,uBAAuB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,YAAY,EAEX,YAAY,EACZ,gBAAgB,EAChB,kBAAkB,EAClB,sBAAsB,EAEtB,YAAY,EAEZ,iBAAiB,EACjB,wBAAwB,EACxB,0BAA0B,EAC1B,2BAA2B,EAC3B,wBAAwB,EACxB,sBAAsB,EACtB,aAAa,EAEb,iBAAiB,EACjB,gBAAgB,EAChB,aAAa,EACb,YAAY,EACZ,gBAAgB,EAChB,UAAU,EACV,cAAc,EAEd,kBAAkB,EAClB,mBAAmB,EAEnB,iBAAiB,EACjB,0BAA0B,EAE1B,QAAQ,EACR,qBAAqB,EACrB,wBAAwB,EAExB,SAAS,EAET,eAAe,EAEf,iBAAiB,EACjB,oBAAoB,GACpB,MAAM,YAAY,CAAA;AAGnB,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAGlD,OAAO,EACN,0BAA0B,EAC1B,sBAAsB,EACtB,oBAAoB,EACpB,gBAAgB,EAChB,sBAAsB,EACtB,kBAAkB,EAClB,wBAAwB,EACxB,oBAAoB,EACpB,qBAAqB,EACrB,iBAAiB,EACjB,4BAA4B,EAC5B,wBAAwB,GACxB,MAAM,eAAe,CAAA;AAGtB,OAAO,EACN,cAAc,EACd,KAAK,qBAAqB,EAC1B,KAAK,aAAa,EAClB,KAAK,KAAK,EACV,KAAK,gBAAgB,EACrB,KAAK,mBAAmB,EACxB,KAAK,WAAW,GAChB,MAAM,yBAAyB,CAAA;AAGhC,OAAO,EAAE,wBAAwB,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAA;AAGvF,OAAO,EACN,uBAAuB,EACvB,KAAK,yBAAyB,EAC9B,+BAA+B,EAC/B,KAAK,iCAAiC,EACtC,yBAAyB,EACzB,KAAK,2BAA2B,EAChC,yBAAyB,EACzB,KAAK,2BAA2B,EAChC,yBAAyB,EACzB,KAAK,2BAA2B,GAChC,MAAM,uBAAuB,CAAA;AAG9B,OAAO,EACN,mBAAmB,EACnB,KAAK,wBAAwB,EAC7B,mBAAmB,EACnB,KAAK,wBAAwB,EAC7B,mBAAmB,EACnB,KAAK,wBAAwB,EAC7B,iBAAiB,EACjB,KAAK,sBAAsB,GAC3B,MAAM,sBAAsB,CAAA;AAG7B,OAAO,EACN,qBAAqB,EACrB,KAAK,WAAW,EAChB,WAAW,GACX,MAAM,kBAAkB,CAAA;AAGzB,OAAO,EACN,QAAQ,EACR,gBAAgB,EAChB,KAAK,aAAa,EAClB,aAAa,EACb,qBAAqB,EACrB,KAAK,kBAAkB,EACvB,gBAAgB,EAChB,KAAK,qBAAqB,EAC1B,eAAe,EACf,KAAK,oBAAoB,EACzB,uBAAuB,EACvB,KAAK,4BAA4B,EACjC,oBAAoB,EACpB,KAAK,yBAAyB,EAC9B,oBAAoB,EACpB,KAAK,yBAAyB,GAC9B,MAAM,uBAAuB,CAAA"}
@@ -1,5 +1,5 @@
1
1
  export { useUploadState, type UseUploadStateResult } from './useUploadState.js';
2
- export { useUploaderDoUpload } from './useUploaderDoUpload.js';
2
+ export { useUploaderDoUpload, type UseUploaderDoUploadArgs } from './useUploaderDoUpload.js';
3
3
  export { useGetPreviewUrls } from './useGetPreviewUrls.js';
4
- export { useFillEntity, type UseFillEntityArgs } from './useFillEntity.js';
4
+ export { useFillEntity, type UseFillEntityArgs, type UseFillEntityResult } from './useFillEntity.js';
5
5
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/internal/hooks/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,KAAK,oBAAoB,EAAE,MAAM,qBAAqB,CAAA;AAC/E,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAA;AAC9D,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAA;AAC1D,OAAO,EAAE,aAAa,EAAE,KAAK,iBAAiB,EAAE,MAAM,oBAAoB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/internal/hooks/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,KAAK,oBAAoB,EAAE,MAAM,qBAAqB,CAAA;AAC/E,OAAO,EAAE,mBAAmB,EAAE,KAAK,uBAAuB,EAAE,MAAM,0BAA0B,CAAA;AAC5F,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAA;AAC1D,OAAO,EAAE,aAAa,EAAE,KAAK,iBAAiB,EAAE,KAAK,mBAAmB,EAAE,MAAM,oBAAoB,CAAA"}
@@ -1,17 +1,26 @@
1
1
  import type { EntityRef, HasOneAccessor } from '@contember/bindx';
2
- import type { FileType, UploaderEvents } from '../../types.js';
3
- export interface UseFillEntityArgs<TEntity = Record<string, unknown>> extends UploaderEvents {
2
+ import type { FileType, PrepareUploadTarget, UploaderEvents } from '../../types.js';
3
+ type FillTarget<TEntity> = EntityRef<TEntity> | HasOneAccessor<TEntity>;
4
+ export interface UseFillEntityArgs<TEntity = Record<string, unknown>> extends Partial<UploaderEvents> {
4
5
  /**
5
6
  * The entity to fill. Can be:
6
7
  * - EntityRef: fill the entity directly
7
8
  * - HasOneAccessor: fill the related entity (disconnect first on upload start)
8
9
  */
9
- entity: EntityRef<TEntity> | HasOneAccessor<TEntity>;
10
+ entity: FillTarget<TEntity>;
10
11
  fileType: FileType<TEntity>;
12
+ /**
13
+ * Resolves the target of a validated batch, before the upload disconnects or fills anything.
14
+ */
15
+ prepareTarget?: PrepareUploadTarget<FillTarget<TEntity>>;
16
+ }
17
+ export interface UseFillEntityResult extends Partial<UploaderEvents> {
18
+ prepareUpload: (files: File[]) => Promise<void>;
11
19
  }
12
20
  /**
13
21
  * Hook that connects upload events to entity field population.
14
22
  * Handles both direct EntityRef and HasOneRef (for has-one relations).
15
23
  */
16
- export declare const useFillEntity: <TEntity extends Record<string, unknown>>({ entity, fileType, ...events }: UseFillEntityArgs<TEntity>) => UploaderEvents;
24
+ export declare const useFillEntity: <TEntity extends Record<string, unknown>>({ entity, fileType, prepareTarget, ...events }: UseFillEntityArgs<TEntity>) => UseFillEntityResult;
25
+ export {};
17
26
  //# sourceMappingURL=useFillEntity.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"useFillEntity.d.ts","sourceRoot":"","sources":["../../../src/internal/hooks/useFillEntity.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAA;AACjE,OAAO,KAAK,EAAE,QAAQ,EAAoB,cAAc,EAAE,MAAM,gBAAgB,CAAA;AAIhF,MAAM,WAAW,iBAAiB,CAAC,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAE,SAAQ,cAAc;IAC3F;;;;OAIG;IACH,MAAM,EAAE,SAAS,CAAC,OAAO,CAAC,GAAG,cAAc,CAAC,OAAO,CAAC,CAAA;IACpD,QAAQ,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAA;CAC3B;AAoBD;;;GAGG;AACH,eAAO,MAAM,aAAa,GAAI,OAAO,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,iCAIpE,iBAAiB,CAAC,OAAO,CAAC,KAAG,cA8C/B,CAAA"}
1
+ {"version":3,"file":"useFillEntity.d.ts","sourceRoot":"","sources":["../../../src/internal/hooks/useFillEntity.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAA;AACjE,OAAO,KAAK,EAAE,QAAQ,EAAE,mBAAmB,EAAoB,cAAc,EAAE,MAAM,gBAAgB,CAAA;AAIrG,KAAK,UAAU,CAAC,OAAO,IAAI,SAAS,CAAC,OAAO,CAAC,GAAG,cAAc,CAAC,OAAO,CAAC,CAAA;AAEvE,MAAM,WAAW,iBAAiB,CAAC,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAE,SAAQ,OAAO,CAAC,cAAc,CAAC;IACpG;;;;OAIG;IACH,MAAM,EAAE,UAAU,CAAC,OAAO,CAAC,CAAA;IAC3B,QAAQ,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAA;IAC3B;;OAEG;IACH,aAAa,CAAC,EAAE,mBAAmB,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,CAAA;CACxD;AAED,MAAM,WAAW,mBAAoB,SAAQ,OAAO,CAAC,cAAc,CAAC;IACnE,aAAa,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CAC/C;AAoBD;;;GAGG;AACH,eAAO,MAAM,aAAa,GAAI,OAAO,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,gDAKpE,iBAAiB,CAAC,OAAO,CAAC,KAAG,mBA2D/B,CAAA"}
@@ -8,5 +8,5 @@ export interface UseUploadStateResult extends UploaderEvents {
8
8
  * Hook that manages the upload state machine.
9
9
  * Tracks file states through the upload lifecycle: initial -> uploading -> finalizing -> success/error
10
10
  */
11
- export declare const useUploadState: ({ onBeforeUpload, onStartUpload, onSuccess, onError, onProgress, onAfterUpload, }: UploaderEvents) => UseUploadStateResult;
11
+ export declare const useUploadState: ({ onBeforeUpload, onStartUpload, onSuccess, onError, onProgress, onAfterUpload, }: Partial<UploaderEvents>) => UseUploadStateResult;
12
12
  //# sourceMappingURL=useUploadState.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"useUploadState.d.ts","sourceRoot":"","sources":["../../../src/internal/hooks/useUploadState.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAOX,cAAc,EAEd,aAAa,EACb,MAAM,gBAAgB,CAAA;AAEvB,MAAM,WAAW,oBAAqB,SAAQ,cAAc;IAC3D,KAAK,EAAE,aAAa,CAAA;IACpB,UAAU,EAAE,MAAM,IAAI,CAAA;IACtB,QAAQ,EAAE,MAAM,IAAI,CAAA;CACpB;AAED;;;GAGG;AACH,eAAO,MAAM,cAAc,GAAI,mFAO5B,cAAc,KAAG,oBAgInB,CAAA"}
1
+ {"version":3,"file":"useUploadState.d.ts","sourceRoot":"","sources":["../../../src/internal/hooks/useUploadState.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAOX,cAAc,EAEd,aAAa,EACb,MAAM,gBAAgB,CAAA;AAEvB,MAAM,WAAW,oBAAqB,SAAQ,cAAc;IAC3D,KAAK,EAAE,aAAa,CAAA;IACpB,UAAU,EAAE,MAAM,IAAI,CAAA;IACtB,QAAQ,EAAE,MAAM,IAAI,CAAA;CACpB;AAED;;;GAGG;AACH,eAAO,MAAM,cAAc,GAAI,mFAO5B,OAAO,CAAC,cAAc,CAAC,KAAG,oBAgI5B,CAAA"}
@@ -1,7 +1,14 @@
1
1
  import type { UploaderEvents } from '../../types.js';
2
+ export interface UseUploaderDoUploadArgs extends Partial<UploaderEvents> {
3
+ /**
4
+ * Runs once per batch with the files that passed validation, before any of them is
5
+ * uploaded or written to the target. Skipped when no file passed.
6
+ */
7
+ onPrepareUpload?: (files: File[]) => Promise<void> | void;
8
+ }
2
9
  /**
3
10
  * Hook that orchestrates the file upload process.
4
11
  * Handles file preparation, validation, and upload execution.
5
12
  */
6
- export declare const useUploaderDoUpload: ({ onBeforeUpload, onError, onProgress, onSuccess, onStartUpload, onAfterUpload, }: UploaderEvents) => ((files: File[]) => Promise<void>);
13
+ export declare const useUploaderDoUpload: ({ onBeforeUpload, onError, onProgress, onSuccess, onStartUpload, onAfterUpload, onPrepareUpload, }: UseUploaderDoUploadArgs) => ((files: File[]) => Promise<void>);
7
14
  //# sourceMappingURL=useUploaderDoUpload.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"useUploaderDoUpload.d.ts","sourceRoot":"","sources":["../../../src/internal/hooks/useUploaderDoUpload.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAgB,cAAc,EAAE,MAAM,gBAAgB,CAAA;AAKlE;;;GAGG;AACH,eAAO,MAAM,mBAAmB,GAAI,mFAOjC,cAAc,KAAG,CAAC,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CA+GpD,CAAA"}
1
+ {"version":3,"file":"useUploaderDoUpload.d.ts","sourceRoot":"","sources":["../../../src/internal/hooks/useUploaderDoUpload.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAgB,cAAc,EAAE,MAAM,gBAAgB,CAAA;AAKlE,MAAM,WAAW,uBAAwB,SAAQ,OAAO,CAAC,cAAc,CAAC;IACvE;;;OAGG;IACH,eAAe,CAAC,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;CACzD;AAED;;;GAGG;AACH,eAAO,MAAM,mBAAmB,GAAI,oGAQjC,uBAAuB,KAAG,CAAC,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CA6H7D,CAAA"}
package/dist/types.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { EntityRef } from '@contember/bindx';
1
+ import type { EntityRef, HasOneRef } from '@contember/bindx';
2
2
  export interface UploadClient<Options = unknown, Result extends FileUploadResult = FileUploadResult> {
3
3
  upload: (args: UploadClientUploadArgs & Omit<Options, keyof UploadClientUploadArgs>) => Promise<Result>;
4
4
  }
@@ -77,6 +77,15 @@ export interface ErrorEvent {
77
77
  error: unknown;
78
78
  fileType?: FileType;
79
79
  }
80
+ /**
81
+ * Fill target of a single-file uploader: an entity, or a has-one relation pointing at one.
82
+ */
83
+ export type UploaderFillTarget<TEntity = Record<string, unknown>> = EntityRef<TEntity> | HasOneRef<TEntity>;
84
+ /**
85
+ * Resolves the target of an upload batch after the files pass validation and before
86
+ * the target is written. Returning nothing keeps the target the uploader was given.
87
+ */
88
+ export type PrepareUploadTarget<TTarget> = (files: File[]) => TTarget | undefined | Promise<TTarget | undefined>;
80
89
  export interface UploaderEvents {
81
90
  onBeforeUpload: (event: BeforeUploadEvent) => Promise<FileType | undefined>;
82
91
  onStartUpload: (event: StartUploadEvent) => void;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAwB,SAAS,EAAE,MAAM,kBAAkB,CAAA;AAMlF,MAAM,WAAW,YAAY,CAC5B,OAAO,GAAG,OAAO,EACjB,MAAM,SAAS,gBAAgB,GAAG,gBAAgB;IAElD,MAAM,EAAE,CAAC,IAAI,EAAE,sBAAsB,GAAG,IAAI,CAAC,OAAO,EAAE,MAAM,sBAAsB,CAAC,KAAK,OAAO,CAAC,MAAM,CAAC,CAAA;CACvG;AAED,MAAM,WAAW,gBAAgB;IAChC,SAAS,EAAE,MAAM,CAAA;CACjB;AAED,MAAM,WAAW,kBAAkB;IAClC,QAAQ,EAAE,MAAM,CAAA;IAChB,aAAa,EAAE,MAAM,CAAA;IACrB,UAAU,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,sBAAsB;IACtC,IAAI,EAAE,IAAI,CAAA;IACV,MAAM,EAAE,WAAW,CAAA;IACnB,UAAU,EAAE,CAAC,QAAQ,EAAE,kBAAkB,KAAK,IAAI,CAAA;CAClD;AAMD,MAAM,WAAW,YAAY;IAC5B,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,IAAI,CAAA;IACV,UAAU,EAAE,MAAM,CAAA;IAClB,eAAe,EAAE,eAAe,CAAA;CAChC;AAMD,MAAM,WAAW,wBAAwB;IACxC,KAAK,EAAE,SAAS,CAAA;IAChB,IAAI,EAAE,YAAY,CAAA;CAClB;AAED,MAAM,WAAW,0BAA0B;IAC1C,KAAK,EAAE,WAAW,CAAA;IAClB,IAAI,EAAE,YAAY,CAAA;IAClB,QAAQ,EAAE,kBAAkB,CAAA;CAC5B;AAED,MAAM,WAAW,2BAA2B;IAC3C,KAAK,EAAE,YAAY,CAAA;IACnB,IAAI,EAAE,YAAY,CAAA;IAClB,MAAM,EAAE,gBAAgB,CAAA;CACxB;AAED,MAAM,WAAW,wBAAwB;IACxC,KAAK,EAAE,SAAS,CAAA;IAChB,IAAI,EAAE,YAAY,CAAA;IAClB,MAAM,EAAE,gBAAgB,CAAA;IACxB,OAAO,EAAE,MAAM,IAAI,CAAA;CACnB;AAED,MAAM,WAAW,sBAAsB;IACtC,KAAK,EAAE,OAAO,CAAA;IACd,IAAI,EAAE,YAAY,CAAA;IAClB,KAAK,EAAE,OAAO,CAAA;IACd,OAAO,EAAE,MAAM,IAAI,CAAA;CACnB;AAED,MAAM,MAAM,iBAAiB,GAC1B,wBAAwB,GACxB,0BAA0B,GAC1B,2BAA2B,GAC3B,wBAAwB,GACxB,sBAAsB,CAAA;AAEzB,MAAM,MAAM,aAAa,GAAG,iBAAiB,EAAE,CAAA;AAM/C,MAAM,WAAW,iBAAiB;IACjC,IAAI,EAAE,YAAY,CAAA;IAClB,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,KAAK,CAAA;CACjC;AAED,MAAM,WAAW,gBAAgB;IAChC,IAAI,EAAE,YAAY,CAAA;IAClB,QAAQ,EAAE,QAAQ,CAAA;CAClB;AAED,MAAM,WAAW,aAAa;IAC7B,IAAI,EAAE,YAAY,CAAA;IAClB,QAAQ,EAAE,kBAAkB,CAAA;IAC5B,QAAQ,EAAE,QAAQ,CAAA;CAClB;AAED,MAAM,WAAW,YAAY;IAC5B,IAAI,EAAE,YAAY,CAAA;IAClB,MAAM,EAAE,gBAAgB,CAAA;IACxB,QAAQ,EAAE,QAAQ,CAAA;CAClB;AAED,MAAM,WAAW,gBAAgB;IAChC,IAAI,EAAE,YAAY,CAAA;IAClB,MAAM,EAAE,gBAAgB,CAAA;IACxB,QAAQ,EAAE,QAAQ,CAAA;CAClB;AAED,MAAM,WAAW,UAAU;IAC1B,IAAI,EAAE,YAAY,CAAA;IAClB,KAAK,EAAE,OAAO,CAAA;IACd,QAAQ,CAAC,EAAE,QAAQ,CAAA;CACnB;AAED;;GAEG;AACH,MAAM,MAAM,kBAAkB,CAAC,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI,SAAS,CAAC,OAAO,CAAC,GAAG,SAAS,CAAC,OAAO,CAAC,CAAA;AAE3G;;;GAGG;AACH,MAAM,MAAM,mBAAmB,CAAC,OAAO,IAAI,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,OAAO,GAAG,SAAS,GAAG,OAAO,CAAC,OAAO,GAAG,SAAS,CAAC,CAAA;AAEhH,MAAM,WAAW,cAAc;IAC9B,cAAc,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,OAAO,CAAC,QAAQ,GAAG,SAAS,CAAC,CAAA;IAC3E,aAAa,EAAE,CAAC,KAAK,EAAE,gBAAgB,KAAK,IAAI,CAAA;IAChD,UAAU,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAA;IAC1C,aAAa,EAAE,CAAC,KAAK,EAAE,gBAAgB,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;IAChE,SAAS,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,CAAA;IACxC,OAAO,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAAA;CACpC;AAMD;;GAEG;AACH,MAAM,MAAM,SAAS,CAAC,OAAO,IAAI,MAAM,OAAO,GAAG,MAAM,CAAA;AAEvD;;GAEG;AACH,MAAM,MAAM,0BAA0B,CAAC,OAAO,IAAI,CAAC,OAAO,EAAE;IAC3D,MAAM,EAAE,SAAS,CAAC,OAAO,CAAC,CAAA;IAC1B,MAAM,EAAE,gBAAgB,CAAA;CACxB,KAAK,IAAI,CAAA;AAEV;;;GAGG;AACH,MAAM,WAAW,iBAAiB,CAAC,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IACnE;;;OAGG;IACH,aAAa,EAAE,MAAM,SAAS,CAAC,OAAO,CAAC,EAAE,CAAA;IAEzC;;OAEG;IACH,eAAe,CAAC,EAAE,CAAC,IAAI,EAAE,YAAY,KAAK,OAAO,CAAC,0BAA0B,CAAC,OAAO,CAAC,GAAG,SAAS,CAAC,GAAG,0BAA0B,CAAC,OAAO,CAAC,GAAG,SAAS,CAAA;IAEpJ;;OAEG;IACH,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE;QAAE,MAAM,EAAE,SAAS,CAAC,OAAO,CAAC,CAAC;QAAC,MAAM,EAAE,gBAAgB,CAAA;KAAE,KAAK,IAAI,CAAA;CAC5F;AAMD;;GAEG;AACH,MAAM,WAAW,QAAQ,CAAC,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAC1D;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,GAAG,SAAS,CAAA;IAE7C;;OAEG;IACH,UAAU,CAAC,EAAE,CAAC,CAAC,IAAI,EAAE,YAAY,KAAK,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,GAAG,SAAS,CAAA;IAE1E;;OAEG;IACH,UAAU,CAAC,EAAE,iBAAiB,CAAC,OAAO,CAAC,EAAE,CAAA;IAEzC;;OAEG;IACH,QAAQ,CAAC,EAAE,YAAY,CAAC,OAAO,CAAC,CAAA;CAChC;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB,CAAC,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAE,SAAQ,QAAQ,CAAC,OAAO,CAAC;IAClG;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;CAClB;AAED;;GAEG;AACH,MAAM,MAAM,wBAAwB,CAAC,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI,MAAM,CAAC,MAAM,EAAE,qBAAqB,CAAC,OAAO,CAAC,CAAC,CAAA;AAMxH,MAAM,WAAW,eAAe;IAC/B,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAA;IACjC,QAAQ,EAAE,OAAO,CAAA;CACjB;AAMD,MAAM,MAAM,iBAAiB,GAC1B,cAAc,GACd,cAAc,GACd,WAAW,GACX,SAAS,GACT,SAAS,CAAA;AAEZ,MAAM,WAAW,oBAAoB;IACpC,IAAI,EAAE,iBAAiB,CAAA;IACvB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,KAAK,CAAC,EAAE,OAAO,CAAA;CACf"}
@@ -1 +1 @@
1
- {"version":3,"file":"S3UploadClient.d.ts","sourceRoot":"","sources":["../../src/uploadClient/S3UploadClient.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAA;AACvE,OAAO,KAAK,EAAE,gBAAgB,EAAuB,WAAW,EAAE,MAAM,YAAY,CAAA;AAGpF,MAAM,WAAW,qBAAqB;IACrC,OAAO,EAAE,WAAW,CAAA;IACpB,gBAAgB,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,gBAAgB,CAAC,CAAA;IAC5D,WAAW,CAAC,EAAE,MAAM,CAAA;CACpB;AAED,MAAM,MAAM,aAAa,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAA;AAErD,qBAAa,cAAe,YAAW,YAAY,CAAC,aAAa,CAAC;aAI9B,OAAO,EAAE,qBAAqB;IAHjE,OAAO,CAAC,WAAW,CAAI;IACvB,OAAO,CAAC,aAAa,CAAwB;gBAEV,OAAO,EAAE,qBAAqB;IAEpD,MAAM,CAAC,EACnB,IAAI,EACJ,MAAM,EACN,UAAU,EACV,GAAG,OAAO,EACV,EAAE,sBAAsB,GAAG,aAAa,GAAG,OAAO,CAAC;QAAE,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;YAe5D,gBAAgB;CAgB9B"}
1
+ {"version":3,"file":"S3UploadClient.d.ts","sourceRoot":"","sources":["../../src/uploadClient/S3UploadClient.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAA;AACvE,OAAO,KAAK,EAAE,gBAAgB,EAAuB,WAAW,EAAE,MAAM,YAAY,CAAA;AAGpF,MAAM,WAAW,qBAAqB;IACrC,OAAO,EAAE,WAAW,CAAA;IACpB,gBAAgB,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,gBAAgB,CAAC,CAAA;IAC5D,WAAW,CAAC,EAAE,MAAM,CAAA;CACpB;AAED,MAAM,MAAM,aAAa,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAA;AAQrD,qBAAa,cAAe,YAAW,YAAY,CAAC,aAAa,CAAC;aAI9B,OAAO,EAAE,qBAAqB;IAHjE,OAAO,CAAC,WAAW,CAAI;IACvB,OAAO,CAAC,aAAa,CAAwB;gBAEV,OAAO,EAAE,qBAAqB;IAEpD,MAAM,CAAC,EACnB,IAAI,EACJ,MAAM,EACN,UAAU,EACV,GAAG,OAAO,EACV,EAAE,sBAAsB,GAAG,aAAa,GAAG,OAAO,CAAC;QAAE,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;YAgB5D,gBAAgB;CAgB9B"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contember/bindx-uploader",
3
- "version": "0.1.49",
3
+ "version": "0.1.50",
4
4
  "description": "File upload components for bindx data binding",
5
5
  "type": "module",
6
6
  "main": "./src/index.ts",
@@ -15,9 +15,9 @@
15
15
  "react": ">=18.0.0"
16
16
  },
17
17
  "dependencies": {
18
- "@contember/bindx": "0.1.49",
19
- "@contember/bindx-react": "0.1.49",
20
- "@contember/bindx-repeater": "0.1.49",
18
+ "@contember/bindx": "0.1.50",
19
+ "@contember/bindx-react": "0.1.50",
20
+ "@contember/bindx-repeater": "0.1.50",
21
21
  "@radix-ui/react-slot": "^1.2.3",
22
22
  "react-dropzone": "^14.3.8"
23
23
  },
@@ -15,7 +15,7 @@ import { resolveAcceptingSingleType } from '../internal/utils/resolveAccept.js'
15
15
  import { executeExtractors } from '../internal/utils/executeExtractors.js'
16
16
  import { uploaderErrorHandler } from '../internal/utils/uploaderErrorHandler.js'
17
17
 
18
- export interface MultiUploaderProps<TEntity = Record<string, unknown>> {
18
+ export interface MultiUploaderProps<TEntity = Record<string, unknown>> extends Partial<UploaderEvents> {
19
19
  /**
20
20
  * The has-many relation to add uploaded files to.
21
21
  */
@@ -53,6 +53,7 @@ export function MultiUploader<TEntity extends Record<string, unknown>>({
53
53
  field,
54
54
  fileType,
55
55
  children,
56
+ ...events
56
57
  }: MultiUploaderProps<TEntity>): ReactNode {
57
58
  const fieldAccessor = useHasMany(field)
58
59
  // Map file ID -> entity ID
@@ -71,7 +72,7 @@ export function MultiUploader<TEntity extends Record<string, unknown>>({
71
72
 
72
73
  // Create entity for file and track mapping
73
74
  const useCreateRepeaterEntityEvents = useCallback(
74
- (events: UploaderEvents): UploaderEvents => ({
75
+ (events: Partial<UploaderEvents>): Partial<UploaderEvents> => ({
75
76
  ...events,
76
77
  onBeforeUpload: async event => {
77
78
  if (!(await resolveAcceptingSingleType(event.file, fileType as FileType))) {
@@ -115,19 +116,10 @@ export function MultiUploader<TEntity extends Record<string, unknown>>({
115
116
  [field, fileType, getEntityForFile],
116
117
  )
117
118
 
118
- const baseEvents: UploaderEvents = useMemo(
119
- () => ({
120
- onError: uploaderErrorHandler,
121
- onStartUpload: () => {},
122
- onBeforeUpload: async () => undefined,
123
- onProgress: () => {},
124
- onAfterUpload: async () => {},
125
- onSuccess: () => {},
126
- }),
127
- [],
128
- )
129
-
130
- const fillEntityEvents = useCreateRepeaterEntityEvents(baseEvents)
119
+ const fillEntityEvents = useCreateRepeaterEntityEvents({
120
+ ...events,
121
+ onError: events.onError ?? uploaderErrorHandler,
122
+ })
131
123
  const { files, ...stateEvents } = useUploadState(fillEntityEvents)
132
124
  const onDrop = useUploaderDoUpload(stateEvents)
133
125
 
@@ -1,8 +1,8 @@
1
1
  import { useMemo, type ReactNode } from 'react'
2
- import type { EntityRef, HasOneRef, SelectionFieldMeta, SelectionMeta } from '@contember/bindx'
2
+ import type { SelectionFieldMeta, SelectionMeta } from '@contember/bindx'
3
3
  import { FIELD_REF_META } from '@contember/bindx'
4
4
  import { BINDX_COMPONENT, type SelectionProvider, createEmptySelection } from '@contember/bindx-react'
5
- import type { FileType } from '../types.js'
5
+ import type { FileType, PrepareUploadTarget, UploaderEvents, UploaderFillTarget } from '../types.js'
6
6
  import {
7
7
  UploaderOptionsContext,
8
8
  UploaderStateContext,
@@ -13,25 +13,30 @@ import { useUploaderDoUpload } from '../internal/hooks/useUploaderDoUpload.js'
13
13
  import { useFillEntity } from '../internal/hooks/useFillEntity.js'
14
14
  import { uploaderErrorHandler } from '../internal/utils/uploaderErrorHandler.js'
15
15
 
16
- export interface UploaderProps<TEntity = Record<string, unknown>> {
16
+ export interface UploaderProps<TEntity = Record<string, unknown>> extends Partial<UploaderEvents> {
17
17
  /**
18
18
  * The entity to fill with uploaded file data.
19
19
  * Can be an EntityRef or HasOneRef.
20
20
  */
21
- entity: EntityRef<TEntity> | HasOneRef<TEntity>
21
+ entity: UploaderFillTarget<TEntity>
22
22
  /**
23
23
  * File type configuration defining accepted files and extractors.
24
24
  * Must be created with the same entity type as the entity prop.
25
25
  */
26
26
  fileType: FileType<TEntity>
27
+ /**
28
+ * Resolves the target of an upload batch: it runs once, after the files pass the accept
29
+ * check and before the uploader disconnects or fills anything, and receives the accepted
30
+ * files. A rejected batch never reaches it. Return another target to fork first
31
+ * (copy-on-write); returning nothing keeps the entity prop.
32
+ */
33
+ prepareTarget?: PrepareUploadTarget<UploaderFillTarget<TEntity>>
27
34
  /**
28
35
  * Children to render within the uploader context.
29
36
  */
30
37
  children?: ReactNode
31
38
  }
32
39
 
33
- const noop = (): Promise<undefined> => Promise.resolve(undefined)
34
-
35
40
  /**
36
41
  * Single file upload component for bindx.
37
42
  * Provides upload state and upload function to children via context.
@@ -63,25 +68,39 @@ const noop = (): Promise<undefined> => Promise.resolve(undefined)
63
68
  * </UploaderEachFile>
64
69
  * </Uploader>
65
70
  * ```
71
+ *
72
+ * @example Copy-on-write: fork a shared asset so the upload lands on a fresh one
73
+ * ```tsx
74
+ * <Uploader
75
+ * entity={block.asset.image}
76
+ * fileType={imageFileType}
77
+ * prepareTarget={() => {
78
+ * block.asset.$disconnect()
79
+ * block.asset.$create()
80
+ * return block.asset.image
81
+ * }}
82
+ * >
83
+ * <DropZone />
84
+ * </Uploader>
85
+ * ```
66
86
  */
67
87
  export function Uploader<TEntity extends Record<string, unknown>>({
68
88
  entity,
69
89
  fileType,
90
+ prepareTarget,
70
91
  children,
92
+ ...events
71
93
  }: UploaderProps<TEntity>): ReactNode {
72
- const fillEntityEvents = useFillEntity({
94
+ const { prepareUpload, ...fillEntityEvents } = useFillEntity({
73
95
  entity,
74
96
  fileType,
75
- onError: uploaderErrorHandler,
76
- onStartUpload: noop,
77
- onBeforeUpload: noop,
78
- onProgress: noop,
79
- onAfterUpload: noop,
80
- onSuccess: noop,
97
+ prepareTarget,
98
+ ...events,
99
+ onError: events.onError ?? uploaderErrorHandler,
81
100
  })
82
101
 
83
102
  const { files, ...stateEvents } = useUploadState(fillEntityEvents)
84
- const onDrop = useUploaderDoUpload(stateEvents)
103
+ const onDrop = useUploaderDoUpload({ ...stateEvents, onPrepareUpload: prepareUpload })
85
104
 
86
105
  const options = useMemo(
87
106
  () => ({
package/src/index.ts CHANGED
@@ -23,6 +23,9 @@ export type {
23
23
  AfterUploadEvent,
24
24
  ErrorEvent,
25
25
  UploaderEvents,
26
+ // Upload target
27
+ UploaderFillTarget,
28
+ PrepareUploadTarget,
26
29
  // Extractors
27
30
  FileDataExtractor,
28
31
  FileDataExtractorPopulator,
@@ -1,4 +1,4 @@
1
1
  export { useUploadState, type UseUploadStateResult } from './useUploadState.js'
2
- export { useUploaderDoUpload } from './useUploaderDoUpload.js'
2
+ export { useUploaderDoUpload, type UseUploaderDoUploadArgs } from './useUploaderDoUpload.js'
3
3
  export { useGetPreviewUrls } from './useGetPreviewUrls.js'
4
- export { useFillEntity, type UseFillEntityArgs } from './useFillEntity.js'
4
+ export { useFillEntity, type UseFillEntityArgs, type UseFillEntityResult } from './useFillEntity.js'
@@ -1,23 +1,33 @@
1
- import { useCallback } from 'react'
1
+ import { useCallback, useRef } from 'react'
2
2
  import type { EntityRef, HasOneAccessor } from '@contember/bindx'
3
- import type { FileType, StartUploadEvent, UploaderEvents } from '../../types.js'
3
+ import type { FileType, PrepareUploadTarget, StartUploadEvent, UploaderEvents } from '../../types.js'
4
4
  import { resolveAcceptingSingleType } from '../utils/resolveAccept.js'
5
5
  import { executeExtractors } from '../utils/executeExtractors.js'
6
6
 
7
- export interface UseFillEntityArgs<TEntity = Record<string, unknown>> extends UploaderEvents {
7
+ type FillTarget<TEntity> = EntityRef<TEntity> | HasOneAccessor<TEntity>
8
+
9
+ export interface UseFillEntityArgs<TEntity = Record<string, unknown>> extends Partial<UploaderEvents> {
8
10
  /**
9
11
  * The entity to fill. Can be:
10
12
  * - EntityRef: fill the entity directly
11
13
  * - HasOneAccessor: fill the related entity (disconnect first on upload start)
12
14
  */
13
- entity: EntityRef<TEntity> | HasOneAccessor<TEntity>
15
+ entity: FillTarget<TEntity>
14
16
  fileType: FileType<TEntity>
17
+ /**
18
+ * Resolves the target of a validated batch, before the upload disconnects or fills anything.
19
+ */
20
+ prepareTarget?: PrepareUploadTarget<FillTarget<TEntity>>
21
+ }
22
+
23
+ export interface UseFillEntityResult extends Partial<UploaderEvents> {
24
+ prepareUpload: (files: File[]) => Promise<void>
15
25
  }
16
26
 
17
27
  /**
18
28
  * Checks if the entity is a HasOneRef (has $disconnect method)
19
29
  */
20
- const isHasOneAccessor = <TEntity>(entity: EntityRef<TEntity> | HasOneAccessor<TEntity>): entity is HasOneAccessor<TEntity> => {
30
+ const isHasOneAccessor = <TEntity>(entity: FillTarget<TEntity>): entity is HasOneAccessor<TEntity> => {
21
31
  return '$disconnect' in entity && typeof entity.$disconnect === 'function'
22
32
  }
23
33
 
@@ -25,7 +35,7 @@ const isHasOneAccessor = <TEntity>(entity: EntityRef<TEntity> | HasOneAccessor<T
25
35
  * Gets the target entity for filling.
26
36
  * For HasOneAccessor, returns the related entity. For EntityRef, returns the entity itself.
27
37
  */
28
- const getTargetEntity = <TEntity>(entity: EntityRef<TEntity> | HasOneAccessor<TEntity>): EntityRef<TEntity> => {
38
+ const getTargetEntity = <TEntity>(entity: FillTarget<TEntity>): EntityRef<TEntity> => {
29
39
  if (isHasOneAccessor(entity)) {
30
40
  return entity.$entity
31
41
  }
@@ -39,8 +49,20 @@ const getTargetEntity = <TEntity>(entity: EntityRef<TEntity> | HasOneAccessor<TE
39
49
  export const useFillEntity = <TEntity extends Record<string, unknown>>({
40
50
  entity,
41
51
  fileType,
52
+ prepareTarget,
42
53
  ...events
43
- }: UseFillEntityArgs<TEntity>): UploaderEvents => {
54
+ }: UseFillEntityArgs<TEntity>): UseFillEntityResult => {
55
+ const preparedTargetRef = useRef<FillTarget<TEntity> | undefined>(undefined)
56
+
57
+ const prepareUpload = useCallback(
58
+ async (files: File[]): Promise<void> => {
59
+ preparedTargetRef.current = await prepareTarget?.(files)
60
+ },
61
+ [prepareTarget],
62
+ )
63
+
64
+ const getTarget = useCallback((): FillTarget<TEntity> => preparedTargetRef.current ?? entity, [entity])
65
+
44
66
  const handleBeforeUpload = useCallback(
45
67
  async (event: Parameters<UploaderEvents['onBeforeUpload']>[0]): Promise<FileType | undefined> => {
46
68
  if (!(await resolveAcceptingSingleType(event.file, fileType as FileType))) {
@@ -54,19 +76,20 @@ export const useFillEntity = <TEntity extends Record<string, unknown>>({
54
76
  const handleStartUpload = useCallback(
55
77
  (event: StartUploadEvent) => {
56
78
  // Disconnect existing relation before upload
57
- if (isHasOneAccessor(entity)) {
58
- entity.$disconnect()
79
+ const target = getTarget()
80
+ if (isHasOneAccessor(target)) {
81
+ target.$disconnect()
59
82
  }
60
83
  events.onStartUpload?.(event)
61
84
  },
62
- [entity, events],
85
+ [events, getTarget],
63
86
  )
64
87
 
65
88
  const handleAfterUpload = useCallback(
66
89
  async (event: Parameters<UploaderEvents['onAfterUpload']>[0]) => {
67
90
  await Promise.all([
68
91
  (async () => {
69
- const targetEntity = getTargetEntity(entity)
92
+ const targetEntity = getTargetEntity(getTarget())
70
93
  const extractionResult = await executeExtractors({
71
94
  fileType: fileType as FileType,
72
95
  result: event.result,
@@ -77,11 +100,12 @@ export const useFillEntity = <TEntity extends Record<string, unknown>>({
77
100
  events.onAfterUpload?.(event),
78
101
  ])
79
102
  },
80
- [entity, events, fileType],
103
+ [events, fileType, getTarget],
81
104
  )
82
105
 
83
106
  return {
84
107
  ...events,
108
+ prepareUpload,
85
109
  onBeforeUpload: handleBeforeUpload,
86
110
  onStartUpload: handleStartUpload,
87
111
  onAfterUpload: handleAfterUpload,
@@ -28,7 +28,7 @@ export const useUploadState = ({
28
28
  onError,
29
29
  onProgress,
30
30
  onAfterUpload,
31
- }: UploaderEvents): UseUploadStateResult => {
31
+ }: Partial<UploaderEvents>): UseUploadStateResult => {
32
32
  const [files, setFiles] = useState<Record<string, UploaderFileState>>({})
33
33
 
34
34
  const purgeFinal = useCallback(() => {
@@ -4,6 +4,14 @@ import { UploaderError } from '../../UploaderError.js'
4
4
  import { useUploaderClient } from '../../contexts.js'
5
5
  import { useGetPreviewUrls } from './useGetPreviewUrls.js'
6
6
 
7
+ export interface UseUploaderDoUploadArgs extends Partial<UploaderEvents> {
8
+ /**
9
+ * Runs once per batch with the files that passed validation, before any of them is
10
+ * uploaded or written to the target. Skipped when no file passed.
11
+ */
12
+ onPrepareUpload?: (files: File[]) => Promise<void> | void
13
+ }
14
+
7
15
  /**
8
16
  * Hook that orchestrates the file upload process.
9
17
  * Handles file preparation, validation, and upload execution.
@@ -15,7 +23,8 @@ export const useUploaderDoUpload = ({
15
23
  onSuccess,
16
24
  onStartUpload,
17
25
  onAfterUpload,
18
- }: UploaderEvents): ((files: File[]) => Promise<void>) => {
26
+ onPrepareUpload,
27
+ }: UseUploaderDoUploadArgs): ((files: File[]) => Promise<void>) => {
19
28
  const getPreviewUrl = useGetPreviewUrls()
20
29
  const defaultUploader = useUploaderClient()
21
30
 
@@ -73,6 +82,19 @@ export const useUploaderDoUpload = ({
73
82
  )
74
83
  .map(p => p.value)
75
84
 
85
+ if (preparedFiles.length === 0) {
86
+ return
87
+ }
88
+
89
+ try {
90
+ await onPrepareUpload?.(preparedFiles.map(({ file }) => file.file))
91
+ } catch (error) {
92
+ for (const { file } of preparedFiles) {
93
+ onError?.({ file, error })
94
+ }
95
+ return
96
+ }
97
+
76
98
  // Upload files
77
99
  await Promise.allSettled(
78
100
  preparedFiles.map(async ({ file, fileType }) => {
@@ -121,6 +143,7 @@ export const useUploaderDoUpload = ({
121
143
  onAfterUpload,
122
144
  onBeforeUpload,
123
145
  onError,
146
+ onPrepareUpload,
124
147
  onProgress,
125
148
  onStartUpload,
126
149
  onSuccess,
package/src/types.ts CHANGED
@@ -120,6 +120,17 @@ export interface ErrorEvent {
120
120
  fileType?: FileType
121
121
  }
122
122
 
123
+ /**
124
+ * Fill target of a single-file uploader: an entity, or a has-one relation pointing at one.
125
+ */
126
+ export type UploaderFillTarget<TEntity = Record<string, unknown>> = EntityRef<TEntity> | HasOneRef<TEntity>
127
+
128
+ /**
129
+ * Resolves the target of an upload batch after the files pass validation and before
130
+ * the target is written. Returning nothing keeps the target the uploader was given.
131
+ */
132
+ export type PrepareUploadTarget<TTarget> = (files: File[]) => TTarget | undefined | Promise<TTarget | undefined>
133
+
123
134
  export interface UploaderEvents {
124
135
  onBeforeUpload: (event: BeforeUploadEvent) => Promise<FileType | undefined>
125
136
  onStartUpload: (event: StartUploadEvent) => void
@@ -10,6 +10,12 @@ export interface S3UploadClientOptions {
10
10
 
11
11
  export type S3FileOptions = Partial<S3FileParameters>
12
12
 
13
+ // generateUploadUrl's `size` is a GraphQLInt (32-bit); larger files omit it rather than fail signing.
14
+ const MAX_GRAPHQL_INT = 2_147_483_647
15
+
16
+ const getDefaultSize = (file: File): number | undefined =>
17
+ file.size <= MAX_GRAPHQL_INT ? file.size : undefined
18
+
13
19
  export class S3UploadClient implements UploadClient<S3FileOptions> {
14
20
  private activeCount = 0
15
21
  private resolverQueue: Array<() => void> = []
@@ -24,6 +30,7 @@ export class S3UploadClient implements UploadClient<S3FileOptions> {
24
30
  }: UploadClientUploadArgs & S3FileOptions): Promise<{ publicUrl: string }> {
25
31
  const parameters: S3FileParameters = {
26
32
  contentType: file.type,
33
+ size: getDefaultSize(file),
27
34
  ...this.options.getUploadOptions?.(file),
28
35
  ...options,
29
36
  }
@@ -57,55 +57,24 @@ function buildGenerateUploadUrlMutation(
57
57
  const params = parameters[alias]
58
58
  if (!params) continue
59
59
 
60
- const hasNewFormat = params.suffix || params.fileName || params.extension
61
-
62
- if (hasNewFormat) {
63
- const inputVarName = `input_${varIndex}`
64
- variableDefinitions.push(`$${inputVarName}: S3GenerateSignedUploadInput`)
65
- variables[inputVarName] = {
66
- contentType: params.contentType,
67
- prefix: params.prefix,
68
- expiration: params.expiration,
69
- acl: params.acl,
70
- size: params.size,
71
- suffix: params.suffix,
72
- fileName: params.fileName,
73
- extension: params.extension,
74
- }
75
- fields.push(`${alias}: generateUploadUrl(input: $${inputVarName}) {
76
- url
77
- publicUrl
78
- method
79
- headers { key value }
80
- }`)
81
- } else {
82
- const contentTypeVar = `contentType_${varIndex}`
83
- const expirationVar = `expiration_${varIndex}`
84
- const prefixVar = `prefix_${varIndex}`
85
- const aclVar = `acl_${varIndex}`
86
-
87
- variableDefinitions.push(`$${contentTypeVar}: String`)
88
- variableDefinitions.push(`$${expirationVar}: Int`)
89
- variableDefinitions.push(`$${prefixVar}: String`)
90
- variableDefinitions.push(`$${aclVar}: S3Acl`)
91
-
92
- variables[contentTypeVar] = params.contentType
93
- variables[expirationVar] = params.expiration
94
- variables[prefixVar] = params.prefix
95
- variables[aclVar] = params.acl
96
-
97
- fields.push(`${alias}: generateUploadUrl(
98
- contentType: $${contentTypeVar}
99
- expiration: $${expirationVar}
100
- prefix: $${prefixVar}
101
- acl: $${aclVar}
102
- ) {
103
- url
104
- publicUrl
105
- method
106
- headers { key value }
107
- }`)
60
+ const inputVarName = `input_${varIndex}`
61
+ variableDefinitions.push(`$${inputVarName}: S3GenerateSignedUploadInput`)
62
+ variables[inputVarName] = {
63
+ contentType: params.contentType,
64
+ prefix: params.prefix,
65
+ expiration: params.expiration,
66
+ acl: params.acl,
67
+ size: params.size,
68
+ suffix: params.suffix,
69
+ fileName: params.fileName,
70
+ extension: params.extension,
108
71
  }
72
+ fields.push(`${alias}: generateUploadUrl(input: $${inputVarName}) {
73
+ url
74
+ publicUrl
75
+ method
76
+ headers { key value }
77
+ }`)
109
78
  varIndex++
110
79
  }
111
80