uploados 0.1.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 (66) hide show
  1. package/android/build.gradle +25 -0
  2. package/android/src/main/AndroidManifest.xml +15 -0
  3. package/android/src/main/java/expo/modules/uploados/UploadosModule.kt +121 -0
  4. package/android/src/main/java/expo/modules/uploados/upload/CompressionPipeline.kt +192 -0
  5. package/android/src/main/java/expo/modules/uploados/upload/FileStager.kt +125 -0
  6. package/android/src/main/java/expo/modules/uploados/upload/ProgressRequestBody.kt +36 -0
  7. package/android/src/main/java/expo/modules/uploados/upload/UploadManager.kt +857 -0
  8. package/android/src/main/java/expo/modules/uploados/upload/UploadModels.kt +209 -0
  9. package/android/src/main/java/expo/modules/uploados/upload/UploadNotificationHelper.kt +93 -0
  10. package/android/src/main/java/expo/modules/uploados/upload/UploadTaskStore.kt +224 -0
  11. package/android/src/main/java/expo/modules/uploados/upload/UploadWorker.kt +31 -0
  12. package/build/Uploados.types.d.ts +226 -0
  13. package/build/Uploados.types.d.ts.map +1 -0
  14. package/build/Uploados.types.js +2 -0
  15. package/build/Uploados.types.js.map +1 -0
  16. package/build/UploadosModule.d.ts +13 -0
  17. package/build/UploadosModule.d.ts.map +1 -0
  18. package/build/UploadosModule.js +3 -0
  19. package/build/UploadosModule.js.map +1 -0
  20. package/build/UploadosModule.web.d.ts +13 -0
  21. package/build/UploadosModule.web.d.ts.map +1 -0
  22. package/build/UploadosModule.web.js +33 -0
  23. package/build/UploadosModule.web.js.map +1 -0
  24. package/build/createUploader.d.ts +3 -0
  25. package/build/createUploader.d.ts.map +1 -0
  26. package/build/createUploader.js +108 -0
  27. package/build/createUploader.js.map +1 -0
  28. package/build/index.d.ts +5 -0
  29. package/build/index.d.ts.map +1 -0
  30. package/build/index.js +5 -0
  31. package/build/index.js.map +1 -0
  32. package/build/normalizeUploadOptions.d.ts +9 -0
  33. package/build/normalizeUploadOptions.d.ts.map +1 -0
  34. package/build/normalizeUploadOptions.js +81 -0
  35. package/build/normalizeUploadOptions.js.map +1 -0
  36. package/build/providers/defineUploadProvider.d.ts +26 -0
  37. package/build/providers/defineUploadProvider.d.ts.map +1 -0
  38. package/build/providers/defineUploadProvider.js +39 -0
  39. package/build/providers/defineUploadProvider.js.map +1 -0
  40. package/build/providers/multipartPlan.d.ts +10 -0
  41. package/build/providers/multipartPlan.d.ts.map +1 -0
  42. package/build/providers/multipartPlan.js +28 -0
  43. package/build/providers/multipartPlan.js.map +1 -0
  44. package/eslint.config.cjs +5 -0
  45. package/expo-module.config.json +10 -0
  46. package/ios/Upload/CompressionPipeline.swift +183 -0
  47. package/ios/Upload/FileStager.swift +67 -0
  48. package/ios/Upload/UploadManager.swift +813 -0
  49. package/ios/Upload/UploadModels.swift +305 -0
  50. package/ios/Upload/UploadSessionDelegate.swift +82 -0
  51. package/ios/Upload/UploadTaskStore.swift +92 -0
  52. package/ios/Upload/UploadosAppDelegate.swift +14 -0
  53. package/ios/Uploados.podspec +23 -0
  54. package/ios/UploadosModule.swift +87 -0
  55. package/jest.config.js +15 -0
  56. package/package.json +54 -0
  57. package/readme.md +169 -0
  58. package/src/Uploados.types.ts +260 -0
  59. package/src/UploadosModule.ts +18 -0
  60. package/src/UploadosModule.web.ts +49 -0
  61. package/src/createUploader.ts +146 -0
  62. package/src/index.ts +4 -0
  63. package/src/normalizeUploadOptions.ts +132 -0
  64. package/src/providers/defineUploadProvider.ts +75 -0
  65. package/src/providers/multipartPlan.ts +43 -0
  66. package/tsconfig.json +42 -0
@@ -0,0 +1,132 @@
1
+ import type {
2
+ CompressionOptions,
3
+ CompressionPreset,
4
+ CreateUploadOptions,
5
+ DirectUploadOptions,
6
+ ProviderUploadOptions,
7
+ UploadErrorPayload,
8
+ UploadHttpMethod,
9
+ UploadNetworkPolicy,
10
+ UploaderConfig,
11
+ } from './Uploados.types';
12
+
13
+ const DEFAULT_MAX_ATTEMPTS = 3;
14
+ const DEFAULT_METHOD: UploadHttpMethod = 'PUT';
15
+ const DEFAULT_NETWORK_POLICY: UploadNetworkPolicy = 'wait';
16
+ const DEFAULT_BACKGROUND = false;
17
+
18
+ function throwValidationError(
19
+ message: string,
20
+ code: UploadErrorPayload['code'] = 'UNKNOWN'
21
+ ): never {
22
+ const error: UploadErrorPayload = {
23
+ code,
24
+ message,
25
+ retryable: false,
26
+ taskId: '',
27
+ };
28
+ throw error;
29
+ }
30
+
31
+ export function assertFutureUploadOptionsSupported(
32
+ compression?: CompressionOptions,
33
+ multipart?: boolean
34
+ ): void {
35
+ if (compression?.enabled === true && compression.preset) {
36
+ const validPresets: CompressionPreset[] = ['balanced', 'inspection', 'avatar'];
37
+ if (!validPresets.includes(compression.preset)) {
38
+ throwValidationError(
39
+ `Unsupported compression preset "${compression.preset}". Use balanced, inspection, or avatar.`,
40
+ 'COMPRESSION_FAILED'
41
+ );
42
+ }
43
+ }
44
+
45
+ if (multipart === true) {
46
+ throwValidationError(
47
+ 'Multipart uploads are not available yet. Use direct signed-URL uploads until P1-E ships.',
48
+ 'MULTIPART_FAILED'
49
+ );
50
+ }
51
+ }
52
+
53
+ export function normalizeDirectUploadOptions(
54
+ config: UploaderConfig,
55
+ input: DirectUploadOptions
56
+ ): CreateUploadOptions {
57
+ assertFutureUploadOptionsSupported(input.compression, input.multipart);
58
+
59
+ const localUri = input.file.trim();
60
+ const uploadUrl = input.uploadUrl.trim();
61
+ if (!localUri) {
62
+ throwValidationError('Upload file URI is required.', 'FILE_NOT_FOUND');
63
+ }
64
+ if (!uploadUrl) {
65
+ throwValidationError('Upload URL is required.', 'PROVIDER_ERROR');
66
+ }
67
+
68
+ const background = input.background ?? config.background ?? DEFAULT_BACKGROUND;
69
+ const networkPolicy = background
70
+ ? 'wait'
71
+ : (input.networkPolicy ?? config.networkPolicy ?? DEFAULT_NETWORK_POLICY);
72
+ const maxAttempts = Math.max(
73
+ 1,
74
+ input.retry?.maxAttempts ?? config.retry?.maxAttempts ?? DEFAULT_MAX_ATTEMPTS
75
+ );
76
+
77
+ return {
78
+ localUri,
79
+ uploadUrl,
80
+ method: input.method ?? DEFAULT_METHOD,
81
+ headers: input.headers ?? {},
82
+ background,
83
+ networkPolicy,
84
+ retry: { maxAttempts },
85
+ compression: input.compression,
86
+ };
87
+ }
88
+
89
+ export function normalizeProviderUploadOptions(
90
+ config: UploaderConfig,
91
+ input: ProviderUploadOptions,
92
+ signed: {
93
+ uploadUrl: string;
94
+ method?: UploadHttpMethod;
95
+ headers?: Record<string, string>;
96
+ }
97
+ ): CreateUploadOptions {
98
+ assertFutureUploadOptionsSupported(input.compression, input.multipart);
99
+
100
+ const localUri = input.file.trim();
101
+ const uploadUrl = signed.uploadUrl.trim();
102
+ const key = input.key.trim();
103
+ if (!localUri) {
104
+ throwValidationError('Upload file URI is required.', 'FILE_NOT_FOUND');
105
+ }
106
+ if (!key) {
107
+ throwValidationError('Upload key is required.', 'PROVIDER_ERROR');
108
+ }
109
+ if (!uploadUrl) {
110
+ throwValidationError('Provider did not return an upload URL.', 'PROVIDER_ERROR');
111
+ }
112
+
113
+ const background = input.background ?? config.background ?? DEFAULT_BACKGROUND;
114
+ const networkPolicy = background
115
+ ? 'wait'
116
+ : (input.networkPolicy ?? config.networkPolicy ?? DEFAULT_NETWORK_POLICY);
117
+ const maxAttempts = Math.max(
118
+ 1,
119
+ input.retry?.maxAttempts ?? config.retry?.maxAttempts ?? DEFAULT_MAX_ATTEMPTS
120
+ );
121
+
122
+ return {
123
+ localUri,
124
+ uploadUrl,
125
+ method: signed.method ?? DEFAULT_METHOD,
126
+ headers: signed.headers ?? {},
127
+ background,
128
+ networkPolicy,
129
+ retry: { maxAttempts },
130
+ compression: input.compression,
131
+ };
132
+ }
@@ -0,0 +1,75 @@
1
+ import type {
2
+ CompleteMultipartInput,
3
+ CompleteMultipartResult,
4
+ CreateMultipartInput,
5
+ CreateMultipartResult,
6
+ SignPartInput,
7
+ SignedPartResult,
8
+ UploadHttpMethod,
9
+ UploadProvider,
10
+ } from '../Uploados.types';
11
+
12
+ export type ProviderUploadUrlInput = {
13
+ key: string;
14
+ contentType?: string;
15
+ metadata?: Record<string, string>;
16
+ };
17
+
18
+ export type ProviderUploadUrlResult = {
19
+ url: string;
20
+ method?: UploadHttpMethod;
21
+ headers?: Record<string, string>;
22
+ };
23
+
24
+ export type UploadProviderDefinition = {
25
+ getUploadUrl?: (input: ProviderUploadUrlInput) => Promise<ProviderUploadUrlResult>;
26
+ createMultipartUpload?: (input: CreateMultipartInput) => Promise<CreateMultipartResult>;
27
+ signPart?: (input: SignPartInput) => Promise<SignedPartResult>;
28
+ completeMultipartUpload?: (input: CompleteMultipartInput) => Promise<CompleteMultipartResult>;
29
+ abortMultipartUpload?: (input: { key: string; uploadId: string }) => Promise<void>;
30
+ };
31
+
32
+ export function defineUploadProvider(definition: UploadProviderDefinition): UploadProvider {
33
+ return {
34
+ getUploadUrl: definition.getUploadUrl,
35
+ createMultipartUpload: definition.createMultipartUpload,
36
+ signPart: definition.signPart,
37
+ completeMultipartUpload: definition.completeMultipartUpload,
38
+ abortMultipartUpload: definition.abortMultipartUpload,
39
+ };
40
+ }
41
+
42
+ export function assertUploadProvider(provider: UploadProvider | undefined): UploadProvider {
43
+ if (!provider) {
44
+ throw providerError('Upload provider is required for provider-based uploads.');
45
+ }
46
+ return provider;
47
+ }
48
+
49
+ export function assertProviderMultipartSupported(provider: UploadProvider): void {
50
+ if (typeof provider.createMultipartUpload !== 'function') {
51
+ throw providerError('Provider must implement createMultipartUpload.');
52
+ }
53
+ if (typeof provider.signPart !== 'function') {
54
+ throw providerError('Provider must implement signPart.');
55
+ }
56
+ if (typeof provider.completeMultipartUpload !== 'function') {
57
+ throw providerError('Provider must implement completeMultipartUpload.');
58
+ }
59
+ }
60
+
61
+ export function assertProviderDirectUploadSupported(provider: UploadProvider): void {
62
+ if (typeof provider.getUploadUrl !== 'function') {
63
+ throw providerError('Provider must implement getUploadUrl for direct signed-URL uploads.');
64
+ }
65
+ }
66
+
67
+ function providerError(
68
+ message: string
69
+ ): Error & { code: string; retryable: boolean; taskId: string } {
70
+ return Object.assign(new Error(message), {
71
+ code: 'PROVIDER_ERROR',
72
+ retryable: false,
73
+ taskId: '',
74
+ });
75
+ }
@@ -0,0 +1,43 @@
1
+ export const DEFAULT_MULTIPART_PART_SIZE = 8 * 1024 * 1024;
2
+ export const MIN_MULTIPART_PART_SIZE = 5 * 1024 * 1024;
3
+
4
+ export type MultipartUploadPlan = {
5
+ partSize: number;
6
+ partCount: number;
7
+ totalBytes: number;
8
+ };
9
+
10
+ export function createMultipartUploadPlan(
11
+ totalBytes: number,
12
+ partSize: number = DEFAULT_MULTIPART_PART_SIZE
13
+ ): MultipartUploadPlan {
14
+ if (!Number.isFinite(totalBytes) || totalBytes <= 0) {
15
+ throw Object.assign(new Error('File size must be greater than zero for multipart uploads.'), {
16
+ code: 'FILE_NOT_FOUND',
17
+ retryable: false,
18
+ taskId: '',
19
+ });
20
+ }
21
+
22
+ const normalizedPartSize = Math.max(MIN_MULTIPART_PART_SIZE, Math.floor(partSize));
23
+ const partCount = Math.max(1, Math.ceil(totalBytes / normalizedPartSize));
24
+
25
+ return {
26
+ partSize: normalizedPartSize,
27
+ partCount,
28
+ totalBytes,
29
+ };
30
+ }
31
+
32
+ export function validateMultipartPartNumber(partNumber: number, partCount: number): void {
33
+ if (!Number.isInteger(partNumber) || partNumber < 1 || partNumber > partCount) {
34
+ throw Object.assign(
35
+ new Error(`Invalid multipart part number ${partNumber}. Expected 1..${partCount}.`),
36
+ {
37
+ code: 'MULTIPART_FAILED',
38
+ retryable: false,
39
+ taskId: '',
40
+ }
41
+ );
42
+ }
43
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "compilerOptions": {
3
+ "lib": [
4
+ "dom",
5
+ "DOM.Iterable",
6
+ "esnext"
7
+ ],
8
+ "types": [
9
+ "jest"
10
+ ],
11
+ "typeRoots": [
12
+ "./ts-declarations",
13
+ "./node_modules/@types"
14
+ ],
15
+ "jsx": "react-native",
16
+ "target": "esnext",
17
+ "moduleResolution": "bundler",
18
+ "module": "esnext",
19
+ "moduleDetection": "force",
20
+ "esModuleInterop": true,
21
+ "sourceMap": true,
22
+ "declaration": true,
23
+ "declarationMap": true,
24
+ "inlineSources": true,
25
+ "skipLibCheck": true,
26
+ "strict": true,
27
+ "noFallthroughCasesInSwitch": true,
28
+ "noPropertyAccessFromIndexSignature": false,
29
+ "noImplicitReturns": true,
30
+ "noUnusedLocals": true,
31
+ "noUnusedParameters": false,
32
+ "rootDir": "./src",
33
+ "outDir": "./build"
34
+ },
35
+ "include": [
36
+ "./src"
37
+ ],
38
+ "exclude": [
39
+ "**/__mocks__/*",
40
+ "**/__tests__/*"
41
+ ]
42
+ }