@useupup/core 3.3.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 (118) hide show
  1. package/LICENSE +21 -0
  2. package/NOTICE +8 -0
  3. package/README.md +75 -0
  4. package/dist/browser-SXR426OQ.cjs +10 -0
  5. package/dist/browser-WD6T5ABJ.js +1 -0
  6. package/dist/chunk-2H5ICKFT.cjs +122 -0
  7. package/dist/chunk-6HF3I2JS.cjs +39 -0
  8. package/dist/chunk-6S5DQDQH.js +212 -0
  9. package/dist/chunk-BMQC27OE.cjs +92 -0
  10. package/dist/chunk-BSGQGINR.cjs +219 -0
  11. package/dist/chunk-CPKZ2CXD.cjs +179 -0
  12. package/dist/chunk-DTKDIMKT.cjs +573 -0
  13. package/dist/chunk-DYE5XJ6Z.cjs +2134 -0
  14. package/dist/chunk-E3ZCNOXG.cjs +74 -0
  15. package/dist/chunk-FEQY2WYY.cjs +2377 -0
  16. package/dist/chunk-FMURBCZ7.js +120 -0
  17. package/dist/chunk-FYQQRULL.js +37 -0
  18. package/dist/chunk-GE4LIVXP.js +133 -0
  19. package/dist/chunk-HFR5HZJZ.js +56 -0
  20. package/dist/chunk-I7ULPCA2.js +72 -0
  21. package/dist/chunk-MHYXDLZR.js +569 -0
  22. package/dist/chunk-N2SLHVAW.cjs +61 -0
  23. package/dist/chunk-OOPDEOTM.js +169 -0
  24. package/dist/chunk-PMBR3OUW.js +2359 -0
  25. package/dist/chunk-QK7HZIVY.cjs +144 -0
  26. package/dist/chunk-T2AQHY7V.cjs +59 -0
  27. package/dist/chunk-TRCP5FUR.js +90 -0
  28. package/dist/chunk-U7KL5M2H.cjs +374 -0
  29. package/dist/chunk-UXNPM54W.js +2108 -0
  30. package/dist/chunk-VV6QCQLL.js +365 -0
  31. package/dist/chunk-ZMS2HXLB.js +56 -0
  32. package/dist/context-shapes-123dd3b6.d.cts +207 -0
  33. package/dist/context-shapes-27b24c8f.d.ts +207 -0
  34. package/dist/contracts-pipeline-cf177270.d.cts +78 -0
  35. package/dist/contracts-pipeline-cf177270.d.ts +78 -0
  36. package/dist/contracts-strategies-378a16b7.d.cts +168 -0
  37. package/dist/contracts-strategies-378a16b7.d.ts +168 -0
  38. package/dist/contracts.cjs +181 -0
  39. package/dist/contracts.d.cts +11 -0
  40. package/dist/contracts.d.ts +11 -0
  41. package/dist/contracts.js +4 -0
  42. package/dist/core-141335ac.d.ts +651 -0
  43. package/dist/core-bea2ceca.d.cts +651 -0
  44. package/dist/create-worker-provider-AA5SRII6.js +58 -0
  45. package/dist/create-worker-provider-KJNUSSTW.cjs +60 -0
  46. package/dist/eligibility-ONLNJ6FB.cjs +10 -0
  47. package/dist/eligibility-ULFC3E4G.js +8 -0
  48. package/dist/error-code-map-9c44a425.d.ts +62 -0
  49. package/dist/error-code-map-dd5becfa.d.cts +62 -0
  50. package/dist/i18n/index.cjs +90 -0
  51. package/dist/i18n/index.d.cts +3 -0
  52. package/dist/i18n/index.d.ts +3 -0
  53. package/dist/i18n/index.js +1 -0
  54. package/dist/index.cjs +1770 -0
  55. package/dist/index.d.cts +280 -0
  56. package/dist/index.d.ts +280 -0
  57. package/dist/index.js +1589 -0
  58. package/dist/internal.cjs +2407 -0
  59. package/dist/internal.d.cts +595 -0
  60. package/dist/internal.d.ts +595 -0
  61. package/dist/internal.js +2275 -0
  62. package/dist/pipeline/index.cjs +10 -0
  63. package/dist/pipeline/index.d.cts +11 -0
  64. package/dist/pipeline/index.d.ts +11 -0
  65. package/dist/pipeline/index.js +1 -0
  66. package/dist/pipeline-worker.cjs +199 -0
  67. package/dist/pipeline-worker.d.cts +2 -0
  68. package/dist/pipeline-worker.d.ts +2 -0
  69. package/dist/pipeline-worker.js +197 -0
  70. package/dist/resolve-locale-81feef21.d.ts +20 -0
  71. package/dist/resolve-locale-9cb63600.d.cts +20 -0
  72. package/dist/resolve-theme-01758105.d.ts +53 -0
  73. package/dist/resolve-theme-66cf8d98.d.cts +53 -0
  74. package/dist/server-mode-drive-controller-b5f8ee12.d.cts +180 -0
  75. package/dist/server-mode-drive-controller-b5f8ee12.d.ts +180 -0
  76. package/dist/steps/compress.cjs +66 -0
  77. package/dist/steps/compress.d.cts +10 -0
  78. package/dist/steps/compress.d.ts +10 -0
  79. package/dist/steps/compress.js +64 -0
  80. package/dist/steps/exif.cjs +44 -0
  81. package/dist/steps/exif.d.cts +5 -0
  82. package/dist/steps/exif.d.ts +5 -0
  83. package/dist/steps/exif.js +42 -0
  84. package/dist/steps/hash.cjs +44 -0
  85. package/dist/steps/hash.d.cts +5 -0
  86. package/dist/steps/hash.d.ts +5 -0
  87. package/dist/steps/hash.js +42 -0
  88. package/dist/steps/heic.cjs +59 -0
  89. package/dist/steps/heic.d.cts +5 -0
  90. package/dist/steps/heic.d.ts +5 -0
  91. package/dist/steps/heic.js +57 -0
  92. package/dist/steps/thumbnail.cjs +54 -0
  93. package/dist/steps/thumbnail.d.cts +10 -0
  94. package/dist/steps/thumbnail.d.ts +10 -0
  95. package/dist/steps/thumbnail.js +52 -0
  96. package/dist/strategies/index.cjs +82 -0
  97. package/dist/strategies/index.d.cts +187 -0
  98. package/dist/strategies/index.d.ts +187 -0
  99. package/dist/strategies/index.js +60 -0
  100. package/dist/strategies/tus-upload.cjs +14 -0
  101. package/dist/strategies/tus-upload.d.cts +15 -0
  102. package/dist/strategies/tus-upload.d.ts +15 -0
  103. package/dist/strategies/tus-upload.js +5 -0
  104. package/dist/theme/index.cjs +38 -0
  105. package/dist/theme/index.d.cts +3 -0
  106. package/dist/theme/index.d.ts +3 -0
  107. package/dist/theme/index.js +1 -0
  108. package/dist/theme-store-2e56a674.d.cts +39 -0
  109. package/dist/theme-store-f13e0315.d.ts +39 -0
  110. package/dist/token-endpoint-5fdb4a35.d.ts +20 -0
  111. package/dist/token-endpoint-a7d15ab6.d.cts +20 -0
  112. package/dist/ui-translations-c5d00392.d.cts +455 -0
  113. package/dist/ui-translations-c5d00392.d.ts +455 -0
  114. package/dist/uploader-props-72242002.d.ts +340 -0
  115. package/dist/uploader-props-7f956e60.d.cts +340 -0
  116. package/dist/vars-d3421f6c.d.cts +240 -0
  117. package/dist/vars-d3421f6c.d.ts +240 -0
  118. package/package.json +182 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023 Devino
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/NOTICE ADDED
@@ -0,0 +1,8 @@
1
+ @useupup/core bundles libheif (via libheif-js) for HEIC/HEIF image decoding.
2
+
3
+ libheif is licensed under the GNU Lesser General Public License v3.0 (LGPL-3.0).
4
+ Source: https://github.com/strukturag/libheif
5
+ libheif-js: https://github.com/catdad-experiments/libheif-js
6
+
7
+ The libheif WebAssembly artifact is a separable component and may be replaced
8
+ with a compatible build under the terms of the LGPL-3.0.
package/README.md ADDED
@@ -0,0 +1,75 @@
1
+ # @useupup/core
2
+
3
+ The headless engine behind [upup](https://github.com/DevinoSolutions/upup): file
4
+ state and upload orchestration, the upload pipeline (image compression, HEIC
5
+ decode, web-worker offload), cloud-drive plugins (Google Drive, OneDrive,
6
+ Dropbox, Box), theming, and ICU i18n. It has **zero framework dependencies** and
7
+ powers every `@useupup/*` UI package.
8
+
9
+ ## Install
10
+
11
+ Most consumers should install a framework package instead —
12
+ [`@useupup/react`](https://www.npmjs.com/package/@useupup/react), `@useupup/vue`,
13
+ `@useupup/svelte`, `@useupup/angular`, `@useupup/vanilla`, or `@useupup/preact` — which bundle
14
+ this engine and render a ready-made UI. Install `@useupup/core` directly only to
15
+ drive uploads headlessly or to build your own UI binding.
16
+
17
+ ```sh
18
+ npm i @useupup/core
19
+ ```
20
+
21
+ ## Headless usage
22
+
23
+ ```ts
24
+ import { UpupCore } from '@useupup/core'
25
+
26
+ const core = new UpupCore({
27
+ provider: 'aws',
28
+ uploadEndpoint: '/api/upload-token',
29
+ })
30
+ ```
31
+
32
+ `UpupCore` owns the file/progress state, the upload lifecycle, and the
33
+ cloud-drive plugins. See the docs for the full event and method surface.
34
+
35
+ ## Errors
36
+
37
+ Upload failures are typed. Catch `UpupError` and branch on its stable
38
+ `UpupErrorCode`; the concrete subclasses (`UpupValidationError`,
39
+ `UpupNetworkError`, `UpupStorageError`, `UpupAuthError`, `UpupQuotaError`,
40
+ `UpupConfigError`) let you narrow further.
41
+
42
+ ```ts
43
+ import { UpupError, UpupErrorCode } from '@useupup/core'
44
+
45
+ function isRetryable(err: unknown): boolean {
46
+ return err instanceof UpupError && err.code === UpupErrorCode.NETWORK_ERROR
47
+ }
48
+ ```
49
+
50
+ `StorageProvider` enumerates the supported storage providers
51
+ (`StorageProvider.AWS`, `StorageProvider.CloudflareR2`, `StorageProvider.MinIO`,
52
+ and more).
53
+
54
+ ## Entry points
55
+
56
+ The main `@useupup/core` entry is a curated allow-list of stable exports (the
57
+ `UpupCore` engine, the drive plugins, the `UpupError` taxonomy — `UpupError`
58
+ plus its typed subclasses and the `UpupErrorCode` enum — the locale bundles, and
59
+ the theme presets). Advanced, deep-import-only subpaths are also published:
60
+
61
+ - `@useupup/core/internal` — implementation internals (managers, orchestrator, controllers)
62
+ - `@useupup/core/contracts`, `@useupup/core/i18n`, `@useupup/core/theme`, `@useupup/core/strategies`
63
+ - `@useupup/core/pipeline` and `@useupup/core/steps/*` (`hash`, `heic`, `exif`, `compress`, `thumbnail`)
64
+
65
+ Heavy capabilities (HEIC decode, resumable/tus uploads) load lazily via these
66
+ subpaths, so they never weigh down the main entry.
67
+
68
+ ## Links
69
+
70
+ - [Documentation](https://useupup.com/docs/)
71
+ - [Source & monorepo](https://github.com/DevinoSolutions/upup)
72
+
73
+ ## License
74
+
75
+ MIT
@@ -0,0 +1,10 @@
1
+ 'use strict';
2
+
3
+ var chunkT2AQHY7V_cjs = require('./chunk-T2AQHY7V.cjs');
4
+
5
+
6
+
7
+ Object.defineProperty(exports, 'BrowserRuntime', {
8
+ enumerable: true,
9
+ get: function () { return chunkT2AQHY7V_cjs.BrowserRuntime; }
10
+ });
@@ -0,0 +1 @@
1
+ export { BrowserRuntime } from './chunk-HFR5HZJZ.js';
@@ -0,0 +1,122 @@
1
+ 'use strict';
2
+
3
+ // src/steps/animated-image.ts
4
+ function ascii(bytes, start, length) {
5
+ let out = "";
6
+ for (let i = start; i < start + length; i += 1) {
7
+ const byte = bytes[i];
8
+ if (byte === void 0) return out;
9
+ out += String.fromCharCode(byte);
10
+ }
11
+ return out;
12
+ }
13
+ function byteAt(bytes, index) {
14
+ return bytes[index] ?? 0;
15
+ }
16
+ function skipSubBlocks(bytes, start) {
17
+ let offset = start;
18
+ while (offset < bytes.length) {
19
+ const size = byteAt(bytes, offset);
20
+ offset += 1;
21
+ if (size === 0) return offset;
22
+ offset += size;
23
+ }
24
+ return bytes.length;
25
+ }
26
+ function isAnimatedGif(bytes) {
27
+ if (bytes.length < 13) return false;
28
+ if (ascii(bytes, 0, 3) !== "GIF") return false;
29
+ const screenPacked = byteAt(bytes, 10);
30
+ let offset = 13;
31
+ if ((screenPacked & 128) !== 0) {
32
+ offset += 3 * (1 << (screenPacked & 7) + 1);
33
+ }
34
+ let frames = 0;
35
+ while (offset < bytes.length) {
36
+ const block = byteAt(bytes, offset);
37
+ if (block === 59) return false;
38
+ if (block === 33) {
39
+ const label = byteAt(bytes, offset + 1);
40
+ offset += 2;
41
+ if (label === 255) {
42
+ const identifier = ascii(
43
+ bytes,
44
+ offset + 1,
45
+ byteAt(bytes, offset)
46
+ );
47
+ if (identifier === "NETSCAPE2.0" || identifier === "ANIMEXTS1.0") {
48
+ return true;
49
+ }
50
+ }
51
+ offset = skipSubBlocks(bytes, offset);
52
+ continue;
53
+ }
54
+ if (block === 44) {
55
+ frames += 1;
56
+ if (frames > 1) return true;
57
+ const imagePacked = byteAt(bytes, offset + 9);
58
+ offset += 10;
59
+ if ((imagePacked & 128) !== 0) {
60
+ offset += 3 * (1 << (imagePacked & 7) + 1);
61
+ }
62
+ offset += 1;
63
+ offset = skipSubBlocks(bytes, offset);
64
+ continue;
65
+ }
66
+ return false;
67
+ }
68
+ return false;
69
+ }
70
+ var PNG_SIGNATURE = [137, 80, 78, 71, 13, 10, 26, 10];
71
+ function isAnimatedPng(bytes) {
72
+ if (bytes.length < PNG_SIGNATURE.length) return false;
73
+ for (const [index, expected] of PNG_SIGNATURE.entries()) {
74
+ if (byteAt(bytes, index) !== expected) return false;
75
+ }
76
+ let offset = PNG_SIGNATURE.length;
77
+ while (offset + 8 <= bytes.length) {
78
+ const length = byteAt(bytes, offset) * 16777216 + byteAt(bytes, offset + 1) * 65536 + byteAt(bytes, offset + 2) * 256 + byteAt(bytes, offset + 3);
79
+ const type = ascii(bytes, offset + 4, 4);
80
+ if (type === "acTL") return true;
81
+ if (type === "IDAT") return false;
82
+ offset += 12 + length;
83
+ }
84
+ return false;
85
+ }
86
+ function isAnimatedWebp(bytes) {
87
+ if (bytes.length < 16) return false;
88
+ if (ascii(bytes, 0, 4) !== "RIFF" || ascii(bytes, 8, 4) !== "WEBP") {
89
+ return false;
90
+ }
91
+ let offset = 12;
92
+ while (offset + 8 <= bytes.length) {
93
+ const fourCC = ascii(bytes, offset, 4);
94
+ const size = byteAt(bytes, offset + 4) + byteAt(bytes, offset + 5) * 256 + byteAt(bytes, offset + 6) * 65536 + byteAt(bytes, offset + 7) * 16777216;
95
+ if (fourCC === "ANIM" || fourCC === "ANMF") return true;
96
+ if (fourCC === "VP8X" && (byteAt(bytes, offset + 8) & 2) !== 0) {
97
+ return true;
98
+ }
99
+ offset += 8 + size + size % 2;
100
+ }
101
+ return false;
102
+ }
103
+ var SNIFFERS = {
104
+ "image/gif": isAnimatedGif,
105
+ "image/png": isAnimatedPng,
106
+ "image/apng": isAnimatedPng,
107
+ "image/webp": isAnimatedWebp
108
+ };
109
+ function baseMimeType(type) {
110
+ return type.split(";")[0]?.trim().toLowerCase() ?? "";
111
+ }
112
+ async function isAnimatedImage(file) {
113
+ const sniff = SNIFFERS[baseMimeType(file.type)];
114
+ if (!sniff) return false;
115
+ try {
116
+ return sniff(new Uint8Array(await file.arrayBuffer()));
117
+ } catch {
118
+ return false;
119
+ }
120
+ }
121
+
122
+ exports.isAnimatedImage = isAnimatedImage;
@@ -0,0 +1,39 @@
1
+ 'use strict';
2
+
3
+ // src/pipeline/engine.ts
4
+ var PipelineEngine = class {
5
+ constructor(steps) {
6
+ this.steps = steps;
7
+ }
8
+ get stepCount() {
9
+ return this.steps.length;
10
+ }
11
+ async process(file, context) {
12
+ context.emit("pipeline-start", {
13
+ fileId: file.id,
14
+ steps: this.steps.map((s) => s.name)
15
+ });
16
+ let current = file;
17
+ for (const step of this.steps) {
18
+ if (step.shouldProcess && !step.shouldProcess(current)) {
19
+ continue;
20
+ }
21
+ current = await step.process(current, context);
22
+ context.emit("pipeline-step", {
23
+ fileId: file.id,
24
+ step: step.name
25
+ });
26
+ }
27
+ context.emit("pipeline-complete", { fileId: file.id });
28
+ return current;
29
+ }
30
+ async processAll(files, context) {
31
+ const results = [];
32
+ for (const file of files) {
33
+ results.push(await this.process(file, context));
34
+ }
35
+ return results;
36
+ }
37
+ };
38
+
39
+ exports.PipelineEngine = PipelineEngine;
@@ -0,0 +1,212 @@
1
+ import { UpupError } from './chunk-GE4LIVXP.js';
2
+
3
+ // src/steps/image-utils.ts
4
+ function hasDocument() {
5
+ return typeof document !== "undefined" && "createElement" in document;
6
+ }
7
+ function clampQuality(value, fallback) {
8
+ if (typeof value !== "number" || Number.isNaN(value)) return fallback;
9
+ return Math.min(1, Math.max(0.05, value));
10
+ }
11
+ function scaleDimensions(width, height, maxWidthOrHeight) {
12
+ if (!maxWidthOrHeight || maxWidthOrHeight <= 0) return { width, height };
13
+ const largest = Math.max(width, height);
14
+ if (largest <= maxWidthOrHeight) return { width, height };
15
+ const scale = maxWidthOrHeight / largest;
16
+ return {
17
+ width: Math.max(1, Math.round(width * scale)),
18
+ height: Math.max(1, Math.round(height * scale))
19
+ };
20
+ }
21
+ function outputTypeFor(file, requestedType) {
22
+ if (requestedType) return requestedType;
23
+ if (file.type === "image/png" || file.type === "image/webp")
24
+ return file.type;
25
+ return "image/jpeg";
26
+ }
27
+ function createCanvas(width, height) {
28
+ if (typeof OffscreenCanvas !== "undefined") {
29
+ return new OffscreenCanvas(width, height);
30
+ }
31
+ if (!hasDocument()) return null;
32
+ const canvas = document.createElement("canvas");
33
+ canvas.width = width;
34
+ canvas.height = height;
35
+ return canvas;
36
+ }
37
+ async function canvasToBlob(canvas, type, quality) {
38
+ if ("convertToBlob" in canvas && typeof canvas.convertToBlob === "function") {
39
+ return canvas.convertToBlob({ type, quality });
40
+ }
41
+ return new Promise((resolve) => {
42
+ canvas.toBlob(
43
+ (blob) => {
44
+ resolve(blob);
45
+ },
46
+ type,
47
+ quality
48
+ );
49
+ });
50
+ }
51
+ async function decodeImage(file) {
52
+ if (typeof createImageBitmap === "function") {
53
+ try {
54
+ const bitmap = await createImageBitmap(file);
55
+ return {
56
+ source: bitmap,
57
+ width: bitmap.width,
58
+ height: bitmap.height,
59
+ close: () => {
60
+ bitmap.close();
61
+ }
62
+ };
63
+ } catch {
64
+ }
65
+ }
66
+ if (!hasDocument()) return null;
67
+ return new Promise((resolve) => {
68
+ const img = new Image();
69
+ const url = URL.createObjectURL(file);
70
+ const cleanup = () => {
71
+ URL.revokeObjectURL(url);
72
+ };
73
+ img.onload = () => {
74
+ cleanup();
75
+ resolve({
76
+ source: img,
77
+ width: img.naturalWidth || img.width,
78
+ height: img.naturalHeight || img.height
79
+ });
80
+ };
81
+ img.onerror = () => {
82
+ cleanup();
83
+ resolve(null);
84
+ };
85
+ img.src = url;
86
+ });
87
+ }
88
+ async function encodeImageFile(file, options = {}) {
89
+ const decoded = await decodeImage(file).catch(() => null);
90
+ if (!decoded) return null;
91
+ try {
92
+ const dimensions = scaleDimensions(
93
+ decoded.width,
94
+ decoded.height,
95
+ options.maxWidthOrHeight
96
+ );
97
+ const canvas = createCanvas(dimensions.width, dimensions.height);
98
+ const ctx = canvas?.getContext("2d");
99
+ if (!canvas || !ctx) return null;
100
+ ctx.drawImage(decoded.source, 0, 0, dimensions.width, dimensions.height);
101
+ const type = outputTypeFor(file, options.type);
102
+ const quality = clampQuality(options.quality, 0.82);
103
+ const blob = await canvasToBlob(canvas, type, quality);
104
+ if (!blob) return null;
105
+ const nextFile = new File([blob], file.name, {
106
+ type: blob.type || type || file.type,
107
+ lastModified: file.lastModified
108
+ });
109
+ return cloneUploadFile(file, nextFile, {
110
+ width: dimensions.width,
111
+ height: dimensions.height,
112
+ ...options.metadata
113
+ });
114
+ } finally {
115
+ decoded.close?.();
116
+ }
117
+ }
118
+ async function blobToDataUrl(blob) {
119
+ if (typeof FileReader !== "undefined") {
120
+ return new Promise((resolve, reject) => {
121
+ const reader = new FileReader();
122
+ reader.onload = () => {
123
+ const result = reader.result;
124
+ resolve(typeof result === "string" ? result : "");
125
+ };
126
+ reader.onerror = () => {
127
+ reject(reader.error ?? new Error("Failed to read blob"));
128
+ };
129
+ reader.readAsDataURL(blob);
130
+ });
131
+ }
132
+ const bytes = new Uint8Array(await blob.arrayBuffer());
133
+ let binary = "";
134
+ for (const byte of bytes) binary += String.fromCharCode(byte);
135
+ const encode = typeof btoa === "function" ? btoa : globalThis.Buffer ? (input) => globalThis.Buffer.from(input, "binary").toString("base64") : null;
136
+ if (!encode)
137
+ throw new UpupError(
138
+ "No base64 encoder available",
139
+ "BAD_REQUEST" /* BAD_REQUEST */
140
+ );
141
+ return `data:${blob.type || "application/octet-stream"};base64,${encode(binary)}`;
142
+ }
143
+ async function createThumbnail(file, options = {}) {
144
+ const decoded = await decodeImage(file).catch(() => null);
145
+ if (!decoded) return null;
146
+ try {
147
+ const maxWidth = options.width ?? 320;
148
+ const maxHeight = options.height ?? maxWidth;
149
+ const scale = Math.min(
150
+ 1,
151
+ maxWidth / decoded.width,
152
+ maxHeight / decoded.height
153
+ );
154
+ const width = Math.max(1, Math.round(decoded.width * scale));
155
+ const height = Math.max(1, Math.round(decoded.height * scale));
156
+ const canvas = createCanvas(width, height);
157
+ const ctx = canvas?.getContext("2d");
158
+ if (!canvas || !ctx) return null;
159
+ ctx.drawImage(decoded.source, 0, 0, width, height);
160
+ const type = options.type ?? "image/jpeg";
161
+ const quality = clampQuality(options.quality, 0.75);
162
+ const blob = await canvasToBlob(canvas, type, quality);
163
+ if (!blob) return null;
164
+ const ext = type === "image/png" ? "png" : type === "image/webp" ? "webp" : "jpg";
165
+ const thumbnailFile = new File(
166
+ [blob],
167
+ `${file.name}.thumbnail.${ext}`,
168
+ {
169
+ type: blob.type || type,
170
+ lastModified: file.lastModified
171
+ }
172
+ );
173
+ return {
174
+ file: thumbnailFile,
175
+ dataUrl: await blobToDataUrl(blob),
176
+ width,
177
+ height
178
+ };
179
+ } finally {
180
+ decoded.close?.();
181
+ }
182
+ }
183
+ function cloneUploadFile(original, replacement, metadata = {}) {
184
+ return Object.assign(replacement, {
185
+ id: original.id,
186
+ source: original.source,
187
+ status: original.status,
188
+ metadata: {
189
+ ...original.metadata,
190
+ ...metadata
191
+ },
192
+ url: original.url,
193
+ relativePath: original.relativePath,
194
+ key: original.key,
195
+ etag: original.etag,
196
+ // eslint-disable-next-line @typescript-eslint/no-deprecated -- v3 removal tracked
197
+ fileHash: original.fileHash,
198
+ // eslint-disable-next-line @typescript-eslint/no-deprecated -- v3 removal tracked
199
+ checksumSHA256: original.checksumSHA256,
200
+ // eslint-disable-next-line @typescript-eslint/no-deprecated -- v3 removal tracked
201
+ thumbnail: original.thumbnail
202
+ });
203
+ }
204
+ function uploadFileFromImageResult(original, result) {
205
+ const file = new File([result.bytes], result.name ?? original.name, {
206
+ type: result.type || original.type,
207
+ lastModified: original.lastModified
208
+ });
209
+ return cloneUploadFile(original, file, result.metadata ?? {});
210
+ }
211
+
212
+ export { canvasToBlob, cloneUploadFile, createCanvas, createThumbnail, encodeImageFile, uploadFileFromImageResult };
@@ -0,0 +1,92 @@
1
+ 'use strict';
2
+
3
+ var chunkQK7HZIVY_cjs = require('./chunk-QK7HZIVY.cjs');
4
+
5
+ // src/strategies/tus-upload.ts
6
+ var TusUpload = class {
7
+ constructor(options) {
8
+ this.options = options;
9
+ }
10
+ async upload(file, _credentials, options) {
11
+ let tus;
12
+ try {
13
+ tus = await import('tus-js-client');
14
+ } catch {
15
+ throw new chunkQK7HZIVY_cjs.UpupError(
16
+ 'Resumable (tus) uploads require the optional dependency "tus-js-client". Install it: npm i tus-js-client',
17
+ "UPLOAD_FAILED" /* UPLOAD_FAILED */
18
+ );
19
+ }
20
+ const fileName = file instanceof File ? file.name : "blob";
21
+ const fileType = file.type || "application/octet-stream";
22
+ return new Promise((resolve, reject) => {
23
+ let settled = false;
24
+ let upload = null;
25
+ const finishReject = (error) => {
26
+ if (settled) return;
27
+ settled = true;
28
+ reject(error);
29
+ };
30
+ const abortUpload = () => {
31
+ upload?.abort().catch((error) => {
32
+ finishReject(
33
+ error instanceof Error ? error : new chunkQK7HZIVY_cjs.UpupNetworkError("Tus upload abort failed")
34
+ );
35
+ });
36
+ finishReject(new chunkQK7HZIVY_cjs.UpupNetworkError("Upload aborted"));
37
+ };
38
+ if (options.signal.aborted) {
39
+ finishReject(new chunkQK7HZIVY_cjs.UpupNetworkError("Upload aborted"));
40
+ return;
41
+ }
42
+ options.signal.addEventListener("abort", abortUpload, {
43
+ once: true
44
+ });
45
+ const uploadOptions = {
46
+ endpoint: this.options.endpoint,
47
+ metadata: {
48
+ filename: fileName,
49
+ filetype: fileType,
50
+ ...this.options.metadata
51
+ },
52
+ onProgress: (bytesUploaded, bytesTotal) => {
53
+ options.onProgress(bytesUploaded, bytesTotal);
54
+ },
55
+ onError: (error) => {
56
+ options.signal.removeEventListener("abort", abortUpload);
57
+ finishReject(
58
+ error instanceof Error ? error : new chunkQK7HZIVY_cjs.UpupNetworkError("Tus upload failed")
59
+ );
60
+ },
61
+ onSuccess: () => {
62
+ if (settled) return;
63
+ settled = true;
64
+ options.signal.removeEventListener("abort", abortUpload);
65
+ resolve({
66
+ key: upload?.url ?? fileName,
67
+ publicUrl: upload?.url ?? void 0
68
+ });
69
+ }
70
+ };
71
+ const legacyChunkSize = this.options.chunkSize;
72
+ const chunkSize = this.options.chunkSizeBytes ?? legacyChunkSize;
73
+ if (this.options.headers !== void 0)
74
+ uploadOptions.headers = this.options.headers;
75
+ if (chunkSize !== void 0) uploadOptions.chunkSize = chunkSize;
76
+ if (this.options.retryDelays !== void 0)
77
+ uploadOptions.retryDelays = this.options.retryDelays;
78
+ if (this.options.storeFingerprintForResuming !== void 0) {
79
+ uploadOptions.storeFingerprintForResuming = this.options.storeFingerprintForResuming;
80
+ }
81
+ if (this.options.removeFingerprintOnSuccess !== void 0) {
82
+ uploadOptions.removeFingerprintOnSuccess = this.options.removeFingerprintOnSuccess;
83
+ }
84
+ if (this.options.parallelUploads !== void 0)
85
+ uploadOptions.parallelUploads = this.options.parallelUploads;
86
+ upload = new tus.Upload(file, uploadOptions);
87
+ upload.start();
88
+ });
89
+ }
90
+ };
91
+
92
+ exports.TusUpload = TusUpload;