@ttt-productions/file-input 0.11.8 → 0.11.9

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.
@@ -20,11 +20,22 @@ export interface ValidateUploadFileOptions {
20
20
  fileSize: number;
21
21
  fileType: string;
22
22
  }) => void;
23
+ /**
24
+ * FAIL-OPEN for indeterminate picker metadata: when the file's kind cannot
25
+ * be determined from browser MIME/extension, pass it through with the
26
+ * NEUTRAL `application/octet-stream` type (never a fabricated media type)
27
+ * instead of throwing, so the server's canonical byte inspector — the only
28
+ * classification authority — decides. The kind-mismatch check still throws
29
+ * when the client metadata IS determinate. Requires the upload path to opt
30
+ * into `allowNeutralContentType`.
31
+ */
32
+ allowIndeterminate?: boolean;
23
33
  }
24
34
  /**
25
35
  * Validates and normalizes a file for upload.
26
36
  *
27
- * - Throws a user-friendly Error if the file's kind cannot be determined.
37
+ * - Throws a user-friendly Error if the file's kind cannot be determined
38
+ * (unless `allowIndeterminate` fails open to the neutral content type).
28
39
  * - Throws a user-friendly Error if `expectedKind` is not 'any' and doesn't
29
40
  * match the detected kind.
30
41
  * - Otherwise returns a File with a guaranteed valid media contentType.
@@ -1 +1 @@
1
- {"version":3,"file":"validate-upload-kind.d.ts","sourceRoot":"","sources":["../../src/lib/validate-upload-kind.ts"],"names":[],"mappings":"AAGA,KAAK,UAAU,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,CAAC;AAC9C,MAAM,MAAM,YAAY,GAAG,UAAU,GAAG,KAAK,GAAG,SAAS,UAAU,EAAE,CAAC;AAEtE;;;GAGG;AACH,MAAM,MAAM,2BAA2B,GACnC,2BAA2B,GAC3B,sBAAsB,CAAC;AAE3B,MAAM,WAAW,yBAAyB;IACxC;;;;;OAKG;IACH,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,2BAA2B,EAAE,KAAK,EAAE;QACnD,UAAU,EAAE,MAAM,CAAC;QACnB,YAAY,EAAE,MAAM,CAAC;QACrB,YAAY,CAAC,EAAE,UAAU,GAAG,OAAO,CAAC;QACpC,QAAQ,EAAE,MAAM,CAAC;QACjB,QAAQ,EAAE,MAAM,CAAC;QACjB,QAAQ,EAAE,MAAM,CAAC;KAClB,KAAK,IAAI,CAAC;CACZ;AAcD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,8BAA8B,CAC5C,IAAI,EAAE,IAAI,EACV,UAAU,EAAE,MAAM,EAClB,YAAY,EAAE,YAAY,EAC1B,OAAO,CAAC,EAAE,yBAAyB,GAClC,IAAI,CAgCN"}
1
+ {"version":3,"file":"validate-upload-kind.d.ts","sourceRoot":"","sources":["../../src/lib/validate-upload-kind.ts"],"names":[],"mappings":"AAGA,KAAK,UAAU,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,CAAC;AAC9C,MAAM,MAAM,YAAY,GAAG,UAAU,GAAG,KAAK,GAAG,SAAS,UAAU,EAAE,CAAC;AAEtE;;;GAGG;AACH,MAAM,MAAM,2BAA2B,GACnC,2BAA2B,GAC3B,sBAAsB,CAAC;AAE3B,MAAM,WAAW,yBAAyB;IACxC;;;;;OAKG;IACH,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,2BAA2B,EAAE,KAAK,EAAE;QACnD,UAAU,EAAE,MAAM,CAAC;QACnB,YAAY,EAAE,MAAM,CAAC;QACrB,YAAY,CAAC,EAAE,UAAU,GAAG,OAAO,CAAC;QACpC,QAAQ,EAAE,MAAM,CAAC;QACjB,QAAQ,EAAE,MAAM,CAAC;QACjB,QAAQ,EAAE,MAAM,CAAC;KAClB,KAAK,IAAI,CAAC;IACX;;;;;;;;OAQG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAC;CAC9B;AAcD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,8BAA8B,CAC5C,IAAI,EAAE,IAAI,EACV,UAAU,EAAE,MAAM,EAClB,YAAY,EAAE,YAAY,EAC1B,OAAO,CAAC,EAAE,yBAAyB,GAClC,IAAI,CAqCN"}
@@ -17,7 +17,8 @@ function matchesExpected(detected, expected) {
17
17
  /**
18
18
  * Validates and normalizes a file for upload.
19
19
  *
20
- * - Throws a user-friendly Error if the file's kind cannot be determined.
20
+ * - Throws a user-friendly Error if the file's kind cannot be determined
21
+ * (unless `allowIndeterminate` fails open to the neutral content type).
21
22
  * - Throws a user-friendly Error if `expectedKind` is not 'any' and doesn't
22
23
  * match the detected kind.
23
24
  * - Otherwise returns a File with a guaranteed valid media contentType.
@@ -31,6 +32,11 @@ function matchesExpected(detected, expected) {
31
32
  export function validateAndNormalizeUploadFile(file, fileOrigin, expectedKind, options) {
32
33
  const detected = getSimplifiedMediaType(file);
33
34
  if (detected === 'other') {
35
+ if (options?.allowIndeterminate === true) {
36
+ // No fallback kind: inferContentType resolves an unknown file to the
37
+ // NEUTRAL type, never a fabricated media fact.
38
+ return ensureFileWithContentType(file);
39
+ }
34
40
  options?.onError?.('upload_kind_indeterminate', {
35
41
  fileOrigin,
36
42
  expectedKind: describeExpected(expectedKind),
@@ -1 +1 @@
1
- {"version":3,"file":"validate-upload-kind.js","sourceRoot":"","sources":["../../src/lib/validate-upload-kind.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,MAAM,gCAAgC,CAAC;AACxE,OAAO,EAAE,yBAAyB,EAAE,MAAM,yBAAyB,CAAC;AA8BpE,SAAS,gBAAgB,CAAC,QAAsB;IAC9C,IAAI,QAAQ,KAAK,KAAK;QAAE,OAAO,wBAAwB,CAAC;IACxD,IAAI,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC;QAAE,OAAO,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAC1D,OAAO,QAAkB,CAAC;AAC5B,CAAC;AAED,SAAS,eAAe,CAAC,QAAoB,EAAE,QAAsB;IACnE,IAAI,QAAQ,KAAK,KAAK;QAAE,OAAO,IAAI,CAAC;IACpC,IAAI,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC;QAAE,OAAQ,QAAkC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;IAC3F,OAAO,QAAQ,KAAK,QAAQ,CAAC;AAC/B,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,8BAA8B,CAC5C,IAAU,EACV,UAAkB,EAClB,YAA0B,EAC1B,OAAmC;IAEnC,MAAM,QAAQ,GAAG,sBAAsB,CAAC,IAAI,CAAC,CAAC;IAE9C,IAAI,QAAQ,KAAK,OAAO,EAAE,CAAC;QACzB,OAAO,EAAE,OAAO,EAAE,CAAC,2BAA2B,EAAE;YAC9C,UAAU;YACV,YAAY,EAAE,gBAAgB,CAAC,YAAY,CAAC;YAC5C,YAAY,EAAE,QAAQ;YACtB,QAAQ,EAAE,IAAI,CAAC,IAAI;YACnB,QAAQ,EAAE,IAAI,CAAC,IAAI;YACnB,QAAQ,EAAE,IAAI,CAAC,IAAI;SACpB,CAAC,CAAC;QACH,MAAM,IAAI,KAAK,CACb,oGAAoG,CACrG,CAAC;IACJ,CAAC;IAED,IAAI,CAAC,eAAe,CAAC,QAAQ,EAAE,YAAY,CAAC,EAAE,CAAC;QAC7C,OAAO,EAAE,OAAO,EAAE,CAAC,sBAAsB,EAAE;YACzC,UAAU;YACV,YAAY,EAAE,gBAAgB,CAAC,YAAY,CAAC;YAC5C,YAAY,EAAE,QAAQ;YACtB,QAAQ,EAAE,IAAI,CAAC,IAAI;YACnB,QAAQ,EAAE,IAAI,CAAC,IAAI;YACnB,QAAQ,EAAE,IAAI,CAAC,IAAI;SACpB,CAAC,CAAC;QACH,MAAM,IAAI,KAAK,CACb,0BAA0B,gBAAgB,CAAC,YAAY,CAAC,gCAAgC,QAAQ,GAAG,CACpG,CAAC;IACJ,CAAC;IAED,OAAO,yBAAyB,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;AACnD,CAAC","sourcesContent":["import { getSimplifiedMediaType } from '@ttt-productions/media-schemas';\nimport { ensureFileWithContentType } from './infer-content-type.js';\n\ntype SingleKind = 'image' | 'video' | 'audio';\nexport type ExpectedKind = SingleKind | 'any' | readonly SingleKind[];\n\n/**\n * Diagnostic codes emitted by validateAndNormalizeUploadFile when validation\n * fails. Consumers can route these to monitoring/Sentry via the onError option.\n */\nexport type ValidateUploadFileErrorCode =\n | 'upload_kind_indeterminate'\n | 'upload_kind_mismatch';\n\nexport interface ValidateUploadFileOptions {\n /**\n * Optional diagnostic callback fired before the function throws.\n * Useful for routing to Sentry / monitoring-core. The thrown Error\n * carries the user-facing message; this callback gets the machine\n * code + structured extras.\n */\n onError?: (code: ValidateUploadFileErrorCode, extra: {\n fileOrigin: string;\n expectedKind: string;\n detectedKind?: SingleKind | 'other';\n fileName: string;\n fileSize: number;\n fileType: string;\n }) => void;\n}\n\nfunction describeExpected(expected: ExpectedKind): string {\n if (expected === 'any') return 'image, video, or audio';\n if (Array.isArray(expected)) return expected.join(' or ');\n return expected as string;\n}\n\nfunction matchesExpected(detected: SingleKind, expected: ExpectedKind): boolean {\n if (expected === 'any') return true;\n if (Array.isArray(expected)) return (expected as readonly SingleKind[]).includes(detected);\n return detected === expected;\n}\n\n/**\n * Validates and normalizes a file for upload.\n *\n * - Throws a user-friendly Error if the file's kind cannot be determined.\n * - Throws a user-friendly Error if `expectedKind` is not 'any' and doesn't\n * match the detected kind.\n * - Otherwise returns a File with a guaranteed valid media contentType.\n *\n * Optional `onError` callback fires with a machine-readable code + structured\n * extras before the throw, so consumers can route to monitoring without this\n * package depending on any monitoring library.\n *\n * Use this at the top of every upload mutation, before uploadFileResumable.\n */\nexport function validateAndNormalizeUploadFile(\n file: File,\n fileOrigin: string,\n expectedKind: ExpectedKind,\n options?: ValidateUploadFileOptions,\n): File {\n const detected = getSimplifiedMediaType(file);\n\n if (detected === 'other') {\n options?.onError?.('upload_kind_indeterminate', {\n fileOrigin,\n expectedKind: describeExpected(expectedKind),\n detectedKind: detected,\n fileName: file.name,\n fileSize: file.size,\n fileType: file.type,\n });\n throw new Error(\n 'Could not determine the file type. Please try a different file or re-export it in a common format.'\n );\n }\n\n if (!matchesExpected(detected, expectedKind)) {\n options?.onError?.('upload_kind_mismatch', {\n fileOrigin,\n expectedKind: describeExpected(expectedKind),\n detectedKind: detected,\n fileName: file.name,\n fileSize: file.size,\n fileType: file.type,\n });\n throw new Error(\n `This slot only accepts ${describeExpected(expectedKind)} files. The selected file is ${detected}.`\n );\n }\n\n return ensureFileWithContentType(file, detected);\n}\n"]}
1
+ {"version":3,"file":"validate-upload-kind.js","sourceRoot":"","sources":["../../src/lib/validate-upload-kind.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,MAAM,gCAAgC,CAAC;AACxE,OAAO,EAAE,yBAAyB,EAAE,MAAM,yBAAyB,CAAC;AAwCpE,SAAS,gBAAgB,CAAC,QAAsB;IAC9C,IAAI,QAAQ,KAAK,KAAK;QAAE,OAAO,wBAAwB,CAAC;IACxD,IAAI,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC;QAAE,OAAO,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAC1D,OAAO,QAAkB,CAAC;AAC5B,CAAC;AAED,SAAS,eAAe,CAAC,QAAoB,EAAE,QAAsB;IACnE,IAAI,QAAQ,KAAK,KAAK;QAAE,OAAO,IAAI,CAAC;IACpC,IAAI,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC;QAAE,OAAQ,QAAkC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;IAC3F,OAAO,QAAQ,KAAK,QAAQ,CAAC;AAC/B,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,8BAA8B,CAC5C,IAAU,EACV,UAAkB,EAClB,YAA0B,EAC1B,OAAmC;IAEnC,MAAM,QAAQ,GAAG,sBAAsB,CAAC,IAAI,CAAC,CAAC;IAE9C,IAAI,QAAQ,KAAK,OAAO,EAAE,CAAC;QACzB,IAAI,OAAO,EAAE,kBAAkB,KAAK,IAAI,EAAE,CAAC;YACzC,qEAAqE;YACrE,+CAA+C;YAC/C,OAAO,yBAAyB,CAAC,IAAI,CAAC,CAAC;QACzC,CAAC;QACD,OAAO,EAAE,OAAO,EAAE,CAAC,2BAA2B,EAAE;YAC9C,UAAU;YACV,YAAY,EAAE,gBAAgB,CAAC,YAAY,CAAC;YAC5C,YAAY,EAAE,QAAQ;YACtB,QAAQ,EAAE,IAAI,CAAC,IAAI;YACnB,QAAQ,EAAE,IAAI,CAAC,IAAI;YACnB,QAAQ,EAAE,IAAI,CAAC,IAAI;SACpB,CAAC,CAAC;QACH,MAAM,IAAI,KAAK,CACb,oGAAoG,CACrG,CAAC;IACJ,CAAC;IAED,IAAI,CAAC,eAAe,CAAC,QAAQ,EAAE,YAAY,CAAC,EAAE,CAAC;QAC7C,OAAO,EAAE,OAAO,EAAE,CAAC,sBAAsB,EAAE;YACzC,UAAU;YACV,YAAY,EAAE,gBAAgB,CAAC,YAAY,CAAC;YAC5C,YAAY,EAAE,QAAQ;YACtB,QAAQ,EAAE,IAAI,CAAC,IAAI;YACnB,QAAQ,EAAE,IAAI,CAAC,IAAI;YACnB,QAAQ,EAAE,IAAI,CAAC,IAAI;SACpB,CAAC,CAAC;QACH,MAAM,IAAI,KAAK,CACb,0BAA0B,gBAAgB,CAAC,YAAY,CAAC,gCAAgC,QAAQ,GAAG,CACpG,CAAC;IACJ,CAAC;IAED,OAAO,yBAAyB,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;AACnD,CAAC","sourcesContent":["import { getSimplifiedMediaType } from '@ttt-productions/media-schemas';\nimport { ensureFileWithContentType } from './infer-content-type.js';\n\ntype SingleKind = 'image' | 'video' | 'audio';\nexport type ExpectedKind = SingleKind | 'any' | readonly SingleKind[];\n\n/**\n * Diagnostic codes emitted by validateAndNormalizeUploadFile when validation\n * fails. Consumers can route these to monitoring/Sentry via the onError option.\n */\nexport type ValidateUploadFileErrorCode =\n | 'upload_kind_indeterminate'\n | 'upload_kind_mismatch';\n\nexport interface ValidateUploadFileOptions {\n /**\n * Optional diagnostic callback fired before the function throws.\n * Useful for routing to Sentry / monitoring-core. The thrown Error\n * carries the user-facing message; this callback gets the machine\n * code + structured extras.\n */\n onError?: (code: ValidateUploadFileErrorCode, extra: {\n fileOrigin: string;\n expectedKind: string;\n detectedKind?: SingleKind | 'other';\n fileName: string;\n fileSize: number;\n fileType: string;\n }) => void;\n /**\n * FAIL-OPEN for indeterminate picker metadata: when the file's kind cannot\n * be determined from browser MIME/extension, pass it through with the\n * NEUTRAL `application/octet-stream` type (never a fabricated media type)\n * instead of throwing, so the server's canonical byte inspector — the only\n * classification authority — decides. The kind-mismatch check still throws\n * when the client metadata IS determinate. Requires the upload path to opt\n * into `allowNeutralContentType`.\n */\n allowIndeterminate?: boolean;\n}\n\nfunction describeExpected(expected: ExpectedKind): string {\n if (expected === 'any') return 'image, video, or audio';\n if (Array.isArray(expected)) return expected.join(' or ');\n return expected as string;\n}\n\nfunction matchesExpected(detected: SingleKind, expected: ExpectedKind): boolean {\n if (expected === 'any') return true;\n if (Array.isArray(expected)) return (expected as readonly SingleKind[]).includes(detected);\n return detected === expected;\n}\n\n/**\n * Validates and normalizes a file for upload.\n *\n * - Throws a user-friendly Error if the file's kind cannot be determined\n * (unless `allowIndeterminate` fails open to the neutral content type).\n * - Throws a user-friendly Error if `expectedKind` is not 'any' and doesn't\n * match the detected kind.\n * - Otherwise returns a File with a guaranteed valid media contentType.\n *\n * Optional `onError` callback fires with a machine-readable code + structured\n * extras before the throw, so consumers can route to monitoring without this\n * package depending on any monitoring library.\n *\n * Use this at the top of every upload mutation, before uploadFileResumable.\n */\nexport function validateAndNormalizeUploadFile(\n file: File,\n fileOrigin: string,\n expectedKind: ExpectedKind,\n options?: ValidateUploadFileOptions,\n): File {\n const detected = getSimplifiedMediaType(file);\n\n if (detected === 'other') {\n if (options?.allowIndeterminate === true) {\n // No fallback kind: inferContentType resolves an unknown file to the\n // NEUTRAL type, never a fabricated media fact.\n return ensureFileWithContentType(file);\n }\n options?.onError?.('upload_kind_indeterminate', {\n fileOrigin,\n expectedKind: describeExpected(expectedKind),\n detectedKind: detected,\n fileName: file.name,\n fileSize: file.size,\n fileType: file.type,\n });\n throw new Error(\n 'Could not determine the file type. Please try a different file or re-export it in a common format.'\n );\n }\n\n if (!matchesExpected(detected, expectedKind)) {\n options?.onError?.('upload_kind_mismatch', {\n fileOrigin,\n expectedKind: describeExpected(expectedKind),\n detectedKind: detected,\n fileName: file.name,\n fileSize: file.size,\n fileType: file.type,\n });\n throw new Error(\n `This slot only accepts ${describeExpected(expectedKind)} files. The selected file is ${detected}.`\n );\n }\n\n return ensureFileWithContentType(file, detected);\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ttt-productions/file-input",
3
- "version": "0.11.8",
3
+ "version": "0.11.9",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/ttt-productions/ttt-packages.git",